package field import ( "math" "testing" ) // The claim the smooth has to earn: it takes the ripple off and leaves the landform. Two surfaces in one // grid - a plane carrying a small corrugation, and a cliff far steeper than slopeRef - and the pass has to // treat them differently or it is a box blur with extra arithmetic. func TestSmoothTakesTheRippleAndLeavesTheCliff(t *testing.T) { const ( w, h = 128, 128 cellM = 8.0 slopeRef = 0.3 ) land := make([]bool, w*h) for i := range land { land[i] = true } hgt := make([]float32, w*h) for y := 0; y < h; y++ { for x := 0; x < w; x++ { v := 100.0 if x >= w/2 { v = 400.0 // a 300 m cliff at mid-grid: 37.5 rise over run, a hundred times slopeRef } // A 4 m corrugation at a four-cell wavelength, which is 0.125 rise over run - under slopeRef. v += 2 * math.Sin(2*math.Pi*float64(y)/4) hgt[y*w+x] = float32(v) } } before := make([]float32, len(hgt)) copy(before, hgt) SmoothEdgePreserving(hgt, w, h, cellM, land, 2, slopeRef, make([]float32, w*h)) // The ripple, measured well away from the cliff. rip := func(f []float32, x int) float64 { lo, hi := math.Inf(1), math.Inf(-1) for y := 8; y < h-8; y++ { v := float64(f[y*w+x]) lo, hi = math.Min(lo, v), math.Max(hi, v) } return hi - lo } ripBefore, ripAfter := rip(before, w/4), rip(hgt, w/4) // The cliff, measured across the step on a row far from the edges. step := func(f []float32) float64 { y := h / 2 return float64(f[y*w+w/2] - f[y*w+w/2-1]) } stepBefore, stepAfter := step(before), step(hgt) t.Logf("ripple %.2f -> %.2f m (%.0f%% removed); cliff %.1f -> %.1f m (%.0f%% kept)", ripBefore, ripAfter, 100*(1-ripAfter/ripBefore), stepBefore, stepAfter, 100*stepAfter/stepBefore) if ripAfter > 0.5*ripBefore { t.Errorf("the ripple is still %.0f%% of what it was; the pass is not smoothing", 100*ripAfter/ripBefore) } if stepAfter < 0.9*stepBefore { t.Errorf("the cliff lost %.0f%% of its height; the edge weight is not preserving", 100*(1-stepAfter/stepBefore)) } } // The waterline is a wall. A sea cell is never written, and a land cell beside one is never pulled towards // sea level - clamping either way would move the shore, and the coastal pass owns the shore. func TestSmoothNeverReachesAcrossTheWaterline(t *testing.T) { const ( w, h = 64, 64 cellM = 8.0 ) land := make([]bool, w*h) hgt := make([]float32, w*h) for y := 0; y < h; y++ { for x := 0; x < w; x++ { i := y*w + x if x < w/2 { hgt[i] = -40 // sea floor } else { land[i] = true hgt[i] = 60 // a plateau meeting it at a hundred-metre cliff } } } before := make([]float32, len(hgt)) copy(before, hgt) SmoothEdgePreserving(hgt, w, h, cellM, land, 4, 0.3, make([]float32, w*h)) for i := range hgt { if !land[i] && hgt[i] != before[i] { t.Fatalf("a sea cell moved %.4f m", hgt[i]-before[i]) } } // The first land column has three land neighbours and five sea ones. On a flat plateau it must not move // at all: the sea neighbours contribute nothing, and the land ones are all at its own height. worst := float32(0) for y := 1; y < h-1; y++ { if d := hgt[y*w+w/2] - before[y*w+w/2]; math.Abs(float64(d)) > float64(worst) { worst = d } } t.Logf("worst move on the shore column: %.6f m", worst) if math.Abs(float64(worst)) > 1e-3 { t.Errorf("the shore column moved %.4f m; the pass is reading across the waterline", worst) } }