diff --git a/res/generators/default.lua b/res/generators/default.lua index 17e92e34..789569e1 100644 --- a/res/generators/default.lua +++ b/res/generators/default.lua @@ -18,6 +18,10 @@ biomes = { {block="base:dirt", height=5, below_sea_level=false}, {block="base:stone", height=-1}, {block="base:bazalt", height=1}, + }, + plant_chance = 0.5, + plants = { + {block="base:grass", weight=1} } }, desert = { diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index 11615ba1..bd9df34c 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -610,6 +610,17 @@ namespace lua { return def; } + inline Number get_number_field( + lua::State* L, const std::string& name, Number def, int idx=-1 + ) { + if (getfield(L, name, idx)) { + auto value = tonumber(L, -1); + pop(L); + return value; + } + return def; + } + inline Integer get_integer_field( lua::State* L, const std::string& name, Integer def, Integer min, Integer max, int idx=-1 diff --git a/src/logic/scripting/scripting_world_generation.cpp b/src/logic/scripting/scripting_world_generation.cpp index 8efdf0bb..2b500cbd 100644 --- a/src/logic/scripting/scripting_world_generation.cpp +++ b/src/logic/scripting/scripting_world_generation.cpp @@ -83,6 +83,9 @@ 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; + } } } @@ -163,18 +166,37 @@ static inline Biome load_biome( } lua::pop(L); - BlocksLayers groundLayers; - BlocksLayers seaLayers; - try { - groundLayers = load_layers(L, "layers"); - seaLayers = load_layers(L, "sea_layers"); - } catch (const std::runtime_error& err) { - throw std::runtime_error("biome "+name+": "+err.what()); + float plantChance = lua::get_number_field(L, "plant_chance", 0.0); + float plantsWeightSum = 0.0f; + std::vector plants; + if (lua::getfield(L, "plants")) { + if (!lua::istable(L, -1)) { + throw std::runtime_error("'plants' must be a table"); + } + int plantsCount = lua::objlen(L, -1); + for (int i = 1; i <= plantsCount; i++) { + lua::rawgeti(L, i); + if (!lua::istable(L, -1)) { + throw std::runtime_error("plant must be a table"); + } + auto block = lua::require_string_field(L, "block"); + float weight = lua::require_number_field(L, "weight"); + // TODO: range check (positive) + plantsWeightSum += weight; + plants.push_back(PlantEntry {block, weight, {}}); + lua::pop(L); + } + lua::pop(L); } + // TODO: sort by weight descending + + BlocksLayers groundLayers = load_layers(L, "layers"); + BlocksLayers seaLayers = load_layers(L, "sea_layers"); lua::pop(L); return Biome { name, std::move(parameters), + BiomePlants {plants, plantsWeightSum, plantChance}, std::move(groundLayers), std::move(seaLayers)}; } diff --git a/src/world/generator/GeneratorDef.hpp b/src/world/generator/GeneratorDef.hpp index 7b469bd3..b424b636 100644 --- a/src/world/generator/GeneratorDef.hpp +++ b/src/world/generator/GeneratorDef.hpp @@ -35,15 +35,56 @@ struct BlocksLayers { struct BiomeParameter { /// @brief Central parameter value for the biome - float origin; + float value; /// @brief Parameter score multiplier - /// (the higher the value, the greater the chance of biome choosing) + /// (the higher the weight, the greater the chance of biome choosing) float weight; }; +struct PlantEntry { + /// @brief Plant block id + std::string block; + /// @brief Plant weight + float weight; + + struct { + blockid_t id; + } rt; +}; + +struct BiomePlants { + static inline float MIN_CHANCE = 0.000001f; + + /// @brief Plant entries sorted by weight descending. + std::vector plants; + /// @brief Sum of weight values + float weightsSum; + /// @brief Plant generation chance + float chance; + + /// @brief Choose plant 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; + } + rand = rand / chance; + rand *= weightsSum; + for (const auto& plant : plants) { + rand -= plant.weight; + if (rand <= 0.0f) { + return plant.rt.id; + } + } + return plants[plants.size()-1].rt.id; + } +}; + struct Biome { std::string name; std::vector parameters; + BiomePlants plants; BlocksLayers groundLayers; BlocksLayers seaLayers; }; diff --git a/src/world/generator/WorldGenerator.cpp b/src/world/generator/WorldGenerator.cpp index eeea5f64..be44f6f3 100644 --- a/src/world/generator/WorldGenerator.cpp +++ b/src/world/generator/WorldGenerator.cpp @@ -3,6 +3,7 @@ #include #include +#include "maths/util.hpp" #include "content/Content.hpp" #include "voxels/Block.hpp" #include "voxels/Chunk.hpp" @@ -63,7 +64,7 @@ static inline const Biome* choose_biome( for (const auto& biome : biomes) { float score = 0.0f; for (uint i = 0; i < paramsCount; i++) { - score += glm::abs((params[i] - biome.parameters[i].origin) / + score += glm::abs((params[i] - biome.parameters[i].value) / biome.parameters[i].weight); } if (score < chosenScore) { @@ -92,6 +93,9 @@ void WorldGenerator::generate( std::memset(voxels, 0, sizeof(voxel) * CHUNK_VOL); + PseudoRandom plantsRand; + plantsRand.setSeed(chunkX, chunkZ); + for (uint z = 0; z < CHUNK_D; z++) { for (uint x = 0; x < CHUNK_W; x++) { const Biome* biome = choose_biome(biomes, biomeParams, x, z); @@ -104,6 +108,15 @@ void WorldGenerator::generate( generate_pole(seaLayers, seaLevel, height, seaLevel, voxels, x, z); generate_pole(groundLayers, height, 0, seaLevel, voxels, x, z); + + if (height+1 > seaLevel) { + // TODO: add underwater plants support + float rand = (plantsRand.randU32() % RAND_MAX) / static_cast(RAND_MAX); + blockid_t plant = biome->plants.choose(rand); + if (plant) { + voxels[vox_index(x, height+1, z)].id = plant; + } + } } } }