diff options
| author | Mel <einebeere@gmail.com> | 2023-06-30 15:15:00 +0200 |
|---|---|---|
| committer | Mel <einebeere@gmail.com> | 2023-06-30 15:15:00 +0200 |
| commit | 424d00eaf7335e1c6427f40260d55782c3fd902c (patch) | |
| tree | 62550b085078d84c8a48cbb01f4f7738bfeeb3da | |
| parent | 6d61b17c4289185d59d37caae8070a40e91fba40 (diff) | |
| download | meowcraft-424d00eaf7335e1c6427f40260d55782c3fd902c.tar.zst meowcraft-424d00eaf7335e1c6427f40260d55782c3fd902c.zip | |
Avoid per-frame chunk copies and don't render block faces between chunks
| -rw-r--r-- | CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/GFX/Binder.cpp | 8 | ||||
| -rw-r--r-- | src/GFX/Binder.hpp | 6 | ||||
| -rw-r--r-- | src/GFX/Mesh.cpp | 4 | ||||
| -rw-r--r-- | src/GFX/Mesh.hpp | 4 | ||||
| -rw-r--r-- | src/Math/Vector.hpp | 29 | ||||
| -rw-r--r-- | src/World/ChunkMeshing.cpp | 170 | ||||
| -rw-r--r-- | src/World/ChunkMeshing.hpp | 11 | ||||
| -rw-r--r-- | src/World/World.cpp | 67 | ||||
| -rw-r--r-- | src/World/World.hpp | 12 | ||||
| -rw-r--r-- | src/main.cpp | 6 |
11 files changed, 260 insertions, 59 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 61fa09b..75f5a2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,8 @@ add_executable(meowcraft src/Compute/Queue.hpp src/Math/Constants.hpp src/Math/Sigmoid.hpp + src/World/ChunkMeshing.cpp + src/World/ChunkMeshing.hpp ) target_link_libraries(meowcraft glfw GLEW::GLEW) diff --git a/src/GFX/Binder.cpp b/src/GFX/Binder.cpp index 0dce06d..e8fc559 100644 --- a/src/GFX/Binder.cpp +++ b/src/GFX/Binder.cpp @@ -4,7 +4,7 @@ namespace MC::GFX { -BindableMesh Binder::load(Mesh mesh) { +BindableMesh Binder::load(const Mesh& mesh) { auto vao = create_vao(); if (!mesh.indices().empty()) { store_indices(mesh.indices().data(), mesh.indices().size()); @@ -30,14 +30,14 @@ uint32_t Binder::create_vao() { glGenVertexArrays(1, &vao); glBindVertexArray(vao); - return static_cast<uint32_t>(vao); + return vao; } void Binder::unbind_vao() { glBindVertexArray(0); } -void Binder::store_indices(uint32_t* indices, size_t indices_size) { +void Binder::store_indices(const uint32_t* indices, size_t indices_size) { GLuint ebo; glGenBuffers(1, &ebo); @@ -46,7 +46,7 @@ void Binder::store_indices(uint32_t* indices, size_t indices_size) { } -void Binder::store_in_attribute_list(uint32_t attribute, int attribute_size, int type_size, void* data, long data_size) { +void Binder::store_in_attribute_list(uint32_t attribute, int attribute_size, int type_size, const void* data, long data_size) { assert(type_size == sizeof(float)); GLuint vbo; diff --git a/src/GFX/Binder.hpp b/src/GFX/Binder.hpp index d8a2be9..92faebe 100644 --- a/src/GFX/Binder.hpp +++ b/src/GFX/Binder.hpp @@ -35,14 +35,14 @@ class Binder { public: Binder() = default;; - static BindableMesh load(Mesh mesh); + static BindableMesh load(const Mesh& mesh); private: static uint32_t create_vao(); static void unbind_vao(); - static void store_in_attribute_list(uint32_t attribute, int attribute_size, int type_size, void* data, long data_size); - static void store_indices(uint32_t* indices, size_t indices_size); + static void store_in_attribute_list(uint32_t attribute, int attribute_size, int type_size, const void* data, long data_size); + static void store_indices(const uint32_t* indices, size_t indices_size); }; } \ No newline at end of file diff --git a/src/GFX/Mesh.cpp b/src/GFX/Mesh.cpp index b0271bb..1869622 100644 --- a/src/GFX/Mesh.cpp +++ b/src/GFX/Mesh.cpp @@ -2,11 +2,11 @@ namespace MC::GFX { -std::vector<uint32_t> Mesh::indices() { +const std::vector<uint32_t>& Mesh::indices() const { return m_indices; } -std::vector<Mesh::Attribute> Mesh::attributes() { +const std::vector<Mesh::Attribute>& Mesh::attributes() const { return m_attributes; } diff --git a/src/GFX/Mesh.hpp b/src/GFX/Mesh.hpp index bfe8eab..1d14ba2 100644 --- a/src/GFX/Mesh.hpp +++ b/src/GFX/Mesh.hpp @@ -49,8 +49,8 @@ public: ) : m_attributes(std::move(attributes)), m_indices() {}; - std::vector<uint32_t> indices(); - std::vector<Attribute> attributes(); + const std::vector<uint32_t>& indices() const; + const std::vector<Attribute>& attributes() const; private: std::vector<Attribute> m_attributes; diff --git a/src/Math/Vector.hpp b/src/Math/Vector.hpp index ccfa556..260891d 100644 --- a/src/Math/Vector.hpp +++ b/src/Math/Vector.hpp @@ -2,17 +2,14 @@ #include <cstddef> #include <sstream> -#include <iostream> -#include <iterator> #include <cmath> #include <functional> template <size_t S, typename T = float> struct Vector { -public: Vector(): elements{} {}; - template<typename ...Args, typename std::enable_if<sizeof...(Args) == S, int>::type = 0> + template<typename ...Args, std::enable_if_t<sizeof...(Args) == S, int> = 0> Vector<S, T>(Args... args) : elements{ args... } {}; Vector<S, T>(T values[S]) { @@ -117,25 +114,17 @@ public: return map([=](auto x) { return x / scalar; }); } - T x() const { - static_assert(S > 0); - return elements[0]; - } + T& x() { static_assert(S > 0); return elements[0]; } + const T& x() const { static_assert(S > 0); return elements[0]; } - T y() const { - static_assert(S > 1); - return elements[1]; - } + T& y() { static_assert(S > 1); return elements[1]; } + const T& y() const { static_assert(S > 1); return elements[1]; } - T z() const { - static_assert(S > 2); - return elements[2]; - } + T& z() { static_assert(S > 2); return elements[2]; } + const T& z() const { static_assert(S > 2); return elements[2]; } - T w() const { - static_assert(S > 3); - return elements[3]; - } + T& w() { static_assert(S > 3); return elements[3]; } + const T& w() const { static_assert(S > 3); return elements[3]; } std::string string() const { std::stringstream str{}; 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); }; } diff --git a/src/main.cpp b/src/main.cpp index b0939c1..a264f3e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -102,10 +102,10 @@ void run() { glClearColor(sky_color.x(), sky_color.y(), sky_color.z(), 1.0f); // #DBDBDB glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - for (auto& chunk : world.get_visible_chunks(camera.position())) { - auto model = Math::MVP::model(chunk.chunk.value().position(), {}); + for (auto chunk : world.get_visible_chunks(camera.position())) { + auto model = Math::MVP::model(chunk->chunk.value().position(), {}); model_uniform.set(model); - render(chunk.mesh.value(), texture); + render(chunk->mesh.value(), texture); } time++; |
