summary refs log tree commit diff
path: root/src/World/Generation
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2023-07-07 21:39:42 +0200
committerMel <einebeere@gmail.com>2023-07-07 21:39:42 +0200
commitf1fc192ddc4c739fa8b4b376c759b7d3218a34eb (patch)
tree9e9afb9a21ba3ca27d1f25d46230aa9d27f8be39 /src/World/Generation
parent24b8124469350d1c80d0553cf3f4bf58cdb1489b (diff)
downloadmeowcraft-f1fc192ddc4c739fa8b4b376c759b7d3218a34eb.tar.zst
meowcraft-f1fc192ddc4c739fa8b4b376c759b7d3218a34eb.zip
Chunk-bound tree decoration
Diffstat (limited to 'src/World/Generation')
-rw-r--r--src/World/Generation/ChunkMeshing.cpp183
-rw-r--r--src/World/Generation/ChunkMeshing.hpp11
-rw-r--r--src/World/Generation/Decoration.cpp97
-rw-r--r--src/World/Generation/Decoration.hpp34
-rw-r--r--src/World/Generation/Generator.cpp331
-rw-r--r--src/World/Generation/Generator.hpp76
6 files changed, 732 insertions, 0 deletions
diff --git a/src/World/Generation/ChunkMeshing.cpp b/src/World/Generation/ChunkMeshing.cpp
new file mode 100644
index 0000000..54abc85
--- /dev/null
+++ b/src/World/Generation/ChunkMeshing.cpp
@@ -0,0 +1,183 @@
+#include "ChunkMeshing.hpp"
+
+namespace MC::World::Generation {
+
+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(1, 1);
+        case BlockType::Water:
+            return block_coords(0, 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::Wood:
+            switch (side) {
+                case BlockSide::Front:
+                case BlockSide::Back:
+                case BlockSide::Right:
+                case BlockSide::Left:
+                    return block_coords(0, 2);
+                case BlockSide::Bottom:
+                case BlockSide::Top:
+                    return block_coords(1, 2);
+            }
+        case BlockType::Leaves:
+            return block_coords(2, 2);
+        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/Generation/ChunkMeshing.hpp b/src/World/Generation/ChunkMeshing.hpp
new file mode 100644
index 0000000..a4fed25
--- /dev/null
+++ b/src/World/Generation/ChunkMeshing.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "../../GFX/Mesh.hpp"
+#include "../Chunk.hpp"
+
+namespace MC::World::Generation::ChunkMeshing {
+
+struct ChunkNeighbors { Chunk &north, &east, &south, &west; };
+GFX::Mesh create_mesh_for_chunk(Chunk& chunk, ChunkNeighbors neighbors);
+
+}
diff --git a/src/World/Generation/Decoration.cpp b/src/World/Generation/Decoration.cpp
new file mode 100644
index 0000000..26eb397
--- /dev/null
+++ b/src/World/Generation/Decoration.cpp
@@ -0,0 +1,97 @@
+#include "Decoration.hpp"
+
+#include <iostream>
+
+namespace MC::World::Generation {
+void Decorator::put_block(Chunk& chunk, Pos pos, BlockType block) {
+    if (!Chunk::is_valid_position(pos.x(), pos.y(), pos.z())) {
+        return;
+    }
+    if (chunk.is_empty(pos.x(), pos.y(), pos.z())) {
+        chunk.set(pos.x(), pos.y(), pos.z(), {block});
+    }
+}
+
+void Decorator::draw_column(Chunk& chunk, Pos pos, uint height, BlockType block) {
+    Pos current_pos = pos;
+    for (uint i = 0; i < height; i++) {
+        put_block(chunk, current_pos, block);
+        current_pos += Pos::up();
+    }
+}
+
+void Decorator::draw_circle(Chunk& chunk, Pos pos, Vector<3> axis, float radius, BlockType block) {
+    auto normalized_axis = axis.normalize();
+
+    auto ortho1 = normalized_axis.any_orthogonal();
+    auto ortho2 = normalized_axis.cross(ortho1);
+
+    auto r = [](const float x) { return static_cast<uint>(std::round(x)); };
+
+    int radius_round = std::round(radius);
+    for (int32_t d1 = -radius_round; d1 <= radius_round; d1++) {
+        float height = std::sqrt(radius * radius - d1 * d1);
+        for (int32_t d2 = -height; d2 <= (int)height; d2++) {
+            auto p = ortho1 * d1 + ortho2 * d2;
+            Pos block_pos = pos + Pos{r(p.x()), r(p.y()), r(p.z())};
+            put_block(chunk, block_pos, block);
+        }
+    }
+}
+
+void TreeDecorator::decorate_chunk(Chunk& chunk) {
+    Pos last_tree = Pos::max();
+    for (uint x = 0; x < Chunk::Width; x++) {
+        for (uint z = 0; z < Chunk::Width; z++) {
+            for (uint y = Chunk::Height; y > 1; y--) {
+                Pos pos{x, y, z};
+                if (!is_valid_position(pos))
+                    continue;
+
+                auto block_below = chunk.get(x, y-1, z);
+                if (block_below.empty())
+                    continue;
+
+                auto type = block_below.type;
+                if (type != BlockType::Snow && type != BlockType::Grass && type != BlockType::Dirt)
+                    break;
+
+                auto noise = m_tree_noise.at({(float)x, (float)z});
+                if (noise < 0.8f)
+                    continue;
+
+                if (last_tree.distance(pos) < s_tree_radius * 3)
+                    continue;
+
+                draw_tree(chunk, pos);
+                chunk.set(x, y-1, z, {BlockType::Dirt});
+                last_tree = pos;
+                break;
+            }
+        }
+    }
+}
+
+void TreeDecorator::draw_tree(Chunk& chunk, Pos pos) const {
+    auto noise = m_tree_noise.at({(float)pos.x(), (float)pos.z()});
+    uint height = std::round(10 * noise - 4.75f);
+
+    draw_column(chunk, pos, height, BlockType::Wood);
+
+    uint max_leaf_height = 4;
+    for (int x = 0; x < max_leaf_height; x++) {
+        Pos p{pos.x(), pos.y() + height + x - 2, pos.z()};
+        float radius = s_tree_radius - 0.5f + x * ((0.3f * x - 1.45f) * x + 1.25f);
+        draw_circle(chunk, p, Vector<3>::up(), radius, BlockType::Leaves);
+    }
+}
+
+bool TreeDecorator::is_valid_position(Vector<3, uint> pos) {
+    int tree_radius = s_tree_radius;
+    return (int)pos.x() - tree_radius >= 0
+        && pos.x() + tree_radius <= Chunk::Width
+        && (int)pos.z() - tree_radius >= 0
+        && pos.z() + tree_radius <= Chunk::Width;
+}
+
+}
diff --git a/src/World/Generation/Decoration.hpp b/src/World/Generation/Decoration.hpp
new file mode 100644
index 0000000..a592e72
--- /dev/null
+++ b/src/World/Generation/Decoration.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "../Chunk.hpp"
+#include "../../Math/Random.hpp"
+
+namespace MC::World::Generation {
+
+class Decorator {
+public:
+    using Pos = Vector<3, uint>;
+
+    virtual ~Decorator() = default;
+
+    virtual void decorate_chunk(Chunk& chunk) = 0;
+
+    static void put_block(Chunk& chunk, Pos pos, BlockType block);
+    static void draw_column(Chunk& chunk, Pos pos, uint height, BlockType block);
+    static void draw_circle(Chunk& chunk, Pos pos, Vector<3> axis, float radius, BlockType block);
+};
+
+class TreeDecorator final : public Decorator {
+public:
+    void decorate_chunk(Chunk& chunk) override;
+private:
+    void draw_tree(Chunk& chunk, Pos pos) const;
+
+    static bool is_valid_position(Pos pos);
+
+    static constexpr uint s_tree_radius = 3;
+
+    Math::Random::Noise<2> m_tree_noise;
+};
+
+}
diff --git a/src/World/Generation/Generator.cpp b/src/World/Generation/Generator.cpp
new file mode 100644
index 0000000..af3c54d
--- /dev/null
+++ b/src/World/Generation/Generator.cpp
@@ -0,0 +1,331 @@
+#include "Generator.hpp"
+#include "../../Math/Interpolation.hpp"
+#include "../../Math/Sigmoid.hpp"
+
+namespace MC::World::Generation {
+
+Chunk Generator::generate(int64_t chunk_x, int64_t chunk_y) {
+    Chunk chunk(chunk_x, chunk_y);
+
+    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 temperature_map = generate_temperature_map(chunk_x, chunk_y);
+    auto humidity_map = generate_humidity_map(chunk_x, chunk_y);
+    auto biome_map = generate_biome_map(landmass_map, hill_map, temperature_map, humidity_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, hill_map, temperature_map, humidity_map, biome_map});
+
+    return chunk;
+}
+
+#define SIMPLE_MAP_GENERATOR(name, getter)                                      \
+Generator::Map2D<float> Generator::name(int64_t chunk_x, int64_t chunk_y) {     \
+    Matrix<Chunk::Width, Chunk::Width> map{};                                   \
+    for (uint x = 0; x < Chunk::Width; x++) {                                   \
+        for (uint y = 0; y < Chunk::Width; y++) {                               \
+            auto pos = chunk_position_to_world_vector(chunk_x, chunk_y, x, y);  \
+            map(x, y) = getter(pos);                                            \
+        }                                                                       \
+    }                                                                           \
+    return map;                                                                 \
+}
+
+SIMPLE_MAP_GENERATOR(generate_landmass_map, get_landmass)
+SIMPLE_MAP_GENERATOR(generate_hill_map, get_hill)
+
+Generator::Map2D<float> Generator::generate_height_map(Map2D<float>& landmass_map, Map2D<float>& hill_map, int64_t chunk_x, int64_t chunk_y) {
+    Map2D<float> height_map{};
+
+    for (uint x = 0; x < Chunk::Width; x++) {
+        for (uint y = 0; y < Chunk::Width; y++) {
+            auto landmass_effect = landmass_map(x, y);
+            auto hill_effect = hill_map(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(x, y) = height;
+        }
+    }
+
+    return height_map;
+}
+
+SIMPLE_MAP_GENERATOR(generate_temperature_map, get_temperature)
+SIMPLE_MAP_GENERATOR(generate_humidity_map, get_humidity)
+
+Generator::Map2D<BiomeType> Generator::generate_biome_map(
+    Map2D<float>& landmass_map, Map2D<float>& hill_map,
+    Map2D<float>& temperature_map, Map2D<float>& humidity_map,
+    int64_t chunk_x, int64_t chunk_y
+) {
+    Map2D<BiomeType> biome_map{};
+
+    for (uint x = 0; x < Chunk::Width; x++) {
+        for (uint y = 0; y < Chunk::Width; y++) {
+            float landmass = landmass_map(x, y);
+            float hill = hill_map(x, y);
+
+            float temperature = temperature_map(x, y);
+            float humidity = humidity_map(x, y);
+
+            HillSlice hill_slice;
+            if (hill > 0.55) { hill_slice = HillSlice::Mountain; }
+            else if (hill > 0.03) { 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.6) { temparature_zone = TemperatureZone::Hot; }
+            else if (temperature > 0.4) { 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(x, y) = biome;
+        }
+    }
+
+    return biome_map;
+}
+
+Generator::Map3D<bool> Generator::generate_terrain(Map2D<float>& height_map, int64_t chunk_x, int64_t chunk_y) {
+    float jaggedness = 0.10f;
+
+    Map3D<bool> terrain_map{};
+    for (uint x = 0; x < Chunk::Width; x++) {
+        for (uint z = 0; z < Chunk::Width; z++) {
+            auto height = height_map(x, z);
+
+            for (uint y = 0; y < Chunk::Height; y++) {
+                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(x, y, z) = true;
+                }
+            }
+        }
+    }
+
+    return terrain_map;
+}
+
+void Generator::decorate_soil(Chunk& chunk, Map2D<BiomeType>& biome_map, Map3D<bool>& terrain_map) {
+    constexpr uint dirt_depth = 4;
+    constexpr uint water_height = 40;
+
+    for (uint x = 0; x < Chunk::Width; x++) {
+        for (uint z = 0; z < Chunk::Width; z++) {
+            auto biome = biome_map(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::Ocean:
+            case BiomeType::River:
+                top_block = BlockType::Grass;
+                soil_block = BlockType::Dirt;
+                break;
+            case BiomeType::Alpine:
+                top_block = BlockType::Snow;
+                soil_block = BlockType::Dirt;
+                break;
+            case BiomeType::Shore:
+            case BiomeType::RockyPeaks:
+                top_block = BlockType::Stone;
+                soil_block = BlockType::Stone;
+            }
+
+            auto column_depth = 0;
+            for (uint y = Chunk::Height - 1; y > 0; y--) {
+                auto block = terrain_map(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;
+                }
+            }
+        }
+    }
+
+    for (auto& decorator : s_decorators) {
+        decorator->decorate_chunk(chunk);
+    }
+}
+
+#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) {
+                        if (temperature_zone == TemperatureZone::Cold) {
+                            biome = BiomeType::Shore;
+                        } else {
+                            biome = BiomeType::Beach;
+                        }
+                        goto set;
+                    }
+
+                    if (hill_slice == HillSlice::Valley) {
+                        biome = BiomeType::River;
+                        goto set;
+                    }
+                    if (hill_slice == HillSlice::Mountain) {
+                        if (temperature_zone == TemperatureZone::Cold) {
+                            biome = BiomeType::Alpine;
+                        } else {
+                            biome = BiomeType::RockyPeaks;
+                        }
+                        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/Generation/Generator.hpp b/src/World/Generation/Generator.hpp
new file mode 100644
index 0000000..464e36f
--- /dev/null
+++ b/src/World/Generation/Generator.hpp
@@ -0,0 +1,76 @@
+#pragma once
+
+#include <cstdint>
+
+#include "Decoration.hpp"
+#include "../Chunk.hpp"
+#include "../BiomeType.hpp"
+#include "../../Math/Perlin.hpp"
+#include "../../Math/Tensor.hpp"
+
+namespace MC::World::Generation {
+
+class Generator {
+public:
+    Generator() = default;
+    Chunk generate(int64_t chunk_x, int64_t chunk_y);
+private:
+    template <typename V> using Map2D = Matrix<Chunk::Width, Chunk::Width, V>;
+    template <typename V> using Map3D = Tensor<3, V, Chunk::Width, Chunk::Height, Chunk::Width>;
+
+    Map2D<float> generate_landmass_map(int64_t chunk_x, int64_t chunk_y);
+    Map2D<float> generate_hill_map(int64_t chunk_x, int64_t chunk_y);
+    Map2D<float> generate_height_map(Map2D<float>& landmass_map, Map2D<float>& hill_map, int64_t chunk_x, int64_t chunk_y);
+
+    Map2D<float> generate_temperature_map(int64_t chunk_x, int64_t chunk_y);
+    Map2D<float> generate_humidity_map(int64_t chunk_x, int64_t chunk_y);
+
+    Map2D<BiomeType> generate_biome_map(
+        Map2D<float>& landmass_map, Map2D<float>& hill_map,
+        Map2D<float>& temperature_map, Map2D<float>& humidity_map,
+        int64_t chunk_x, int64_t chunk_y
+    );
+
+    Map3D<bool> generate_terrain(Map2D<float>& height_map, int64_t chunk_x, int64_t chunk_y);
+
+    void decorate_soil(Chunk& chunk, Map2D<BiomeType>& biome_map, Map3D<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);
+
+    static inline std::vector<Decorator*> s_decorators = {
+        new TreeDecorator(),
+    };
+
+    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=4, .persistence=0.4f, .lacunarity=3.0f};
+    Math::Perlin::Noise<2> m_humidity_noise{.scale=400.0f, .octaves=2, .persistence=0.4f, .lacunarity=3.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();
+};
+
+}