add automatic biome-based structures placement

This commit is contained in:
MihailRis 2024-09-24 17:08:25 +03:00
parent 323c2f2935
commit 806ed4b155
8 changed files with 174 additions and 106 deletions

View File

@ -53,10 +53,11 @@ biomes = {
{block="base:stone", height=-1}, {block="base:stone", height=-1},
{block="base:bazalt", height=1}, {block="base:bazalt", height=1},
}, },
structure_chance = 0.032,
structures = { structures = {
"tree0", {name="tree0", weight=1},
"tree1", {name="tree1", weight=1},
"tree2" {name="tree2", weight=1},
} }
} }
} }
@ -74,24 +75,6 @@ end
function place_structures(x, z, w, d, seed, hmap) function place_structures(x, z, w, d, seed, hmap)
local placements = {} 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 return placements
end end

View File

@ -89,7 +89,7 @@ std::unique_ptr<Content> ContentBuilder::build() {
} }
for (auto& [name, def] : content->generators.getDefs()) { for (auto& [name, def] : content->generators.getDefs()) {
def->script->prepare(content.get()); def->script->prepare(*def, content.get());
} }
return content; return content;

View File

@ -45,7 +45,12 @@ static std::vector<std::unique_ptr<GeneratingVoxelStructure>> load_structures(
} }
static void load_structures(GeneratorDef& def, const fs::path& structuresFile) { 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 // build indices map
for (size_t i = 0; i < def.structures.size(); i++) { for (size_t i = 0; i < def.structures.size(); i++) {
auto& structure = def.structures[i]; auto& structure = def.structures[i];

View File

@ -12,7 +12,7 @@
#include "voxels/Chunk.hpp" #include "voxels/Chunk.hpp"
#include "data/dv.hpp" #include "data/dv.hpp"
#include "world/generator/GeneratorDef.hpp" #include "world/generator/GeneratorDef.hpp"
#include "util/stringutil.hpp"
#include "util/timeutil.hpp" #include "util/timeutil.hpp"
class LuaGeneratorScript : public GeneratorScript { class LuaGeneratorScript : public GeneratorScript {
@ -140,7 +140,7 @@ public:
return placements; return placements;
} }
void prepare(const Content* content) override { void prepare(const GeneratorDef& def, const Content* content) override {
for (auto& biome : biomes) { for (auto& biome : biomes) {
for (auto& layer : biome.groundLayers.layers) { for (auto& layer : biome.groundLayers.layers) {
layer.rt.id = content->blocks.require(layer.block).rt.id; layer.rt.id = content->blocks.require(layer.block).rt.id;
@ -148,8 +148,16 @@ public:
for (auto& layer : biome.seaLayers.layers) { for (auto& layer : biome.seaLayers.layers) {
layer.rt.id = content->blocks.require(layer.block).rt.id; layer.rt.id = content->blocks.require(layer.block).rt.id;
} }
for (auto& plant : biome.plants.plants) { for (auto& plant : biome.plants.entries) {
plant.rt.id = content->blocks.require(plant.block).rt.id; 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}; return BlocksLayers {std::move(layers), lastLayersHeight};
} }
static inline BiomePlants load_plants( static inline BiomeElementList load_biome_element_list(
const dv::value& biomeMap const dv::value map,
const std::string& chanceName,
const std::string& arrName,
const std::string& nameName
) { ) {
float plantChance = 0.0f; float chance = 0.0f;
biomeMap.at("plant_chance").get(plantChance); map.at(chanceName).get(chance);
float plantsWeightSum = 0.0f; std::vector<WeightedEntry> entries;
if (map.has(arrName)) {
std::vector<PlantEntry> plants; const auto& arr = map[arrName];
if (biomeMap.has("plants")) { for (const auto& entry : arr) {
const auto& plantsArr = biomeMap["plants"]; const auto& name = entry[nameName].asString();
for (const auto& entry : plantsArr) {
const auto& block = entry["block"].asString();
float weight = entry["weight"].asNumber(); float weight = entry["weight"].asNumber();
if (weight <= 0.0f) { if (weight <= 0.0f) {
throw std::runtime_error("weight must be positive"); throw std::runtime_error("weight must be positive");
} }
plantsWeightSum += weight; entries.push_back(WeightedEntry {name, weight, {}});
plants.push_back(PlantEntry {block, weight, {}});
} }
} }
std::sort(plants.begin(), plants.end(), std::greater<PlantEntry>()); std::sort(entries.begin(), entries.end(), std::greater<WeightedEntry>());
return BiomePlants { return BiomeElementList(std::move(entries), chance);
std::move(plants), plantsWeightSum, plantChance}; }
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( static inline Biome load_biome(
@ -252,13 +267,19 @@ static inline Biome load_biome(
parameters.push_back(BiomeParameter {value, weight}); parameters.push_back(BiomeParameter {value, weight});
} }
BiomePlants plants = load_plants(biomeMap); auto plants = load_plants(biomeMap);
BlocksLayers groundLayers = load_layers(biomeMap["layers"], "layers"); auto groundLayers = load_layers(biomeMap["layers"], "layers");
BlocksLayers seaLayers = load_layers(biomeMap["sea_layers"], "sea_layers"); auto seaLayers = load_layers(biomeMap["sea_layers"], "sea_layers");
BiomeElementList structures;
if (biomeMap.has("structures")) {
structures = load_structures(biomeMap);
}
return Biome { return Biome {
name, name,
std::move(parameters), std::move(parameters),
std::move(plants), std::move(plants),
std::move(structures),
std::move(groundLayers), std::move(groundLayers),
std::move(seaLayers)}; std::move(seaLayers)};
} }

View File

@ -5,7 +5,7 @@
GeneratingVoxelStructure::GeneratingVoxelStructure( GeneratingVoxelStructure::GeneratingVoxelStructure(
VoxelStructureMeta meta, VoxelStructureMeta meta,
std::unique_ptr<VoxelFragment> structure std::unique_ptr<VoxelFragment> 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)) {} GeneratorDef::GeneratorDef(std::string name) : name(std::move(name)) {}

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <array>
#include <string> #include <string>
#include <vector> #include <vector>
#include <glm/glm.hpp> #include <glm/glm.hpp>
@ -11,6 +12,7 @@
class Content; class Content;
class VoxelFragment; class VoxelFragment;
struct GeneratorDef;
struct VoxelStructureMeta { struct VoxelStructureMeta {
std::string name; std::string name;
@ -49,56 +51,70 @@ struct BiomeParameter {
float weight; float weight;
}; };
/// @brief Plant is a single-block structure randomly generating in world struct WeightedEntry {
struct PlantEntry { std::string name;
/// @brief Plant block id
std::string block;
/// @brief Plant weight
float weight; float weight;
struct { struct {
blockid_t id; size_t id;
} rt; } rt;
bool operator>(const PlantEntry& other) const { bool operator>(const WeightedEntry& other) const {
return weight > other.weight; return weight > other.weight;
} }
}; };
struct BiomePlants { struct BiomeElementList {
static inline float MIN_CHANCE = 0.000001f; static inline float MIN_CHANCE = 0.000001f;
/// @brief Plant entries sorted by weight descending. /// @brief Entries sorted by weight descending.
std::vector<PlantEntry> plants; std::vector<WeightedEntry> entries;
/// @brief Sum of weight values /// @brief Sum of weight values
float weightsSum; float weightsSum;
/// @brief Plant generation chance /// @brief Value generation chance
float chance; float chance;
/// @brief Choose plant based on weight BiomeElementList() {}
BiomeElementList(std::vector<WeightedEntry> 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) /// @param rand some random value in range [0, 1)
/// @return index of chosen plant block /// @return *.index of chosen value
inline blockid_t choose(float rand) const { inline size_t choose(float rand, size_t def=0) const {
if (plants.empty() || rand > chance || chance < MIN_CHANCE) { if (entries.empty() || rand > chance || chance < MIN_CHANCE) {
return 0; return def;
} }
rand = rand / chance; rand = rand / chance;
rand *= weightsSum; rand *= weightsSum;
for (const auto& plant : plants) { for (const auto& entry : entries) {
rand -= plant.weight; rand -= entry.weight;
if (rand <= 0.0f) { 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 { struct Biome {
/// @brief Biome name
std::string name; std::string name;
std::vector<BiomeParameter> parameters; std::vector<BiomeParameter> parameters;
BiomePlants plants;
/// @brief Plant is a single-block structure randomly generating in world
BiomeElementList plants;
BiomeElementList structures;
BlocksLayers groundLayers; BlocksLayers groundLayers;
BlocksLayers seaLayers; BlocksLayers seaLayers;
}; };
@ -136,12 +152,12 @@ public:
/// @brief Build the runtime cache /// @brief Build the runtime cache
/// @param content built content /// @param content built content
virtual void prepare(const Content* content) = 0; virtual void prepare(const GeneratorDef& def, const Content* content) = 0;
}; };
struct GeneratingVoxelStructure { struct GeneratingVoxelStructure {
VoxelStructureMeta meta; VoxelStructureMeta meta;
std::unique_ptr<VoxelFragment> structure; std::array<std::unique_ptr<VoxelFragment>, 4> fragments;
GeneratingVoxelStructure( GeneratingVoxelStructure(
VoxelStructureMeta meta, VoxelStructureMeta meta,

View File

@ -49,15 +49,12 @@ WorldGenerator::WorldGenerator(
generateStructures(requirePrototype(x, z), x, z); generateStructures(requirePrototype(x, z), x, z);
}); });
auto rawStructures = def.script->loadStructures(); for (int i = 0; i < def.structures.size(); i++) {
structures.resize(rawStructures.size());
for (int i = 0; i < rawStructures.size(); i++) {
structures[i][0] = std::move(rawStructures[i]);
structures[i][0]->prepare(*content);
// pre-calculate rotated structure variants // pre-calculate rotated structure variants
def.structures[i]->fragments[0]->prepare(*content);
for (int j = 1; j < 4; j++) { 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}); {(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( void WorldGenerator::generateStructures(
ChunkPrototype& prototype, int chunkX, int chunkZ ChunkPrototype& prototype, int chunkX, int chunkZ
) { ) {
if (prototype.level >= ChunkPrototypeLevel::STRUCTURES) { if (prototype.level >= ChunkPrototypeLevel::STRUCTURES) {
return; return;
} }
const auto& biomes = prototype.biomes;
const auto& heightmap = prototype.heightmap;
const auto& heights = heightmap->getValues();
util::concat(prototype.structures, def.script->placeStructures( util::concat(prototype.structures, def.script->placeStructures(
{chunkX * CHUNK_W, chunkZ * CHUNK_D}, {CHUNK_W, CHUNK_D}, seed, {chunkX * CHUNK_W, chunkZ * CHUNK_D}, {CHUNK_W, CHUNK_D}, seed,
prototype.heightmap heightmap
)); ));
for (const auto& placement : prototype.structures) { for (const auto& placement : prototype.structures) {
const auto& offset = placement.position; 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; logger.error() << "invalid structure index " << placement.structure;
continue; continue;
} }
auto& structure = *structures[placement.structure][placement.rotation]; placeStructure(
auto position = glm::ivec3(chunkX * CHUNK_W, 0, chunkZ * CHUNK_D)+offset; offset, placement.structure, placement.rotation, chunkX, chunkZ);
auto size = structure.getSize() + glm::ivec3(0, CHUNK_H, 0); }
AABB aabb(position, position + size);
for (int lcz = -1; lcz <= 1; lcz++) { PseudoRandom structsRand;
for (int lcx = -1; lcx <= 1; lcx++) { structsRand.setSeed(chunkX, chunkZ);
if (lcx == 0 && lcz == 0) {
continue; for (uint z = 0; z < CHUNK_D; z++) {
} for (uint x = 0; x < CHUNK_W; x++) {
auto& otherPrototype = requirePrototype( float rand = (structsRand.randU32() % RAND_MAX) /
chunkX + lcx, chunkZ + lcz static_cast<float>(RAND_MAX);
); const Biome* biome = biomes[z * CHUNK_W + x];
auto chunkAABB = gen_chunk_aabb(chunkX + lcx, chunkZ + lcz); size_t structureId = biome->structures.choose(rand, -1);
if (chunkAABB.intersect(aabb)) { if (structureId == -1) {
otherPrototype.structures.emplace_back( continue;
placement.structure,
placement.position -
glm::ivec3(lcx * CHUNK_W, 0, lcz * CHUNK_D),
placement.rotation
);
}
} }
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<int>(structureId), position, rotation});
placeStructure(
position,
structureId,
rotation,
chunkX,
chunkZ
);
} }
} }
prototype.level = ChunkPrototypeLevel::STRUCTURES; prototype.level = ChunkPrototypeLevel::STRUCTURES;
@ -260,14 +302,12 @@ void WorldGenerator::generate(voxel* voxels, int chunkX, int chunkZ) {
} }
} }
// TODO: put automatic placement here
for (const auto& placement : prototype.structures) { 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; logger.error() << "invalid structure index " << placement.structure;
continue; continue;
} }
auto& structure = *structures[placement.structure][placement.rotation]; auto& structure = *def.structures[placement.structure]->fragments[placement.rotation];
auto& structVoxels = structure.getRuntimeVoxels(); auto& structVoxels = structure.getRuntimeVoxels();
const auto& offset = placement.position; const auto& offset = placement.position;
const auto& size = structure.getSize(); const auto& size = structure.getSize();

View File

@ -47,8 +47,6 @@ class WorldGenerator {
/// @brief Chunk prototypes loading surround map /// @brief Chunk prototypes loading surround map
SurroundMap surroundMap; SurroundMap surroundMap;
std::vector<std::array<std::shared_ptr<VoxelFragment>, 4>> structures;
/// @brief Generate chunk prototype (see ChunkPrototype) /// @brief Generate chunk prototype (see ChunkPrototype)
/// @param x chunk position X divided by CHUNK_W /// @param x chunk position X divided by CHUNK_W
/// @param z chunk position Y divided by CHUNK_D /// @param z chunk position Y divided by CHUNK_D
@ -61,6 +59,11 @@ class WorldGenerator {
void generateBiomes(ChunkPrototype& prototype, int x, int z); void generateBiomes(ChunkPrototype& prototype, int x, int z);
void generateHeightmap(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: public:
WorldGenerator( WorldGenerator(
const GeneratorDef& def, const GeneratorDef& def,