#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Matrix {
pub a: f64,
pub b: f64,
pub c: f64,
pub d: f64,
pub e: f64,
pub f: f64,
}
impl Matrix {
pub const IDENTITY: Matrix = Matrix::new(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
pub const fn new(a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> Self {
Self { a, b, c, d, e, f }
}
pub const fn translate(x: f64, y: f64) -> Self {
Self::new(1.0, 0.0, 0.0, 1.0, x, y)
}
pub fn from_slice(v: &[f64]) -> Option<Self> {
match v {
[a, b, c, d, e, f] if v.iter().all(|x| x.is_finite()) => {
Some(Self::new(*a, *b, *c, *d, *e, *f))
}
_ => None,
}
}
pub fn then(&self, other: &Matrix) -> Matrix {
Matrix {
a: self.a * other.a + self.b * other.c,
b: self.a * other.b + self.b * other.d,
c: self.c * other.a + self.d * other.c,
d: self.c * other.b + self.d * other.d,
e: self.e * other.a + self.f * other.c + other.e,
f: self.e * other.b + self.f * other.d + other.f,
}
}
pub fn apply(&self, x: f64, y: f64) -> (f64, f64) {
(
x * self.a + y * self.c + self.e,
x * self.b + y * self.d + self.f,
)
}
pub fn apply_vector(&self, x: f64, y: f64) -> (f64, f64) {
(x * self.a + y * self.c, x * self.b + y * self.d)
}
}
impl Default for Matrix {
fn default() -> Self {
Self::IDENTITY
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn composition_order() {
let scale = Matrix::new(2.0, 0.0, 0.0, 2.0, 0.0, 0.0);
let shift = Matrix::translate(10.0, 0.0);
assert_eq!(scale.then(&shift).apply(1.0, 1.0), (12.0, 2.0));
assert_eq!(shift.then(&scale).apply(1.0, 1.0), (22.0, 2.0));
assert_eq!(Matrix::IDENTITY.then(&shift), shift);
assert_eq!(scale.apply_vector(1.0, 0.0), (2.0, 0.0));
assert!(Matrix::from_slice(&[1.0, 0.0, 0.0, 1.0, 0.0]).is_none());
assert!(Matrix::from_slice(&[f64::NAN, 0.0, 0.0, 1.0, 0.0, 0.0]).is_none());
}
}