affine_test.go raw

   1  // SPDX-License-Identifier: Unlicense OR MIT
   2  
   3  package f32
   4  
   5  import (
   6  	"math"
   7  	"testing"
   8  )
   9  
  10  func eq(p1, p2 Point) bool {
  11  	tol := 1e-5
  12  	dx, dy := p2.X-p1.X, p2.Y-p1.Y
  13  	return math.Abs(math.Sqrt(float64(dx*dx+dy*dy))) < tol
  14  }
  15  
  16  func eqaff(x, y Affine2D) bool {
  17  	tol := 1e-5
  18  	return math.Abs(float64(x.a-y.a)) < tol &&
  19  		math.Abs(float64(x.b-y.b)) < tol &&
  20  		math.Abs(float64(x.c-y.c)) < tol &&
  21  		math.Abs(float64(x.d-y.d)) < tol &&
  22  		math.Abs(float64(x.e-y.e)) < tol &&
  23  		math.Abs(float64(x.f-y.f)) < tol
  24  }
  25  
  26  func TestTransformOffset(t *testing.T) {
  27  	p := Point{X: 1, Y: 2}
  28  	o := Point{X: 2, Y: -3}
  29  
  30  	r := Affine2D{}.Offset(o).Transform(p)
  31  	if !eq(r, Pt(3, -1)) {
  32  		t.Errorf("offset transformation mismatch: have %v, want {3 -1}", r)
  33  	}
  34  	i := Affine2D{}.Offset(o).Invert().Transform(r)
  35  	if !eq(i, p) {
  36  		t.Errorf("offset transformation inverse mismatch: have %v, want %v", i, p)
  37  	}
  38  }
  39  
  40  func TestTransformScale(t *testing.T) {
  41  	p := Point{X: 1, Y: 2}
  42  	s := Point{X: -1, Y: 2}
  43  
  44  	r := Affine2D{}.Scale(Point{}, s).Transform(p)
  45  	if !eq(r, Pt(-1, 4)) {
  46  		t.Errorf("scale transformation mismatch: have %v, want {-1 4}", r)
  47  	}
  48  	i := Affine2D{}.Scale(Point{}, s).Invert().Transform(r)
  49  	if !eq(i, p) {
  50  		t.Errorf("scale transformation inverse mismatch: have %v, want %v", i, p)
  51  	}
  52  }
  53  
  54  func TestTransformRotate(t *testing.T) {
  55  	p := Point{X: 1, Y: 0}
  56  	a := float32(math.Pi / 2)
  57  
  58  	r := Affine2D{}.Rotate(Point{}, a).Transform(p)
  59  	if !eq(r, Pt(0, 1)) {
  60  		t.Errorf("rotate transformation mismatch: have %v, want {0 1}", r)
  61  	}
  62  	i := Affine2D{}.Rotate(Point{}, a).Invert().Transform(r)
  63  	if !eq(i, p) {
  64  		t.Errorf("rotate transformation inverse mismatch: have %v, want %v", i, p)
  65  	}
  66  }
  67  
  68  func TestTransformShear(t *testing.T) {
  69  	p := Point{X: 1, Y: 1}
  70  
  71  	r := Affine2D{}.Shear(Point{}, math.Pi/4, 0).Transform(p)
  72  	if !eq(r, Pt(2, 1)) {
  73  		t.Errorf("shear transformation mismatch: have %v, want {2 1}", r)
  74  	}
  75  	i := Affine2D{}.Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
  76  	if !eq(i, p) {
  77  		t.Errorf("shear transformation inverse mismatch: have %v, want %v", i, p)
  78  	}
  79  }
  80  
  81  func TestTransformMultiply(t *testing.T) {
  82  	p := Point{X: 1, Y: 2}
  83  	o := Point{X: 2, Y: -3}
  84  	s := Point{X: -1, Y: 2}
  85  	a := float32(-math.Pi / 2)
  86  
  87  	r := Affine2D{}.Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Transform(p)
  88  	if !eq(r, Pt(1, 3)) {
  89  		t.Errorf("complex transformation mismatch: have %v, want {1 3}", r)
  90  	}
  91  	i := Affine2D{}.Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
  92  	if !eq(i, p) {
  93  		t.Errorf("complex transformation inverse mismatch: have %v, want %v", i, p)
  94  	}
  95  }
  96  
  97  func TestPrimes(t *testing.T) {
  98  	xa := NewAffine2D(9, 11, 13, 17, 19, 23)
  99  	xb := NewAffine2D(29, 31, 37, 43, 47, 53)
 100  
 101  	pa := Point{X: 2, Y: 3}
 102  	pb := Point{X: 5, Y: 7}
 103  
 104  	for _, test := range []struct {
 105  		x   Affine2D
 106  		p   Point
 107  		exp Point
 108  	}{
 109  		{x: xa, p: pa, exp: Pt(64, 114)},
 110  		{x: xa, p: pb, exp: Pt(135, 241)},
 111  		{x: xb, p: pa, exp: Pt(188, 280)},
 112  		{x: xb, p: pb, exp: Pt(399, 597)},
 113  	} {
 114  		got := test.x.Transform(test.p)
 115  		if !eq(got, test.exp) {
 116  			t.Errorf("%v.Transform(%v): have %v, want %v", test.x, test.p, got, test.exp)
 117  		}
 118  	}
 119  
 120  	for _, test := range []struct {
 121  		x   Affine2D
 122  		exp Affine2D
 123  	}{
 124  		{x: xa, exp: NewAffine2D(-1.1875, 0.6875, -0.375, 1.0625, -0.5625, -0.875)},
 125  		{x: xb, exp: NewAffine2D(1.5666667, -1.0333333, -3.2000008, -1.4333333, 1-0.03333336, 1.7999992)},
 126  	} {
 127  		got := test.x.Invert()
 128  		if !eqaff(got, test.exp) {
 129  			t.Errorf("%v.Invert(): have %v, want %v", test.x, got, test.exp)
 130  		}
 131  	}
 132  
 133  	got := xa.Mul(xb)
 134  	exp := NewAffine2D(734, 796, 929, 1310, 1420, 1659)
 135  	if !eqaff(got, exp) {
 136  		t.Errorf("%v.Mul(%v): have %v, want %v", xa, xb, got, exp)
 137  	}
 138  }
 139  
 140  func TestTransformScaleAround(t *testing.T) {
 141  	p := Pt(-1, -1)
 142  	target := Pt(-6, -13)
 143  	pt := Affine2D{}.Scale(Pt(4, 5), Pt(2, 3)).Transform(p)
 144  	if !eq(pt, target) {
 145  		t.Log(pt, "!=", target)
 146  		t.Error("Scale not as expected")
 147  	}
 148  }
 149  
 150  func TestTransformRotateAround(t *testing.T) {
 151  	p := Pt(-1, -1)
 152  	pt := Affine2D{}.Rotate(Pt(1, 1), -math.Pi/2).Transform(p)
 153  	target := Pt(-1, 3)
 154  	if !eq(pt, target) {
 155  		t.Log(pt, "!=", target)
 156  		t.Error("Rotate not as expected")
 157  	}
 158  }
 159  
 160  func TestMulOrder(t *testing.T) {
 161  	A := Affine2D{}.Offset(Pt(100, 100))
 162  	B := Affine2D{}.Scale(Point{}, Pt(2, 2))
 163  	_ = A
 164  	_ = B
 165  
 166  	T1 := Affine2D{}.Offset(Pt(100, 100)).Scale(Point{}, Pt(2, 2))
 167  	T2 := B.Mul(A)
 168  
 169  	if T1 != T2 {
 170  		t.Log(T1)
 171  		t.Log(T2)
 172  		t.Error("multiplication / transform order not as expected")
 173  	}
 174  }
 175  
 176  func BenchmarkTransformOffset(b *testing.B) {
 177  	p := Point{X: 1, Y: 2}
 178  	o := Point{X: 0.5, Y: 0.5}
 179  	aff := Affine2D{}.Offset(o)
 180  
 181  	for i := 0; i < b.N; i++ {
 182  		p = aff.Transform(p)
 183  	}
 184  	_ = p
 185  }
 186  
 187  func BenchmarkTransformScale(b *testing.B) {
 188  	p := Point{X: 1, Y: 2}
 189  	s := Point{X: 0.5, Y: 0.5}
 190  	aff := Affine2D{}.Scale(Point{}, s)
 191  	for i := 0; i < b.N; i++ {
 192  		p = aff.Transform(p)
 193  	}
 194  	_ = p
 195  }
 196  
 197  func BenchmarkTransformRotate(b *testing.B) {
 198  	p := Point{X: 1, Y: 2}
 199  	a := float32(math.Pi / 2)
 200  	aff := Affine2D{}.Rotate(Point{}, a)
 201  	for i := 0; i < b.N; i++ {
 202  		p = aff.Transform(p)
 203  	}
 204  	_ = p
 205  }
 206  
 207  func BenchmarkTransformTranslateMultiply(b *testing.B) {
 208  	a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
 209  	t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5})
 210  
 211  	for i := 0; i < b.N; i++ {
 212  		a = a.Mul(t)
 213  	}
 214  }
 215  
 216  func BenchmarkTransformScaleMultiply(b *testing.B) {
 217  	a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
 218  	t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5}).Scale(Point{}, Point{X: 0.4, Y: -0.5})
 219  
 220  	for i := 0; i < b.N; i++ {
 221  		a = a.Mul(t)
 222  	}
 223  }
 224  
 225  func BenchmarkTransformMultiply(b *testing.B) {
 226  	a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
 227  	t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5}).Rotate(Point{}, math.Pi/7)
 228  
 229  	for i := 0; i < b.N; i++ {
 230  		a = a.Mul(t)
 231  	}
 232  }
 233