diff options
Diffstat (limited to 'src/World')
| -rw-r--r-- | src/World/BlockSide.hpp | 46 | ||||
| -rw-r--r-- | src/World/Generation/ChunkMeshing.cpp | 217 | ||||
| -rw-r--r-- | src/World/Generation/ChunkMeshing.hpp | 5 | ||||
| -rw-r--r-- | src/World/World.cpp | 31 | ||||
| -rw-r--r-- | src/World/World.hpp | 1 |
5 files changed, 182 insertions, 118 deletions
diff --git a/src/World/BlockSide.hpp b/src/World/BlockSide.hpp index 69df159..831982a 100644 --- a/src/World/BlockSide.hpp +++ b/src/World/BlockSide.hpp @@ -16,8 +16,8 @@ public: Top, Bottom, - Left, Right, + Left, }; BlockSide() = default; @@ -26,31 +26,27 @@ public: operator Value() const { return m_side; } std::array<Vector<3, F32>, 4> face() { + // Winding order: (0, 1, 2) (2, 3, 0) + // Note: OpenGL Coordinate system has a flipped z axis. switch (m_side) { - case Front: - return {{ - {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 1.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f, 1.0f}, - }}; - case Back: - return {{ - {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, - }}; - case Top: - return {{ - {0.0f, 1.0f, 1.0f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, - }}; - case Bottom: - return {{ - {0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, - }}; - case Left: - return {{ - {0.0f, 0.0f, 0.0f},{0.0f, 0.0f, 1.0f},{0.0f, 1.0f, 1.0f}, {0.0f, 1.0f, 0.0f}, - }}; - case Right: - return {{ - {1.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, - }}; + case Front: return {{ + {0, 1, 1}, {0, 0, 1}, {1, 0, 1}, {1, 1, 1} + }}; + case Back: return {{ + {0, 1, 0}, {1, 1, 0}, {1, 0, 0}, {0, 0, 0} + }}; + case Top: return {{ + {0, 1, 1}, {1, 1, 1}, {1, 1, 0}, {0, 1, 0} + }}; + case Bottom: return {{ + {0, 0, 1}, {0, 0, 0}, {1, 0, 0}, {1, 0, 1} + }}; + case Right: return {{ + {1, 1, 0}, {1, 1, 1}, {1, 0, 1}, {1, 0, 0} + }}; + case Left: return {{ + {0, 1, 0}, {0, 0, 0}, {0, 0, 1}, {0, 1, 1} + }}; } } diff --git a/src/World/Generation/ChunkMeshing.cpp b/src/World/Generation/ChunkMeshing.cpp index 8996169..8944c53 100644 --- a/src/World/Generation/ChunkMeshing.cpp +++ b/src/World/Generation/ChunkMeshing.cpp @@ -1,6 +1,35 @@ #include "ChunkMeshing.hpp" -namespace MC::World::Generation { +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<Vector<2, F32>, 4> face_tex_coords(BlockType type, BlockSide side) { U8 atlas_width = 4; @@ -9,15 +38,21 @@ std::array<Vector<2, F32>, 4> face_tex_coords(BlockType type, BlockSide side) { Real width_step = 1.0f / atlas_width; Real height_step = 1.0f / atlas_height; - auto block_coords = [=](U8 x, U8 y) { + auto block_coords = [=](U8 x, U8 y) -> std::array<Vector<2, F32>, 4> { auto t = y * height_step; auto l = x * width_step; auto b = t + height_step; auto r = l + width_step; - return std::array<Vector<2, F32>, 4>{{ - {l, b}, {r, b}, {r, t}, {l, t}, - }}; + // 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) { @@ -25,15 +60,15 @@ std::array<Vector<2, F32>, 4> face_tex_coords(BlockType type, BlockSide side) { 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 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); @@ -43,26 +78,26 @@ std::array<Vector<2, F32>, 4> face_tex_coords(BlockType type, BlockSide side) { 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 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 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); @@ -84,76 +119,101 @@ std::array<Vector<3, F32>, 4> face_normals(BlockSide side) { } Bool is_face_visible(Chunk& chunk, const ChunkMeshing::ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) { +std::array<F32, 4> face_ao_values(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) { + std::array<Vector<3, I32>, 4> offsets{}; + switch (side) { + case BlockSide::Front: + offsets = {{{0, 1, 1}, {-1, 0, 1}, {0, -1, 1}, {1, 0, 1}}}; // works! + break; + case BlockSide::Back: + offsets = {{{-1, 0, -1}, {0, 1, -1}, {1, 0, -1}, {0, -1, -1}}}; // works! + break; + case BlockSide::Top: + offsets = {{{-1, 1, 0}, {0, 1, 1}, {1, 1, 0}, {0, 1, -1}}}; + break; + case BlockSide::Bottom: + offsets = {{{0, -1, 1}, {-1, -1, 0}, {0, -1, -1}, {1, -1, 0}}}; + break; + case BlockSide::Right: + offsets = {{{1, 0, -1}, {1, 1, 0}, {1, 0, 1}, {1, -1, 0}}}; + break; + case BlockSide::Left: + offsets = {{{-1, 1, 0}, {-1, 0, -1}, {-1, -1, 0}, {-1, 0, 1}}}; + break; + } + + + std::array<F32, 4> vertex_ao{}; + + for (UInt vertex = 0; vertex < 4; vertex++) { + auto a = offsets[vertex]; + auto b = offsets[(vertex + 1) % 4]; + + 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()}); + + F32 occlusion_a = block_a.empty() ? 0 : 1; + F32 occlusion_b = block_b.empty() ? 0 : 1; + + vertex_ao[vertex] = (occlusion_a + occlusion_b) / 2; + } + + 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::Left: - offset[0] = -1; - break; - case BlockSide::Right: - offset[0] = 1; - break; + 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{ - (I32)x + offset.x(), (I32)y + offset.y(), (I32)z + offset.z(), + x + offset.x(), y + offset.y(), z + offset.z(), }; - Chunk* chunk_to_ask; - - if (neighbor_pos.z() < 0) { - chunk_to_ask = &neighbors.north; - neighbor_pos.z() += Chunk::Width; - } else if (neighbor_pos.x() >= (I32)Chunk::Width) { - chunk_to_ask = &neighbors.east; - neighbor_pos.x() -= Chunk::Width; - } else if (neighbor_pos.z() >= (I32)Chunk::Width) { - chunk_to_ask = &neighbors.south; - neighbor_pos.z() -= Chunk::Width; - } else if (neighbor_pos.x() < 0) { - chunk_to_ask = &neighbors.west; - neighbor_pos.x() += Chunk::Width; - } else { - chunk_to_ask = &chunk; - } - - auto [neighbor] = chunk_to_ask->get(neighbor_pos.x(), neighbor_pos.y(), neighbor_pos.z()); + auto [neighbor] = get_block_wrapping(chunk, neighbors, neighbor_pos); return neighbor.is_transparent(); } -GFX::Mesh ChunkMeshing::create_mesh_for_chunk(Chunk& chunk, const ChunkNeighbors& neighbors) { +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 type = chunk.get(x, y, z).type; - if (type == BlockType::Air) { + 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)) { + 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(type, 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}; @@ -164,14 +224,15 @@ GFX::Mesh ChunkMeshing::create_mesh_for_chunk(Chunk& chunk, const ChunkNeighbors 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()); - indices.insert(indices.end(), {s, s + 1, s + 3, s + 1, s + 2, s + 3}); + 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}, + {positions, normals, tex_coords, ambient_occlusion_values}, indices, }; } diff --git a/src/World/Generation/ChunkMeshing.hpp b/src/World/Generation/ChunkMeshing.hpp index 6ec77af..dd9446b 100644 --- a/src/World/Generation/ChunkMeshing.hpp +++ b/src/World/Generation/ChunkMeshing.hpp @@ -5,7 +5,10 @@ namespace MC::World::Generation::ChunkMeshing { -struct ChunkNeighbors { Chunk &north, &east, &south, &west; }; +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); } diff --git a/src/World/World.cpp b/src/World/World.cpp index a0b2b0d..d77ebe8 100644 --- a/src/World/World.cpp +++ b/src/World/World.cpp @@ -114,23 +114,28 @@ U64 World::timestamp() { void World::try_to_create_mesh_for_chunk(ChunkData& data) { auto index = data.index; - auto north = get({index.x, index.y - 1}); - auto east = get({index.x + 1, index.y}); - auto south = get({index.x, index.y + 1}); - auto west = get({index.x - 1, index.y}); + UInt neighbor_index = 0; + std::array<Chunk*, 8> neighbors; + for (I32 x = -1; x <= 1; x++) { + for (I32 y = -1; y <= 1; y++) { + if (x == 0 && y == 0) continue; - auto no_terrain = [](const ChunkData& d){ - return !d.chunk.has_value(); - }; + auto& neighbor_data = get({index.x + x, index.y + y}); + if (!neighbor_data.chunk.has_value()) return; // All neighbors need to be generated first. - if (no_terrain(north) || no_terrain(east) || no_terrain(south) || no_terrain(west)) { - return; + neighbors[neighbor_index++] = &neighbor_data.chunk.value(); + } } - data.mesh_data = Generation::ChunkMeshing::create_mesh_for_chunk( - data.chunk.value(), - {north.chunk.value(), east.chunk.value(), south.chunk.value(), west.chunk.value()} - ); + // Layout of neighboring chunks in `neighbors` array: + // (-1; -1) > (-1; 0) > (-1; 1) > (0; -1) + // ( 0; 1) > ( 1; -1) > ( 1; 0) > (1; 1) + Generation::ChunkMeshing::ChunkNeighbors chunk_neighbors { + neighbors[3], neighbors[6], neighbors[4], neighbors[1], + neighbors[5], neighbors[7], neighbors[2], neighbors[0], + }; + + data.mesh_data = create_mesh_for_chunk(data.chunk.value(), chunk_neighbors); data.mesh = GFX::Binder::load(data.mesh_data.value()); data.status = ChunkStatus::Done; } diff --git a/src/World/World.hpp b/src/World/World.hpp index ae9f2a4..a094526 100644 --- a/src/World/World.hpp +++ b/src/World/World.hpp @@ -1,6 +1,5 @@ #pragma once -#include <memory> #include <unordered_map> #include <unordered_set> #include "Generation/Generator.hpp" |
