use libm;
use core::ops::{Mul, MulAssign};
use crate::{Point, Rect, Vec2};
#[derive(Clone, Copy, Debug)]
pub struct Affine(pub [f64; 6]);
impl Affine {
pub const FLIP_Y: Affine = Affine::new([1.0, 0., 0., -1.0, 0., 0.]);
pub const FLIP_X: Affine = Affine::new([-1.0, 0., 0., 1.0, 0., 0.]);
#[inline]
pub const fn new(c: [f64; 6]) -> Affine {
Affine(c)
}
#[inline]
pub const fn scale(s: f64) -> Affine {
Affine([s, 0.0, 0.0, s, 0.0, 0.0])
}
#[inline]
pub fn rotate(th: f64) -> Affine {
let s = libm::sin(th);
let c = libm::cos(th);
Affine([c, s, -s, c, 0.0, 0.0])
}
#[inline]
pub fn translate<V: Into<Vec2>>(p: V) -> Affine {
let p = p.into();
Affine([1.0, 0.0, 0.0, 1.0, p.x, p.y])
}
#[inline]
pub fn as_coeffs(self) -> [f64; 6] {
self.0
}
pub fn determinant(self) -> f64 {
self.0[0] * self.0[3] - self.0[1] * self.0[2]
}
pub fn inverse(self) -> Affine {
let inv_det = self.determinant().recip();
Affine([
inv_det * self.0[3],
-inv_det * self.0[1],
-inv_det * self.0[2],
inv_det * self.0[0],
inv_det * (self.0[2] * self.0[5] - self.0[3] * self.0[4]),
inv_det * (self.0[1] * self.0[4] - self.0[0] * self.0[5]),
])
}
pub fn transform_rect_bbox(self, rect: Rect) -> Rect {
let p00 = self * Point::new(rect.x0, rect.y0);
let p01 = self * Point::new(rect.x0, rect.y1);
let p10 = self * Point::new(rect.x1, rect.y0);
let p11 = self * Point::new(rect.x1, rect.y1);
Rect::from_points(p00, p01).union(Rect::from_points(p10, p11))
}
}
impl Default for Affine {
#[inline]
fn default() -> Affine {
Affine::scale(1.0)
}
}
impl Mul<Point> for Affine {
type Output = Point;
#[inline]
fn mul(self, other: Point) -> Point {
Point::new(
self.0[0] * other.x + self.0[2] * other.y + self.0[4],
self.0[1] * other.x + self.0[3] * other.y + self.0[5],
)
}
}
impl Mul for Affine {
type Output = Affine;
#[inline]
fn mul(self, other: Affine) -> Affine {
Affine([
self.0[0] * other.0[0] + self.0[2] * other.0[1],
self.0[1] * other.0[0] + self.0[3] * other.0[1],
self.0[0] * other.0[2] + self.0[2] * other.0[3],
self.0[1] * other.0[2] + self.0[3] * other.0[3],
self.0[0] * other.0[4] + self.0[2] * other.0[5] + self.0[4],
self.0[1] * other.0[4] + self.0[3] * other.0[5] + self.0[5],
])
}
}
impl MulAssign for Affine {
#[inline]
fn mul_assign(&mut self, other: Affine) {
*self = self.mul(other);
}
}
impl Mul<Affine> for f64 {
type Output = Affine;
#[inline]
fn mul(self, other: Affine) -> Affine {
Affine([
self * other.0[0],
self * other.0[1],
self * other.0[2],
self * other.0[3],
self * other.0[4],
self * other.0[5],
])
}
}
#[cfg(feature = "mint")]
impl From<Affine> for mint::ColumnMatrix2x3<f64> {
#[inline]
fn from(a: Affine) -> mint::ColumnMatrix2x3<f64> {
mint::ColumnMatrix2x3 {
x: mint::Vector2 {
x: a.0[0],
y: a.0[1],
},
y: mint::Vector2 {
x: a.0[2],
y: a.0[3],
},
z: mint::Vector2 {
x: a.0[4],
y: a.0[5],
},
}
}
}
#[cfg(feature = "mint")]
impl From<mint::ColumnMatrix2x3<f64>> for Affine {
#[inline]
fn from(m: mint::ColumnMatrix2x3<f64>) -> Affine {
Affine([m.x.x, m.x.y, m.y.x, m.y.y, m.z.x, m.z.y])
}
}
#[cfg(test)]
mod tests {
use crate::{Affine, Point};
use core::f64::consts::PI;
fn assert_near(p0: Point, p1: Point) {
assert!((p1 - p0).hypot() < 1e-9, "{:?} != {:?}", p0, p1);
}
#[test]
fn affine_basic() {
let p = Point::new(3.0, 4.0);
assert_near(Affine::default() * p, p);
assert_near(Affine::scale(2.0) * p, Point::new(6.0, 8.0));
assert_near(Affine::rotate(0.0) * p, p);
assert_near(Affine::rotate(PI / 2.0) * p, Point::new(-4.0, 3.0));
assert_near(Affine::translate((5.0, 6.0)) * p, Point::new(8.0, 10.0));
}
#[test]
fn affine_mul() {
let a1 = Affine::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
let a2 = Affine::new([0.1, 1.2, 2.3, 3.4, 4.5, 5.6]);
let px = Point::new(1.0, 0.0);
let py = Point::new(0.0, 1.0);
let pxy = Point::new(1.0, 1.0);
assert_near(a1 * (a2 * px), (a1 * a2) * px);
assert_near(a1 * (a2 * py), (a1 * a2) * py);
assert_near(a1 * (a2 * pxy), (a1 * a2) * pxy);
}
#[test]
fn affine_inv() {
let a = Affine::new([0.1, 1.2, 2.3, 3.4, 4.5, 5.6]);
let a_inv = a.inverse();
let px = Point::new(1.0, 0.0);
let py = Point::new(0.0, 1.0);
let pxy = Point::new(1.0, 1.0);
assert_near(a * (a_inv * px), px);
assert_near(a * (a_inv * py), py);
assert_near(a * (a_inv * pxy), pxy);
assert_near(a_inv * (a * px), px);
assert_near(a_inv * (a * py), py);
assert_near(a_inv * (a * pxy), pxy);
}
}