affine.go raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 package f32
4
5 import (
6 "fmt"
7 "math"
8 )
9
10 // Affine2D represents an affine 2D transformation. The zero value if Affine2D
11 // represents the identity transform.
12 type Affine2D struct {
13 // in order to make the zero value of Affine2D represent the identity
14 // transform we store it with the identity matrix subtracted, that is
15 // if the actual transformation matrix is:
16 // [sx, hx, ox]
17 // [hy, sy, oy]
18 // [ 0, 0, 1]
19 // we store a = sx-1 and e = sy-1
20 a, b, c float32
21 d, e, f float32
22 }
23
24 // NewAffine2D creates a new Affine2D transform from the matrix elements
25 // in row major order. The rows are: [sx, hx, ox], [hy, sy, oy], [0, 0, 1].
26 func NewAffine2D(sx, hx, ox, hy, sy, oy float32) Affine2D {
27 return Affine2D{
28 a: sx - 1, b: hx, c: ox,
29 d: hy, e: sy - 1, f: oy,
30 }
31 }
32
33 // Offset the transformation.
34 func (a Affine2D) Offset(offset Point) Affine2D {
35 return Affine2D{
36 a.a, a.b, a.c + offset.X,
37 a.d, a.e, a.f + offset.Y,
38 }
39 }
40
41 // Scale the transformation around the given origin.
42 func (a Affine2D) Scale(origin, factor Point) Affine2D {
43 if origin == (Point{}) {
44 return a.scale(factor)
45 }
46 a = a.Offset(origin.Mul(-1))
47 a = a.scale(factor)
48 return a.Offset(origin)
49 }
50
51 // Rotate the transformation by the given angle (in radians) counter clockwise around the given origin.
52 func (a Affine2D) Rotate(origin Point, radians float32) Affine2D {
53 if origin == (Point{}) {
54 return a.rotate(radians)
55 }
56 a = a.Offset(origin.Mul(-1))
57 a = a.rotate(radians)
58 return a.Offset(origin)
59 }
60
61 // Shear the transformation by the given angle (in radians) around the given origin.
62 func (a Affine2D) Shear(origin Point, radiansX, radiansY float32) Affine2D {
63 if origin == (Point{}) {
64 return a.shear(radiansX, radiansY)
65 }
66 a = a.Offset(origin.Mul(-1))
67 a = a.shear(radiansX, radiansY)
68 return a.Offset(origin)
69 }
70
71 // Mul returns A*B.
72 func (A Affine2D) Mul(B Affine2D) (r Affine2D) {
73 r.a = (A.a+1)*(B.a+1) + A.b*B.d - 1
74 r.b = (A.a+1)*B.b + A.b*(B.e+1)
75 r.c = (A.a+1)*B.c + A.b*B.f + A.c
76 r.d = A.d*(B.a+1) + (A.e+1)*B.d
77 r.e = A.d*B.b + (A.e+1)*(B.e+1) - 1
78 r.f = A.d*B.c + (A.e+1)*B.f + A.f
79 return r
80 }
81
82 // Invert the transformation. Note that if the matrix is close to singular
83 // numerical errors may become large or infinity.
84 func (a Affine2D) Invert() Affine2D {
85 if a.a == 0 && a.b == 0 && a.d == 0 && a.e == 0 {
86 return Affine2D{a: 0, b: 0, c: -a.c, d: 0, e: 0, f: -a.f}
87 }
88 a.a += 1
89 a.e += 1
90 det := a.a*a.e - a.b*a.d
91 a.a, a.e = a.e/det, a.a/det
92 a.b, a.d = -a.b/det, -a.d/det
93 temp := a.c
94 a.c = -a.a*a.c - a.b*a.f
95 a.f = -a.d*temp - a.e*a.f
96 a.a -= 1
97 a.e -= 1
98 return a
99 }
100
101 // Transform p by returning a*p.
102 func (a Affine2D) Transform(p Point) Point {
103 return Point{
104 X: p.X*(a.a+1) + p.Y*a.b + a.c,
105 Y: p.X*a.d + p.Y*(a.e+1) + a.f,
106 }
107 }
108
109 // Elems returns the matrix elements of the transform in row-major order. The
110 // rows are: [sx, hx, ox], [hy, sy, oy], [0, 0, 1].
111 func (a Affine2D) Elems() (sx, hx, ox, hy, sy, oy float32) {
112 return a.a + 1, a.b, a.c, a.d, a.e + 1, a.f
113 }
114
115 func (a Affine2D) scale(factor Point) Affine2D {
116 return Affine2D{
117 (a.a+1)*factor.X - 1, a.b * factor.X, a.c * factor.X,
118 a.d * factor.Y, (a.e+1)*factor.Y - 1, a.f * factor.Y,
119 }
120 }
121
122 func (a Affine2D) rotate(radians float32) Affine2D {
123 sin, cos := math.Sincos(float64(radians))
124 s, c := float32(sin), float32(cos)
125 return Affine2D{
126 (a.a+1)*c - a.d*s - 1, a.b*c - (a.e+1)*s, a.c*c - a.f*s,
127 (a.a+1)*s + a.d*c, a.b*s + (a.e+1)*c - 1, a.c*s + a.f*c,
128 }
129 }
130
131 func (a Affine2D) shear(radiansX, radiansY float32) Affine2D {
132 tx := float32(math.Tan(float64(radiansX)))
133 ty := float32(math.Tan(float64(radiansY)))
134 return Affine2D{
135 (a.a + 1) + a.d*tx - 1, a.b + (a.e+1)*tx, a.c + a.f*tx,
136 (a.a+1)*ty + a.d, a.b*ty + (a.e + 1) - 1, a.f*ty + a.f,
137 }
138 }
139
140 func (a Affine2D) String() string {
141 sx, hx, ox, hy, sy, oy := a.Elems()
142 return fmt.Sprintf("[[%f %f %f] [%f %f %f]]", sx, hx, ox, hy, sy, oy)
143 }
144