summary refs log tree commit diff
path: root/src/World
diff options
context:
space:
mode:
Diffstat (limited to 'src/World')
-rw-r--r--src/World/BiomeType.hpp22
-rw-r--r--src/World/BlockType.hpp6
-rw-r--r--src/World/Chunk.cpp64
-rw-r--r--src/World/Chunk.hpp25
-rw-r--r--src/World/Generator.cpp314
-rw-r--r--src/World/Generator.hpp59
-rw-r--r--src/World/World.cpp12
-rw-r--r--src/World/World.hpp3
8 files changed, 443 insertions, 62 deletions
diff --git a/src/World/BiomeType.hpp b/src/World/BiomeType.hpp
index 026b3ef..2e3d8c6 100644
--- a/src/World/BiomeType.hpp
+++ b/src/World/BiomeType.hpp
@@ -8,34 +8,32 @@ namespace MC::World {
 class BiomeType {
 public:
     enum Value : uint8_t {
-        Forest,
         Plains,
+        Forest,
+        Alpine,
         Desert,
+        Jungle,
+        Beach,
+        River,
         Ocean,
     };
 
-    static constexpr const size_t Size = 4;
+    static constexpr uint8_t Size = Ocean + 1;
 
-    BiomeType() = default;
-    BiomeType(Value biome) : m_biome(biome) {}
+    BiomeType() : m_biome(Plains) {}
+    BiomeType(const Value biome) : m_biome(biome) {}
 
     operator Value() const { return m_biome; }
 
     static std::vector<BiomeType> all() {
-        return {
-            Plains, Forest, Desert, Ocean
-        };
+        return { Plains, Forest, Alpine, Desert, Jungle, Beach, River, Ocean };
     }
 
     static std::vector<BiomeType> all_ground() {
-        return {
-            Plains, Forest, Desert
-        };
+        return { Plains, Forest, Alpine, Desert, Jungle, Beach };
     }
 
 private:
-
-
     Value m_biome;
 };
 
diff --git a/src/World/BlockType.hpp b/src/World/BlockType.hpp
index 65afe0d..dcb8e44 100644
--- a/src/World/BlockType.hpp
+++ b/src/World/BlockType.hpp
@@ -13,12 +13,13 @@ public:
         Grass,
         Stone,
         Sand,
+        Snow,
         Water,
     };
 
-    static constexpr const size_t Size = 6;
+    static constexpr uint8_t Size = Water + 1;
 
-    BlockType() = default;
+    BlockType() : m_block(Air) {}
     BlockType(Value block) : m_block(block) {}
 
     operator Value() const { return m_block; }
@@ -30,6 +31,7 @@ public:
             Grass,
             Stone,
             Sand,
+            Snow,
             Water,
         };
     }
diff --git a/src/World/Chunk.cpp b/src/World/Chunk.cpp
index 1874950..9642fa9 100644
--- a/src/World/Chunk.cpp
+++ b/src/World/Chunk.cpp
@@ -4,11 +4,11 @@
 namespace MC::World {
 
 void Chunk::set(uint32_t x, uint32_t y, uint32_t z, BlockData data) {
-    m_blocks[pos(x, y, z)] = data;
+    m_blocks.at(pos(x, y, z)) = data;
 }
 
-Chunk::BlockData Chunk::get(uint32_t x, uint32_t y, uint32_t z) {
-    return m_blocks[pos(x, y, z)];
+Chunk::BlockData Chunk::get(uint32_t x, uint32_t y, uint32_t z) const {
+    return m_blocks.at(pos(x, y, z));
 }
 
 GFX::Mesh Chunk::mesh() {
@@ -17,9 +17,9 @@ GFX::Mesh Chunk::mesh() {
     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++) {
+    for (int x = 0; x < Width; x++) {
+        for (int y = 0; y < Height; y++) {
+            for (int z = 0; z < Width; z++) {
                 auto type = get(x, y, z).type;
                 if (type == BlockType::Air) {
                     continue;
@@ -31,8 +31,8 @@ GFX::Mesh Chunk::mesh() {
                     }
 
                     auto side_positions = side.face();
-                    auto side_normals = Chunk::face_normals(side);
-                    auto side_tex_coords = Chunk::face_tex_coords(type, side);
+                    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)};
@@ -87,9 +87,9 @@ bool Chunk::is_face_visible(uint32_t x, uint32_t y, uint32_t z, BlockSide side)
     };
 
     if (
-        neighbor_pos.x() >= Chunk::Width || neighbor_pos.x() < 0 ||
-        neighbor_pos.y() >= Chunk::Height || neighbor_pos.y() < 0 ||
-        neighbor_pos.z() >= Chunk::Width || neighbor_pos.z() < 0
+        neighbor_pos.x() >= Width || neighbor_pos.x() < 0 ||
+        neighbor_pos.y() >= Height || neighbor_pos.y() < 0 ||
+        neighbor_pos.z() >= Width || neighbor_pos.z() < 0
     ) {
         return true;
     }
@@ -103,8 +103,8 @@ bool Chunk::is_face_visible(uint32_t x, uint32_t y, uint32_t z, BlockSide side)
 }
 
 std::array<Vector<2>, 4> Chunk::face_tex_coords(BlockType type, BlockSide side) {
-    uint8_t atlas_width = 2;
-    uint8_t atlas_height = 3;
+    uint8_t atlas_width = 4;
+    uint8_t atlas_height = 4;
 
     float width_step = 1.0f / atlas_width;
     float height_step = 1.0f / atlas_height;
@@ -125,22 +125,34 @@ std::array<Vector<2>, 4> Chunk::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(0, 1);
-                case BlockSide::Top:
-                    return block_coords(0, 0);
-                case BlockSide::Bottom:
-                    return block_coords(1, 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(1, 1);
+            return block_coords(3, 0);
         case BlockType::Sand:
-            return block_coords(0, 2);
+            return block_coords(0, 1);
         case BlockType::Water:
-            return block_coords(1, 2);
+            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 {};
     }
@@ -159,7 +171,7 @@ std::array<Vector<3>, 4> Chunk::face_normals(BlockSide side) {
 }
 
 uint64_t Chunk::pos(uint32_t x, uint32_t y, uint32_t z) {
-    return x + Chunk::Width * y + Chunk::Width * Chunk::Height * z;
+    return x + Width * y + Width * Height * z;
 }
 
 }
diff --git a/src/World/Chunk.hpp b/src/World/Chunk.hpp
index 2b0379c..d200039 100644
--- a/src/World/Chunk.hpp
+++ b/src/World/Chunk.hpp
@@ -1,29 +1,36 @@
 #pragma once
 
 #include <cstdint>
-#include <optional>
+#include "BiomeType.hpp"
 #include "BlockType.hpp"
 #include "../GFX/Mesh.hpp"
 #include "BlockSide.hpp"
-#include "../GFX/Binder.hpp"
 
 namespace MC::World {
 
 class Chunk {
 public:
-    static constexpr const uint32_t Width = 16;
-    static constexpr const uint32_t Height = 128;
+    static constexpr uint32_t Width = 16;
+    static constexpr uint32_t Height = 128;
 
     Chunk(int64_t x, int64_t y)
-        : m_blocks{Chunk::Width * Chunk::Height * Chunk::Width, {BlockType::Air}},
-        m_position{(float)x * Chunk::Width, 0.0f, (float)y * Chunk::Width} {};
+        : m_blocks{Width * Height * Width, {BlockType::Air}},
+        m_position{(float)x * Width, 0.0f, (float)y * Width} {}
 
     struct BlockData {
         BlockType type;
     };
 
-    void set(uint32_t x, uint32_t y, uint32_t z, BlockData type);
-    BlockData get(uint32_t x, uint32_t y, uint32_t z);
+    void set(uint32_t x, uint32_t y, uint32_t z, BlockData data);
+    BlockData get(uint32_t x, uint32_t y, uint32_t z) const;
+
+    struct Details {
+        Matrix<Width, Width> landmass_values{};
+        Matrix<Width, Width> hill_values{};
+        Matrix<Width, Width, BiomeType> biome_values{};
+    };
+    void set_details(const Details& details) { m_details = details; }
+    Details& details(){ return m_details; }
 
     Vector<3> position();
     GFX::Mesh mesh();
@@ -37,6 +44,8 @@ private:
 
     Vector<3> m_position;
     std::vector<BlockData> m_blocks;
+
+    Details m_details;
 };
 
 }
diff --git a/src/World/Generator.cpp b/src/World/Generator.cpp
index e8b3bbd..f528278 100644
--- a/src/World/Generator.cpp
+++ b/src/World/Generator.cpp
@@ -1,30 +1,320 @@
 #include "Generator.hpp"
-#include "../Math/Perlin.hpp"
+#include "../Math/Interpolation.hpp"
+#include "../Math/Sigmoid.hpp"
 
 namespace MC::World {
 
 Chunk Generator::generate(int64_t chunk_x, int64_t chunk_y) {
     Chunk chunk(chunk_x, chunk_y);
 
-    Math::Perlin::Noise<3> noise{.scale=20.0f, .octaves=2};
+    auto landmass_map = generate_landmass_map(chunk_x, chunk_y);
+    auto hill_map = generate_hill_map(chunk_x, chunk_y);
+    auto height_map = generate_height_map(landmass_map, hill_map, chunk_x, chunk_y);
+
+    auto biome_map = generate_biome_map(landmass_map, hill_map, height_map, chunk_x, chunk_y);
+    auto terrain_map = generate_terrain(height_map, chunk_x, chunk_y);
+
+    decorate_soil(chunk, biome_map, terrain_map);
+
+    chunk.set_details({{landmass_map.map}, {hill_map.map}, {biome_map.map}});
+
+    return chunk;
+}
+
+Generator::ChunkMap2D<float> Generator::generate_landmass_map(int64_t chunk_x, int64_t chunk_y) {
+    ChunkMap2D<float> landmass_map{};
+
+    for (uint x = 0; x < Chunk::Width; x++) {
+        for (uint y = 0; y < Chunk::Width; y++) {
+            landmass_map.set(x, y, get_landmass(chunk_position_to_world_vector(chunk_x, chunk_y, x, y)));
+        }
+    }
+
+    return landmass_map;
+}
+
+Generator::ChunkMap2D<float> Generator::generate_hill_map(int64_t chunk_x, int64_t chunk_y) {
+    ChunkMap2D<float> hill_map{};
+
+    for (uint x = 0; x < Chunk::Width; x++) {
+        for (uint y = 0; y < Chunk::Width; y++) {
+            hill_map.set(x, y, get_hill(chunk_position_to_world_vector(chunk_x, chunk_y, x, y)));
+        }
+    }
+
+    return hill_map;
+}
+
+Generator::ChunkMap2D<float> Generator::generate_height_map(ChunkMap2D<float>& landmass_map, ChunkMap2D<float>& hill_map, int64_t chunk_x, int64_t chunk_y) {
+    ChunkMap2D<float> height_map{};
 
     for (uint x = 0; x < Chunk::Width; x++) {
+        for (uint y = 0; y < Chunk::Width; y++) {
+            auto landmass_effect = landmass_map.get(x, y);
+            auto hill_effect = hill_map.get(x, y);
+
+            auto hill = hill_effect * landmass_effect * 1.4 - 0.4;
+            auto landmass = landmass_effect * 0.6 + 0.4;
+
+            auto height = (hill + landmass) / 2.0f;
+
+            height_map.set(x, y, height);
+        }
+    }
+
+    return height_map;
+}
+
+Generator::ChunkMap2D<BiomeType> Generator::generate_biome_map(ChunkMap2D<float>& landmass_map, ChunkMap2D<float>& hill_map, ChunkMap2D<float>& height_map, int64_t chunk_x, int64_t chunk_y) {
+    ChunkMap2D<BiomeType> biome_map{};
+
+    for (uint x = 0; x < Chunk::Width; x++) {
+        for (uint y = 0; y < Chunk::Width; y++) {
+            float landmass = landmass_map.get(x, y);
+            float hill = hill_map.get(x, y);
+
+            auto world_pos = chunk_position_to_world_vector(chunk_x, chunk_y, x, y);
+            float temperature = get_temperature(world_pos);
+            float humidity = get_humidity(world_pos);
+
+            HillSlice hill_slice;
+            if (hill > 0.9) { hill_slice = HillSlice::Mountain; }
+            else if (hill > 0.33) { hill_slice = HillSlice::Middle; }
+            else { hill_slice = HillSlice::Valley; }
+
+            LandmassSlice landmass_slice;
+            if (landmass > 0.8) { landmass_slice = LandmassSlice::Land; }
+            else if (landmass > 0.45) { landmass_slice = LandmassSlice::Beach; }
+            else { landmass_slice = LandmassSlice::Ocean; }
+
+            TemperatureZone temparature_zone;
+            if (temperature > 0.66) { temparature_zone = TemperatureZone::Hot; }
+            else if (temperature > 0.33) { temparature_zone = TemperatureZone::Fair; }
+            else { temparature_zone = TemperatureZone::Cold; }
+
+            HumidityZone humidity_zone;
+            if (humidity > 0.66) { humidity_zone = HumidityZone::Wet; }
+            else if (humidity > 0.33) { humidity_zone = HumidityZone::Temperate; }
+            else { humidity_zone = HumidityZone::Dry; }
+
+            auto biome = lookup_biome(hill_slice, landmass_slice, temparature_zone, humidity_zone);
+            biome_map.set(x, y, biome);
+        }
+    }
+
+    return biome_map;
+}
+
+Generator::ChunkMap3D<bool> Generator::generate_terrain(ChunkMap2D<float>& height_map, int64_t chunk_x, int64_t chunk_y) {
+    float jaggedness = 0.10f;
+
+    ChunkMap3D<bool> terrain_map{};
+    for (uint x = 0; x < Chunk::Width; x++) {
         for (uint z = 0; z < Chunk::Width; z++) {
+            auto height = height_map.get(x, z);
+
             for (uint y = 0; y < Chunk::Height; y++) {
-                auto value = noise.at({
-                    (float)(chunk_x * Chunk::Width + x),
-                    (float)y,
-                    (float)(chunk_y * Chunk::Width + z),
-                });
-
-                if (value > 0.6f && value < 0.7f) {
-                    chunk.set(x, y, z, {BlockType::Stone});
+                float density = get_density({Chunk::Width * chunk_x + (float)x, (float)y, Chunk::Width * chunk_y + (float)z});
+                float threshold = Math::sigmoid(((float)y / (Chunk::Height * height * 2) - 0.5f) / jaggedness);
+
+                if (density > threshold) {
+                    terrain_map.set(x, y, z, true);
                 }
             }
         }
     }
 
-    return chunk;
+    return terrain_map;
 }
 
-}
\ No newline at end of file
+void Generator::decorate_soil(Chunk& chunk, ChunkMap2D<BiomeType>& biome_map, ChunkMap3D<bool>& terrain_map) {
+    constexpr uint dirt_depth = 4;
+    constexpr uint water_height = 39;
+
+    for (uint x = 0; x < Chunk::Width; x++) {
+        for (uint z = 0; z < Chunk::Width; z++) {
+            auto biome = biome_map.get(x, z);
+
+            BlockType top_block{};
+            BlockType soil_block{};
+            switch (biome) {
+            case BiomeType::Beach:
+            case BiomeType::Desert:
+                top_block = BlockType::Sand;
+                soil_block = BlockType::Sand;
+                break;
+            case BiomeType::Jungle:
+            case BiomeType::Forest:
+            case BiomeType::Plains:
+            case BiomeType::River:
+            case BiomeType::Ocean:
+                top_block = BlockType::Grass;
+                soil_block = BlockType::Dirt;
+                break;
+            case BiomeType::Alpine:
+                top_block = BlockType::Snow;
+                soil_block = BlockType::Dirt;
+                break;
+            }
+
+            auto column_depth = 0;
+            for (uint y = Chunk::Height - 1; y > 0; y--) {
+                auto block = terrain_map.get(x, y, z);
+                if (block) {
+                    if (column_depth == 0 && y >= water_height - 1) {
+                        chunk.set(x, y, z, {top_block});
+                    } else if (column_depth < dirt_depth) {
+                        chunk.set(x, y, z, {soil_block});
+                    } else {
+                        chunk.set(x, y, z, {BlockType::Stone});
+                    }
+                    column_depth++;
+                } else {
+                    if (y < water_height) {
+                        chunk.set(x, y, z, {BlockType::Water});
+                    }
+                    column_depth = 0;
+                }
+            }
+        }
+    }
+}
+
+#define CURVE_START(y) constexpr auto lerp = Math::linear_interpolation; float _py = y; float _px = 0.0f;
+#define CURVE_POINT(x, y) if (v < x) return lerp({_py, y}, _px, x, v); _py = y; _px = x
+#define CURVE_END(y) return lerp({_py, y}, _px, 1.0f, v);
+
+float Generator::get_landmass(Vector<2> pos) const {
+    auto v = m_landmass_noise.at(pos);
+
+    CURVE_START(1.0f);
+    CURVE_POINT(0.1f, 0.8f);
+    CURVE_POINT(0.3f, 0.8f);
+    CURVE_POINT(0.37f, 0.125f);
+    CURVE_POINT(0.4f, 0.4f);
+    CURVE_POINT(0.46f, 0.45f);
+    CURVE_POINT(0.47f, 0.875f);
+    CURVE_POINT(0.48f, 0.9f);
+    CURVE_POINT(0.55f, 0.95f);
+    CURVE_END(1.0f);
+}
+
+float Generator::get_hill(Vector<2> pos) const {
+    auto v = m_hill_noise.at(pos);
+
+    CURVE_START(0.33f);
+    CURVE_POINT(0.25f, 1.0f);
+
+    CURVE_POINT(0.49f, 0.1f);
+    CURVE_POINT(0.5f, 0.0f);
+    CURVE_POINT(0.51f, 0.1f);
+
+    CURVE_POINT(0.75f, 1.0f);
+    CURVE_END(0.33f);
+}
+
+float Generator::get_humidity(Vector<2> pos) const {
+    auto v = m_humidity_noise.at(pos);
+    return v;
+}
+
+float Generator::get_temperature(Vector<2> pos) const {
+    auto v = m_temperature_noise.at(pos);
+    return v;
+}
+
+float Generator::get_density(Vector<3> pos) const {
+    auto v = m_density_noise.at(pos);
+    return v;
+}
+
+Vector<2> Generator::chunk_position_to_world_vector(int64_t chunk_x, int64_t chunk_y, uint x, uint y) {
+    return {(float)x + chunk_x * Chunk::Width, (float)y + chunk_y * Chunk::Width};
+}
+
+std::array<BiomeType, Generator::biome_lookup_table_size> Generator::create_biome_lookup_table() {
+    std::array<BiomeType, biome_lookup_table_size> table{};
+
+    auto inc = [](auto& x) { x = (std::remove_reference_t<decltype(x)>)((uint)x + 1); };
+    auto cmp = [](auto x, auto s) { return (uint)x < (uint)s; };
+
+    for (HillSlice hill_slice{}; cmp(hill_slice, HillSliceSize); inc(hill_slice)) {
+        for (LandmassSlice landmass_slice{}; cmp(landmass_slice, LandmassSliceSize); inc(landmass_slice)) {
+            for (TemperatureZone temperature_zone{}; cmp(temperature_zone, TemperatureZoneSize); inc(temperature_zone)) {
+                for (HumidityZone humidity_zone{}; cmp(humidity_zone, HumidityZoneSize); inc(humidity_zone)) {
+                    BiomeType biome{};
+                    if (landmass_slice == LandmassSlice::Ocean) {
+                        biome = BiomeType::Ocean;
+                        goto set;
+                    }
+                    if (landmass_slice == LandmassSlice::Beach) {
+                        biome = BiomeType::Beach;
+                        goto set;
+                    }
+
+                    if (hill_slice == HillSlice::Valley) {
+                        biome = BiomeType::River;
+                        goto set;
+                    }
+                    if (hill_slice == HillSlice::Mountain) {
+                        if (temperature_zone == TemperatureZone::Hot) {
+                            biome = BiomeType::Desert;
+                        } else {
+                            biome = BiomeType::Alpine;
+                        }
+                        goto set;
+                    }
+
+                    switch (temperature_zone) {
+                    case TemperatureZone::Hot:
+                        biome = BiomeType::Desert;
+                        break;
+                    case TemperatureZone::Fair:
+                        switch (humidity_zone) {
+                        case HumidityZone::Wet:
+                            biome = BiomeType::Jungle;
+                            break;
+                        case HumidityZone::Lush:
+                            biome = BiomeType::Forest;
+                            break;
+                        case HumidityZone::Temperate:
+                        case HumidityZone::Dry:
+                            biome = BiomeType::Plains;
+                            break;
+                        }
+                        break;
+                    case TemperatureZone::Cold:
+                        switch (humidity_zone) {
+                        case HumidityZone::Wet:
+                        case HumidityZone::Lush:
+                            biome = BiomeType::Alpine;
+                            break;
+                        case HumidityZone::Temperate:
+                        case HumidityZone::Dry:
+                            biome = BiomeType::Plains;
+                            break;
+                        }
+                        break;
+                    }
+
+                    set:
+                    table[biome_lookup_table_index(hill_slice, landmass_slice, temperature_zone, humidity_zone)] = biome;
+                }
+            }
+        }
+    }
+
+    return table;
+}
+
+size_t Generator::biome_lookup_table_index(HillSlice hill_slice, LandmassSlice landmass_slice, TemperatureZone temperature_zone, HumidityZone humidity_zone) {
+    auto hs = (uint8_t)hill_slice; auto ls = (uint8_t)landmass_slice; auto tz = (uint8_t)temperature_zone; auto hz = (uint8_t)humidity_zone;
+    auto LS_S = (uint8_t)LandmassSliceSize; auto TZ_S = (uint8_t)TemperatureZoneSize; auto HZ_S = (uint8_t)HumidityZoneSize;
+    return (hs * LS_S * TZ_S * HZ_S) + (ls * TZ_S * HZ_S) + (tz * HZ_S) + hz;
+}
+
+BiomeType Generator::lookup_biome(HillSlice hill_slice, LandmassSlice landmass_slice, TemperatureZone temperature_zone, HumidityZone humidity_zone) {
+    return biome_lookup_table.at(biome_lookup_table_index(hill_slice, landmass_slice, temperature_zone, humidity_zone));
+}
+
+}
diff --git a/src/World/Generator.hpp b/src/World/Generator.hpp
index afe43c6..523136a 100644
--- a/src/World/Generator.hpp
+++ b/src/World/Generator.hpp
@@ -3,6 +3,7 @@
 #include <cstdint>
 #include "Chunk.hpp"
 #include "BiomeType.hpp"
+#include "../Math/Perlin.hpp"
 
 namespace MC::World {
 
@@ -10,6 +11,64 @@ class Generator {
 public:
     Generator() = default;
     Chunk generate(int64_t chunk_x, int64_t chunk_y);
+private:
+    template <typename V>
+    struct ChunkMap2D {
+        V map[Chunk::Width * Chunk::Width];
+        void set(uint x, uint y, V v) { map[x * Chunk::Width + y] = v; }
+        V get(uint x, uint y) { return map[x * Chunk::Width + y]; }
+    };
+
+    template <typename V>
+    struct ChunkMap3D {
+        V map[Chunk::Width * Chunk::Width * Chunk::Height];
+        void set(uint x, uint y, uint z, V v) { map[pos(x, y, z)] = v; }
+        V get(uint x, uint y, uint z) { return map[pos(x, y, z)]; }
+        static uint pos(uint x, uint y, uint z) { return x + Chunk::Width * y + Chunk::Width * Chunk::Height * z; }
+    };
+
+    ChunkMap2D<float> generate_landmass_map(int64_t chunk_x, int64_t chunk_y);
+    ChunkMap2D<float> generate_hill_map(int64_t chunk_x, int64_t chunk_y);
+    ChunkMap2D<float> generate_height_map(ChunkMap2D<float>& landmass_map, ChunkMap2D<float>& hill_map, int64_t chunk_x, int64_t chunk_y);
+
+    ChunkMap2D<BiomeType> generate_biome_map(ChunkMap2D<float>& landmass_map, ChunkMap2D<float>& hill_map, ChunkMap2D<float>& height_map, int64_t chunk_x, int64_t chunk_y);
+
+    ChunkMap3D<bool> generate_terrain(ChunkMap2D<float>& height_map, int64_t chunk_x, int64_t chunk_y);
+
+    void decorate_soil(Chunk& chunk, ChunkMap2D<BiomeType>& biome_map, ChunkMap3D<bool>& terrain_map);
+
+    [[nodiscard]] float get_landmass(Vector<2> pos) const;
+    [[nodiscard]] float get_hill(Vector<2> pos) const;
+
+    [[nodiscard]] float get_humidity(Vector<2> pos) const;
+    [[nodiscard]] float get_temperature(Vector<2> pos) const;
+
+    [[nodiscard]] float get_density(Vector<3> pos) const;
+
+    static Vector<2> chunk_position_to_world_vector(int64_t chunk_x, int64_t chunk_y, uint x, uint y);
+
+    Math::Perlin::Noise<2> m_landmass_noise{.scale=800.0f, .octaves=4, .persistence=0.3f, .lacunarity=3.5f};
+    Math::Perlin::Noise<2> m_hill_noise{.scale=400.0f, .octaves=3, .persistence=0.5f, .lacunarity=2.0f};
+
+    Math::Perlin::Noise<2> m_temperature_noise{.scale=700.0f, .octaves=3, .persistence=0.5f, .lacunarity=2.0f};
+    Math::Perlin::Noise<2> m_humidity_noise{.scale=400.0f, .octaves=2, .persistence=0.5f, .lacunarity=2.0f};
+
+    Math::Perlin::Noise<3> m_density_noise{.scale=30.0f, .octaves=2, .persistence=0.7f, .lacunarity=2.0f};
+
+    enum class HillSlice { Mountain, Middle, Valley };
+    enum class LandmassSlice { Land, Beach, Ocean };
+    enum class TemperatureZone { Hot, Fair, Cold };
+    enum class HumidityZone { Wet, Lush, Temperate, Dry };
+    static constexpr uint HillSliceSize = (uint)HillSlice::Valley + 1;
+    static constexpr uint LandmassSliceSize = (uint)LandmassSlice::Ocean + 1;
+    static constexpr uint TemperatureZoneSize = (uint)TemperatureZone::Cold + 1;
+    static constexpr uint HumidityZoneSize = (uint)HumidityZone::Dry + 1;
+
+    static constexpr size_t biome_lookup_table_size = (size_t)HillSliceSize * (size_t)LandmassSliceSize * (size_t)TemperatureZoneSize * (size_t)HumidityZoneSize;
+    static std::array<BiomeType, biome_lookup_table_size> create_biome_lookup_table();
+    static size_t biome_lookup_table_index(HillSlice hill_slice, LandmassSlice landmass_slice, TemperatureZone temperature_zone, HumidityZone humidity_zone);
+    static BiomeType lookup_biome(HillSlice hill_slice, LandmassSlice landmass_slice, TemperatureZone temperature_zone, HumidityZone humidity_zone);
+    static inline std::array<BiomeType, biome_lookup_table_size> biome_lookup_table = create_biome_lookup_table();
 };
 
 }
diff --git a/src/World/World.cpp b/src/World/World.cpp
index 9d88af3..03ce60e 100644
--- a/src/World/World.cpp
+++ b/src/World/World.cpp
@@ -19,7 +19,7 @@ std::vector<World::ChunkData> World::get_visible_chunks(Vector<3> position) {
         m_visible_chunks = visible_chunks;
     }
 
-    std::vector<World::ChunkData> chunks{};
+    std::vector<ChunkData> chunks{};
     chunks.reserve(visible_chunks.size());
     for (auto index : visible_chunks) {
         auto& data = get(index);
@@ -31,6 +31,16 @@ std::vector<World::ChunkData> World::get_visible_chunks(Vector<3> position) {
     return chunks;
 }
 
+Chunk* World::get_chunk_for_positon(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});
+    if (data.chunk.has_value()) {
+        return &data.chunk.value();
+    }
+    return nullptr;
+}
+
 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);
diff --git a/src/World/World.hpp b/src/World/World.hpp
index d842600..a5f73ec 100644
--- a/src/World/World.hpp
+++ b/src/World/World.hpp
@@ -3,9 +3,9 @@
 #include <memory>
 #include <unordered_map>
 #include <unordered_set>
-#include <utility>
 #include "Generator.hpp"
 #include "ChunkIndex.hpp"
+#include "../GFX/Binder.hpp"
 #include "../Compute/Queue.hpp"
 
 namespace MC::World {
@@ -28,6 +28,7 @@ public:
     };
 
     std::vector<ChunkData> get_visible_chunks(Vector<3> position);
+    Chunk* get_chunk_for_positon(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();