Files
UnrealPrototyping/Tools/Terrain/internal/dt/dt_test.go
T
2026-09-25 17:02:24 +03:00

117 lines
3.5 KiB
Go

package dt
import (
"math"
"testing"
)
func scatter(w, h int, seed uint32) []bool {
seeds := make([]bool, w*h)
for i := range seeds {
seed = seed*1664525 + 1013904223
seeds[i] = seed>>20&7 == 0
}
seeds[0] = true // guarantee at least one
return seeds
}
// brute is the definition: the smallest squared distance to any seed, with dx measured the short way round
// when the grid is a cylinder.
func brute(seeds []bool, w, h, x, y int, wrapX bool) float64 {
best := math.Inf(1)
for sy := 0; sy < h; sy++ {
for sx := 0; sx < w; sx++ {
if !seeds[sy*w+sx] {
continue
}
dx := float64(x - sx)
if wrapX {
if d := math.Abs(dx); d > float64(w)/2 {
dx = float64(w) - d
}
}
dy := float64(y - sy)
if d := dx*dx + dy*dy; d < best {
best = d
}
}
}
return best
}
func check(t *testing.T, w, h int, wrapX bool) {
t.Helper()
seeds := scatter(w, h, 99)
d2, near := Transform(seeds, w, h, wrapX)
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
want := brute(seeds, w, h, x, y, wrapX)
i := y*w + x
if math.Abs(float64(d2[i])-want) > 1e-3 {
t.Fatalf("wrap=%v cell (%d,%d): d2 %g, brute force %g", wrapX, x, y, d2[i], want)
}
// The feature index must be a seed, and it must be one at exactly that distance.
n := int(near[i])
if n < 0 || !seeds[n] {
t.Fatalf("wrap=%v cell (%d,%d): nearest %d is not a seed", wrapX, x, y, n)
}
got := brute(onlyAt(w, h, n), w, h, x, y, wrapX)
if math.Abs(got-want) > 1e-3 {
t.Fatalf("wrap=%v cell (%d,%d): nearest seed %d is at %g, not %g", wrapX, x, y, n, got, want)
}
}
}
}
func onlyAt(w, h, i int) []bool {
s := make([]bool, w*h)
s[i] = true
return s
}
// The one test the coastal pass rests on. Everything there is written in terms of "how far is this cell from
// the waterline and which stretch does it belong to", so a distance transform that is subtly wrong would not
// fail loudly - it would put the shelf break in slightly the wrong place everywhere. The transform is exact,
// so the comparison is against an exhaustive search and the tolerance is float32 rounding.
func TestMatchesBruteForce(t *testing.T) { check(t, 41, 37, false) }
// And the same on a cylinder, which is what a planet is. The failure this catches is a shelf that stops dead
// at the seam.
func TestMatchesBruteForceOnACylinder(t *testing.T) { check(t, 41, 37, true) }
// A seed on one edge must be found from the other edge, and by the short way round.
func TestWrapFindsTheSeedAcrossTheSeam(t *testing.T) {
const w, h = 9, 3
seeds := make([]bool, w*h)
seeds[h/2*w+0] = true // one seed, at column 0 of the middle row
d2, near := Transform(seeds, w, h, true)
// Column 8 is one step from column 0 the short way round, eight steps the long way.
if got := d2[h/2*w+8]; math.Abs(float64(got)-1) > 1e-6 {
t.Errorf("d2 at column 8 = %g, want 1", got)
}
if got := near[h/2*w+8]; got != int32(h/2*w) {
t.Errorf("near at column 8 = %d, want %d", got, h/2*w)
}
// The far side of the cylinder is four steps away either way.
if got := d2[h/2*w+4]; math.Abs(float64(got)-16) > 1e-6 {
t.Errorf("d2 at column 4 = %g, want 16", got)
}
// Without the wrap the same grid gives eight.
d2f, _ := Transform(seeds, w, h, false)
if got := d2f[h/2*w+8]; math.Abs(float64(got)-64) > 1e-6 {
t.Errorf("unwrapped d2 at column 8 = %g, want 64", got)
}
}
func TestNoSeedsAtAll(t *testing.T) {
const w, h = 5, 4
seeds := make([]bool, w*h)
_, near := Transform(seeds, w, h, true)
for i, n := range near {
if n != -1 {
t.Fatalf("cell %d reports a nearest seed %d on an empty grid", i, n)
}
}
}