summary refs log tree commit diff
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2023-07-22 17:35:00 +0200
committerMel <einebeere@gmail.com>2023-07-22 17:35:00 +0200
commit2eef7cf49b7a15559ee7bb6719411bcf67386213 (patch)
tree11eb7a4f437da7bfdde620c10a043960fd423cfb
parent23d88e5f1c8f0c8652a07050fcfa8ff126e85d4a (diff)
downloadmeowcraft-2eef7cf49b7a15559ee7bb6719411bcf67386213.tar.zst
meowcraft-2eef7cf49b7a15559ee7bb6719411bcf67386213.zip
Propagation in lighting system
-rw-r--r--CMakeLists.txt27
-rw-r--r--src/World/BlockType.hpp2
-rw-r--r--src/World/Chunk.cpp12
-rw-r--r--src/World/Chunk.hpp12
-rw-r--r--src/World/ChunkDimensions.hpp14
-rw-r--r--src/World/ChunkIndex.hpp11
-rw-r--r--src/World/ChunkRegistry.cpp28
-rw-r--r--src/World/ChunkRegistry.hpp42
-rw-r--r--src/World/Generation/Lighting.cpp54
-rw-r--r--src/World/Generation/Lighting.hpp30
-rw-r--r--src/World/Position.hpp45
-rw-r--r--src/World/World.cpp50
-rw-r--r--src/World/World.hpp37
13 files changed, 267 insertions, 97 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 20992a0..58ddaf0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -45,25 +45,20 @@ add_executable(meowcraft
     src/Compute/Queue.hpp
     src/Math/Constants.hpp
     src/Math/Sigmoid.hpp
-    src/World/Generation/ChunkMeshing.cpp
-    src/World/Generation/ChunkMeshing.hpp
+    src/World/Generation/ChunkMeshing.cpp src/World/Generation/ChunkMeshing.hpp
     src/Math/Tensor.hpp
-    src/World/Generation/Decoration.cpp
-    src/World/Generation/Decoration.hpp
-    src/Math/Random.cpp
-    src/Math/Random.hpp
-    src/World/Clouds.cpp
-    src/World/Clouds.hpp
+    src/World/Generation/Decoration.cpp src/World/Generation/Decoration.hpp
+    src/Math/Random.cpp src/Math/Random.hpp
+    src/World/Clouds.cpp src/World/Clouds.hpp
     src/GFX/Util/MeshBuilder.hpp
-    src/GFX/Util/Primitives.cpp
-    src/GFX/Util/Primitives.hpp
+    src/GFX/Util/Primitives.cpp src/GFX/Util/Primitives.hpp
     src/Math/AABB.hpp
-    src/Time.cpp
-    src/Time.hpp
-    src/World/Generation/Lighting.cpp
-    src/World/Generation/Lighting.hpp
-    src/World/Generation/ChunkNeighbors.hpp
-    src/World/Generation/ChunkNeighbors.cpp
+    src/Time.cpp src/Time.hpp
+    src/World/Generation/Lighting.cpp src/World/Generation/Lighting.hpp
+    src/World/Generation/ChunkNeighbors.hpp src/World/Generation/ChunkNeighbors.cpp
+    src/World/ChunkRegistry.cpp src/World/ChunkRegistry.hpp
+    src/World/Position.hpp
+    src/World/ChunkDimensions.hpp
 )
 target_link_libraries(meowcraft glfw GLEW::GLEW)
 
diff --git a/src/World/BlockType.hpp b/src/World/BlockType.hpp
index a63d96a..2a9a652 100644
--- a/src/World/BlockType.hpp
+++ b/src/World/BlockType.hpp
@@ -35,7 +35,7 @@ public:
         case Water:
             return 0.05;
         case Leaves:
-            return 0.2;
+            return 0.3;
         default:
             return 1.0;
         }
diff --git a/src/World/Chunk.cpp b/src/World/Chunk.cpp
index ab727bd..31a2c90 100644
--- a/src/World/Chunk.cpp
+++ b/src/World/Chunk.cpp
@@ -11,10 +11,22 @@ Chunk::BlockData& Chunk::at(U32 x, U32 y, U32 z) {
     return m_blocks.at(pos(x, y, z));
 }
 
+const Chunk::BlockData& Chunk::at(Position::BlockLocal pos) const {
+    return at(pos.x(), pos.y(), pos.z());
+}
+
+Chunk::BlockData& Chunk::at(Position::BlockLocal pos) {
+    return at(pos.x(), pos.y(), pos.z());
+}
+
 Bool Chunk::is_empty(U32 x, U32 y, U32 z) const {
     return at(x, y, z).empty();
 }
 
+ChunkIndex Chunk::index() const {
+    return m_index;
+}
+
 Vector<3> Chunk::position() const {
     return m_position;
 }
diff --git a/src/World/Chunk.hpp b/src/World/Chunk.hpp
index 5f4844e..32d3ab3 100644
--- a/src/World/Chunk.hpp
+++ b/src/World/Chunk.hpp
@@ -1,19 +1,23 @@
 #pragma once
 
 #include "../Common/Sizes.hpp"
+#include "ChunkDimensions.hpp"
 #include "BiomeType.hpp"
 #include "BlockType.hpp"
+#include "ChunkIndex.hpp"
+#include "Position.hpp"
 #include "../GFX/Mesh.hpp"
 
 namespace MC::World {
 
 class Chunk {
 public:
-    static constexpr U32 Width = 16;
-    static constexpr U32 Height = 128;
+    static constexpr U32 Width = ChunkDimensions::Width;
+    static constexpr U32 Height = ChunkDimensions::Height;
 
     Chunk(I64 x, I64 y)
         : m_blocks{Width * Height * Width, {BlockType::Air}},
+        m_index(x, y),
         m_position{(Real)x * Width, 0.0f, (Real)y * Width} {}
 
     struct BlockData {
@@ -28,6 +32,8 @@ public:
 
     const BlockData& at(U32 x, U32 y, U32 z) const;
     BlockData& at(U32 x, U32 y, U32 z);
+    const BlockData& at(Position::BlockLocal pos) const;
+    BlockData& at(Position::BlockLocal pos);
 
     Bool is_empty(U32 x, U32 y, U32 z) const;
 
@@ -43,12 +49,14 @@ public:
     void set_details(const Details& details) { m_details = details; }
     Details& details(){ return m_details; }
 
+    ChunkIndex index() const;
     Vector<3> position() const;
 
     static Bool is_valid_position(U32 x, U32 y, U32 z);
 private:
     static U64 pos(U32 x, U32 y, U32 z);
 
+    ChunkIndex m_index;
     Vector<3> m_position;
     std::vector<BlockData> m_blocks;
 
diff --git a/src/World/ChunkDimensions.hpp b/src/World/ChunkDimensions.hpp
new file mode 100644
index 0000000..99f2824
--- /dev/null
+++ b/src/World/ChunkDimensions.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "../Common/Sizes.hpp"
+
+// This file defines chunk dimensions outside of the `Chunk` class.
+// We need this to avoid cyclic dependencies for headers on which Chunk depends,
+// but which need the dimensions, too (i.e. `ChunkIndex`).
+// `Chunk` re-exports these so other units may use `Chunk::Width` and `Chunk::Height` as before.
+namespace MC::World::ChunkDimensions {
+
+static constexpr U32 Width = 16;
+static constexpr U32 Height = 128;
+
+}
\ No newline at end of file
diff --git a/src/World/ChunkIndex.hpp b/src/World/ChunkIndex.hpp
index 7d6fdbf..ed3347b 100644
--- a/src/World/ChunkIndex.hpp
+++ b/src/World/ChunkIndex.hpp
@@ -1,6 +1,9 @@
 #pragma once
 
 #include "../Common/Sizes.hpp"
+#include "../Math/Vector.hpp"
+#include "ChunkDimensions.hpp"
+#include "Position.hpp"
 
 namespace MC::World {
 
@@ -9,7 +12,13 @@ struct ChunkIndex {
     ChunkIndex(I32 x, I32 y) : x(x), y(y) {}
 
     Vector<3> middle() const {
-        return {(x + 0.5f) * Chunk::Width, Chunk::Height / 2.0f, (y + 0.5f) * Chunk::Width};
+        using namespace ChunkDimensions;
+        return {(x + 0.5f) * Width, Height / 2.0f, (y + 0.5f) * Width};
+    }
+
+    Position::BlockWorld world(Position::BlockLocal local) const {
+        using namespace ChunkDimensions;
+        return {x * Width + local.x(), local.y(), y * Width + local.z()};
     }
 
     I32 x, y;
diff --git a/src/World/ChunkRegistry.cpp b/src/World/ChunkRegistry.cpp
new file mode 100644
index 0000000..41fd1b0
--- /dev/null
+++ b/src/World/ChunkRegistry.cpp
@@ -0,0 +1,28 @@
+#include "ChunkRegistry.hpp"
+
+namespace MC::World {
+
+ChunkRegistry::Data& ChunkRegistry::get(ChunkIndex index) {
+    auto entry = m_chunks.find(index);
+    if (entry == m_chunks.end()) {
+        Data data{index, Status::Empty};
+
+        m_chunks.insert({index, std::move(data)});
+        return m_chunks.at(index);
+    }
+
+    return entry->second;
+}
+
+ChunkRegistry::Data& ChunkRegistry::find(Position::BlockWorld pos) {
+    return get({
+        static_cast<I32>(pos.x() / Chunk::Width),
+        static_cast<I32>(pos.z() / Chunk::Width)
+    });
+}
+
+ChunkRegistry::Data& ChunkRegistry::find(ChunkIndex chunk, Position::BlockLocal pos) {
+    return find(chunk.world(pos));
+}
+
+}
\ No newline at end of file
diff --git a/src/World/ChunkRegistry.hpp b/src/World/ChunkRegistry.hpp
new file mode 100644
index 0000000..86e5d06
--- /dev/null
+++ b/src/World/ChunkRegistry.hpp
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <optional>
+#include <unordered_map>
+#include "Chunk.hpp"
+#include "ChunkIndex.hpp"
+#include "Position.hpp"
+#include "../GFX/Binder.hpp"
+
+namespace MC::World {
+
+class ChunkRegistry {
+public:
+    enum class Status {
+        Empty,
+        WaitingForGeneration,
+        WaitingForReification,
+        Done
+    };
+
+    // I think a Chunk entity should just store all this by itself...
+    struct Data {
+        ChunkIndex index;
+        Status status;
+        std::optional<Chunk> chunk = {};
+
+        std::optional<GFX::Mesh> land_mesh_data = {};
+        std::optional<GFX::Mesh> water_mesh_data = {};
+
+        std::optional<GFX::BindableMesh> land_mesh = {};
+        std::optional<GFX::BindableMesh> water_mesh = {};
+    };
+
+    Data& get(ChunkIndex index);
+
+    Data& find(Position::BlockWorld pos);
+    Data& find(ChunkIndex chunk, Position::BlockLocal pos);
+private:
+    std::unordered_map<ChunkIndex, Data> m_chunks;
+};
+
+}
diff --git a/src/World/Generation/Lighting.cpp b/src/World/Generation/Lighting.cpp
index 8df6e2a..39d8320 100644
--- a/src/World/Generation/Lighting.cpp
+++ b/src/World/Generation/Lighting.cpp
@@ -1,20 +1,60 @@
 #include "Lighting.hpp"
 
-namespace MC::World::Generation::Lighting {
+namespace MC::World::Generation {
 
-void light_chunk(Chunk& chunk, ChunkNeighbors& _) {
-    for (UInt x = 0; x < Chunk::Width; x++) {
-        for (UInt z = 0; z < Chunk::Width; z++) {
-            U8 current_light_exposure = LightSun;
-            for (UInt y = Chunk::Height - 1; y != 0; y--) {
+void Lighting::add_chunk(Chunk& chunk) {
+    for (U8 x = 0; x < Chunk::Width; x++) {
+        for (U8 z = 0; z < Chunk::Width; z++) {
+            U8 current_light_exposure = SunBrightness;
+            for (U8 y = Chunk::Height - 1; y != 0; y--) {
                 auto& block = chunk.at(x, y, z);
                 if (!block.type.is_translucent()) break;
 
                 current_light_exposure = (Real)current_light_exposure * (1 - block.type.opacity());
-                block.light = current_light_exposure;
+                add_block(chunk, {x, y, z}, current_light_exposure);
             }
         }
     }
 }
 
+void Lighting::add_block(Chunk& chunk, Position::BlockLocal position, U8 light) {
+    auto& block = chunk.at(position);
+    block.light = light;
+    enqueue(chunk.index(), position, light);
+}
+
+void Lighting::illuminate(ChunkRegistry& chunks) {
+    while (!m_queue.empty()) {
+        auto op = m_queue.front();
+        m_queue.pop();
+
+        process(chunks, op);
+    }
+}
+
+void Lighting::enqueue(ChunkIndex chunk, Position::BlockLocal position, U8 origin) {
+    m_queue.push({chunk, position, origin});
+}
+
+void Lighting::process(ChunkRegistry& chunks, Operation op) {
+    auto& chunk_data = chunks.get(op.chunk);
+    auto& chunk = chunk_data.chunk.value();
+
+    for (auto direction : Position::axis_directions) {
+        auto neighbor_pos = op.position.offset(direction);
+        if (!neighbor_pos.fits_within_chunk()) continue;
+
+        auto& neighbor = chunk.at(neighbor_pos);
+        if (!neighbor.type.is_translucent()) continue;
+
+        U8 falloff = (neighbor.type.opacity() + 1) * DefaultFalloff;
+        U8 target = std::max(op.origin - falloff, 0);
+
+        if (neighbor.light < target) {
+            neighbor.light = target;
+            enqueue(op.chunk, neighbor_pos, target);
+        }
+    }
+}
+
 }
diff --git a/src/World/Generation/Lighting.hpp b/src/World/Generation/Lighting.hpp
index 7bb8fcc..72af0e1 100644
--- a/src/World/Generation/Lighting.hpp
+++ b/src/World/Generation/Lighting.hpp
@@ -1,13 +1,33 @@
 #pragma once
 
+#include <queue>
 #include "../Chunk.hpp"
-#include "ChunkNeighbors.hpp"
+#include "../ChunkIndex.hpp"
+#include "../ChunkRegistry.hpp"
+#include "../Position.hpp"
 
-namespace MC::World::Generation::Lighting {
+namespace MC::World::Generation {
 
-constexpr U8 LightSun = 200;
-constexpr U8 LightTorch = 100;
+class Lighting {
+public:
+    void add_chunk(Chunk& chunk);
+    void add_block(Chunk& chunk, Position::BlockLocal position, U8 light);
 
-void light_chunk(Chunk& chunk, ChunkNeighbors& neighbors);
+    void illuminate(ChunkRegistry& chunks);
+private:
+    static constexpr U8 SunBrightness = 200;
+    static constexpr U8 DefaultFalloff = 10;
+
+    struct Operation {
+        ChunkIndex chunk;
+        Position::BlockLocal position;
+        U8 origin;
+    };
+
+    void enqueue(ChunkIndex chunk, Position::BlockLocal position, U8 origin);
+    void process(ChunkRegistry& chunks, Operation op);
+
+    std::queue<Operation> m_queue;
+};
 
 }
diff --git a/src/World/Position.hpp b/src/World/Position.hpp
new file mode 100644
index 0000000..49cbbb6
--- /dev/null
+++ b/src/World/Position.hpp
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "array"
+#include "../Math/Vector.hpp"
+
+namespace MC::World::Position {
+
+// Position within entire world.
+using World = Vector<3>;
+
+// Offset between block positions within single chunk.
+class OffsetBlock : public Vector<3, I8> {
+public:
+    OffsetBlock(I8 x, I8 y, I8 z) : Vector(x, y, z) {}
+    Bool fits_within_chunk() const {
+        using namespace ChunkDimensions;
+        return x() >= 0 && x() < Width && y() >= 0 && y() < Height && z() >= 0 && z() < Width;
+    }
+};
+
+// Position of a block within entire world.
+class BlockWorld : public Vector<3, I64> {
+public:
+    BlockWorld(I64 x, I64 y, I64 z) : Vector(x, y, z) {}
+};
+
+// Position of a block within single chunk.
+class BlockLocal : public Vector<3, U8> {
+public:
+    BlockLocal(U8 x, U8 y, U8 z) : Vector(x, y, z) {}
+    BlockLocal(OffsetBlock offset) : BlockLocal(offset.x(), offset.y(), offset.z()) {}
+    OffsetBlock offset(OffsetBlock by) {
+        return {
+            static_cast<I8>(x() + by.x()),
+            static_cast<I8>(y() + by.y()),
+            static_cast<I8>(z() + by.z())
+        };
+    }
+};
+
+const std::array<OffsetBlock, 6> axis_directions = {{
+    {1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1},
+}};
+
+}
diff --git a/src/World/World.cpp b/src/World/World.cpp
index 912a5cf..5548866 100644
--- a/src/World/World.cpp
+++ b/src/World/World.cpp
@@ -5,25 +5,25 @@
 
 namespace MC::World {
 
-std::vector<World::ChunkData*> World::get_visible_chunks(Vector<3> position) {
+std::vector<ChunkRegistry::Data*> World::get_visible_chunks(Vector<3> position) {
     load_finished_chunks_from_queue();
 
     auto visible_chunks = get_visible_chunk_indices(position);
 
-    std::vector<ChunkData*> chunks{};
+    std::vector<ChunkRegistry::Data*> chunks{};
     chunks.reserve(visible_chunks.size());
     for (auto index : visible_chunks) {
-        auto& data = get(index);
-        if (data.status == ChunkStatus::Empty) {
+        auto& data = m_registry.get(index);
+        if (data.status == ChunkRegistry::Status::Empty) {
             request_generation(index, position.distance(index.middle()));
-            data.status = ChunkStatus::WaitingForGeneration;
+            data.status = ChunkRegistry::Status::WaitingForGeneration;
             continue;
         }
 
-        if (data.status == ChunkStatus::WaitingForReification) {
+        if (data.status == ChunkRegistry::Status::WaitingForReification) {
             try_to_reify_chunk(data);
         }
-        if (data.status == ChunkStatus::Done) {
+        if (data.status == ChunkRegistry::Status::Done) {
             chunks.push_back(&data);
         }
     }
@@ -31,16 +31,6 @@ std::vector<World::ChunkData*> World::get_visible_chunks(Vector<3> position) {
     return chunks;
 }
 
-Chunk* World::get_chunk_for_position(Vector<3> position) {
-    I32 x = std::round(position.x() / Chunk::Width);
-    I32 y = std::round(position.z() / Chunk::Width);
-    auto& data = get({x, y});
-    if (data.chunk.has_value()) {
-        return &data.chunk.value();
-    }
-    return nullptr;
-}
-
 std::vector<ChunkIndex> World::get_visible_chunk_indices(const Vector<3> position) const {
     I32 center_x = std::round(position.x() / Chunk::Width);
     I32 center_y = std::round(position.z() / Chunk::Width);
@@ -66,7 +56,7 @@ std::vector<ChunkIndex> World::get_visible_chunk_indices(const Vector<3> positio
 void World::load_finished_chunks_from_queue() {
     auto results = m_queue.done();
     for (auto& [id, res] : results) {
-        get(id) = {id, ChunkStatus::WaitingForReification, {res.chunk}};
+        m_registry.get(id) = {id, ChunkRegistry::Status::WaitingForReification, {res.chunk}};
         log_chunk_time(res.generation_duration);
     }
 }
@@ -79,20 +69,9 @@ void World::request_generation(ChunkIndex index, Real priority) {
     });
 }
 
-World::ChunkData& World::get(ChunkIndex index) {
-    auto entry = m_chunks.find(index);
-    if (entry == m_chunks.end()) {
-        ChunkData data{index, ChunkStatus::Empty};
-
-        m_chunks.insert({index, std::move(data)});
-        return m_chunks.at(index);
-    }
-
-    return entry->second;
-}
-
-void World::try_to_reify_chunk(ChunkData& data) {
+void World::try_to_reify_chunk(ChunkRegistry::Data& data) {
     auto index = data.index;
+    auto& chunk = data.chunk.value();
 
     UInt neighbor_index = 0;
     std::array<Chunk*, 8> neighbors;
@@ -100,7 +79,7 @@ void World::try_to_reify_chunk(ChunkData& data) {
         for (I32 y = -1; y <= 1; y++) {
             if (x == 0 && y == 0) continue;
 
-            auto& neighbor_data = get({index.x + x, index.y + y});
+            auto& neighbor_data = m_registry.get({index.x + x, index.y + y});
             if (!neighbor_data.chunk.has_value()) return; // All neighbors need to be generated first.
 
             neighbors[neighbor_index++] = &neighbor_data.chunk.value();
@@ -116,10 +95,11 @@ void World::try_to_reify_chunk(ChunkData& data) {
     };
 
     // Lighting
-    Generation::Lighting::light_chunk(data.chunk.value(), chunk_neighbors);
+    m_lighting.add_chunk(chunk);
+    m_lighting.illuminate(m_registry);
 
     // Meshing
-    auto meshes = Generation::ChunkMeshing::mesh_chunk(data.chunk.value(), chunk_neighbors);
+    auto meshes = Generation::ChunkMeshing::mesh_chunk(chunk, chunk_neighbors);
 
     data.land_mesh_data = meshes.land_mesh;
     data.land_mesh = GFX::Binder::load(data.land_mesh_data.value());
@@ -127,7 +107,7 @@ void World::try_to_reify_chunk(ChunkData& data) {
     data.water_mesh_data = meshes.water_mesh;
     data.water_mesh = GFX::Binder::load(data.water_mesh_data.value());
 
-    data.status = ChunkStatus::Done;
+    data.status = ChunkRegistry::Status::Done;
 }
 
 void World::log_chunk_time(U64 chunk_time_ms) {
diff --git a/src/World/World.hpp b/src/World/World.hpp
index 3635d8a..db9d0db 100644
--- a/src/World/World.hpp
+++ b/src/World/World.hpp
@@ -1,12 +1,10 @@
 #pragma once
 
-#include <unordered_map>
-#include <unordered_set>
-#include <optional>
 #include "Generation/Generator.hpp"
 #include "ChunkIndex.hpp"
-#include "../GFX/Binder.hpp"
+#include "ChunkRegistry.hpp"
 #include "../Compute/Queue.hpp"
+#include "Generation/Lighting.hpp"
 
 namespace MC::World {
 
@@ -14,39 +12,18 @@ class World {
 public:
     World() : m_queue(8) {}
 
-    enum class ChunkStatus {
-        Empty,
-        WaitingForGeneration,
-        WaitingForReification,
-        Done
-    };
-
-    struct ChunkData {
-        ChunkIndex index;
-        ChunkStatus status;
-        std::optional<Chunk> chunk = {};
-
-        std::optional<GFX::Mesh> land_mesh_data = {};
-        std::optional<GFX::Mesh> water_mesh_data = {};
-
-        std::optional<GFX::BindableMesh> land_mesh = {};
-        std::optional<GFX::BindableMesh> water_mesh = {};
-    };
-
-    std::vector<ChunkData*> get_visible_chunks(Vector<3> position);
-    Chunk* get_chunk_for_position(Vector<3> position);
+    std::vector<ChunkRegistry::Data*> get_visible_chunks(Vector<3> position);
 
     Real get_average_chunk_time() const;
 private:
     std::vector<ChunkIndex> get_visible_chunk_indices(Vector<3> position) const;
+
     void load_finished_chunks_from_queue();
     void request_generation(ChunkIndex index, Real priority);
-    void try_to_reify_chunk(ChunkData& data);
+    void try_to_reify_chunk(ChunkRegistry::Data& data);
 
     void log_chunk_time(U64 chunk_time_ms);
 
-    ChunkData& get(ChunkIndex index);
-
     U8 m_view_distance_radius = 10;
 
     struct GenerationResult {
@@ -55,8 +32,8 @@ private:
     };
     Compute::Queue<GenerationResult, ChunkIndex> m_queue;
     Generation::Generator m_generator;
-
-    std::unordered_map<ChunkIndex, ChunkData> m_chunks;
+    Generation::Lighting m_lighting;
+    ChunkRegistry m_registry;
 
     struct Statistics {
         UInt chunk_time_sample_count;