1use std::ops::{Add, Mul};
6
7use crate::{Matrix4, Offset};
8
9#[derive(Clone, Copy, Debug, Default, PartialEq)]
14pub struct Vector3 {
15 pub x: f32,
17 pub y: f32,
19 pub z: f32,
21}
22
23impl Vector3 {
24 pub const fn new(x: f32, y: f32, z: f32) -> Vector3 {
26 Vector3 { x, y, z }
27 }
28
29 pub const ZERO: Vector3 = Vector3::new(0.0, 0.0, 0.0);
31
32 pub fn length(&self) -> f32 {
34 (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
35 }
36}
37
38impl Add for Vector3 {
39 type Output = Vector3;
40
41 fn add(self, other: Vector3) -> Vector3 {
42 Vector3::new(self.x + other.x, self.y + other.y, self.z + other.z)
43 }
44}
45
46impl Mul<f32> for Vector3 {
47 type Output = Vector3;
48
49 fn mul(self, scale: f32) -> Vector3 {
50 Vector3::new(self.x * scale, self.y * scale, self.z * scale)
51 }
52}
53
54#[derive(Clone, Copy, Debug, PartialEq)]
59pub struct Quaternion {
60 pub x: f32,
62 pub y: f32,
64 pub z: f32,
66 pub w: f32,
68}
69
70impl Quaternion {
71 pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Quaternion {
73 Quaternion { x, y, z, w }
74 }
75
76 pub const IDENTITY: Quaternion = Quaternion::new(0.0, 0.0, 0.0, 1.0);
78
79 pub fn length(&self) -> f32 {
81 self.length2().sqrt()
82 }
83
84 pub fn length2(&self) -> f32 {
86 self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w
87 }
88
89 pub fn scaled(&self, scale: f32) -> Quaternion {
91 Quaternion::new(
92 self.x * scale,
93 self.y * scale,
94 self.z * scale,
95 self.w * scale,
96 )
97 }
98
99 pub fn normalized(&self) -> Quaternion {
104 let length = self.length();
105 if length == 0.0 {
106 return *self;
107 }
108 self.scaled(1.0 / length)
109 }
110}
111
112impl Default for Quaternion {
113 fn default() -> Quaternion {
114 Quaternion::IDENTITY
115 }
116}
117
118impl Add for Quaternion {
119 type Output = Quaternion;
120
121 fn add(self, other: Quaternion) -> Quaternion {
122 Quaternion::new(
123 self.x + other.x,
124 self.y + other.y,
125 self.z + other.z,
126 self.w + other.w,
127 )
128 }
129}
130
131pub fn transform3(matrix: Matrix4, point: Offset) -> Offset {
138 let m = matrix.to_flutter_array();
139 let (x, y) = (point.dx() as f32, point.dy() as f32);
140 Offset::new(
141 (m[0] * x + m[4] * y + m[12]) as f64,
142 (m[1] * x + m[5] * y + m[13]) as f64,
143 )
144}
145
146pub fn decompose(matrix: Matrix4) -> (Vector3, Quaternion, Vector3) {
151 let m = matrix.to_flutter_array();
152 let mut sx = Vector3::new(m[0], m[1], m[2]).length();
153 let sy = Vector3::new(m[4], m[5], m[6]).length();
154 let sz = Vector3::new(m[8], m[9], m[10]).length();
155
156 if determinant(&m) < 0.0 {
157 sx = -sx;
158 }
159
160 let translation = Vector3::new(m[12], m[13], m[14]);
161
162 let inv_sx = 1.0 / sx;
163 let inv_sy = 1.0 / sy;
164 let inv_sz = 1.0 / sz;
165
166 let rotation_matrix = [
169 m[0] * inv_sx,
170 m[1] * inv_sx,
171 m[2] * inv_sx,
172 m[4] * inv_sy,
173 m[5] * inv_sy,
174 m[6] * inv_sy,
175 m[8] * inv_sz,
176 m[9] * inv_sz,
177 m[10] * inv_sz,
178 ];
179 let rotation = quaternion_from_rotation(&rotation_matrix);
180
181 (translation, rotation, Vector3::new(sx, sy, sz))
182}
183
184pub fn compose(translation: Vector3, rotation: Quaternion, scale: Vector3) -> Matrix4 {
189 let (x, y, z, w) = (rotation.x, rotation.y, rotation.z, rotation.w);
190 let (x2, y2, z2) = (x + x, y + y, z + z);
191 let (xx, xy, xz) = (x * x2, x * y2, x * z2);
192 let (yy, yz, zz) = (y * y2, y * z2, z * z2);
193 let (wx, wy, wz) = (w * x2, w * y2, w * z2);
194
195 let mut m = [
196 1.0 - (yy + zz),
197 xy + wz,
198 xz - wy,
199 0.0,
200 xy - wz,
201 1.0 - (xx + zz),
202 yz + wx,
203 0.0,
204 xz + wy,
205 yz - wx,
206 1.0 - (xx + yy),
207 0.0,
208 translation.x,
209 translation.y,
210 translation.z,
211 1.0,
212 ];
213
214 for (column, factor) in [scale.x, scale.y, scale.z, 1.0].into_iter().enumerate() {
216 for row in 0..4 {
217 m[column * 4 + row] *= factor;
218 }
219 }
220
221 Matrix4::from_flutter_array(&m)
222}
223
224fn determinant(m: &[f32; 16]) -> f32 {
226 let det2_01_01 = m[0] * m[5] - m[1] * m[4];
227 let det2_01_02 = m[0] * m[6] - m[2] * m[4];
228 let det2_01_03 = m[0] * m[7] - m[3] * m[4];
229 let det2_01_12 = m[1] * m[6] - m[2] * m[5];
230 let det2_01_13 = m[1] * m[7] - m[3] * m[5];
231 let det2_01_23 = m[2] * m[7] - m[3] * m[6];
232 let det3_201_012 = m[8] * det2_01_12 - m[9] * det2_01_02 + m[10] * det2_01_01;
233 let det3_201_013 = m[8] * det2_01_13 - m[9] * det2_01_03 + m[11] * det2_01_01;
234 let det3_201_023 = m[8] * det2_01_23 - m[10] * det2_01_03 + m[11] * det2_01_02;
235 let det3_201_123 = m[9] * det2_01_23 - m[10] * det2_01_13 + m[11] * det2_01_12;
236 -det3_201_123 * m[12] + det3_201_023 * m[13] - det3_201_013 * m[14] + det3_201_012 * m[15]
237}
238
239fn quaternion_from_rotation(m: &[f32; 9]) -> Quaternion {
241 fn index(row: usize, col: usize) -> usize {
243 col * 3 + row
244 }
245
246 let trace = m[0] + m[4] + m[8];
247 let mut q = [0.0f32; 4];
248 if trace > 0.0 {
249 let mut s = (trace + 1.0).sqrt();
250 q[3] = s * 0.5;
251 s = 0.5 / s;
252 q[0] = (m[5] - m[7]) * s;
253 q[1] = (m[6] - m[2]) * s;
254 q[2] = (m[1] - m[3]) * s;
255 } else {
256 let i = if m[0] < m[4] {
257 if m[4] < m[8] { 2 } else { 1 }
258 } else if m[0] < m[8] {
259 2
260 } else {
261 0
262 };
263 let j = (i + 1) % 3;
264 let k = (i + 2) % 3;
265 let mut s = (m[index(i, i)] - m[index(j, j)] - m[index(k, k)] + 1.0).sqrt();
266 q[i] = s * 0.5;
267 s = 0.5 / s;
268 q[3] = (m[index(k, j)] - m[index(j, k)]) * s;
269 q[j] = (m[index(j, i)] + m[index(i, j)]) * s;
270 q[k] = (m[index(k, i)] + m[index(i, k)]) * s;
271 }
272 Quaternion::new(q[0], q[1], q[2], q[3])
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 fn assert_close(a: Matrix4, b: Matrix4) {
280 let (a, b) = (a.to_flutter_array(), b.to_flutter_array());
281 for (index, (a, b)) in a.iter().zip(b.iter()).enumerate() {
282 assert!((a - b).abs() < 1e-5, "entry {index}: {a} vs {b}");
283 }
284 }
285
286 #[test]
287 fn decompose_reads_back_a_translation_and_a_scale() {
288 let matrix = Matrix4::translation(3.0, 4.0).then(&Matrix4::scale(2.0, 5.0));
289 let (translation, rotation, scale) = decompose(matrix);
290 assert_eq!(translation, Vector3::new(3.0, 4.0, 0.0));
291 assert_eq!(rotation, Quaternion::IDENTITY);
292 assert_eq!(scale, Vector3::new(2.0, 5.0, 1.0));
293 }
294
295 #[test]
296 fn compose_inverts_decompose() {
297 let matrix = Matrix4::translation(3.0, 4.0)
298 .then(&Matrix4::rotation(0.7))
299 .then(&Matrix4::scale(2.0, 5.0));
300 let (translation, rotation, scale) = decompose(matrix);
301 assert_close(compose(translation, rotation, scale), matrix);
302 }
303
304 #[test]
305 fn a_rotation_round_trips_through_a_quaternion() {
306 let matrix = Matrix4::rotation(std::f32::consts::FRAC_PI_3);
307 let (translation, rotation, scale) = decompose(matrix);
308 assert!((rotation.length() - 1.0).abs() < 1e-6);
309 assert_close(compose(translation, rotation, scale), matrix);
310 }
311
312 #[test]
313 fn a_mirrored_matrix_decomposes_with_a_negative_x_scale() {
314 let matrix = Matrix4::scale(-2.0, 3.0);
315 let (_, _, scale) = decompose(matrix);
316 assert_eq!(scale.x, -2.0);
317 assert_eq!(scale.y, 3.0);
318 }
319}