github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/f32/affine.go (about)

     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  }