#include "ChunkMeshing.hpp" namespace MC::World::Generation::ChunkMeshing { Chunk::BlockData get_block_wrapping(const Chunk& chunk, const ChunkNeighbors& neighbors, Vector<3, I32> pos) { const Chunk* chunk_to_ask; auto overflow = [](I32& c, I32 max) -> I8 { if (c < 0) { c += max; return -1; } if (c >= max) { c -= max; return 1; } return 0; }; auto xo = overflow(pos.x(), Chunk::Width); auto yo = overflow(pos.y(), Chunk::Height); auto zo = overflow(pos.z(), Chunk::Width); // Blocks above and below a chunk are always Air. if (yo != 0) return {}; if (xo == 1 && zo == 1) { chunk_to_ask = neighbors.south_east; } else if (xo == 1 && zo == -1) { chunk_to_ask = neighbors.north_east; } else if (xo == -1 && zo == 1) { chunk_to_ask = neighbors.south_west; } else if (xo == -1 && zo == -1) { chunk_to_ask = neighbors.north_west; } else if (xo == 1) { chunk_to_ask = neighbors.east; } else if (xo == -1) { chunk_to_ask = neighbors.west; } else if (zo == 1) { chunk_to_ask = neighbors.south; } else if (zo == -1) { chunk_to_ask = neighbors.north; } else { chunk_to_ask = &chunk; } return chunk_to_ask->get(pos.x(), pos.y(), pos.z()); } std::array, 4> face_tex_coords(BlockType type, BlockSide side) { U8 atlas_width = 4; U8 atlas_height = 4; Real width_step = 1.0f / atlas_width; Real height_step = 1.0f / atlas_height; auto block_coords = [=](U8 x, U8 y) -> std::array, 4> { auto t = y * height_step; auto l = x * width_step; auto b = t + height_step; auto r = l + width_step; // This is horrible and it was better before I restructured the vertex order... // In the last version the front and back side pairs had the same structure in different winding, // so a back_side check wasn't needed... // Note: BlockSide::Front counts as a back side, because OpenGL has an inverted Z-axis compared to us. Bool back_side = side == BlockSide::Front || side == BlockSide::Bottom || side == BlockSide::Left; if (back_side) { return {{{l, t}, {l, b}, {r, b}, {r, t}}}; } return {{{r, t}, {l, t}, {l, b}, {r, b}}}; }; switch (type) { case BlockType::Dirt: return block_coords(1, 0); case BlockType::Grass: switch (side) { case BlockSide::Front: case BlockSide::Back: case BlockSide::Left: case BlockSide::Right: return block_coords(2, 0); case BlockSide::Bottom: return block_coords(1, 0); case BlockSide::Top: return block_coords(0, 0); } case BlockType::Stone: return block_coords(3, 0); case BlockType::Sand: return block_coords(1, 1); case BlockType::Water: return block_coords(0, 1); case BlockType::Snow: switch (side) { case BlockSide::Front: case BlockSide::Back: case BlockSide::Left: case BlockSide::Right: return block_coords(3, 1); case BlockSide::Bottom: return block_coords(1, 0); case BlockSide::Top: return block_coords(2, 1); } case BlockType::Wood: switch (side) { case BlockSide::Front: case BlockSide::Back: case BlockSide::Right: case BlockSide::Left: return block_coords(0, 2); case BlockSide::Bottom: case BlockSide::Top: return block_coords(1, 2); } case BlockType::Leaves: return block_coords(2, 2); case BlockType::Air: return {}; } } std::array, 4> face_normals(BlockSide side) { auto is_side = [=](BlockSide s) -> Real { return s == side; }; Vector<3, F32> normal = { is_side(BlockSide::Right) - is_side(BlockSide::Left), is_side(BlockSide::Top) - is_side(BlockSide::Bottom), is_side(BlockSide::Front) - is_side(BlockSide::Back), }; return {normal, normal, normal, normal}; } std::array face_ao_values(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) { std::array, 8> offsets{}; // Given a block position, these offsets can be added to it to get the 8 blocks necessary to calculate AO. // There are 4 corners and 4 sides, corners are visually distinguished by the lack of spaces and // but you can recognize them by the fact that they have no 0 offsets. // Note: Is there a way to compute these? I tried but I couldn't... If the vertex windings ever change again I don't want to hardcode these... auto nowhere = Vector<3, I32>::max(); switch (side) { case BlockSide::Front: offsets = {{{0, 1, 1}, {-1,1,1}, {-1, 0, 1}, {-1,-1,1}, {0, -1, 1}, {1,-1,1}, {1, 0, 1}, {1,1,1}}}; break; case BlockSide::Back: offsets = {{{-1, 0, -1}, {-1,1,-1}, {0, 1, -1}, {1,1,-1}, {1, 0, -1}, {1,-1,-1}, {0, -1, -1}, {-1,-1,-1}}}; break; case BlockSide::Top: offsets = {{{-1, 1, 0}, {-1,1,1}, {0, 1, 1}, {1,1,1}, {1, 1, 0}, {1,1,-1}, {0, 1, -1}, {-1,1,-1}}}; break; case BlockSide::Bottom: offsets = {{{0, -1, 1}, {-1,-1,1}, {-1, -1, 0}, {-1,-1,-1}, {0, -1, -1}, {1,-1,-1}, {1, -1, 0}, {1,-1,1}}}; break; case BlockSide::Right: offsets = {{{1, 0, -1}, {1,1,-1}, {1, 1, 0}, {1,1,1}, {1, 0, 1}, {1,-1,1}, {1, -1, 0}, {1,-1,-1}}}; break; case BlockSide::Left: offsets = {{{-1, 1, 0}, {-1,1,-1}, {-1, 0, -1}, {-1,-1,-1}, {-1, -1, 0}, {-1,-1,1}, {-1, 0, 1}, {-1,1,1}}}; break; } std::array vertex_ao{}; UInt offset_index = 0; for (UInt vertex = 0; vertex < 4; vertex++) { auto a = offsets[offset_index]; auto b = offsets[++offset_index]; // corner auto c = offsets[++offset_index % 8]; auto block_a = get_block_wrapping(chunk, neighbors, {x + a.x(), y + a.y(), z + a.z()}); auto block_b = get_block_wrapping(chunk, neighbors, {x + b.x(), y + b.y(), z + b.z()}); auto block_c = get_block_wrapping(chunk, neighbors, {x + c.x(), y + c.y(), z + c.z()}); F32 occlusion_a = block_a.empty() ? 0 : 1; F32 occlusion_b = block_b.empty() ? 0 : 1; F32 occlusion_c = block_c.empty() ? 0 : 1; if (occlusion_a + occlusion_c == 2.0f) { vertex_ao[vertex] = 1.0f; } else { vertex_ao[vertex] = (occlusion_a + occlusion_b + occlusion_c) / 3; } } return vertex_ao; } Bool is_face_visible(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) { Vector<3, I32> offset{}; switch (side) { case BlockSide::Front: offset[2] = 1; break; case BlockSide::Back: offset[2] = -1; break; case BlockSide::Top: offset[1] = 1; break; case BlockSide::Bottom: offset[1] = -1; break; case BlockSide::Right: offset[0] = 1; break; case BlockSide::Left: offset[0] = -1; break; } Vector<3, I32> neighbor_pos{ x + offset.x(), y + offset.y(), z + offset.z(), }; auto [neighbor] = get_block_wrapping(chunk, neighbors, neighbor_pos); return neighbor.is_transparent(); } GFX::Mesh create_mesh_for_chunk(Chunk& chunk, const ChunkNeighbors& neighbors) { std::vector> positions{}; std::vector> normals{}; std::vector> tex_coords{}; std::vector ambient_occlusion_values{}; std::vector indices{}; for (Int x = 0; x < Chunk::Width; x++) { for (Int y = 0; y < Chunk::Height; y++) { for (Int z = 0; z < Chunk::Width; z++) { auto block = chunk.get(x, y, z); if (block.empty()) continue; for (auto side: BlockSide::all()) { if (!is_face_visible(chunk, neighbors, x, y, z, side)) continue; auto side_positions = side.face(); auto side_normals = face_normals(side); auto side_tex_coords = face_tex_coords(block.type, side); auto side_ao = face_ao_values(chunk, neighbors, x, y, z, side); for (auto& position : side_positions) { position = position + Vector<3, F32>{x, y, z}; } U32 s = positions.size(); positions.insert(positions.end(), side_positions.begin(), side_positions.end()); normals.insert(normals.end(), side_normals.begin(), side_normals.end()); tex_coords.insert(tex_coords.end(), side_tex_coords.begin(), side_tex_coords.end()); ambient_occlusion_values.insert(ambient_occlusion_values.end(), side_ao.begin(), side_ao.end()); indices.insert(indices.end(), {s + 0, s + 1, s + 2, s + 2, s + 3, s + 0 }); } } } } return { {positions, normals, tex_coords, ambient_occlusion_values}, indices, }; } }