feat(editor): biome vegetation auto-population system

One-click procedural object placement based on biome rules:

- 10 biome vegetation rulesets with density, scale range, and slope
  constraints per asset type (trees, bushes, rocks, ferns, etc.)
- Grassland: pine trees + bushes + rocks
- Forest: ashenvale trees + ferns + forest rocks (dense canopy)
- Jungle: palm trees + ferns + vines (high density)
- Desert: cacti + desert rocks + bones (sparse)
- Barrens: scattered trees + dry bushes + rocks
- Snow: snow pines + snowdrifts + rocks
- Swamp: dark trees + mushrooms + logs
- Rocky: rock formations + rock piles
- Beach: palm trees + beach rocks
- Volcanic: lava rocks + charred trees

Objects panel > Auto-Populate Biome: select biome, set seed, click
"Populate Zone" to fill the entire tile with biome-appropriate
vegetation at rule-defined densities.
This commit is contained in:
Kelsi 2026-05-05 16:42:41 -07:00
parent 0d401c3eb8
commit 110f250150
4 changed files with 143 additions and 0 deletions

View file

@ -1,4 +1,5 @@
#include "object_placer.hpp"
#include "terrain_biomes.hpp"
#include "core/logger.hpp"
#include <nlohmann/json.hpp>
#include <algorithm>
@ -199,6 +200,45 @@ void ObjectPlacer::scatter(const glm::vec3& center, float radius, int count,
LOG_INFO("Scattered ", count, " objects in radius ", radius);
}
int ObjectPlacer::populateBiome(const BiomeVegetation& vegetation,
float tileSize, const glm::vec3& tileOrigin,
uint32_t seed) {
int placed = 0;
std::mt19937 rng(seed);
std::uniform_real_distribution<float> distPos(0.0f, 1.0f);
std::uniform_real_distribution<float> distRot(0.0f, 360.0f);
for (const auto& asset : vegetation.assets) {
// Calculate object count from density (per 100x100 area)
float areaFactor = (tileSize * tileSize) / 10000.0f;
int count = static_cast<int>(asset.density * areaFactor);
std::uniform_real_distribution<float> distScale(asset.minScale, asset.maxScale);
for (int i = 0; i < count; i++) {
float u = distPos(rng);
float v = distPos(rng);
glm::vec3 pos = tileOrigin + glm::vec3(
-u * tileSize, -v * tileSize, 0.0f);
PlacedObject obj;
obj.type = PlaceableType::M2;
obj.path = asset.path;
obj.nameId = 0;
obj.uniqueId = nextUniqueId();
obj.position = pos;
obj.rotation = glm::vec3(0.0f, distRot(rng), 0.0f);
obj.scale = distScale(rng);
obj.selected = false;
objects_.push_back(obj);
placed++;
}
}
LOG_INFO("Biome populated: ", vegetation.name, "", placed, " objects placed");
return placed;
}
void ObjectPlacer::undoLastPlace() {
if (undoStack_.empty()) return;
int idx = undoStack_.back();