1use crate::numerics::{Matrix4x4, Quaternion, Vector3};
4use crate::{Matrix, Vector, Vector3D};
5
6pub struct MatrixUtils;
8
9impl MatrixUtils {
10 #[allow(clippy::too_many_arguments)]
13 pub fn compute_transform(
14 size: Vector,
15 anchor_point: Vector,
16 center_point: Vector3D,
17 transform_matrix: Matrix,
18 scale: Vector3D,
19 rotation_angle: f32,
20 orientation: Quaternion,
21 offset: Vector3D,
22 ) -> Option<Matrix> {
23 let anchor = Vector::multiply(size, anchor_point);
26 let mut mat = Matrix::create_translation(-anchor.x, -anchor.y);
27
28 let center = Vector3D::new(center_point.x, center_point.y, center_point.z);
29 let center3: Vector3 = center.into();
30
31 if !transform_matrix.is_identity() {
32 mat = transform_matrix * mat;
33 }
34
35 if scale != Vector3D::new(1.0, 1.0, 1.0) {
36 mat *= Self::to_matrix(Matrix4x4::create_scale_at(scale.into(), center3));
37 }
38
39 if rotation_angle != 0.0 {
41 mat *= Self::to_matrix(Matrix4x4::create_rotation_z_at(rotation_angle, center3));
42 }
43
44 if orientation != Quaternion::IDENTITY {
45 if center_point != Vector3D::default() {
46 mat *= Self::to_matrix(
47 Matrix4x4::create_translation(-center3)
48 * Matrix4x4::create_from_quaternion(orientation)
49 * Matrix4x4::create_translation(center3),
50 );
51 } else {
52 mat *= Self::to_matrix(Matrix4x4::create_from_quaternion(orientation));
53 }
54 }
55
56 if offset != Vector3D::default() {
57 if offset.z == 0.0 {
58 mat *= Matrix::create_translation(offset.x, offset.y);
59 } else {
60 mat *= Self::to_matrix(Matrix4x4::create_translation(offset.into()));
61 }
62 }
63
64 if mat.is_identity() {
65 return None;
66 }
67 Some(mat)
68 }
69
70 pub fn to_matrix4x4(matrix: Matrix) -> Matrix4x4 {
72 Matrix4x4::new(
73 matrix.m11 as f32,
74 matrix.m12 as f32,
75 0.0,
76 matrix.m13 as f32,
77 matrix.m21 as f32,
78 matrix.m22 as f32,
79 0.0,
80 matrix.m23 as f32,
81 0.0,
82 0.0,
83 1.0,
84 0.0,
85 matrix.m31 as f32,
86 matrix.m32 as f32,
87 0.0,
88 matrix.m33 as f32,
89 )
90 }
91
92 pub fn to_matrix(matrix44: Matrix4x4) -> Matrix {
94 Matrix::new_3x3(
95 matrix44.m11 as f64,
96 matrix44.m12 as f64,
97 matrix44.m14 as f64,
98 matrix44.m21 as f64,
99 matrix44.m22 as f64,
100 matrix44.m24 as f64,
101 matrix44.m41 as f64,
102 matrix44.m42 as f64,
103 matrix44.m44 as f64,
104 )
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::Point;
112
113 const ONE: Vector3D = Vector3D::new(1.0, 1.0, 1.0);
114 const ZERO: Vector3D = Vector3D::new(0.0, 0.0, 0.0);
115
116 #[track_caller]
117 fn close(actual: Matrix, expected: Matrix) {
118 let a = [
119 actual.m11, actual.m12, actual.m13, actual.m21, actual.m22, actual.m23, actual.m31, actual.m32, actual.m33,
120 ];
121 let e = [
122 expected.m11,
123 expected.m12,
124 expected.m13,
125 expected.m21,
126 expected.m22,
127 expected.m23,
128 expected.m31,
129 expected.m32,
130 expected.m33,
131 ];
132 for (a, e) in a.iter().zip(e) {
133 assert!((a - e).abs() < 1e-5, "{actual:?} vs {expected:?}");
134 }
135 }
136
137 #[test]
138 fn identity_inputs_give_no_transform() {
139 assert_eq!(
140 MatrixUtils::compute_transform(
141 Vector::new(100.0, 50.0),
142 Vector::ZERO,
143 ZERO,
144 Matrix::IDENTITY,
145 ONE,
146 0.0,
147 Quaternion::IDENTITY,
148 ZERO
149 ),
150 None
151 );
152 }
153
154 #[test]
155 fn offset_and_anchor_translate() {
156 assert_eq!(
157 MatrixUtils::compute_transform(
158 Vector::new(100.0, 50.0),
159 Vector::new(0.5, 1.0),
160 ZERO,
161 Matrix::IDENTITY,
162 ONE,
163 0.0,
164 Quaternion::IDENTITY,
165 Vector3D::new(10.0, 20.0, 0.0)
166 ),
167 Some(Matrix::create_translation(-40.0, -30.0))
168 );
169 assert_eq!(
171 MatrixUtils::compute_transform(
172 Vector::ZERO,
173 Vector::ZERO,
174 ZERO,
175 Matrix::IDENTITY,
176 ONE,
177 0.0,
178 Quaternion::IDENTITY,
179 Vector3D::new(10.0, 20.0, 5.0)
180 ),
181 Some(Matrix::create_translation(10.0, 20.0))
182 );
183 }
184
185 #[test]
186 fn scale_and_rotation_are_applied_around_the_center_point() {
187 let center = Vector3D::new(10.0, 20.0, 0.0);
188 let scaled = MatrixUtils::compute_transform(
189 Vector::ZERO,
190 Vector::ZERO,
191 center,
192 Matrix::IDENTITY,
193 Vector3D::new(2.0, 3.0, 1.0),
194 0.0,
195 Quaternion::IDENTITY,
196 ZERO,
197 )
198 .unwrap();
199 assert_eq!(scaled, Matrix::new(2.0, 0.0, 0.0, 3.0, -10.0, -40.0));
200 assert_eq!(scaled.transform(Point::new(10.0, 20.0)), Point::new(10.0, 20.0));
201
202 let angle = 0.7f32;
203 let rotated = MatrixUtils::compute_transform(
204 Vector::ZERO,
205 Vector::ZERO,
206 center,
207 Matrix::IDENTITY,
208 ONE,
209 angle,
210 Quaternion::IDENTITY,
211 ZERO,
212 )
213 .unwrap();
214 close(
215 rotated,
216 Matrix::create_rotation_at(angle as f64, Point::new(10.0, 20.0)),
217 );
218
219 let oriented = MatrixUtils::compute_transform(
221 Vector::ZERO,
222 Vector::ZERO,
223 center,
224 Matrix::IDENTITY,
225 ONE,
226 0.0,
227 Quaternion::create_from_axis_angle(Vector3::UNIT_Z, angle),
228 ZERO,
229 )
230 .unwrap();
231 close(oriented, rotated);
232
233 let oriented_at_origin = MatrixUtils::compute_transform(
234 Vector::ZERO,
235 Vector::ZERO,
236 ZERO,
237 Matrix::IDENTITY,
238 ONE,
239 0.0,
240 Quaternion::create_from_axis_angle(Vector3::UNIT_Z, angle),
241 ZERO,
242 )
243 .unwrap();
244 close(oriented_at_origin, Matrix::create_rotation(angle as f64));
245 }
246
247 #[test]
248 fn transform_matrix_is_applied_before_the_anchor_translation() {
249 let transform = Matrix::create_scale(2.0, 2.0);
250 let actual = MatrixUtils::compute_transform(
251 Vector::new(10.0, 10.0),
252 Vector::new(1.0, 1.0),
253 ZERO,
254 transform,
255 ONE,
256 0.0,
257 Quaternion::IDENTITY,
258 ZERO,
259 )
260 .unwrap();
261 assert_eq!(actual, transform * Matrix::create_translation(-10.0, -10.0));
262 }
263
264 #[test]
265 fn matrix_conversions_drop_and_restore_the_z_axis() {
266 let m = Matrix::new_3x3(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
267 let m44 = MatrixUtils::to_matrix4x4(m);
268 assert_eq!(
269 m44,
270 Matrix4x4::new(1.0, 2.0, 0.0, 3.0, 4.0, 5.0, 0.0, 6.0, 0.0, 0.0, 1.0, 0.0, 7.0, 8.0, 0.0, 9.0)
271 );
272 assert_eq!(MatrixUtils::to_matrix(m44), m);
273 }
274}