Ecosystems and Ground Cover¶
The acron.vegetation module is the native half of plant ecosystems
and grass: a terrain description, a simulator that grows woods over it
(after Deussen et al., “Realistic Modeling and Rendering of Plant
Ecosystems”, SIGGRAPH 1998, and Makowski et al., “Synthetic Silviculture:
Multi-scale Modeling of Plant Ecosystems”, SIGGRAPH 2019), and the CPU side
of instanced grass (where the blades stand, which cells draw at which level
of detail, a trample map). It is CPU code with no graphics dependency
beyond writing textures, so a C++ game, a tool or a server can place plants
with it directly. The Python tier, acron.ecosystem, adds the presets,
the planting into acron.trees and the grass renderer and shaders (see
Ecosystems and Grass).
Everything is deterministic: every random draw is a hash of world integers (cell indices, the step, the seed), never a running generator, and the module is compiled without fused multiply-adds or fast maths.
The classes¶
EcoTerrainA heightfield over a rectangle of the world (
originits south-west corner,sizein metres) from aPNMImage(grey level times a scale plus an offset) or aPTA_float, and optional masks over the same rectangle: moisture (0..1; without it, derived from the relief: hollows wetter, ridges and steep slopes drier, banks wettest), exclusion (1: no plants;paint_pathadds a road along a polyline) and water. Images are read with their first row at the north edge.get_height,get_normal,get_slope(degrees),get_moisture,get_exclusion,get_watersample bilinearly;set_altitude(offset, scale)maps heights onto the altitudes species’ limits are written in.EcoSpeciesA species’ ecology as plain values:
max_height,crown_ratio,growth_rate,max_age,maturity_age,shade_tolerance,moisture_pref/moisture_width,slope_max,altitude_min/altitude_max,seed_radius,seed_rate,density(seed rain per hectare a year),crown_opacity,patchiness/patch_scaleandunderstory.light_response(light)is the vigour a species draws from a share of the open sky’s light.EcosystemThe simulator: a lattice of
cell_sizecells (4 m), each with at most one tree and one understory plant, stepped everystepyears foryearsyears – light from the neighbours’ crowns, vigour from light and site, growth, mortality (background, stress, senescence, windthrow patches:disturbance), seeding within the seed radius plus patchy seed rain.set_clearingskeeps a share of the land open,set_initial_coverandset_stand_sizeset the starting mosaic.simulate_tile(tx, ty),simulate_tiles(coords, threads)(on every core) andsimulate_region(lo, hi)areBLOCKING. A tile runs over a halo of (steps + 1) xget_reach()cells that shrinks by the reach each step, so its own cells are exactly those of a simulation of the whole world: tiles are byte-identical in any order, on any thread, and seamless (on one platform: the C library’sexpandpowmay round differently on another, and the plants with them). When a thread can’t be started, or withsupport-threads #fin Config.prc, the tiles left run on the calling thread, with the same bytes. Kept tiles answerget_ground_light(x, y), the light under the crowns.EcoTileA simulated tile’s plants,
FLOATS(10) floats each inget_table(): x, y, z, species, age, height, crown radius, health, light, layer; per speciesget_positions(x, y, z triples),get_heights,get_ages,get_healths; and the ground light per cell (get_ground_grid,get_ground_light).GrassFieldBlade layout and cells for instanced grass: a world-anchored jittered grid of
densityblades per square metre, thinned byget_density_at(exclusion, water, slope, dry ground, an optional density image, an ecosystem’s ground light), gathered in clumps, sorted by a random rank. A cell’s table is one RGBA32F texel a blade (ROW= 512 a row): the root’s x, y, z and a 24-bit integer (height, facing, the clump’s tone).update(camera)picks the cells within the last distance, gives each a level (0, 1, 2) by distance with hysteresis and a draw count (the far ones draw a prefix of their ranked table), builds new cells, frees far ones and reports the changed cells;write_cell_textureuploads one.GrassTrampleA square map of trodden grass (push direction, flatness) that follows a centre in whole texels;
stamp,recover,write_texture(RGBA16F).
Using it¶
#include "ecoTerrain.h"
#include "ecosystem.h"
#include "grassField.h"
PT(EcoTerrain) terrain = new EcoTerrain(LPoint2f(0, 0),
LVecBase2f(1024, 1024));
terrain->set_heights(PNMImage("heights.png"), 80.0f);
PT(EcoSpecies) birch = new EcoSpecies("birch");
birch->set_shade_tolerance(0.1f);
birch->set_max_height(20.0f);
PT(Ecosystem) eco = new Ecosystem(terrain, 7);
eco->add_species(birch);
PT(EcoTile) tile = eco->simulate_tile(0, 0);
PTA_float xyz = tile->get_positions(0); // 3 floats a plant
PT(GrassField) grass = new GrassField(terrain, 16.0f, 1);
grass->set_ecosystem(eco);
// every frame
grass->update(camera_pos);
for (int i = 0; i < grass->get_num_changed(); ++i) {
int cell = grass->get_changed(i);
grass->write_cell_texture(cell, tables[cell]);
}
Timings (Apple M4 Pro): a square kilometre, ten species, 100 years, in 16 tiles on every core in 0.5 s; grass layout 0.12 s per million blades.