diff options
Diffstat (limited to 'src/World/Generation')
| -rw-r--r-- | src/World/Generation/ChunkMeshing.cpp | 286 | ||||
| -rw-r--r-- | src/World/Generation/ChunkMeshing.hpp | 69 |
2 files changed, 208 insertions, 147 deletions
diff --git a/src/World/Generation/ChunkMeshing.cpp b/src/World/Generation/ChunkMeshing.cpp index 130bbad..7158734 100644 --- a/src/World/Generation/ChunkMeshing.cpp +++ b/src/World/Generation/ChunkMeshing.cpp @@ -2,36 +2,49 @@ 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; +ChunkMesh mesh_chunk(Chunk& chunk, const ChunkNeighbors& neighbors) { + using namespace Detail; - auto overflow = [](I32& c, I32 max) -> I8 { - if (c < 0) { c += max; return -1; } - if (c >= max) { c -= max; return 1; } - return 0; + return { + create_mesh<DefaultMeshDecisions>(chunk, neighbors), + create_mesh<WaterMeshDecisions>(chunk, neighbors) }; +} - 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 {}; +namespace Detail { - 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; } +std::array<Vector<3, F32>, 4> DefaultMeshDecisions::face_positions(BlockSide side, U32 x, U32 y, U32 z) { + // Winding order: (0, 1, 2) (2, 3, 0) + // Note: OpenGL Coordinate system has a flipped z axis. + std::array<Vector<3, F32>, 4> face{}; + switch (side) { + case BlockSide::Front: + face = {{{0, 1, 1}, {0, 0, 1}, {1, 0, 1}, {1, 1, 1}}}; + break; + case BlockSide::Back: + face = {{{0, 1, 0}, {1, 1, 0}, {1, 0, 0}, {0, 0, 0}}}; + break; + case BlockSide::Top: + face = {{{0, 1, 1}, {1, 1, 1}, {1, 1, 0}, {0, 1, 0}}}; + break; + case BlockSide::Bottom: + face = {{{0, 0, 1}, {0, 0, 0}, {1, 0, 0}, {1, 0, 1}}}; + break; + case BlockSide::Right: + face = {{{1, 1, 0}, {1, 1, 1}, {1, 0, 1}, {1, 0, 0}}}; + break; + case BlockSide::Left: + face = {{{0, 1, 0}, {0, 0, 0}, {0, 0, 1}, {0, 1, 1}}}; + break; + } - return chunk_to_ask->get(pos.x(), pos.y(), pos.z()); + for (auto& p : face) { + p += {x, y, z}; + } + return face; } -std::array<Vector<2, F32>, 4> face_tex_coords(BlockType type, BlockSide side) { +std::array<Vector<2, F32>, 4> DefaultMeshDecisions::face_tex_coords(BlockType type, BlockSide side) { U8 atlas_width = 4; U8 atlas_height = 4; @@ -56,75 +69,67 @@ std::array<Vector<2, F32>, 4> face_tex_coords(BlockType type, BlockSide side) { }; switch (type) { - case BlockType::Dirt: + 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 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 {}; + 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<Vector<3, F32>, 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), - }; - +std::array<Vector<3, F32>, 4> DefaultMeshDecisions::face_normals(BlockSide side) { + Vector<3, F32> normal{get_face_normal(side)}; return {normal, normal, normal, normal}; } -std::array<F32, 4> face_ao_values(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) { +std::array<F32, 4> DefaultMeshDecisions::face_ao_values(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) { std::array<Vector<3, I32>, 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}}}; @@ -172,80 +177,69 @@ std::array<F32, 4> face_ao_values(Chunk& chunk, const ChunkNeighbors& neighbors, 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; - } +Chunk::BlockData DefaultMeshDecisions::get_block_wrapping(const Chunk& chunk, const ChunkNeighbors& neighbors, Vector<3, I32> pos) { + const Chunk* chunk_to_ask; - Vector<3, I32> neighbor_pos{ - x + offset.x(), y + offset.y(), z + offset.z(), + auto overflow = [](I32& c, I32 max) -> I8 { + if (c < 0) { c += max; return -1; } + if (c >= max) { c -= max; return 1; } + return 0; }; - auto [neighbor] = get_block_wrapping(chunk, neighbors, neighbor_pos); - return neighbor.is_transparent(); + 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()); } -GFX::Mesh create_mesh_for_chunk(Chunk& chunk, const ChunkNeighbors& neighbors) { - std::vector<Vector<3, F32>> positions{}; - std::vector<Vector<3, F32>> normals{}; - std::vector<Vector<2, F32>> tex_coords{}; - std::vector<F32> ambient_occlusion_values{}; - std::vector<U32> 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 }); - } - } - } - } +Vector<3, I32> DefaultMeshDecisions::get_face_normal(BlockSide side) { + auto is_side = [=](BlockSide s) -> I8 { return s == side; }; return { - {positions, normals, tex_coords, ambient_occlusion_values}, - indices, + is_side(BlockSide::Right) - is_side(BlockSide::Left), + is_side(BlockSide::Top) - is_side(BlockSide::Bottom), + is_side(BlockSide::Front) - is_side(BlockSide::Back), }; } +Bool DefaultMeshDecisions::is_face_visible(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) { + Vector<3, I32> offset = get_face_normal(side); + auto neighbor_position = offset + Vector<3, I32>{x, y, z}; + + auto block = get_block_wrapping(chunk, neighbors, neighbor_position); + return block.type.is_transparent(); +} + +Bool DefaultMeshDecisions::should_ignore_block(Chunk::BlockData block) { + return block.empty() || block.type == BlockType::Water; +} + +Bool WaterMeshDecisions::is_face_visible(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) { + Vector<3, I32> offset = get_face_normal(side); + auto neighbor_position = offset + Vector<3, I32>{x, y, z}; + + auto [neighbor] = get_block_wrapping(chunk, neighbors, neighbor_position); + return neighbor.is_transparent() && neighbor != BlockType::Water; +} + +Bool WaterMeshDecisions::should_ignore_block(Chunk::BlockData block) { + return block.type != BlockType::Water; +} + +} + } \ No newline at end of file diff --git a/src/World/Generation/ChunkMeshing.hpp b/src/World/Generation/ChunkMeshing.hpp index dd9446b..75023f5 100644 --- a/src/World/Generation/ChunkMeshing.hpp +++ b/src/World/Generation/ChunkMeshing.hpp @@ -9,6 +9,73 @@ struct ChunkNeighbors { Chunk *north, *east, *south, *west; Chunk *north_east, *south_east, *south_west, *north_west; }; -GFX::Mesh create_mesh_for_chunk(Chunk& chunk, const ChunkNeighbors& neighbors); +struct ChunkMesh { GFX::Mesh land_mesh, water_mesh; }; +ChunkMesh mesh_chunk(Chunk& chunk, const ChunkNeighbors& neighbors); + +namespace Detail { + +template <typename Decisions> +GFX::Mesh create_mesh(Chunk& chunk, const ChunkNeighbors& neighbors) { + std::vector<Vector<3, F32>> positions{}; + std::vector<Vector<3, F32>> normals{}; + std::vector<Vector<2, F32>> tex_coords{}; + std::vector<F32> ambient_occlusion_values{}; + std::vector<U32> 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 (Decisions::should_ignore_block(block)) + continue; + + for (auto side: BlockSide::all()) { + if (!Decisions::is_face_visible(chunk, neighbors, x, y, z, side)) + continue; + + auto side_positions = Decisions::face_positions(side, x, y, z); + auto side_normals = Decisions::face_normals(side); + auto side_tex_coords = Decisions::face_tex_coords(block.type, side); + auto side_ao = Decisions::face_ao_values(chunk, neighbors, x, y, z, side); + + 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, + }; +} + +class DefaultMeshDecisions { +public: + static std::array<Vector<3, F32>, 4> face_positions(BlockSide side, U32 x, U32 y, U32 z); + static std::array<Vector<2, F32>, 4> face_tex_coords(BlockType type, BlockSide side); + static std::array<Vector<3, F32>, 4> face_normals(BlockSide side); + static std::array<F32, 4> face_ao_values(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side); + + static Chunk::BlockData get_block_wrapping(const Chunk& chunk, const ChunkNeighbors& neighbors, Vector<3, I32> pos); + static Vector<3, I32> get_face_normal(BlockSide side); + + static Bool is_face_visible(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side); + static Bool should_ignore_block(Chunk::BlockData block); +}; + +class WaterMeshDecisions final : public DefaultMeshDecisions { +public: + static Bool is_face_visible(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side); + static Bool should_ignore_block(Chunk::BlockData block); +}; + +} } |
