This commit is contained in:
Rainer Leit
2026-09-25 17:02:24 +03:00
parent cc43ed8dc8
commit 9597629951
2149 changed files with 460234 additions and 1770 deletions
@@ -22,6 +22,12 @@ type bucketPQ struct {
const bucketWidthM = 0.01
// reposeOrderBuckets is how far ClampToRepose scatters a cell from its own bucket, in buckets, either way.
// Sixteen buckets is sixteen centimetres: enough that a tie spreads over about thirty of them and the pop
// order stops tracking the raster, far below the metres the clamp's ordering could ever depend on. See
// pushJittered.
const reposeOrderBuckets = 16
func newBucketPQ(loM, hiM float64) *bucketPQ {
if hiM <= loM {
hiM = loM + 1
@@ -54,6 +60,34 @@ func (q *bucketPQ) push(elev float32, idx int32) {
q.count++
}
// pushJittered is push with the bucket chosen from the elevation plus j buckets, so cells at the same
// elevation land in different buckets instead of in one and pop in hash order rather than in reverse raster
// order.
//
// Only ClampToRepose wants this. The flood already scatters its own order through the epsilon it adds to the
// height, so its cells rarely share a bucket; the clamp reads the raw surface, where flat ground puts every
// cell in one bucket and the LIFO below then processes them bottom-right to top-left, every time, everywhere.
//
// Half a bucket is not enough - it splits a tie across two buckets and halves the correlation instead of
// removing it - so the caller scatters over reposeOrderBuckets, and that is safe for a reason worth writing
// down. The clamp's order can only matter between two cells whose heights differ by about the talus
// allowance, which is metres: a cell popped early is marked closed and never lowered again, but every cell
// popping after it stands within the jitter width of it, so its limit is the other cell's height plus the
// full allowance and cannot bind. Reordering cells that are centimetres apart therefore cannot break a
// constraint that only bites metres apart. The bound is talus*cell/2, which is 2.8 m at 35 degrees on an 8 m
// cell; the constant below is two orders of magnitude inside it.
func (q *bucketPQ) pushJittered(elev float32, idx int32, j float32) {
b := int((float64(elev)-q.lo)/q.width + float64(j))
if b < q.cur {
b = q.cur
}
if b >= len(q.buckets) {
b = len(q.buckets) - 1
}
q.buckets[b] = append(q.buckets[b], idx)
q.count++
}
// pop returns the lowest cell. The cursor only moves forward, so the total scan cost over a whole flood is
// the number of buckets, not the number of pops.
func (q *bucketPQ) pop() int32 {