swf/types/
matrix.rs

1use crate::{Fixed16, Point, Twips};
2use std::ops;
3
4/// The transformation matrix used by Flash display objects.
5///
6/// The matrix is a 2x3 affine transformation matrix. A point (x, y) is transformed by the matrix
7/// in the following way:
8/// ```text
9///  [a c tx] *  [x] = [a*x + c*y + tx]
10///  [b d ty]    [y]   [b*x + d*y + ty]
11///  [0 0 1 ]    [1]   [1             ]
12/// ```
13///
14/// The SWF format uses 16.16 format for `a`, `b`, `c`, `d`. Twips are used for `tx` and `ty`.
15/// This means that objects in Flash can only move in units of twips, or 1/20 pixels.
16///
17/// [SWF19 pp.22-24](https://web.archive.org/web/20220205011833if_/https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf#page=22)
18#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
19pub struct Matrix {
20    /// The matrix element at `[0, 0]`. Labeled `ScaleX` in SWF19.
21    pub a: Fixed16,
22
23    /// The matrix element at `[1, 0]`. Labeled `RotateSkew0` in SWF19.
24    pub b: Fixed16,
25
26    /// The matrix element at `[0, 1]`. Labeled `RotateSkew1` in SWF19.
27    pub c: Fixed16,
28
29    /// The matrix element at `[1, 1]`. Labeled `ScaleY` in SWF19.
30    pub d: Fixed16,
31
32    /// The X translation in twips. Labeled `TranslateX` in SWF19.
33    pub tx: Twips,
34
35    /// The Y translation in twips. Labeled `TranslateX` in SWF19.
36    pub ty: Twips,
37}
38
39impl Matrix {
40    /// The identity matrix.
41    ///
42    /// Transforming an object by this matrix has no effect.
43    pub const IDENTITY: Self = Self {
44        a: Fixed16::ONE,
45        c: Fixed16::ZERO,
46        b: Fixed16::ZERO,
47        d: Fixed16::ONE,
48        ty: Twips::ZERO,
49        tx: Twips::ZERO,
50    };
51
52    /// Returns a scale matrix.
53    #[inline]
54    pub const fn scale(scale_x: Fixed16, scale_y: Fixed16) -> Self {
55        Self {
56            a: scale_x,
57            d: scale_y,
58            ..Self::IDENTITY
59        }
60    }
61
62    /// Returns a rotation matrix that rotates by `angle` radians.
63    #[inline]
64    pub fn rotate(angle: f32) -> Self {
65        Self {
66            a: Fixed16::from_f32(angle.cos()),
67            c: Fixed16::from_f32(-angle.sin()),
68            b: Fixed16::from_f32(angle.sin()),
69            d: Fixed16::from_f32(angle.cos()),
70            ..Default::default()
71        }
72    }
73
74    /// Returns a translation matrix.
75    #[inline]
76    pub const fn translate(x: Twips, y: Twips) -> Self {
77        Self {
78            tx: x,
79            ty: y,
80            ..Self::IDENTITY
81        }
82    }
83
84    /// Inverts the matrix.
85    ///
86    /// If the matrix is not invertible, the resulting matrix will be invalid.
87    #[inline]
88    pub fn invert(&mut self) {
89        // If we actually use this, may want to do this directly in fixed point instead of casting to f32.
90        let (tx, ty) = (self.tx.get() as f32, self.ty.get() as f32);
91        let a = self.a.to_f32();
92        let b = self.b.to_f32();
93        let c = self.c.to_f32();
94        let d = self.d.to_f32();
95        let det = a * d - b * c;
96        let a = d / det;
97        let b = b / -det;
98        let c = c / -det;
99        let d = a / det;
100        let (out_tx, out_ty) = ((d * tx - c * ty) / -det, (b * tx - a * ty) / det);
101        *self = Matrix {
102            a: Fixed16::from_f32(a),
103            b: Fixed16::from_f32(b),
104            c: Fixed16::from_f32(c),
105            d: Fixed16::from_f32(d),
106            tx: Twips::new(out_tx as i32),
107            ty: Twips::new(out_ty as i32),
108        };
109    }
110}
111
112impl ops::Mul for Matrix {
113    type Output = Self;
114    #[inline]
115    fn mul(self, rhs: Self) -> Self {
116        let (rhs_tx, rhs_ty) = (rhs.tx.get(), rhs.ty.get());
117        let (out_tx, out_ty) = (
118            self.a
119                .wrapping_mul_int(rhs_tx)
120                .wrapping_add(self.c.mul_int(rhs_ty))
121                .wrapping_add(self.tx.get()),
122            self.b
123                .wrapping_mul_int(rhs_tx)
124                .wrapping_add(self.d.mul_int(rhs_ty))
125                .wrapping_add(self.ty.get()),
126        );
127        Matrix {
128            a: self.a * rhs.a + self.c * rhs.b,
129            b: self.b * rhs.a + self.d * rhs.b,
130            c: self.a * rhs.c + self.c * rhs.d,
131            d: self.b * rhs.c + self.d * rhs.d,
132            tx: Twips::new(out_tx),
133            ty: Twips::new(out_ty),
134        }
135    }
136}
137
138impl ops::Mul<Point<Twips>> for Matrix {
139    type Output = Point<Twips>;
140
141    #[inline]
142    fn mul(self, point: Point<Twips>) -> Point<Twips> {
143        let x = point.x.get();
144        let y = point.y.get();
145        let out_x = Twips::new(
146            (self
147                .a
148                .wrapping_mul_int(x)
149                .wrapping_add(self.c.wrapping_mul_int(y)))
150            .wrapping_add(self.tx.get()),
151        );
152        let out_y = Twips::new(
153            (self
154                .b
155                .wrapping_mul_int(x)
156                .wrapping_add(self.d.wrapping_mul_int(y)))
157            .wrapping_add(self.ty.get()),
158        );
159        Point::new(out_x, out_y)
160    }
161}
162
163impl Default for Matrix {
164    #[inline]
165    fn default() -> Matrix {
166        Matrix::IDENTITY
167    }
168}
169
170impl ops::MulAssign for Matrix {
171    #[inline]
172    fn mul_assign(&mut self, rhs: Self) {
173        *self = *self * rhs;
174    }
175}