Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit61a33237e0

README.md

terrain_erode_droplet

Droplet hydraulic erosion over one tile, one batch at a time.

Each thread carries one droplet. It is placed by a hash of its index, the op's seed, the batch number and the tile's field-wide base cell, then runs downhill: it picks up material where it moves fast over steep ground and drops it where the ground flattens or rises. That is what cuts channels into a ridge and lays the fans out below them, and it is why the result looks like water shaped it rather than like a filter ran over it.

Reproducibility

The pass never writes height. Every change accumulates into delta as fixed-point integers, 1/65536 m per unit, through atomicAdd. Integer addition commutes; float addition does not. Thousands of droplets landing on one cell therefore sum to the same total whatever order the threads resolve in, and the same seed over the same stack gives the same terrain every run — on any GPU, with any scheduling.

A float accumulator would be a few last bits different each run, and everything downstream that assumes a seed names a landscape (storage, streaming, a shared world) would drift.

What one batch may move

A batch works out every droplet's change from the ground as it stood when the batch began and adds them all at the end, so nothing in a batch can see what the rest of it is doing to the cell it is standing on. Two bounds are what keep that from running away.

share is the part of one cell's rainfall a droplet carries. A batch rains dropletCount droplets over the interior and each walks up to apron cells, so a cell is stepped on dropletCount * apron / interiorCells times a batch and every one of those steps adds into the same accumulator. Scaling each droplet's change by the reciprocal is what makes rain a sampling density: doubling it resolves the flow better and moves the same material. Rain too sparse to step on every cell gives a droplet one cell's rainfall and no more, because a droplet carrying several cells' worth would put a whole batch's cut into a single step and reopen the swing below. A run below that density — fewer than interiorCells / apron droplets a batch — therefore samples the flow sparsely and moves proportionally less; above it the result stops changing with rain.

MAX_SHARE is the most of the drop below a cell one batch may cut, and the most of the rise above it one batch may fill. Without it a droplet cuts the whole drop, which leaves the cell level with the one it drains into — and every other droplet in the batch cuts that same drop from the same starting ground, so the cell ends the batch far below its neighbour. The next batch finds the pair reversed and cuts back further, and the swing grows every batch until the fixed-point accumulator wraps. terrain_erode_thermal names the same bound; a quarter rather than a half, because water gathers where the ground funnels it and a cell along a channel is stepped on several times over what an average cell gets.

Tiling

A droplet takes at most maxSteps steps, and the caller keeps that at or below the apron width. Past that it would read cells whose real owner is a neighbouring tile and whose contents have not been exchanged since the batch began, and the two tiles would disagree about the ground at their seam.

Droplets spawn in the interior only. A spawn in the apron would place a second droplet on ground the neighbouring tile has already rained on, doubling the rainfall in a band along every seam. Material a droplet carries out of the interior and into the apron is not lost: terrain_delta_gather adds it into the neighbour's interior before either tile folds its accumulator into height.

What a droplet is still carrying when its walk ends goes with it. A run therefore lowers the ground it cuts: material leaves the uplands, and a field has no basin outside itself for it to arrive in. The fall in the field's mean is a small fraction of the depth the channels reach.

Parameters

Lane 1 carries seed, batch, dropletCount, maxSteps; lane 2 solubility, deposition, evaporation, inertia; lane 4 minSlope, capacity, gravity, apron. Heights are metres and horizontal distances are cells, so capacity and minSlope read as metres of fall per cell of run.

Every op carries a mask, and for a simulation it bounds what a step MOVES. The mask scales both what a step cuts and what it lays down, and the material a step declines to move stays where it was — in the ground, or in the droplet — so a droplet carries its load ACROSS ground the mask holds out rather than losing it there.

What a droplet still holds when its walk ends lands where the walk ended, whatever the mask reads there. The ground it walked is already lighter by exactly that much, so scaling that last drop would leave the difference nowhere and the pass would remove material instead of moving it. The mask therefore bounds where a run cuts exactly, and where it deposits everywhere except the cell a walk ends on. An op that names no mask erodes at full strength everywhere.