summary refs log tree commit diff
path: root/src
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
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')
-rw-r--r--src/GFX/Binder.cpp8
-rw-r--r--src/GFX/Binder.hpp6
-rw-r--r--src/GFX/Mesh.cpp4
-rw-r--r--src/GFX/Mesh.hpp4
-rw-r--r--src/Math/Vector.hpp29
-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
-rw-r--r--src/main.cpp6
10 files changed, 258 insertions, 59 deletions
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++;