summary refs log tree commit diff
path: root/src/World/Generator.cpp
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2023-06-29 22:15:32 +0200
committerMel <einebeere@gmail.com>2023-06-29 22:15:32 +0200
commit52732d71e72b02ff45e25f44e414f87ec9ab7666 (patch)
treea3db775e01e04abaf0291e7d172740ff47ff1006 /src/World/Generator.cpp
parent92ac46df6afa8ee76f972cceb681cf32658f84a2 (diff)
downloadmeowcraft-52732d71e72b02ff45e25f44e414f87ec9ab7666.tar.zst
meowcraft-52732d71e72b02ff45e25f44e414f87ec9ab7666.zip
Pretty terrain generation
Diffstat (limited to 'src/World/Generator.cpp')
-rw-r--r--src/World/Generator.cpp314
1 files changed, 302 insertions, 12 deletions
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));
+}
+
+}