index/utils/
linear_algebra.rs

1use std::ops::{Mul, MulAssign};
2
3use wasm_bindgen::prelude::*;
4
5use super::point2d::{Path2D, Point2D};
6
7/// A TransformationMatrix is a 2D transformation matrix following the CSS matrix transform format.
8#[wasm_bindgen]
9#[derive(Clone, Copy, Debug, PartialEq)]
10pub struct TransformationMatrix {
11    /// The a component of the matrix.
12    pub a: f32,
13    /// The b component of the matrix.
14    pub b: f32,
15    /// The c component of the matrix.
16    pub c: f32,
17    /// The d component of the matrix.
18    pub d: f32,
19    /// The e component of the matrix.
20    pub e: f32,
21    /// The f component of the matrix.
22    pub f: f32,
23}
24
25#[wasm_bindgen]
26impl TransformationMatrix {
27    /// Creates a new TransformationMatrix with the given components.
28    #[wasm_bindgen(constructor, return_description = "A 2D transformation matrix following the CSS matrix transform format.")]
29    pub fn new(
30        #[wasm_bindgen(param_description = "The a component of the matrix.")]
31        a: f32,
32        #[wasm_bindgen(param_description = "The b component of the matrix.")]
33        b: f32,
34        #[wasm_bindgen(param_description = "The c component of the matrix.")]
35        c: f32,
36        #[wasm_bindgen(param_description = "The d component of the matrix.")]
37        d: f32,
38        #[wasm_bindgen(param_description = "The e component of the matrix.")]
39        e: f32,
40        #[wasm_bindgen(param_description = "The f component of the matrix.")]
41        f: f32
42    ) -> TransformationMatrix {
43        TransformationMatrix { a, b, c, d, e, f }
44    }
45
46    /// Returns the identity matrix.
47    #[wasm_bindgen(return_description = "The identity matrix.")]
48    pub fn identity() -> TransformationMatrix {
49        TransformationMatrix {
50            a: 1.0,
51            b: 0.0,
52            c: 0.0,
53            d: 1.0,
54            e: 0.0,
55            f: 0.0,
56        }
57    }
58
59    /// Gets the matrix that translates by a given x and y value.
60    #[wasm_bindgen(return_description = "The translated matrix.")]
61    pub fn translate(
62        #[wasm_bindgen(param_description = "The x value to translate by.")]
63        x: f32,
64        #[wasm_bindgen(param_description = "The y value to translate by.")]
65        y: f32
66    ) -> TransformationMatrix {
67        TransformationMatrix {
68            a: 1.0,
69            b: 0.0,
70            c: 0.0,
71            d: 1.0,
72            e: x,
73            f: y,
74        }
75    }
76
77    /// Clones the TransformationMatrix.
78    #[wasm_bindgen(js_name = clone)]
79    pub fn copy(&self) -> TransformationMatrix {
80        *self
81    }
82
83    /// Gets the matrix that scales by a given x and y value.
84    #[wasm_bindgen(return_description = "The scaled matrix.")]
85    pub fn scale(
86        #[wasm_bindgen(param_description = "The x value to scale by.")]
87        x: f32,
88        #[wasm_bindgen(param_description = "The y value to scale by.")]
89        y: f32
90    ) -> TransformationMatrix {
91        TransformationMatrix {
92            a: x,
93            b: 0.0,
94            c: 0.0,
95            d: y,
96            e: 0.0,
97            f: 0.0,
98        }
99    }
100
101    /// Applies another TransformationMatrix to this matrix.
102    #[wasm_bindgen]
103    pub fn apply(&mut self, other: &TransformationMatrix) {
104        *self = *other * *self;
105    }
106
107    /// Gets the matrix that rotates by a given angle in radians.
108    #[wasm_bindgen(return_description = "The rotated matrix.")]
109    pub fn rotate(
110        #[wasm_bindgen(param_description = "The angle in radians to rotate by.")]
111        angle: f32
112    ) -> TransformationMatrix {
113        let (sin, cos) = angle.sin_cos();
114        TransformationMatrix {
115            a: cos,
116            b: sin,
117            c: -sin,
118            d: cos,
119            e: 0.0,
120            f: 0.0,
121        }
122    }
123
124    /// Gets the matrix that undoes this TransformationMatrix.
125    #[wasm_bindgen(return_description = "The inverse matrix.")]
126    pub fn inverse(self) -> TransformationMatrix {
127        let det = self.a * self.d - self.b * self.c;
128        TransformationMatrix {
129            a: self.d / det,
130            b: -self.b / det,
131            c: -self.c / det,
132            d: self.a / det,
133            e: (self.c * self.f - self.d * self.e) / det,
134            f: (self.b * self.e - self.a * self.f) / det,
135        }
136    }
137}
138
139impl TransformationMatrix {
140    pub fn from_svg_transform(transform_matrix: usvg::Transform) -> TransformationMatrix {
141        TransformationMatrix {
142            a: transform_matrix.sx,
143            b: transform_matrix.kx,
144            c: transform_matrix.ky,
145            d: transform_matrix.sy,
146            e: transform_matrix.tx,
147            f: transform_matrix.ty,
148        }
149    }
150}
151
152impl Mul<Vec<Point2D>> for TransformationMatrix {
153    type Output = Vec<Point2D>;
154
155    fn mul(self, points: Vec<Point2D>) -> Vec<Point2D> {
156        points.into_iter().map(|point| self * point).collect()
157    }
158}
159
160impl Mul<TransformationMatrix> for TransformationMatrix {
161    type Output = TransformationMatrix;
162
163    fn mul(self, other: TransformationMatrix) -> TransformationMatrix {
164        TransformationMatrix {
165            a: self.a * other.a + self.c * other.b,
166            b: self.b * other.a + self.d * other.b,
167            c: self.a * other.c + self.c * other.d,
168            d: self.b * other.c + self.d * other.d,
169            e: self.a * other.e + self.c * other.f + self.e,
170            f: self.b * other.e + self.d * other.f + self.f,
171        }
172    }
173}
174
175impl MulAssign<TransformationMatrix> for TransformationMatrix {
176    fn mul_assign(&mut self, other: TransformationMatrix) {
177        *self = *self * other;
178    }
179}
180
181impl Mul<Point2D> for TransformationMatrix {
182    type Output = Point2D;
183
184    fn mul(self, point: Point2D) -> Point2D {
185        Point2D {
186            x: self.a * point.x + self.c * point.y + self.e,
187            y: self.b * point.x + self.d * point.y + self.f,
188        }
189    }
190}
191
192impl Mul<Path2D> for TransformationMatrix {
193    type Output = Path2D;
194
195    fn mul(self, path: Path2D) -> Path2D {
196        Path2D::new(path.points().iter().map(|point| self * *point).collect())
197    }
198}
199
200/// Multiplies two matrices together.
201#[wasm_bindgen(return_description = "The product of the two matrices.", unchecked_return_type = "number[]")]
202pub fn matrix_product(
203    #[wasm_bindgen(param_description = "The first matrix to multiply as a flat array.", unchecked_param_type = "number[]")]
204    a: Vec<f32>,
205    #[wasm_bindgen(param_description = "The second matrix to multiply as a flat array.", unchecked_param_type = "number[]")]
206    b: Vec<f32>,
207    #[wasm_bindgen(param_description = "The number of rows in the first matrix.")]
208    a_rows: usize,
209    #[wasm_bindgen(param_description = "The number of columns in the first matrix.")]
210    a_columns: usize,
211    #[wasm_bindgen(param_description = "The number of columns in the second matrix.")]
212    b_columns: usize,
213) -> Vec<f32> {
214    let mut result = vec![0.0; a_rows * b_columns];
215    for i in 0..a_rows {
216        for j in 0..b_columns {
217            for k in 0..a_columns {
218                result[i * b_columns + j] += a[i * a_columns + k] * b[k * b_columns + j];
219            }
220        }
221    }
222    result
223}
224
225/// Multiplies a matrix by a path of 2D points, returning a new path.
226#[wasm_bindgen(return_description = "The product of the matrix and the path.")]
227pub fn matrix_product_path(
228    #[wasm_bindgen(param_description = "The matrix to multiply as a flat array.", unchecked_param_type = "number[]")]
229    matrix: Vec<f32>,
230    #[wasm_bindgen(param_description = "The path to multiply.")]
231    path: &Path2D,
232    #[wasm_bindgen(param_description = "The number of rows in the first matrix.")]
233    a_rows: usize,
234    #[wasm_bindgen(param_description = "The number of columns in the first matrix.")]
235    a_columns: usize,
236    #[wasm_bindgen(param_description = "The number of columns in the second matrix.")]
237    b_columns: usize
238) -> Path2D {
239    let result = matrix_product(matrix, path.points().iter().map(|point| vec![point.x, point.y]).flatten().collect(), a_rows, a_columns, b_columns);
240    Path2D::new(result.chunks(2).map(|chunk| Point2D { x: chunk[0], y: chunk[1] }).collect())
241}