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