#include "Generator.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); 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 Generator::generate_landmass_map(int64_t chunk_x, int64_t chunk_y) { ChunkMap2D 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 Generator::generate_hill_map(int64_t chunk_x, int64_t chunk_y) { ChunkMap2D 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 Generator::generate_height_map(ChunkMap2D& landmass_map, ChunkMap2D& hill_map, int64_t chunk_x, int64_t chunk_y) { ChunkMap2D 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 Generator::generate_biome_map(ChunkMap2D& landmass_map, ChunkMap2D& hill_map, ChunkMap2D& height_map, int64_t chunk_x, int64_t chunk_y) { ChunkMap2D 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 Generator::generate_terrain(ChunkMap2D& height_map, int64_t chunk_x, int64_t chunk_y) { float jaggedness = 0.10f; ChunkMap3D 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++) { 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 terrain_map; } void Generator::decorate_soil(Chunk& chunk, ChunkMap2D& biome_map, ChunkMap3D& 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 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) { 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)); } }