github.com/utopiagio/gio@v0.0.8/f32/affine.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package f32 4 5 import ( 6 "math" 7 "strconv" 8 ) 9 10 // Affine2D represents an affine 2D transformation. The zero value of 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 // Split a transform into two parts, one which is pure offset and the 116 // other representing the scaling, shearing and rotation part. 117 func (a *Affine2D) Split() (srs Affine2D, offset Point) { 118 return Affine2D{ 119 a: a.a, b: a.b, c: 0, 120 d: a.d, e: a.e, f: 0, 121 }, Point{X: a.c, Y: a.f} 122 } 123 124 func (a Affine2D) scale(factor Point) Affine2D { 125 return Affine2D{ 126 (a.a+1)*factor.X - 1, a.b * factor.X, a.c * factor.X, 127 a.d * factor.Y, (a.e+1)*factor.Y - 1, a.f * factor.Y, 128 } 129 } 130 131 func (a Affine2D) rotate(radians float32) Affine2D { 132 sin, cos := math.Sincos(float64(radians)) 133 s, c := float32(sin), float32(cos) 134 return Affine2D{ 135 (a.a+1)*c - a.d*s - 1, a.b*c - (a.e+1)*s, a.c*c - a.f*s, 136 (a.a+1)*s + a.d*c, a.b*s + (a.e+1)*c - 1, a.c*s + a.f*c, 137 } 138 } 139 140 func (a Affine2D) shear(radiansX, radiansY float32) Affine2D { 141 tx := float32(math.Tan(float64(radiansX))) 142 ty := float32(math.Tan(float64(radiansY))) 143 return Affine2D{ 144 (a.a + 1) + a.d*tx - 1, a.b + (a.e+1)*tx, a.c + a.f*tx, 145 (a.a+1)*ty + a.d, a.b*ty + (a.e + 1) - 1, a.f*ty + a.f, 146 } 147 } 148 149 func (a Affine2D) String() string { 150 sx, hx, ox, hy, sy, oy := a.Elems() 151 152 // precision 6, one period, negative sign and space per number 153 const prec = 6 154 const charsPerFloat = prec + 2 + 1 155 s := make([]byte, 0, 6*charsPerFloat+6) 156 157 s = append(s, '[', '[') 158 s = strconv.AppendFloat(s, float64(sx), 'g', prec, 32) 159 s = append(s, ' ') 160 s = strconv.AppendFloat(s, float64(hx), 'g', prec, 32) 161 s = append(s, ' ') 162 s = strconv.AppendFloat(s, float64(ox), 'g', prec, 32) 163 s = append(s, ']', ' ', '[') 164 s = strconv.AppendFloat(s, float64(hy), 'g', prec, 32) 165 s = append(s, ' ') 166 s = strconv.AppendFloat(s, float64(sy), 'g', prec, 32) 167 s = append(s, ' ') 168 s = strconv.AppendFloat(s, float64(oy), 'g', prec, 32) 169 s = append(s, ']', ']') 170 171 return string(s) 172 }