package planet import ( "math" "testing" "salty/terrain/internal/template" ) // The profile is the whole of what a crater is, so it is worth pinning down: shore at sea level, a crest, an // inner wall, a floor, and nothing anywhere that is not monotonic on its own segment. func TestCraterProfileIsRimThenFloor(t *testing.T) { c := template.Crater{RimM: 340, FloorM: 60, RimAt: 0.30, WallAt: 0.62} if got := craterProfile(c, 0, 0); got != 0 { t.Errorf("at the shore = %.1f m, want sea level: the island keeps the outline that was painted", got) } if got := craterProfile(c, 0, c.RimAt); math.Abs(got-c.RimM) > 1e-9 { t.Errorf("at rim_at = %.1f m, want the rim %.1f", got, c.RimM) } if got := craterProfile(c, 0, c.WallAt); math.Abs(got-c.FloorM) > 1e-9 { t.Errorf("at wall_at = %.1f m, want the floor %.1f", got, c.FloorM) } if got := craterProfile(c, 0, 1); got != c.FloorM { t.Errorf("at the centre = %.1f m, want the floor %.1f", got, c.FloorM) } // Rising to the crest, falling to the floor, and the floor is dry. prev := craterProfile(c, 0, 0) for i := 1; i <= 30; i++ { v := craterProfile(c, 0, c.RimAt*float64(i)/30) if v < prev-1e-9 { t.Fatalf("the outer flank dips at t = %.3f", c.RimAt*float64(i)/30) } prev = v } prev = craterProfile(c, 0, c.RimAt) for i := 1; i <= 30; i++ { v := craterProfile(c, 0, c.RimAt+(c.WallAt-c.RimAt)*float64(i)/30) if v > prev+1e-9 { t.Fatalf("the inner wall rises at t = %.3f", c.RimAt+(c.WallAt-c.RimAt)*float64(i)/30) } prev = v } if c.FloorM <= 0 { t.Error("this crater's floor is not above sea level, which is what was asked for") } } // A blob is normalised by its own widest point, so the same numbers describe a small crater and a large one. func TestCraterComponentsMeasureEachBlobsOwnRadius(t *testing.T) { const w, h = 40, 12 mask := make([]bool, w*h) dist := make([]float64, w*h) set := func(x0, y0, wid, hei int, d float64) { for y := y0; y < y0+hei; y++ { for x := x0; x < x0+wid; x++ { mask[y*w+x] = true dist[y*w+x] = d } } } set(2, 2, 6, 6, 3) // a blob whose widest point is 3 set(20, 4, 4, 4, 1.5) // and a smaller one at 1.5 wrap := func(x int) int { return ((x % w) + w) % w } comp, maxDist, n := craterComponents(mask, dist, w, h, wrap) if n != 2 { t.Fatalf("found %d blobs, want 2", n) } a := maxDist[comp[3*w+3]] b := maxDist[comp[5*w+21]] if a != 3 || b != 1.5 { t.Errorf("radii %.1f and %.1f, want 3 and 1.5", a, b) } } // A crater on the seam is one crater, not two half craters with two different radii. func TestACraterOnTheSeamIsOneBlob(t *testing.T) { const w, h = 40, 12 mask := make([]bool, w*h) dist := make([]float64, w*h) for y := 4; y < 8; y++ { for _, x := range []int{38, 39, 0, 1} { mask[y*w+x] = true dist[y*w+x] = 2 } } wrap := func(x int) int { return ((x % w) + w) % w } _, _, n := craterComponents(mask, dist, w, h, wrap) if n != 1 { t.Fatalf("found %d blobs across the seam, want 1", n) } }