Files
UnrealPrototyping/Tools/Terrain/internal/fluvial/jitter.go
T

37 lines
2.1 KiB
Go

package fluvial
// Deterministic per-cell jitter, and why a router needs one.
//
// D8 lets a cell drain to one of eight neighbours, so every channel is a chain of 0, 45 and 90 degree
// segments. In mountains the slope hides it. On a plain it is the dominant artefact, and for a specific
// reason: across a filled flat the only gradient present is the priority-flood's own epsilon, one millimetre
// a cell, applied in the order the flood happened to reach the cells. The router then faithfully follows the
// flood's traversal geometry and draws it as rivers — ruler-straight diagonals, the polygonal network that
// killed the first attempt at flat plains.
//
// The fix is to stop the epsilon being uniform. A hash of the cell index scatters it by plus or minus half,
// which is far below anything that matters to the solve (a millimetre against metre-scale relief) and far
// above the difference the flood's ordering would otherwise leave, so the descent direction on a flat is
// decided by the hash rather than by scan order. The same hash breaks near-ties between two equally steep
// neighbours, which is the other place a fixed direction order leaks a grid axis into the result.
//
// It is a hash rather than a random source because cross-cutting rule 12 is determinism from a seed: the
// value for a cell must not depend on how many cells were visited before it, on which goroutine ran, or on
// how many steps the solve has taken.
// hash01 is splitmix64 finalised to the unit interval. Cheap, no state, and well enough distributed that
// neighbouring indices get unrelated values — which is the whole requirement here.
func hash01(seed uint64, i int32) float32 {
x := seed ^ (uint64(uint32(i)) * 0x9e3779b97f4a7c15)
x ^= x >> 30
x *= 0xbf58476d1ce4e5b9
x ^= x >> 27
x *= 0x94d049bb133111eb
x ^= x >> 31
return float32(x>>11) / float32(1<<53)
}
// SetSeed ties the jitter to the run's seed, so two seeds do not share the same flat-routing geometry.
// Zero is a perfectly good seed; it is the default and nothing depends on it being set.
func (g *Grid) SetSeed(seed int64) { g.seed = uint64(seed)*0x9e3779b97f4a7c15 + 0x243f6a8885a308d3 }