///|
/// `AffineMatrix` represents a 2D affine transform that preserves parallel lines.
/// See: https://en.wikipedia.org/wiki/Affine_transformation
pub(all) struct AffineMatrix {
mut a : Double
mut b : Double
mut c : Double
mut d : Double
mut tx : Double
mut ty : Double
} derive(Debug, Eq)
///|
pub impl Show for AffineMatrix with fn output(self, logger) {
let { a, b, c, d, tx, ty } = self
logger.write_string(
(
$|{a: \{a}, b: \{b}, c: \{c}, d: \{d}, tx: \{tx}, ty: \{ty}}
),
)
}
///|
test "AffineMatrix show interface" {
let m = AffineMatrix::new(a=1, b=0, c=0, d=1, tx=10, ty=20)
inspect(
m,
content=(
#|{a: 1, b: 0, c: 0, d: 1, tx: 10, ty: 20}
),
)
}
///|
/// AffineMatrix::new returns a new 2D affine matrix.
pub fn AffineMatrix::new(
a? : Double = 1,
b? : Double = 0,
c? : Double = 0,
d? : Double = 1,
tx? : Double = 0,
ty? : Double = 0,
) -> AffineMatrix {
{ a, b, c, d, tx, ty }
}
///|
/// AffineMatrix::from_transform returns a new 2D affine matrix from a Transform.
pub fn AffineMatrix::from_transform(transform : Transform) -> AffineMatrix {
let { position, rotation, scale, skew, origin } = transform
AffineMatrix::new()
.translate(position)
.rotate(rotation)
.skew(skew)
.scale(scale)
.origin(origin)
}
///|
/// AffineMatrix::from_translation returns a new 2D affine matrix from translation `v`.
pub fn AffineMatrix::from_translation(v : Vec2) -> AffineMatrix {
AffineMatrix::new(tx=v.x, ty=v.y)
}
///|
/// AffineMatrix::from_translation_points returns a new 2D affine matrix that
/// translates from p1 to p2.
pub fn AffineMatrix::from_translation_points(
p1 : Vec2,
p2 : Vec2,
) -> AffineMatrix {
AffineMatrix::new(tx=p2.x - p1.x, ty=p2.y - p1.y)
}
///|
/// AffineMatrix::from_rotation returns a new 2D affine matrix from a rotation `angle`
/// in degrees.
pub fn AffineMatrix::from_rotation(angle : Double) -> AffineMatrix {
let rad = angle * @math.PI / 180
let x = @math.cos(rad)
let y = @math.sin(rad)
AffineMatrix::new(a=x, b=y, c=-y, d=x)
}
///|
/// AffineMatrix::from_scale returns a new 2D affine matrix from scale `v`.
pub fn AffineMatrix::from_scale(v : Vec2) -> AffineMatrix {
AffineMatrix::new(a=v.x, d=v.y)
}
///|
/// AffineMatrix::from_scale_scalar returns a new 2D affine matrix from uniform scale `s`.
pub fn AffineMatrix::from_scale_scalar(s : Double) -> AffineMatrix {
AffineMatrix::new(a=s, d=s)
}
///|
/// AffineMatrix::from_center_scale returns a new 2D affine matrix that scales
/// from the provided center point.
pub fn AffineMatrix::from_center_scale(
center : Vec2,
scale : Vec2,
) -> AffineMatrix {
let { x, y } = center
AffineMatrix::new(
a=scale.x,
d=scale.y,
tx=x - x * scale.x,
ty=y - y * scale.y,
)
}
///|
/// clone returns a copy of this 2D affine matrix.
pub fn AffineMatrix::clone(self : AffineMatrix) -> AffineMatrix {
{ ..self }
}
///|
/// copy copies another 2D affine matrix into itself.
pub fn AffineMatrix::copy(self : AffineMatrix, other : AffineMatrix) -> Unit {
self.a = other.a
self.b = other.b
self.c = other.c
self.d = other.d
self.tx = other.tx
self.ty = other.ty
}
///|
/// invert inverts this 2D affine matrix, returning a new one.
pub fn AffineMatrix::invert(self : AffineMatrix) -> AffineMatrix {
let { a: a0, b: b0, c: c0, d: d0, tx: tx0, ty: ty0 } = self
let cross = a0 * d0 - b0 * c0
let dot = b0 * c0 - a0 * d0
let result = self.clone()
result.a = d0 / cross
result.b = b0 / dot
result.c = c0 / dot
result.d = a0 / cross
result.tx = (d0 * tx0 - c0 * ty0) / dot
result.ty = (b0 * tx0 - a0 * ty0) / cross
result
}
///|
/// self_mul multiplies this 2D affine matrix with another, storing the result in itself.
pub fn AffineMatrix::self_mul(self : AffineMatrix, m : AffineMatrix) -> Unit {
let { a: m0a, b: m0b, c: m0c, d: m0d, tx: m0tx, ty: m0ty } = self
let { a: m1a, b: m1b, c: m1c, d: m1d, tx: m1tx, ty: m1ty } = m
self.a = m0a * m1a + m0c * m1b
self.b = m0b * m1a + m0d * m1b
self.c = m0a * m1c + m0c * m1d
self.d = m0b * m1c + m0d * m1d
self.tx = m0a * m1tx + m0c * m1ty + m0tx
self.ty = m0b * m1tx + m0d * m1ty + m0ty
}
///|
/// mul multiplies two 2D affine matrices together, returning a new one.
pub impl Mul for AffineMatrix with fn mul(self, b) {
let result = self.clone()
result.self_mul(b)
result
}
///|
/// mul_without_translation multiplies this 2D affine matrix with another,
/// discarding the transation, and returning a new one.
pub fn AffineMatrix::mul_without_translation(
self : AffineMatrix,
m : AffineMatrix,
) -> AffineMatrix {
let { a: m0a, b: m0b, c: m0c, d: m0d, .. } = self
let { a: m1a, b: m1b, c: m1c, d: m1d, .. } = m
let result = self.clone()
result.a = m0a * m1a + m0c * m1b
result.b = m0b * m1a + m0d * m1b
result.c = m0a * m1c + m0c * m1d
result.d = m0b * m1c + m0d * m1d
result
}
///|
/// pre_mul multiplies another matrix `m` by this 2D affine matrix, returning a new one.
pub fn AffineMatrix::pre_mul(
self : AffineMatrix,
m : AffineMatrix,
) -> AffineMatrix {
let { a: m0a, b: m0b, c: m0c, d: m0d, tx: m0tx, ty: m0ty } = m
let { a: m1a, b: m1b, c: m1c, d: m1d, tx: m1tx, ty: m1ty } = self
let result = self.clone()
result.a = m0a * m1a + m0c * m1b
result.b = m0b * m1a + m0d * m1b
result.c = m0a * m1c + m0c * m1d
result.d = m0b * m1c + m0d * m1d
result.tx = m0a * m1tx + m0c * m1ty + m0tx
result.ty = m0b * m1tx + m0d * m1ty + m0ty
result
}
///|
/// pre_mul_without_translation multiplies another matrix `m` by this 2D affine matrix,
/// discarding the translate and returning a new affine matrix.
pub fn AffineMatrix::pre_mul_without_translation(
self : AffineMatrix,
m : AffineMatrix,
) -> AffineMatrix {
let { a: m0a, b: m0b, c: m0c, d: m0d, .. } = m
let { a: m1a, b: m1b, c: m1c, d: m1d, .. } = self
let result = self.clone()
result.a = m0a * m1a + m0c * m1b
result.b = m0b * m1a + m0d * m1b
result.c = m0a * m1c + m0c * m1d
result.d = m0b * m1c + m0d * m1d
result
}
///|
/// translate translates this 2D affine matrix by position `v`, returning a new one.
pub fn AffineMatrix::translate(self : AffineMatrix, v : Vec2) -> AffineMatrix {
let { a, b, c, d, .. } = self
let tx = self.tx + a * v.x + c * v.y
let ty = self.ty + b * v.x + d * v.y
{ ..self, tx, ty }
}
///|
/// rotate rotates this 2D affine matrix by `angle` degrees, returning a new one.
pub fn AffineMatrix::rotate(
self : AffineMatrix,
angle : Double,
) -> AffineMatrix {
self.mul(AffineMatrix::from_rotation(angle))
}
///|
/// skew skews the Y basis vector of this 2D affine matrix by `angle` degrees, returning a new one.
pub fn AffineMatrix::skew(self : AffineMatrix, angle : Double) -> AffineMatrix {
let rad = angle * @math.PI / 180
let t = @math.tan(rad)
let c = self.c + t * self.a
let d = self.d + t * self.b
{ ..self, c, d }
}
///|
/// scale scales this 2D affine matrix by `v`, returning a new one.
pub fn AffineMatrix::scale(self : AffineMatrix, v : Vec2) -> AffineMatrix {
let a = self.a * v.x
let b = self.b * v.x
let c = self.c * v.y
let d = self.d * v.y
{ ..self, a, b, c, d }
}
///|
/// scale_scalar scales this 2D affine matrix uniformly by `s`, returning a new one.
pub fn AffineMatrix::scale_scalar(
self : AffineMatrix,
s : Double,
) -> AffineMatrix {
let a = self.a * s
let b = self.b * s
let c = self.c * s
let d = self.d * s
{ ..self, a, b, c, d }
}
///|
/// origin translates the matrix such that the center of future
/// scale, rotate, and skew transformations will be `v`, returning a new one.
pub fn AffineMatrix::origin(self : AffineMatrix, v : Vec2) -> AffineMatrix {
let { x, y } = v
let tx = self.tx + self.a * x + self.c * y
let ty = self.ty + self.b * x + self.d * y
{ ..self, tx, ty }
}
///|
/// normalize scales the basis vectors of this 2D affine matrix
/// so that they have unit length, returning a new one.
pub fn AffineMatrix::normalize(self : AffineMatrix) -> AffineMatrix {
let { a, b, c, d, .. } = self
let result = self.clone()
let x = a * a + b * b
if x > 0 {
let f = 1.0 / x.sqrt()
result.a *= f
result.b *= f
}
let y = c * c + d * d
if y > 0 {
let f = 1.0 / y.sqrt()
result.c *= f
result.d *= f
}
result
}
///|
/// determinant returns the determinant of the 2D affine matrix.
pub fn AffineMatrix::determinant(self : AffineMatrix) -> Double {
let { a, b, c, d, .. } = self
a * d - b * c
}
///|
/// is_orthogonal returns true if the two basis vectors of the 2D affine matrix
/// are orthogonal within the provided tolerance.
pub fn AffineMatrix::is_orthogonal(
self : AffineMatrix,
tolerance? : Double = DEFAULT_TOLERANCE,
) -> Bool {
let { a, b, c, d, .. } = self
(a * c + b * d).abs() <= tolerance
}
///|
/// is_invertible returns true if this 2D affine matrix is invertible.
pub fn AffineMatrix::is_invertible(self : AffineMatrix) -> Bool {
self.determinant() != 0
}
///|
/// is_uniform_scale returns true if both basis vectors of this 2D affine matrix
/// are of the same length.
pub fn AffineMatrix::is_uniform_scale(
self : AffineMatrix,
tolerance? : Double = DEFAULT_TOLERANCE,
) -> Bool {
let { a, b, c, d, .. } = self
(a * a + b * b - (c * c + d * d)).abs() <= tolerance
}
///|
/// is_mirror returns true if this 2D affine matrix mirrors either axis.
pub fn AffineMatrix::is_mirror(self : AffineMatrix) -> Bool {
self.determinant() < 0
}
///|
/// is_identity returns true if this 2D affine matrix is the identity matrix.
pub fn AffineMatrix::is_identity(self : AffineMatrix) -> Bool {
self.a == 1 &&
self.b == 0 &&
self.c == 0 &&
self.d == 1 &&
self.tx == 0 &&
self.ty == 0
}
///|
/// is_nan returns true if any elements of this 2D affine matrix are NaN (not a number).
pub fn AffineMatrix::is_nan(self : AffineMatrix) -> Bool {
self.a.is_nan() ||
self.b.is_nan() ||
self.c.is_nan() ||
self.d.is_nan() ||
self.tx.is_nan() ||
self.ty.is_nan()
}
///|
/// is_inf returns true if any elements of this 2D affine matrix are infinite.
pub fn AffineMatrix::is_inf(self : AffineMatrix) -> Bool {
self.a.is_inf() ||
self.b.is_inf() ||
self.c.is_inf() ||
self.d.is_inf() ||
self.tx.is_inf() ||
self.ty.is_inf()
}
///|
fn signed_mod(a : Double, b : Double) -> Double {
let tmp = @math.floor(a / b)
let mut n = a - tmp * b
if n < 0.0 {
n += b
}
if n == b {
return 0
}
n
}
///|
/// to_transform converts this 2D affine matrix to a Transform.
pub fn AffineMatrix::to_transform(
self : AffineMatrix,
origin? : Vec2 = vec2(0, 0),
) -> Transform {
let { a, b, c, d, tx, ty } = self
let xnz = a * a + b * b > 0.00000001
let ynz = c * c + d * d > 0.00000001
let mut rad = 0.0
let mut skew = 0.0
if xnz {
rad = @math.atan2(b, a)
if ynz {
skew = (rad - @math.atan2(-c, d)) * 180 / @math.PI
skew = signed_mod(skew, 180)
if skew > 90 {
skew -= 180
}
}
} else if ynz {
rad = @math.atan2(-c, d)
}
let position = vec2(tx, ty)
let rotation = signed_mod(rad * 180 / @math.PI, 360)
let x = @math.cos(-rad)
let y = @math.sin(-rad)
let scale = vec2(a * x - b * y, c * y + d * x)
{ position, rotation, scale, skew, origin }
}