affine.go 4.7 KB

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