#include "Generator.hpp" #include "../../Math/Interpolation.hpp" #include "../../Math/Sigmoid.hpp" namespace MC::World::Generation { Chunk Generator::generate(I64 chunk_x, I64 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); 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); 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 Generator::name(I64 chunk_x, I64 chunk_y) { \ Matrix 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 Generator::generate_height_map(Map2D& landmass_map, Map2D& hill_map) { Map2D 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 Generator::generate_biome_map( Map2D& landmass_map, Map2D& hill_map, Map2D& temperature_map, Map2D& humidity_map ) { Map2D biome_map{}; for (UInt x = 0; x < Chunk::Width; x++) { for (UInt y = 0; y < Chunk::Width; y++) { Real landmass = landmass_map(x, y); Real hill = hill_map(x, y); Real temperature = temperature_map(x, y); Real 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 Generator::generate_terrain(Map2D& height_map, I64 chunk_x, I64 chunk_y) { Real jaggedness = 0.10f; Map3D 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++) { Real density = get_density({Chunk::Width * chunk_x + (Real)x, (Real)y, Chunk::Width * chunk_y + (Real)z}); Real threshold = Math::sigmoid(((Real)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& biome_map, Map3D& terrain_map) { // TODO: Put all of this into decorators. 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& place = chunk.at(x, y, z); auto block = terrain_map(x, y, z); if (block) { if (column_depth == 0 && y >= water_height - 1) { place = {top_block}; } else if (column_depth < dirt_depth) { place = {soil_block}; } else { place = {BlockType::Stone}; } column_depth++; } else { if (y < water_height) { place = {BlockType::Water}; } column_depth = 0; } } } } for (auto decorator : s_decorators) { decorator->decorate_chunk(chunk); } } #define CURVE_START(y) constexpr auto lerp = Math::linear_interpolation; Real _py = (y); Real _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); Real 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); } Real 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); } Real Generator::get_humidity(Vector<2> pos) const { auto v = m_humidity_noise.at(pos); return v; } Real Generator::get_temperature(Vector<2> pos) const { auto v = m_temperature_noise.at(pos); return v; } Real Generator::get_density(Vector<3> pos) const { auto v = m_density_noise.at(pos); return v; } Vector<2> Generator::chunk_position_to_world_vector(I64 chunk_x, I64 chunk_y, UInt x, UInt y) { return {(Real)x + chunk_x * Chunk::Width, (Real)y + chunk_y * Chunk::Width}; } std::array Generator::create_biome_lookup_table() { std::array table{}; auto inc = [](auto& x) { x = (std::remove_reference_t)((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; } USize Generator::biome_lookup_table_index(HillSlice hill_slice, LandmassSlice landmass_slice, TemperatureZone temperature_zone, HumidityZone humidity_zone) { auto hs = (U8)hill_slice; auto ls = (U8)landmass_slice; auto tz = (U8)temperature_zone; auto hz = (U8)humidity_zone; auto LS_S = (U8)LandmassSliceSize; auto TZ_S = (U8)TemperatureZoneSize; auto HZ_S = (U8)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)); } }