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: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

View File

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

View File

@ -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<PlantEntry> 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<WeightedEntry> 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<PlantEntry>());
return BiomePlants {
std::move(plants), plantsWeightSum, plantChance};
std::sort(entries.begin(), entries.end(), std::greater<WeightedEntry>());
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)};
}

View File

@ -5,7 +5,7 @@
GeneratingVoxelStructure::GeneratingVoxelStructure(
VoxelStructureMeta meta,
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)) {}

View File

@ -1,5 +1,6 @@
#pragma once
#include <array>
#include <string>
#include <vector>
#include <glm/glm.hpp>
@ -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<PlantEntry> plants;
/// @brief Entries sorted by weight descending.
std::vector<WeightedEntry> 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<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)
/// @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<BiomeParameter> 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<VoxelFragment> structure;
std::array<std::unique_ptr<VoxelFragment>, 4> fragments;
GeneratingVoxelStructure(
VoxelStructureMeta meta,

View File

@ -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<float>(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<int>(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();

View File

@ -47,8 +47,6 @@ class WorldGenerator {
/// @brief Chunk prototypes loading surround map
SurroundMap surroundMap;
std::vector<std::array<std::shared_ptr<VoxelFragment>, 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,