Files
UnrealPrototyping/Tools/Terrain/internal/field/smooth_test.go
T
2026-09-25 17:02:24 +03:00

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