Tooling
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user