1use serde_json::Value;
32
33use crate::error::DecodeError;
34use crate::json;
35use crate::math::Vec3;
36
37pub const ATTRIBUTE: &str = "usd::xformop";
39
40#[derive(Debug, Clone, Copy, PartialEq)]
45pub struct Transform {
46 rows: [[f64; 4]; 4],
47}
48
49impl Default for Transform {
50 fn default() -> Self {
51 Self::IDENTITY
52 }
53}
54
55impl Transform {
56 pub const IDENTITY: Self = Self {
58 rows: [
59 [1.0, 0.0, 0.0, 0.0],
60 [0.0, 1.0, 0.0, 0.0],
61 [0.0, 0.0, 1.0, 0.0],
62 [0.0, 0.0, 0.0, 1.0],
63 ],
64 };
65
66 pub fn from_rows(rows: [[f64; 4]; 4]) -> Result<Self, DecodeError> {
70 for (i, row) in rows.iter().enumerate() {
71 for (j, v) in row.iter().enumerate() {
72 if !v.is_finite() {
73 return Err(DecodeError::WrongShape {
74 at: format!("transform[{i}][{j}]"),
75 expected: "finite number",
76 });
77 }
78 }
79 }
80 let last_column = [rows[0][3], rows[1][3], rows[2][3], rows[3][3]];
81 if last_column != [0.0, 0.0, 0.0, 1.0] {
82 return Err(DecodeError::NonAffineTransform { last_column });
83 }
84 Ok(Self { rows })
85 }
86
87 pub fn from_translation(t: Vec3) -> Self {
89 let mut rows = Self::IDENTITY.rows;
90 rows[3] = [t[0], t[1], t[2], 1.0];
91 Self { rows }
92 }
93
94 pub fn from_attribute(value: &Value) -> Result<Self, DecodeError> {
109 let object = json::object(value, ATTRIBUTE)?;
110 Self::from_matrix_json(json::field(object, "transform")?)
111 }
112
113 pub fn from_matrix_json(value: &Value) -> Result<Self, DecodeError> {
115 let shape_error = || DecodeError::WrongShape {
116 at: "transform".to_owned(),
117 expected: "array of 4 rows of 4 numbers",
118 };
119 let rows_json = json::array(value, "transform")?;
120 if rows_json.len() != 4 {
121 return Err(shape_error());
122 }
123 let mut rows = [[0.0; 4]; 4];
124 for (i, (row, row_json)) in rows.iter_mut().zip(rows_json).enumerate() {
125 let cells = match row_json.as_array() {
126 Some(cells) if cells.len() == 4 => cells,
127 _ => {
128 return Err(DecodeError::WrongShape {
129 at: format!("transform[{i}]"),
130 expected: "array of 4 numbers",
131 })
132 }
133 };
134 for (j, (cell, v)) in row.iter_mut().zip(cells).enumerate() {
135 *cell = json::number(v, || format!("transform[{i}][{j}]"))?;
136 }
137 }
138 Self::from_rows(rows)
139 }
140
141 pub fn rows(&self) -> &[[f64; 4]; 4] {
143 &self.rows
144 }
145
146 pub fn translation(&self) -> Vec3 {
148 let r = self.rows[3];
149 [r[0], r[1], r[2]]
150 }
151
152 pub fn to_column_major(&self) -> [f64; 16] {
156 let mut out = [0.0; 16];
157 for (i, row) in self.rows.iter().enumerate() {
158 out[i * 4..i * 4 + 4].copy_from_slice(row);
159 }
160 out
161 }
162
163 pub fn then(&self, outer: &Transform) -> Transform {
170 let a = &self.rows;
171 let b = &outer.rows;
172 let mut rows = [[0.0; 4]; 4];
173 for (i, row) in rows.iter_mut().enumerate() {
174 for (j, cell) in row.iter_mut().enumerate() {
175 *cell = (0..4).map(|k| a[i][k] * b[k][j]).sum();
176 }
177 }
178 for row in rows.iter_mut().take(3) {
180 row[3] = 0.0;
181 }
182 rows[3][3] = 1.0;
183 Transform { rows }
184 }
185
186 pub fn transform_point(&self, p: Vec3) -> Vec3 {
188 let v = self.transform_vector(p);
189 let t = self.rows[3];
190 [v[0] + t[0], v[1] + t[1], v[2] + t[2]]
191 }
192
193 pub fn transform_vector(&self, v: Vec3) -> Vec3 {
196 let m = &self.rows;
197 [
198 v[0] * m[0][0] + v[1] * m[1][0] + v[2] * m[2][0],
199 v[0] * m[0][1] + v[1] * m[1][1] + v[2] * m[2][1],
200 v[0] * m[0][2] + v[1] * m[1][2] + v[2] * m[2][2],
201 ]
202 }
203
204 pub fn determinant(&self) -> f64 {
207 let m = &self.rows;
208 m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
209 - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
210 + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])
211 }
212}
213
214pub fn world_from_parent(parent_world: &Transform, local: Option<&Transform>) -> Transform {
219 match local {
220 Some(local) => local.then(parent_world),
221 None => *parent_world,
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use serde_json::json;
229
230 fn close(a: Vec3, b: Vec3) -> bool {
231 a.iter().zip(&b).all(|(x, y)| (x - y).abs() < 1e-12)
232 }
233
234 fn rot_z90() -> Transform {
236 Transform::from_rows([
237 [0.0, 1.0, 0.0, 0.0],
238 [-1.0, 0.0, 0.0, 0.0],
239 [0.0, 0.0, 1.0, 0.0],
240 [0.0, 0.0, 0.0, 1.0],
241 ])
242 .unwrap()
243 }
244
245 #[test]
246 fn decodes_translation_from_last_row() {
247 let t = Transform::from_attribute(&json!({"transform": [
248 [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [1.5, -2, 3.25, 1]
249 ]}))
250 .unwrap();
251 assert_eq!(t.translation(), [1.5, -2.0, 3.25]);
252 assert_eq!(t.transform_point([0.0; 3]), [1.5, -2.0, 3.25]);
253 assert_eq!(t.transform_vector([1.0, 0.0, 0.0]), [1.0, 0.0, 0.0]);
254 }
255
256 #[test]
257 fn column_major_matches_file_order() {
258 let t = Transform::from_translation([7.0, 8.0, 9.0]);
259 let m = t.to_column_major();
260 assert_eq!(&m[12..16], &[7.0, 8.0, 9.0, 1.0]);
261 assert_eq!(m[0], 1.0);
262 }
263
264 #[test]
265 fn rotation_maps_rows_as_images_of_axes() {
266 let r = rot_z90();
267 assert!(close(r.transform_point([1.0, 0.0, 0.0]), [0.0, 1.0, 0.0]));
268 assert!(close(r.transform_point([0.0, 1.0, 0.0]), [-1.0, 0.0, 0.0]));
269 assert_eq!(r.determinant(), 1.0);
270 }
271
272 #[test]
273 fn three_level_hierarchy() {
274 let root = world_from_parent(
279 &Transform::IDENTITY,
280 Some(&Transform::from_translation([10.0, 0.0, 0.0])),
281 );
282 let a = world_from_parent(&root, Some(&rot_z90()));
283 let b = world_from_parent(&a, None);
284 let c = world_from_parent(&b, Some(&Transform::from_translation([1.0, 0.0, 0.0])));
285
286 assert_eq!(b, a, "a node without a transform inherits");
287 assert!(close(c.translation(), [10.0, 1.0, 0.0]));
289 assert!(close(c.transform_point([0.0, 1.0, 0.0]), [9.0, 1.0, 0.0]));
290 assert_eq!(
291 c.rows(),
292 &[
293 [0.0, 1.0, 0.0, 0.0],
294 [-1.0, 0.0, 0.0, 0.0],
295 [0.0, 0.0, 1.0, 0.0],
296 [10.0, 1.0, 0.0, 1.0],
297 ]
298 );
299 }
300
301 #[test]
302 fn then_is_associative_with_point_application() {
303 let a = rot_z90();
304 let b = Transform::from_translation([3.0, 4.0, 5.0]);
305 let p = [1.0, 2.0, 3.0];
306 assert!(close(
307 a.then(&b).transform_point(p),
308 b.transform_point(a.transform_point(p))
309 ));
310 }
311
312 #[test]
313 fn rejects_wrong_shapes() {
314 let err = |v: Value| Transform::from_attribute(&v).unwrap_err();
315 assert_eq!(
316 err(json!([[1, 0, 0, 0]])),
317 DecodeError::WrongShape {
318 at: "usd::xformop".into(),
319 expected: "object"
320 }
321 );
322 assert_eq!(
323 err(json!({})),
324 DecodeError::MissingField { field: "transform" }
325 );
326 assert!(matches!(
327 err(json!({"transform": [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]]})),
328 DecodeError::WrongShape { .. }
329 ));
330 assert_eq!(
331 err(json!({"transform": [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1], [0, 0, 0, 1]]})),
332 DecodeError::WrongShape {
333 at: "transform[2]".into(),
334 expected: "array of 4 numbers"
335 }
336 );
337 assert_eq!(
338 err(json!({"transform": [[1, 0, 0, 0], [0, 1, "x", 0], [0, 0, 1, 0], [0, 0, 0, 1]]})),
339 DecodeError::WrongShape {
340 at: "transform[1][2]".into(),
341 expected: "number"
342 }
343 );
344 }
345
346 #[test]
347 fn rejects_translation_in_last_column() {
348 let e = Transform::from_attribute(&json!({"transform": [
350 [1, 0, 0, 5], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]
351 ]}))
352 .unwrap_err();
353 assert_eq!(
354 e,
355 DecodeError::NonAffineTransform {
356 last_column: [5.0, 0.0, 0.0, 1.0]
357 }
358 );
359 }
360}