summaryrefslogtreecommitdiff
path: root/src/World
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2023-06-30 15:15:00 +0200
committerMel <einebeere@gmail.com>2023-06-30 15:15:00 +0200
commit424d00eaf7335e1c6427f40260d55782c3fd902c (patch)
tree62550b085078d84c8a48cbb01f4f7738bfeeb3da /src/World
parent6d61b17c4289185d59d37caae8070a40e91fba40 (diff)
downloadmeowcraft-424d00eaf7335e1c6427f40260d55782c3fd902c.tar.zst
meowcraft-424d00eaf7335e1c6427f40260d55782c3fd902c.zip
Avoid per-frame chunk copies and don't render block faces between chunks
Diffstat (limited to 'src/World')
-rw-r--r--src/World/ChunkMeshing.cpp170
-rw-r--r--src/World/ChunkMeshing.hpp11
-rw-r--r--src/World/World.cpp67
-rw-r--r--src/World/World.hpp12
4 files changed, 235 insertions, 25 deletions
diff --git a/src/World/ChunkMeshing.cpp b/src/World/ChunkMeshing.cpp
new file mode 100644
index 0000000..3e50427
--- /dev/null
+++ b/src/World/ChunkMeshing.cpp
@@ -0,0 +1,170 @@
+#include "ChunkMeshing.hpp"
+
+namespace MC::World {
+
+std::array<Vector<2>, 4> face_tex_coords(BlockType type, BlockSide side) {
+ uint8_t atlas_width = 4;
+ uint8_t atlas_height = 4;
+
+ float width_step = 1.0f / atlas_width;
+ float height_step = 1.0f / atlas_height;
+
+ auto block_coords = [=](uint8_t x, uint8_t y) {
+ 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>, 4>{{
+ {l, b}, {r, b}, {r, t}, {l, t},
+ }};
+ };
+
+ 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(0, 1);
+ case BlockType::Water:
+ return block_coords(1, 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::Air:
+ return {};
+ }
+}
+
+std::array<Vector<3>, 4> face_normals(BlockSide side) {
+ auto is_side = [=](BlockSide s) -> float { return s == side; };
+
+ Vector<3> 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};
+}
+
+bool is_face_visible(Chunk& chunk, ChunkMeshing::ChunkNeighbors neighbors, uint32_t x, uint32_t y, uint32_t z, BlockSide side) {
+ Vector<3, int32_t> 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;
+ }
+
+ Vector<3, int32_t> neighbor_pos{
+ (int32_t)x + offset.x(), (int32_t)y + offset.y(), (int32_t)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() >= (int32_t)Chunk::Width) {
+ chunk_to_ask = &neighbors.east;
+ neighbor_pos.x() -= Chunk::Width;
+ } else if (neighbor_pos.z() >= (int32_t)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());
+ if (neighbor.type == BlockType::Air) {
+ return true;
+ }
+
+ return false;
+}
+
+GFX::Mesh ChunkMeshing::create_mesh_for_chunk(Chunk& chunk, ChunkMeshing::ChunkNeighbors neighbors) {
+ std::vector<Vector<3>> positions{};
+ std::vector<Vector<3>> normals{};
+ std::vector<Vector<2>> tex_coords{};
+ std::vector<uint32_t> 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) {
+ 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(type, side);
+
+ for (auto& position : side_positions) {
+ position = position + Vector<3>{static_cast<float>(x), static_cast<float>(y), static_cast<float>(z)};
+ }
+
+ uint32_t 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());
+ indices.insert(indices.end(), {s, s + 1, s + 3, s + 1, s + 2, s + 3});
+ }
+ }
+ }
+ }
+
+ return {
+ {positions, normals, tex_coords},
+ indices,
+ };
+}
+
+} \ No newline at end of file
diff --git a/src/World/ChunkMeshing.hpp b/src/World/ChunkMeshing.hpp
new file mode 100644
index 0000000..1348817
--- /dev/null
+++ b/src/World/ChunkMeshing.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "../GFX/Mesh.hpp"
+#include "Chunk.hpp"
+
+namespace MC::World::ChunkMeshing {
+
+struct ChunkNeighbors { Chunk &north, &east, &south, &west; };
+GFX::Mesh create_mesh_for_chunk(Chunk& chunk, ChunkNeighbors neighbors);
+
+}
diff --git a/src/World/World.cpp b/src/World/World.cpp
index 03ce60e..af70766 100644
--- a/src/World/World.cpp
+++ b/src/World/World.cpp
@@ -1,8 +1,9 @@
#include "World.hpp"
+#include "ChunkMeshing.hpp"
namespace MC::World {
-std::vector<World::ChunkData> World::get_visible_chunks(Vector<3> position) {
+std::vector<World::ChunkData*> World::get_visible_chunks(Vector<3> position) {
auto finished_chunks = load_finished_chunks_from_queue();
auto visible_chunks = get_visible_chunk_indices(position);
@@ -19,19 +20,22 @@ std::vector<World::ChunkData> World::get_visible_chunks(Vector<3> position) {
m_visible_chunks = visible_chunks;
}
- std::vector<ChunkData> chunks{};
+ std::vector<ChunkData*> chunks{};
chunks.reserve(visible_chunks.size());
for (auto index : visible_chunks) {
auto& data = get(index);
+ if (data.status == ChunkStatus::NeedsMesh) {
+ try_to_create_mesh_for_chunk(data);
+ }
if (data.status == ChunkStatus::Done) {
- chunks.push_back(data);
+ chunks.push_back(&data);
}
}
return chunks;
}
-Chunk* World::get_chunk_for_positon(Vector<3> position) {
+Chunk* World::get_chunk_for_position(Vector<3> position) {
int32_t x = std::round(position.x() / Chunk::Width);
int32_t y = std::round(position.z() / Chunk::Width);
auto& data = get({x, y});
@@ -44,17 +48,9 @@ Chunk* World::get_chunk_for_positon(Vector<3> position) {
void World::process_chunk_visibility_updates(std::unordered_set<ChunkIndex>& new_chunks, Vector<3> player) {
for (auto new_index: new_chunks) {
auto& data = get(new_index);
- switch (data.status) {
- case ChunkStatus::Empty:
- request_generation(new_index, player.distance(new_index.middle()));
- data.status = ChunkStatus::WaitingForGeneration;
- break;
- case ChunkStatus::Done:
- data.mesh = GFX::Binder::load(data.chunk.value().mesh());
- data.status = ChunkStatus::Done;
- break;
- case ChunkStatus::WaitingForGeneration:
- break;
+ if (data.status == ChunkStatus::Empty) {
+ request_generation(new_index, player.distance(new_index.middle()));
+ data.status = ChunkStatus::WaitingForGeneration;
}
}
}
@@ -79,11 +75,9 @@ std::unordered_set<ChunkIndex> World::get_visible_chunk_indices(Vector<3> positi
std::unordered_set<ChunkIndex> World::load_finished_chunks_from_queue() {
std::unordered_set<ChunkIndex> indices;
auto results = m_queue.done();
- for (auto& result : results) {
- auto& data = get(result.id);
- data.chunk = {result.res};
- data.status = ChunkStatus::Done;
- indices.insert(result.id);
+ for (auto& [id, res] : results) {
+ get(id) = {id, ChunkStatus::NeedsMesh, {res}};
+ indices.insert(id);
}
return indices;
@@ -100,11 +94,42 @@ World::ChunkData& World::get(ChunkIndex index) {
if (entry == m_chunks.end()) {
ChunkData data{index, ChunkStatus::Empty};
- m_chunks.insert({index, data});
+ m_chunks.insert({index, std::move(data)});
return m_chunks.at(index);
}
return entry->second;
}
+void World::try_to_create_mesh_for_chunk(World::ChunkData& data) {
+// Chunk empty{0, 0};
+// data.mesh_data = ChunkMeshing::create_mesh_for_chunk(data.chunk.value(), {
+// empty, empty, empty, empty,
+// });
+// data.mesh = GFX::Binder::load(data.mesh_data.value());
+// data.status = ChunkStatus::Done;
+
+ 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});
+
+ auto no_terrain = [](ChunkData& data){
+ return !data.chunk.has_value();
+ };
+
+ if (no_terrain(north) || no_terrain(east) || no_terrain(south) || no_terrain(west)) {
+ return;
+ }
+
+ data.mesh_data = ChunkMeshing::create_mesh_for_chunk(
+ data.chunk.value(),
+ {north.chunk.value(), east.chunk.value(), south.chunk.value(), west.chunk.value()}
+ );
+ data.mesh = GFX::Binder::load(data.mesh_data.value());
+ data.status = ChunkStatus::Done;
+}
+
} \ No newline at end of file
diff --git a/src/World/World.hpp b/src/World/World.hpp
index a5f73ec..03ed517 100644
--- a/src/World/World.hpp
+++ b/src/World/World.hpp
@@ -12,11 +12,12 @@ namespace MC::World {
class World {
public:
- World() : m_queue(8), m_chunks(), m_visible_chunks() {}
+ World() : m_queue(8) {}
enum class ChunkStatus {
Empty,
WaitingForGeneration,
+ NeedsMesh,
Done
};
@@ -24,11 +25,12 @@ public:
ChunkIndex index;
ChunkStatus status;
std::optional<Chunk> chunk = {};
+ std::optional<GFX::Mesh> mesh_data = {};
std::optional<GFX::BindableMesh> mesh = {};
};
- std::vector<ChunkData> get_visible_chunks(Vector<3> position);
- Chunk* get_chunk_for_positon(Vector<3> position);
+ std::vector<ChunkData*> get_visible_chunks(Vector<3> position);
+ Chunk* get_chunk_for_position(Vector<3> position);
private:
std::unordered_set<ChunkIndex> get_visible_chunk_indices(Vector<3> position) const;
std::unordered_set<ChunkIndex> load_finished_chunks_from_queue();
@@ -37,13 +39,15 @@ private:
ChunkData& get(ChunkIndex index);
- uint8_t m_view_distance_radius = 12;
+ uint8_t m_view_distance_radius = 13;
Compute::Queue<Chunk, ChunkIndex> m_queue;
Generator m_generator;
std::unordered_map<ChunkIndex, ChunkData> m_chunks;
std::unordered_set<ChunkIndex> m_visible_chunks;
+
+ void try_to_create_mesh_for_chunk(ChunkData& data);
};
}