Tooling
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
package tile
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"salty/terrain/internal/field"
|
||||
"salty/terrain/internal/world"
|
||||
)
|
||||
|
||||
func testPlanet(t *testing.T) world.Planet {
|
||||
t.Helper()
|
||||
// 64 geology columns of 8 m is a 512 m circumference, 40 painted rows, 4 of polar pad.
|
||||
p := world.Planet{CellM: 8, W: 64, H: 48, PadY: 4, NoisePeriodM: 512}
|
||||
if err := p.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func TestNewGridDivides(t *testing.T) {
|
||||
p := testPlanet(t)
|
||||
// 64 detail cells at factor 4 is 16 geology cells, and 64 divides by 16.
|
||||
g, err := NewGrid(p, 4, 64, 8)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if g.NX != 4 {
|
||||
t.Errorf("NX = %d, want 4", g.NX)
|
||||
}
|
||||
// 40 painted rows over 16-cell tiles is 3 rows, the last one short.
|
||||
if g.NY != 3 {
|
||||
t.Errorf("NY = %d, want 3", g.NY)
|
||||
}
|
||||
if g.MarginGeo != 2 {
|
||||
t.Errorf("MarginGeo = %d, want 2 (8 detail cells at factor 4)", g.MarginGeo)
|
||||
}
|
||||
}
|
||||
|
||||
// X wraps, so a tile grid that did not come out whole would leave the last tile overlapping the first by an
|
||||
// arbitrary amount and there would be no honest way to name the seam.
|
||||
func TestNewGridRefusesASideThatDoesNotDivide(t *testing.T) {
|
||||
p := testPlanet(t)
|
||||
_, err := NewGrid(p, 4, 4*13, 8) // 13 geology cells does not divide 64
|
||||
if err == nil {
|
||||
t.Fatal("accepted a tile side that does not divide the circumference")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "X wraps") {
|
||||
t.Errorf("error %q does not say why", err)
|
||||
}
|
||||
if _, err := NewGrid(p, 4, 66, 8); err == nil {
|
||||
t.Fatal("accepted a tile side that is not a whole number of geology cells")
|
||||
}
|
||||
}
|
||||
|
||||
// The last row is short rather than padded: padding would put invented ground in the output.
|
||||
func TestTilesCoverThePaintedRowsExactly(t *testing.T) {
|
||||
p := testPlanet(t)
|
||||
g, err := NewGrid(p, 4, 64, 8)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
seen := make([]int, p.W*p.PaintH())
|
||||
for _, tl := range g.Tiles() {
|
||||
for y := tl.Y0; y < tl.Y0+tl.H; y++ {
|
||||
for x := tl.X0; x < tl.X0+tl.W; x++ {
|
||||
seen[y*p.W+x]++
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, n := range seen {
|
||||
if n != 1 {
|
||||
t.Fatalf("cell %d covered %d times, want exactly 1", i, n)
|
||||
}
|
||||
}
|
||||
last := g.Tiles()[len(g.Tiles())-1]
|
||||
if last.H != p.PaintH()-2*g.SideGeo {
|
||||
t.Errorf("the last tile row is %d cells, want %d", last.H, p.PaintH()-2*g.SideGeo)
|
||||
}
|
||||
}
|
||||
|
||||
// A tile at the seam reads its left margin from the far side of the map, and one at a pole clamps rather
|
||||
// than wrapping: the top and bottom of the map are the poles, not each other.
|
||||
func TestCutWrapsInXAndClampsInY(t *testing.T) {
|
||||
p := testPlanet(t)
|
||||
g, err := NewGrid(p, 4, 64, 8)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// A source whose value encodes its own position, so a misplaced read is obvious.
|
||||
src := field.New(p.W, p.PaintH(), p.CellM)
|
||||
for y := 0; y < src.H; y++ {
|
||||
for x := 0; x < src.W; x++ {
|
||||
src.Data[y*src.W+x] = float32(y*1000 + x)
|
||||
}
|
||||
}
|
||||
|
||||
first := g.Tiles()[0] // X0 = 0, Y0 = 0: both edges
|
||||
out, ix, iy := g.Cut(first, src, 0)
|
||||
if ix != g.MarginGeo*g.Factor || iy != g.MarginGeo*g.Factor {
|
||||
t.Errorf("interior offset %d,%d, want %d", ix, iy, g.MarginGeo*g.Factor)
|
||||
}
|
||||
// The left margin is the far side of the cylinder.
|
||||
if got, want := out.Data[g.MarginGeo*out.W+0], float32(0*1000+p.W-g.MarginGeo); got != want {
|
||||
t.Errorf("left margin reads %v, want %v (the columns across the seam)", got, want)
|
||||
}
|
||||
// The top margin is row 0 repeated, not the bottom of the map.
|
||||
if got, want := out.Data[0*out.W+g.MarginGeo], float32(0*1000+0); got != want {
|
||||
t.Errorf("top margin reads %v, want %v (row 0 clamped)", got, want)
|
||||
}
|
||||
// And the interior is itself.
|
||||
if got, want := out.Data[g.MarginGeo*out.W+g.MarginGeo], float32(0); got != want {
|
||||
t.Errorf("interior corner reads %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// The frame is what every hash and noise lattice in the detail passes is keyed on, so it has to name the
|
||||
// right physical place at detail resolution.
|
||||
func TestFrameIsTheDetailWorldPosition(t *testing.T) {
|
||||
p := testPlanet(t)
|
||||
g, err := NewGrid(p, 4, 64, 8)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tiles := g.Tiles()
|
||||
second := tiles[1] // X0 = 16 geology cells
|
||||
|
||||
f := g.Frame(second)
|
||||
if f.P.CellM != 2 {
|
||||
t.Errorf("frame cell = %v m, want 2", f.P.CellM)
|
||||
}
|
||||
if f.P.W != p.W*4 {
|
||||
t.Errorf("frame planet width = %d, want %d detail columns", f.P.W, p.W*4)
|
||||
}
|
||||
// The cut starts a margin before the interior: (16 - 2) geology cells is 56 detail columns.
|
||||
if f.X0 != (16-g.MarginGeo)*4 {
|
||||
t.Errorf("frame X0 = %d, want %d", f.X0, (16-g.MarginGeo)*4)
|
||||
}
|
||||
// The interior's first detail column is the margin in, and it must name geology column 16.
|
||||
wx, _ := f.PlanetXY(g.MarginGeo*g.Factor, 0)
|
||||
if wx != 16*4 {
|
||||
t.Errorf("the interior's first column is detail column %d, want %d", wx, 16*4)
|
||||
}
|
||||
if got, want := g.OriginXM(second), 16*8.0; got != want {
|
||||
t.Errorf("OriginXM = %v, want %v", got, want)
|
||||
}
|
||||
// Row 0 of the painted map is world Y zero; the polar pad is behind it.
|
||||
if got := g.OriginYM(tiles[0]); got != 0 {
|
||||
t.Errorf("OriginYM of the first tile row = %v, want 0", got)
|
||||
}
|
||||
}
|
||||
|
||||
// A frame that straddles the seam names the same physical columns as one that does not.
|
||||
func TestASeamTileNamesTheSameColumns(t *testing.T) {
|
||||
p := testPlanet(t)
|
||||
g, err := NewGrid(p, 4, 64, 8)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
first := g.Tiles()[0] // X0 = 0, so its left margin is across the seam
|
||||
f := g.Frame(first)
|
||||
// Detail column 0 of the cut is (0 - margin) geology cells, wrapped.
|
||||
wx, _ := f.PlanetXY(0, 0)
|
||||
if want := (p.W - g.MarginGeo) * 4; wx != want {
|
||||
t.Errorf("the cut's first column is detail column %d, want %d across the seam", wx, want)
|
||||
}
|
||||
// And the interior's first column is detail column 0.
|
||||
wx, _ = f.PlanetXY(g.MarginGeo*g.Factor, 0)
|
||||
if wx != 0 {
|
||||
t.Errorf("the interior's first column is %d, want 0", wx)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user