#include "ChunkMeshing.hpp" namespace MC::World::Generation::ChunkMeshing { ChunkMesh mesh_chunk(Chunk& chunk, const SurroundingContext& context) { using namespace Detail; return { create_mesh(chunk, context), create_mesh(chunk, context) }; } SurroundingContext::Block& SurroundingContext::at(Position::BlockLocalOffset p) { return m_blocks[pos(p)]; } const SurroundingContext::Block& SurroundingContext::at(Position::BlockLocalOffset p) const { return m_blocks[pos(p)]; } USize SurroundingContext::pos(Position::BlockLocalOffset p) { // First we calculate the index as if there were no gaps. USize pos = 0; pos += p.x() + 1; pos += (p.z() + 1) * (Chunk::Width + 2); // Then we substract the gaps. Int g = (p.z() + 1) - (p.x() < 0); UInt gap_count = std::clamp(g, 0, Chunk::Width); pos -= gap_count * Chunk::Width; pos += p.y() * surrounding_block_count; return pos; } SurroundingContext create_meshing_context(const Chunk& chunk, ChunkNeighbors& neighbors) { SurroundingContext context{}; for (I16 x = -1; x <= (I16)Chunk::Width; x++) { for (I16 z = -1; z <= (I16)Chunk::Width; z++) { for (I16 y = 0; y < (I16)Chunk::Height; y++) { Position::BlockLocalOffset pos{x, y, z}; if (pos.fits_within_chunk()) continue; auto [does_exist, block] = get_block_wrapping(chunk, neighbors, {pos.x(), pos.y(), pos.z()}); context.at(pos) = {does_exist, block}; } } } return context; } namespace Detail { Face 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. Face 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; } for (auto& p : face) { p += {x, y, z}; } return face; } Face DefaultMeshDecisions::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) -> Face { 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 {}; } } Face DefaultMeshDecisions::face_normals(BlockSide side) { Vector<3, F32> normal{get_face_normal(side)}; return {normal, normal, normal, normal}; } Face DefaultMeshDecisions::face_light(Chunk& chunk, const SurroundingContext& context, U32 x, U32 y, U32 z, BlockSide side) { auto neighbor = get_opposing_neighbor(chunk, context, x, y, z, side).block; if (!neighbor.type.is_translucent()) return {}; auto light = (F32)neighbor.light / (F32)255; return {light, light, light, light}; } Face DefaultMeshDecisions::face_ao_values(Chunk& chunk, const SurroundingContext& context, 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... 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; } Face 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 p = [=](auto o) -> Position::BlockLocalOffset { return {(I16)x + o.x(), (I16)y + o.y(), (I16)z + o.z()}; }; auto block_a = get_block_from_chunk_or_context(chunk, context, p(a)); auto block_b = get_block_from_chunk_or_context(chunk, context, p(b)); auto block_c = get_block_from_chunk_or_context(chunk, context, p(c)); auto is_occupied = [](auto b) -> F32 { return b.does_exist && !b.block.empty(); }; F32 occlusion_a = is_occupied(block_a); F32 occlusion_b = is_occupied(block_b); F32 occlusion_c = is_occupied(block_c); 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; } Vector<3, I32> DefaultMeshDecisions::get_face_normal(BlockSide side) { auto is_side = [=](BlockSide s) -> I8 { return s == side; }; return { is_side(BlockSide::Right) - is_side(BlockSide::Left), is_side(BlockSide::Top) - is_side(BlockSide::Bottom), is_side(BlockSide::Front) - is_side(BlockSide::Back), }; } SurroundingContext::Block DefaultMeshDecisions::get_block_from_chunk_or_context( const Chunk& chunk, const SurroundingContext& context, Position::BlockLocalOffset pos ) { if (pos.fits_within_chunk()) return {true, chunk.at(pos)}; return context.at(pos); } SurroundingContext::Block DefaultMeshDecisions::get_opposing_neighbor(const Chunk& chunk, const SurroundingContext& context, U32 x, U32 y, U32 z, BlockSide side) { Vector<3, I32> offset = get_face_normal(side); auto pos = offset + Vector<3, I32>{x, y, z}; return get_block_from_chunk_or_context(chunk, context, {(I16)pos.x(), (I16)pos.y(), (I16)pos.z()}); } Bool DefaultMeshDecisions::is_face_visible(Chunk& chunk, const SurroundingContext& context, U32 x, U32 y, U32 z, BlockSide side) { auto [does_exist, block] = get_opposing_neighbor(chunk, context, x, y, z, side); // Faces on the chunk border are hidden by default, even if the opposing chunk does not exist. if (!does_exist) return false; return block.type.is_translucent(); } Bool DefaultMeshDecisions::should_ignore_block(Chunk::BlockData block) { return block.empty() || block.type == BlockType::Water; } Bool WaterMeshDecisions::is_face_visible(Chunk& chunk, const SurroundingContext& context, U32 x, U32 y, U32 z, BlockSide side) { auto [does_exist, block] = get_opposing_neighbor(chunk, context, x, y, z, side); if (!does_exist) return false; // See above. return block.type.is_translucent() && block.type != BlockType::Water; } Bool WaterMeshDecisions::should_ignore_block(Chunk::BlockData block) { return block.type != BlockType::Water; } } }