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) } } }