From 806ed4b155115feb62e535a43e2c687a9854f5b6 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 24 Sep 2024 17:08:25 +0300 Subject: [PATCH] add automatic biome-based structures placement --- res/generators/default.lua | 25 +--- src/content/ContentBuilder.cpp | 2 +- src/content/loading/GeneratorLoader.cpp | 7 +- .../scripting/scripting_world_generation.cpp | 67 +++++++---- src/world/generator/GeneratorDef.cpp | 2 +- src/world/generator/GeneratorDef.hpp | 62 ++++++---- src/world/generator/WorldGenerator.cpp | 108 ++++++++++++------ src/world/generator/WorldGenerator.hpp | 7 +- 8 files changed, 174 insertions(+), 106 deletions(-) diff --git a/res/generators/default.lua b/res/generators/default.lua index 41790163..921bfa06 100644 --- a/res/generators/default.lua +++ b/res/generators/default.lua @@ -53,10 +53,11 @@ biomes = { {block="base:stone", height=-1}, {block="base:bazalt", height=1}, }, + structure_chance = 0.032, structures = { - "tree0", - "tree1", - "tree2" + {name="tree0", weight=1}, + {name="tree1", weight=1}, + {name="tree2", weight=1}, } } } @@ -74,24 +75,6 @@ end function place_structures(x, z, w, d, seed, hmap) local placements = {} - for i=0,math.floor(math.random()*3)+5 do - local px = math.random() * w - local pz = math.random() * d - local py = hmap:at(px, pz) * 256 - if py <= sea_level then - goto continue - end - table.insert(placements, - {math.floor(math.random() * 3), {px-8, py, pz-8}, math.floor(math.random()*4)}) - ::continue:: - end - - if math.random() < 0.01 then - local px = math.random() * w - local pz = math.random() * d - local py = hmap:at(px, pz) * 256 - table.insert(placements, {3, {px-8, py, pz-8}, 0}) - end return placements end diff --git a/src/content/ContentBuilder.cpp b/src/content/ContentBuilder.cpp index 46d4f909..2ce7a963 100644 --- a/src/content/ContentBuilder.cpp +++ b/src/content/ContentBuilder.cpp @@ -89,7 +89,7 @@ std::unique_ptr ContentBuilder::build() { } for (auto& [name, def] : content->generators.getDefs()) { - def->script->prepare(content.get()); + def->script->prepare(*def, content.get()); } return content; diff --git a/src/content/loading/GeneratorLoader.cpp b/src/content/loading/GeneratorLoader.cpp index a0f8ba49..256a1b28 100644 --- a/src/content/loading/GeneratorLoader.cpp +++ b/src/content/loading/GeneratorLoader.cpp @@ -45,7 +45,12 @@ static std::vector> load_structures( } static void load_structures(GeneratorDef& def, const fs::path& structuresFile) { - def.structures = load_structures(structuresFile); + auto rawStructures = load_structures(structuresFile); + def.structures.resize(rawStructures.size()); + + for (int i = 0; i < rawStructures.size(); i++) { + def.structures[i] = std::move(rawStructures[i]); + } // build indices map for (size_t i = 0; i < def.structures.size(); i++) { auto& structure = def.structures[i]; diff --git a/src/logic/scripting/scripting_world_generation.cpp b/src/logic/scripting/scripting_world_generation.cpp index 59712dff..16ffaee2 100644 --- a/src/logic/scripting/scripting_world_generation.cpp +++ b/src/logic/scripting/scripting_world_generation.cpp @@ -12,7 +12,7 @@ #include "voxels/Chunk.hpp" #include "data/dv.hpp" #include "world/generator/GeneratorDef.hpp" - +#include "util/stringutil.hpp" #include "util/timeutil.hpp" class LuaGeneratorScript : public GeneratorScript { @@ -140,7 +140,7 @@ public: return placements; } - void prepare(const Content* content) override { + void prepare(const GeneratorDef& def, const Content* content) override { for (auto& biome : biomes) { for (auto& layer : biome.groundLayers.layers) { layer.rt.id = content->blocks.require(layer.block).rt.id; @@ -148,8 +148,16 @@ public: for (auto& layer : biome.seaLayers.layers) { layer.rt.id = content->blocks.require(layer.block).rt.id; } - for (auto& plant : biome.plants.plants) { - plant.rt.id = content->blocks.require(plant.block).rt.id; + for (auto& plant : biome.plants.entries) { + plant.rt.id = content->blocks.require(plant.name).rt.id; + } + for (auto& structure : biome.structures.entries) { + const auto& found = def.structuresIndices.find(structure.name); + if (found == def.structuresIndices.end()) { + throw std::runtime_error( + "no structure "+util::quote(structure.name)+" found"); + } + structure.rt.id = found->second; } } } @@ -207,29 +215,36 @@ static inline BlocksLayers load_layers( return BlocksLayers {std::move(layers), lastLayersHeight}; } -static inline BiomePlants load_plants( - const dv::value& biomeMap +static inline BiomeElementList load_biome_element_list( + const dv::value map, + const std::string& chanceName, + const std::string& arrName, + const std::string& nameName ) { - float plantChance = 0.0f; - biomeMap.at("plant_chance").get(plantChance); - float plantsWeightSum = 0.0f; - - std::vector plants; - if (biomeMap.has("plants")) { - const auto& plantsArr = biomeMap["plants"]; - for (const auto& entry : plantsArr) { - const auto& block = entry["block"].asString(); + float chance = 0.0f; + map.at(chanceName).get(chance); + std::vector entries; + if (map.has(arrName)) { + const auto& arr = map[arrName]; + for (const auto& entry : arr) { + const auto& name = entry[nameName].asString(); float weight = entry["weight"].asNumber(); if (weight <= 0.0f) { throw std::runtime_error("weight must be positive"); } - plantsWeightSum += weight; - plants.push_back(PlantEntry {block, weight, {}}); + entries.push_back(WeightedEntry {name, weight, {}}); } } - std::sort(plants.begin(), plants.end(), std::greater()); - return BiomePlants { - std::move(plants), plantsWeightSum, plantChance}; + std::sort(entries.begin(), entries.end(), std::greater()); + return BiomeElementList(std::move(entries), chance); +} + +static inline BiomeElementList load_plants(const dv::value& biomeMap) { + return load_biome_element_list(biomeMap, "plant_chance", "plants", "block"); +} + +static inline BiomeElementList load_structures(const dv::value map) { + return load_biome_element_list(map, "structure_chance", "structures", "name"); } static inline Biome load_biome( @@ -252,13 +267,19 @@ static inline Biome load_biome( parameters.push_back(BiomeParameter {value, weight}); } - BiomePlants plants = load_plants(biomeMap); - BlocksLayers groundLayers = load_layers(biomeMap["layers"], "layers"); - BlocksLayers seaLayers = load_layers(biomeMap["sea_layers"], "sea_layers"); + auto plants = load_plants(biomeMap); + auto groundLayers = load_layers(biomeMap["layers"], "layers"); + auto seaLayers = load_layers(biomeMap["sea_layers"], "sea_layers"); + + BiomeElementList structures; + if (biomeMap.has("structures")) { + structures = load_structures(biomeMap); + } return Biome { name, std::move(parameters), std::move(plants), + std::move(structures), std::move(groundLayers), std::move(seaLayers)}; } diff --git a/src/world/generator/GeneratorDef.cpp b/src/world/generator/GeneratorDef.cpp index 32abee49..123f5f98 100644 --- a/src/world/generator/GeneratorDef.cpp +++ b/src/world/generator/GeneratorDef.cpp @@ -5,7 +5,7 @@ GeneratingVoxelStructure::GeneratingVoxelStructure( VoxelStructureMeta meta, std::unique_ptr structure -) : structure(std::move(structure)), meta(std::move(meta)) {} +) : fragments({std::move(structure)}), meta(std::move(meta)) {} GeneratorDef::GeneratorDef(std::string name) : name(std::move(name)) {} diff --git a/src/world/generator/GeneratorDef.hpp b/src/world/generator/GeneratorDef.hpp index 33432b09..acf8f61d 100644 --- a/src/world/generator/GeneratorDef.hpp +++ b/src/world/generator/GeneratorDef.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -11,6 +12,7 @@ class Content; class VoxelFragment; +struct GeneratorDef; struct VoxelStructureMeta { std::string name; @@ -49,56 +51,70 @@ struct BiomeParameter { float weight; }; -/// @brief Plant is a single-block structure randomly generating in world -struct PlantEntry { - /// @brief Plant block id - std::string block; - /// @brief Plant weight +struct WeightedEntry { + std::string name; float weight; struct { - blockid_t id; + size_t id; } rt; - bool operator>(const PlantEntry& other) const { + bool operator>(const WeightedEntry& other) const { return weight > other.weight; } }; -struct BiomePlants { +struct BiomeElementList { static inline float MIN_CHANCE = 0.000001f; - /// @brief Plant entries sorted by weight descending. - std::vector plants; + /// @brief Entries sorted by weight descending. + std::vector entries; /// @brief Sum of weight values float weightsSum; - /// @brief Plant generation chance + /// @brief Value generation chance float chance; - /// @brief Choose plant based on weight + BiomeElementList() {} + + BiomeElementList(std::vector entries, float chance) + : entries(entries), chance(chance) { + for (const auto& entry : entries) { + weightsSum += entry.weight; + } + } + + /// @brief Choose value based on weight /// @param rand some random value in range [0, 1) - /// @return index of chosen plant block - inline blockid_t choose(float rand) const { - if (plants.empty() || rand > chance || chance < MIN_CHANCE) { - return 0; + /// @return *.index of chosen value + inline size_t choose(float rand, size_t def=0) const { + if (entries.empty() || rand > chance || chance < MIN_CHANCE) { + return def; } rand = rand / chance; rand *= weightsSum; - for (const auto& plant : plants) { - rand -= plant.weight; + for (const auto& entry : entries) { + rand -= entry.weight; if (rand <= 0.0f) { - return plant.rt.id; + return entry.rt.id; } } - return plants[plants.size()-1].rt.id; + return entries[entries.size()-1].rt.id; } }; struct Biome { + /// @brief Biome name std::string name; + std::vector parameters; - BiomePlants plants; + + /// @brief Plant is a single-block structure randomly generating in world + BiomeElementList plants; + + BiomeElementList structures; + BlocksLayers groundLayers; + BlocksLayers seaLayers; }; @@ -136,12 +152,12 @@ public: /// @brief Build the runtime cache /// @param content built content - virtual void prepare(const Content* content) = 0; + virtual void prepare(const GeneratorDef& def, const Content* content) = 0; }; struct GeneratingVoxelStructure { VoxelStructureMeta meta; - std::unique_ptr structure; + std::array, 4> fragments; GeneratingVoxelStructure( VoxelStructureMeta meta, diff --git a/src/world/generator/WorldGenerator.cpp b/src/world/generator/WorldGenerator.cpp index 90778870..1f297068 100644 --- a/src/world/generator/WorldGenerator.cpp +++ b/src/world/generator/WorldGenerator.cpp @@ -49,15 +49,12 @@ WorldGenerator::WorldGenerator( generateStructures(requirePrototype(x, z), x, z); }); - auto rawStructures = def.script->loadStructures(); - structures.resize(rawStructures.size()); - - for (int i = 0; i < rawStructures.size(); i++) { - structures[i][0] = std::move(rawStructures[i]); - structures[i][0]->prepare(*content); + for (int i = 0; i < def.structures.size(); i++) { // pre-calculate rotated structure variants + def.structures[i]->fragments[0]->prepare(*content); for (int j = 1; j < 4; j++) { - structures[i][j] = structures[i][j-1]->rotated(*content); + def.structures[i]->fragments[j] = + def.structures[i]->fragments[j-1]->rotated(*content); } } } @@ -140,44 +137,89 @@ inline AABB gen_chunk_aabb(int chunkX, int chunkZ) { {(chunkX + 1)*CHUNK_W, 256, (chunkZ + 1) * CHUNK_D}); } +void WorldGenerator::placeStructure( + const glm::ivec3 offset, size_t structureId, uint8_t rotation, + int chunkX, int chunkZ +) { + auto& structure = *def.structures[structureId]->fragments[rotation]; + auto position = glm::ivec3(chunkX * CHUNK_W, 0, chunkZ * CHUNK_D)+offset; + auto size = structure.getSize() + glm::ivec3(0, CHUNK_H, 0); + AABB aabb(position, position + size); + for (int lcz = -1; lcz <= 1; lcz++) { + for (int lcx = -1; lcx <= 1; lcx++) { + if (lcx == 0 && lcz == 0) { + continue; + } + auto& otherPrototype = requirePrototype( + chunkX + lcx, chunkZ + lcz + ); + auto chunkAABB = gen_chunk_aabb(chunkX + lcx, chunkZ + lcz); + if (chunkAABB.intersect(aabb)) { + otherPrototype.structures.emplace_back( + structureId, + offset - + glm::ivec3(lcx * CHUNK_W, 0, lcz * CHUNK_D), + rotation + ); + } + } + } +} + void WorldGenerator::generateStructures( ChunkPrototype& prototype, int chunkX, int chunkZ ) { if (prototype.level >= ChunkPrototypeLevel::STRUCTURES) { return; } + const auto& biomes = prototype.biomes; + const auto& heightmap = prototype.heightmap; + const auto& heights = heightmap->getValues(); + util::concat(prototype.structures, def.script->placeStructures( {chunkX * CHUNK_W, chunkZ * CHUNK_D}, {CHUNK_W, CHUNK_D}, seed, - prototype.heightmap + heightmap )); for (const auto& placement : prototype.structures) { const auto& offset = placement.position; - if (placement.structure < 0 || placement.structure >= structures.size()) { + if (placement.structure < 0 || placement.structure >= def.structures.size()) { logger.error() << "invalid structure index " << placement.structure; continue; } - auto& structure = *structures[placement.structure][placement.rotation]; - auto position = glm::ivec3(chunkX * CHUNK_W, 0, chunkZ * CHUNK_D)+offset; - auto size = structure.getSize() + glm::ivec3(0, CHUNK_H, 0); - AABB aabb(position, position + size); - for (int lcz = -1; lcz <= 1; lcz++) { - for (int lcx = -1; lcx <= 1; lcx++) { - if (lcx == 0 && lcz == 0) { - continue; - } - auto& otherPrototype = requirePrototype( - chunkX + lcx, chunkZ + lcz - ); - auto chunkAABB = gen_chunk_aabb(chunkX + lcx, chunkZ + lcz); - if (chunkAABB.intersect(aabb)) { - otherPrototype.structures.emplace_back( - placement.structure, - placement.position - - glm::ivec3(lcx * CHUNK_W, 0, lcz * CHUNK_D), - placement.rotation - ); - } + placeStructure( + offset, placement.structure, placement.rotation, chunkX, chunkZ); + } + + PseudoRandom structsRand; + structsRand.setSeed(chunkX, chunkZ); + + for (uint z = 0; z < CHUNK_D; z++) { + for (uint x = 0; x < CHUNK_W; x++) { + float rand = (structsRand.randU32() % RAND_MAX) / + static_cast(RAND_MAX); + const Biome* biome = biomes[z * CHUNK_W + x]; + size_t structureId = biome->structures.choose(rand, -1); + if (structureId == -1) { + continue; } + uint8_t rotation = structsRand.randU32() % 4; + int height = heights[z * CHUNK_W + x] * CHUNK_H; + if (height < def.script->getSeaLevel()) { + continue; + } + auto& structure = *def.structures[structureId]->fragments[rotation]; + glm::ivec3 position {x, height, z}; + position.x -= structure.getSize().x / 2; + position.z -= structure.getSize().z / 2; + prototype.structures.push_back({ + static_cast(structureId), position, rotation}); + placeStructure( + position, + structureId, + rotation, + chunkX, + chunkZ + ); } } prototype.level = ChunkPrototypeLevel::STRUCTURES; @@ -259,15 +301,13 @@ void WorldGenerator::generate(voxel* voxels, int chunkX, int chunkZ) { } } } - - // TODO: put automatic placement here for (const auto& placement : prototype.structures) { - if (placement.structure < 0 || placement.structure >= structures.size()) { + if (placement.structure < 0 || placement.structure >= def.structures.size()) { logger.error() << "invalid structure index " << placement.structure; continue; } - auto& structure = *structures[placement.structure][placement.rotation]; + auto& structure = *def.structures[placement.structure]->fragments[placement.rotation]; auto& structVoxels = structure.getRuntimeVoxels(); const auto& offset = placement.position; const auto& size = structure.getSize(); diff --git a/src/world/generator/WorldGenerator.hpp b/src/world/generator/WorldGenerator.hpp index 29de778b..9b90cb3f 100644 --- a/src/world/generator/WorldGenerator.hpp +++ b/src/world/generator/WorldGenerator.hpp @@ -47,8 +47,6 @@ class WorldGenerator { /// @brief Chunk prototypes loading surround map SurroundMap surroundMap; - std::vector, 4>> structures; - /// @brief Generate chunk prototype (see ChunkPrototype) /// @param x chunk position X divided by CHUNK_W /// @param z chunk position Y divided by CHUNK_D @@ -61,6 +59,11 @@ class WorldGenerator { void generateBiomes(ChunkPrototype& prototype, int x, int z); void generateHeightmap(ChunkPrototype& prototype, int x, int z); + + void placeStructure( + const glm::ivec3 offset, size_t structure, uint8_t rotation, + int chunkX, int chunkZ + ); public: WorldGenerator( const GeneratorDef& def,