stipple_geometry/
affine.rs1use crate::{Point, Vec2};
2
3#[derive(Clone, Copy, Debug, PartialEq)]
15pub struct Affine([f64; 6]);
16
17impl Default for Affine {
18 #[inline]
19 fn default() -> Self {
20 Self::IDENTITY
21 }
22}
23
24impl Affine {
25 pub const IDENTITY: Self = Self([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
26
27 #[inline]
28 pub const fn new(a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> Self {
29 Self([a, b, c, d, e, f])
30 }
31
32 #[inline]
33 pub const fn translate(v: Vec2) -> Self {
34 Self([1.0, 0.0, 0.0, 1.0, v.dx, v.dy])
35 }
36
37 #[inline]
38 pub const fn scale(sx: f64, sy: f64) -> Self {
39 Self([sx, 0.0, 0.0, sy, 0.0, 0.0])
40 }
41
42 #[inline]
43 pub fn rotate(radians: f64) -> Self {
44 let (s, c) = radians.sin_cos();
45 Self([c, s, -s, c, 0.0, 0.0])
46 }
47
48 #[inline]
49 pub const fn as_array(self) -> [f64; 6] {
50 self.0
51 }
52
53 pub fn then(self, outer: Affine) -> Affine {
56 let [a1, b1, c1, d1, e1, f1] = self.0;
57 let [a2, b2, c2, d2, e2, f2] = outer.0;
58 Affine([
59 a1 * a2 + b1 * c2,
60 a1 * b2 + b1 * d2,
61 c1 * a2 + d1 * c2,
62 c1 * b2 + d1 * d2,
63 e1 * a2 + f1 * c2 + e2,
64 e1 * b2 + f1 * d2 + f2,
65 ])
66 }
67
68 #[inline]
70 pub fn apply(self, p: Point) -> Point {
71 let [a, b, c, d, e, f] = self.0;
72 Point::new(a * p.x + c * p.y + e, b * p.x + d * p.y + f)
73 }
74
75 #[inline]
77 pub fn determinant(self) -> f64 {
78 let [a, b, c, d, ..] = self.0;
79 a * d - b * c
80 }
81
82 pub fn inverse(self) -> Option<Affine> {
84 let det = self.determinant();
85 if det.abs() < f64::EPSILON {
86 return None;
87 }
88 let [a, b, c, d, e, f] = self.0;
89 let inv = 1.0 / det;
90 Some(Affine([
91 d * inv,
92 -b * inv,
93 -c * inv,
94 a * inv,
95 (c * f - d * e) * inv,
96 (b * e - a * f) * inv,
97 ]))
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn translate_then_scale_order() {
107 let t = Affine::translate(Vec2::new(10.0, 0.0)).then(Affine::scale(2.0, 2.0));
109 assert_eq!(t.apply(Point::new(1.0, 0.0)), Point::new(22.0, 0.0));
110 }
111
112 #[test]
113 fn inverse_roundtrip() {
114 let t = Affine::translate(Vec2::new(5.0, -3.0)).then(Affine::scale(2.0, 4.0));
115 let p = Point::new(7.0, 9.0);
116 let back = t.inverse().unwrap().apply(t.apply(p));
117 assert!((back.x - p.x).abs() < 1e-9 && (back.y - p.y).abs() < 1e-9);
118 }
119
120 #[test]
121 fn singular_has_no_inverse() {
122 assert!(Affine::scale(0.0, 1.0).inverse().is_none());
123 }
124}