107 lines
3.4 KiB
Go
107 lines
3.4 KiB
Go
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)
|
|
}
|
|
}
|