dae_parser/core/
transform.rs

1use crate::*;
2
3#[cfg(feature = "nalgebra")]
4use nalgebra::{Matrix4, Point3, Vector3};
5
6/// A transformation, that can be represented as a matrix
7/// (but may be expressed in another way for convenience).
8#[derive(Clone, Debug)]
9pub enum Transform {
10    /// Contains a position and orientation transformation suitable for aiming a camera.
11    LookAt(LookAt),
12    /// A generic 4x4 matrix.
13    Matrix(Matrix),
14    /// An axis-angle rotation.
15    Rotate(Rotate),
16    /// A scale along the three dimentions.
17    Scale(Scale),
18    /// A skew deformation.
19    Skew(Skew),
20    /// A translation by a vector.
21    Translate(Translate),
22}
23
24impl From<LookAt> for Transform {
25    fn from(v: LookAt) -> Self {
26        Self::LookAt(v)
27    }
28}
29
30impl From<Matrix> for Transform {
31    fn from(v: Matrix) -> Self {
32        Self::Matrix(v)
33    }
34}
35
36impl From<Rotate> for Transform {
37    fn from(v: Rotate) -> Self {
38        Self::Rotate(v)
39    }
40}
41
42impl From<Scale> for Transform {
43    fn from(v: Scale) -> Self {
44        Self::Scale(v)
45    }
46}
47
48impl From<Skew> for Transform {
49    fn from(v: Skew) -> Self {
50        Self::Skew(v)
51    }
52}
53
54impl From<Translate> for Transform {
55    fn from(v: Translate) -> Self {
56        Self::Translate(v)
57    }
58}
59
60impl Transform {
61    /// Parse a [`Transform`] from an XML element.
62    pub fn parse(e: &Element) -> Result<Option<Self>> {
63        match e.name() {
64            LookAt::NAME => Ok(Some(Self::LookAt(LookAt::parse(e)?))),
65            Matrix::NAME => Ok(Some(Self::Matrix(Matrix::parse(e)?))),
66            Rotate::NAME => Ok(Some(Self::Rotate(Rotate::parse(e)?))),
67            Scale::NAME => Ok(Some(Self::Scale(Scale::parse(e)?))),
68            Skew::NAME => Ok(Some(Self::Skew(Skew::parse(e)?))),
69            Translate::NAME => Ok(Some(Self::Translate(Translate::parse(e)?))),
70            _ => Ok(None),
71        }
72    }
73
74    #[cfg(feature = "nalgebra")]
75    /// Convert this transformation to a [`nalgebra::Matrix4`].
76    pub fn as_matrix(&self) -> Matrix4<f32> {
77        match self {
78            Transform::Translate(tr) => tr.as_matrix(),
79            Transform::Rotate(tr) => tr.as_matrix(),
80            Transform::LookAt(tr) => tr.as_matrix(),
81            Transform::Matrix(tr) => tr.as_matrix(),
82            Transform::Scale(tr) => tr.as_matrix(),
83            Transform::Skew(tr) => tr.as_matrix(),
84        }
85    }
86
87    #[cfg(feature = "nalgebra")]
88    /// Prepend this transformation to the matrix. Equivalent to `*mat *= self.as_matrix()`.
89    pub fn prepend_to_matrix(&self, mat: &mut Matrix4<f32>) {
90        match self {
91            Transform::Translate(tr) => tr.prepend_to_matrix(mat),
92            Transform::Scale(tr) => tr.prepend_to_matrix(mat),
93            _ => *mat *= self.as_matrix(),
94        }
95    }
96
97    #[cfg(feature = "nalgebra")]
98    /// Append this transformation to the matrix. Equivalent to `*mat = self.as_matrix() * *mat`.
99    pub fn append_to_matrix(&self, mat: &mut Matrix4<f32>) {
100        match self {
101            Transform::Translate(tr) => tr.append_to_matrix(mat),
102            Transform::Scale(tr) => tr.append_to_matrix(mat),
103            _ => *mat = self.as_matrix() * *mat,
104        }
105    }
106}
107
108impl XNodeWrite for Transform {
109    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
110        match self {
111            Self::Translate(e) => e.write_to(w),
112            Self::Rotate(e) => e.write_to(w),
113            Self::LookAt(e) => e.write_to(w),
114            Self::Matrix(e) => e.write_to(w),
115            Self::Scale(e) => e.write_to(w),
116            Self::Skew(e) => e.write_to(w),
117        }
118    }
119}
120
121/// A [`RigidBody`] transform is a subset of the full set of [`Transform`]s
122/// which restricts to euclidean transformations (translation and rotation).
123#[derive(Clone, Debug)]
124pub enum RigidTransform {
125    /// A translation.
126    Translate(Translate),
127    /// A rotation.
128    Rotate(Rotate),
129}
130
131impl From<Translate> for RigidTransform {
132    fn from(v: Translate) -> Self {
133        Self::Translate(v)
134    }
135}
136
137impl From<Rotate> for RigidTransform {
138    fn from(v: Rotate) -> Self {
139        Self::Rotate(v)
140    }
141}
142
143impl From<RigidTransform> for Transform {
144    fn from(tr: RigidTransform) -> Self {
145        match tr {
146            RigidTransform::Translate(v) => v.into(),
147            RigidTransform::Rotate(v) => v.into(),
148        }
149    }
150}
151
152impl RigidTransform {
153    /// Parse a [`RigidTransform`] from an XML element.
154    pub fn parse(e: &Element) -> Result<Option<Self>> {
155        match e.name() {
156            Translate::NAME => Ok(Some(Self::Translate(Translate::parse(e)?))),
157            Rotate::NAME => Ok(Some(Self::Rotate(Rotate::parse(e)?))),
158            _ => Ok(None),
159        }
160    }
161
162    #[cfg(feature = "nalgebra")]
163    /// Convert this transformation to a [`nalgebra::Matrix4`].
164    pub fn as_matrix(&self) -> Matrix4<f32> {
165        match self {
166            RigidTransform::Translate(tr) => tr.as_matrix(),
167            RigidTransform::Rotate(tr) => tr.as_matrix(),
168        }
169    }
170
171    #[cfg(feature = "nalgebra")]
172    /// Prepend this transformation to the matrix. Equivalent to `*mat *= self.as_matrix()`.
173    pub fn prepend_to_matrix(&self, mat: &mut Matrix4<f32>) {
174        match self {
175            RigidTransform::Translate(tr) => tr.prepend_to_matrix(mat),
176            _ => *mat *= self.as_matrix(),
177        }
178    }
179
180    #[cfg(feature = "nalgebra")]
181    /// Append this transformation to the matrix. Equivalent to `*mat = self.as_matrix() * *mat`.
182    pub fn append_to_matrix(&self, mat: &mut Matrix4<f32>) {
183        match self {
184            RigidTransform::Translate(tr) => tr.append_to_matrix(mat),
185            _ => *mat = self.as_matrix() * *mat,
186        }
187    }
188}
189
190impl XNodeWrite for RigidTransform {
191    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
192        match self {
193            Self::Translate(e) => e.write_to(w),
194            Self::Rotate(e) => e.write_to(w),
195        }
196    }
197}
198
199/// Contains a position and orientation transformation suitable for aiming a camera.
200#[derive(Clone, Debug)]
201pub struct LookAt(
202    /// A list of 9 floating-point values.
203    /// These values are organized into three vectors as follows:
204    /// 1.  Eye position is given as Px, Py, Pz.
205    /// 2.  Interest point is given as Ix, Iy, Iz.
206    /// 3.  Up-axis direction is given as UPx, UPy, UPz.
207    pub Box<[f32; 9]>,
208);
209
210impl XNode for LookAt {
211    const NAME: &'static str = "lookat";
212    fn parse(element: &Element) -> Result<Self> {
213        debug_assert_eq!(element.name(), Self::NAME);
214        Ok(LookAt(parse_array_n(element)?))
215    }
216}
217
218impl XNodeWrite for LookAt {
219    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
220        ElemBuilder::print_arr(Self::NAME, &*self.0, w)
221    }
222}
223
224impl LookAt {
225    /// Construct a look-at transform from the given eye position,
226    /// target position, and up direction.
227    pub fn new(eye: [f32; 3], target: [f32; 3], up: [f32; 3]) -> Self {
228        Self(Box::new([
229            eye[0], eye[1], eye[2], target[0], target[1], target[2], up[0], up[1], up[2],
230        ]))
231    }
232
233    /// The eye position for this look-at orientation.
234    #[inline]
235    pub fn eye(&self) -> &[f32; 3] {
236        self.0[0..3].try_into().unwrap()
237    }
238
239    /// The target position (interest point) for this look-at orientation.
240    #[inline]
241    pub fn target(&self) -> &[f32; 3] {
242        self.0[3..6].try_into().unwrap()
243    }
244
245    /// The up direction for this look-at orientation.
246    #[inline]
247    pub fn up(&self) -> &[f32; 3] {
248        self.0[6..9].try_into().unwrap()
249    }
250
251    #[cfg(feature = "nalgebra")]
252    /// Convert this transformation to a [`nalgebra::Matrix4`].
253    pub fn as_matrix(&self) -> Matrix4<f32> {
254        let eye = Point3::from_slice(self.eye());
255        let target = Point3::from_slice(self.target());
256        let up = Vector3::from_column_slice(self.up());
257        Matrix4::look_at_rh(&eye, &target, &up)
258    }
259}
260
261/// Describes transformations that embody mathematical changes to points
262/// within a coordinate system or the coordinate system itself.
263#[derive(Clone, Debug)]
264pub struct Matrix(
265    /// A list of 16 floating-point values.
266    /// These values are organized into a 4-by-4
267    /// column-order matrix suitable for matrix composition.
268    pub Box<[f32; 16]>,
269);
270
271impl XNode for Matrix {
272    const NAME: &'static str = "matrix";
273    fn parse(element: &Element) -> Result<Self> {
274        debug_assert_eq!(element.name(), Self::NAME);
275        Ok(Matrix(parse_array_n(element)?))
276    }
277}
278
279impl XNodeWrite for Matrix {
280    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
281        ElemBuilder::print_arr(Self::NAME, &*self.0, w)
282    }
283}
284
285impl Matrix {
286    /// Construct a matrix transform from a column order 4x4 matrix.
287    pub fn new(data: [f32; 16]) -> Self {
288        Self(Box::new(data))
289    }
290
291    #[cfg(feature = "nalgebra")]
292    /// Convert this transformation to a [`nalgebra::Matrix4`].
293    pub fn as_matrix(&self) -> Matrix4<f32> {
294        // We have to transpose the matrix to match nalgebra conventions
295        // on projective transformations
296        Matrix4::from_row_slice(&*self.0)
297    }
298}
299
300#[cfg(feature = "nalgebra")]
301impl From<Matrix4<f32>> for Matrix {
302    fn from(mut mat: Matrix4<f32>) -> Self {
303        // We have to transpose the matrix to match nalgebra conventions
304        // on projective transformations
305        mat.transpose_mut();
306        Self(Box::new(mat.as_slice().try_into().expect("impossible")))
307    }
308}
309
310#[cfg(feature = "nalgebra")]
311impl From<Matrix4<f32>> for Transform {
312    fn from(mat: Matrix4<f32>) -> Self {
313        Self::Matrix(mat.into())
314    }
315}
316
317/// Specifies how to rotate an object around an axis.
318#[derive(Clone, Debug)]
319pub struct Rotate(
320    /// A list of four floating-point values.
321    /// These values are organized into a column vector `[X, Y, Z]`
322    /// specifying the axis of rotation, followed by an angle in degrees.
323    pub Box<[f32; 4]>,
324);
325
326impl XNode for Rotate {
327    const NAME: &'static str = "rotate";
328    fn parse(element: &Element) -> Result<Self> {
329        debug_assert_eq!(element.name(), Self::NAME);
330        Ok(Rotate(parse_array_n(element)?))
331    }
332}
333
334impl XNodeWrite for Rotate {
335    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
336        ElemBuilder::print_arr(Self::NAME, &*self.0, w)
337    }
338}
339
340impl Rotate {
341    /// Construct a new axis-angle transformation.
342    pub fn new(axis: [f32; 3], angle: f32) -> Self {
343        Self(Box::new([axis[0], axis[1], axis[2], angle]))
344    }
345
346    /// The rotation axis of this rotation transform.
347    pub fn axis(&self) -> &[f32; 3] {
348        self.0[0..3].try_into().unwrap()
349    }
350
351    /// The magnitude of this rotation transform in degrees.
352    pub fn angle(&self) -> f32 {
353        self.0[3]
354    }
355
356    #[cfg(feature = "nalgebra")]
357    /// Convert this transformation to a [`nalgebra::Matrix4`].
358    pub fn as_matrix(&self) -> Matrix4<f32> {
359        let axis = Vector3::from_column_slice(self.axis()).normalize();
360        Matrix4::from_axis_angle(
361            &nalgebra::Unit::new_normalize(axis),
362            self.angle().to_radians(),
363        )
364    }
365}
366
367/// Specifies how to change an object’s size.
368#[derive(Clone, Debug)]
369pub struct Scale(
370    /// A list of three floating-point values.
371    /// These values are organized into a column vector suitable for matrix composition.
372    pub Box<[f32; 3]>,
373);
374
375impl XNode for Scale {
376    const NAME: &'static str = "scale";
377    fn parse(element: &Element) -> Result<Self> {
378        debug_assert_eq!(element.name(), Self::NAME);
379        Ok(Scale(parse_array_n(element)?))
380    }
381}
382
383impl XNodeWrite for Scale {
384    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
385        ElemBuilder::print_arr(Self::NAME, &*self.0, w)
386    }
387}
388
389impl Scale {
390    /// Construct a new nonuniform scale transformation.
391    pub fn new(val: [f32; 3]) -> Self {
392        Self(Box::new(val))
393    }
394
395    /// Construct a new uniform scale transformation.
396    pub fn uniform(val: f32) -> Self {
397        Self::new([val, val, val])
398    }
399
400    #[cfg(feature = "nalgebra")]
401    /// Convert this transformation to a [`nalgebra::Matrix4`].
402    pub fn as_matrix(&self) -> Matrix4<f32> {
403        Matrix4::new_nonuniform_scaling(&Vector3::from_row_slice(&*self.0))
404    }
405
406    #[cfg(feature = "nalgebra")]
407    /// Convert this transformation to a [`nalgebra::Matrix4`].
408    pub fn prepend_to_matrix(&self, mat: &mut Matrix4<f32>) {
409        mat.prepend_nonuniform_scaling_mut(&Vector3::from_row_slice(&*self.0))
410    }
411
412    #[cfg(feature = "nalgebra")]
413    /// Convert this transformation to a [`nalgebra::Matrix4`].
414    pub fn append_to_matrix(&self, mat: &mut Matrix4<f32>) {
415        mat.append_nonuniform_scaling_mut(&Vector3::from_row_slice(&*self.0))
416    }
417}
418
419/// Specifies how to deform an object along one axis.
420#[derive(Clone, Debug)]
421pub struct Skew(
422    /// A list of seven floating-point values.
423    /// These values are organized into an angle in degrees
424    /// followed by two column vectors specifying the axes of rotation and translation.
425    pub Box<[f32; 7]>,
426);
427
428impl XNode for Skew {
429    const NAME: &'static str = "skew";
430    fn parse(element: &Element) -> Result<Self> {
431        debug_assert_eq!(element.name(), Self::NAME);
432        Ok(Skew(parse_array_n(element)?))
433    }
434}
435
436impl XNodeWrite for Skew {
437    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
438        ElemBuilder::print_arr(Self::NAME, &*self.0, w)
439    }
440}
441
442impl Skew {
443    /// Construct a new skew transformation from the given parameters.
444    pub fn new(angle: f32, axis: [f32; 3], trans: [f32; 3]) -> Self {
445        Self(Box::new([
446            angle, axis[0], axis[1], axis[2], trans[0], trans[1], trans[2],
447        ]))
448    }
449
450    /// The angle of this skew transformation.
451    #[inline]
452    pub fn angle(&self) -> f32 {
453        self.0[0]
454    }
455
456    /// The axis of rotation for this skew transformation.
457    #[inline]
458    pub fn axis(&self) -> &[f32; 3] {
459        self.0[1..4].try_into().unwrap()
460    }
461
462    /// The axis of translation for this skew transformation.
463    #[inline]
464    pub fn trans(&self) -> &[f32; 3] {
465        self.0[4..7].try_into().unwrap()
466    }
467
468    #[cfg(feature = "nalgebra")]
469    /// Convert this transformation to a [`nalgebra::Matrix4`].
470    pub fn as_matrix(&self) -> Matrix4<f32> {
471        // From the RenderMan spec (on which the COLLADA operation is based):
472        //
473        // This operation shifts all points along lines
474        // parallel to the axis vector (dx2, dy2, dz2).
475        // Points along the axis vector (dx1, dy1, dz1)
476        // are mapped onto the vector (x, y, z),
477        // where angle specifies the angle (in degrees)
478        // between the vectors (dx1, dy1, dz1) and (x, y, z),
479        // The two axes are not required to be perpendicular,
480        // however it is an error to specify an angle that is greater than
481        // or equal to the angle between them.
482        // A negative angle can be specified,
483        // but it must be greater than 180 degrees minus the angle between the two axes.
484        unimplemented!("<skew> transforms are not supported")
485    }
486}
487
488/// Changes the position of an object in a local coordinate system.
489#[derive(Clone, Debug)]
490pub struct Translate(
491    /// A list of three floating-point values.
492    /// These values are organized into a column vector suitable for a matrix composition.
493    pub Box<[f32; 3]>,
494);
495
496impl XNode for Translate {
497    const NAME: &'static str = "translate";
498    fn parse(element: &Element) -> Result<Self> {
499        debug_assert_eq!(element.name(), Self::NAME);
500        Ok(Translate(parse_array_n(element)?))
501    }
502}
503
504impl XNodeWrite for Translate {
505    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
506        ElemBuilder::print_arr(Self::NAME, &*self.0, w)
507    }
508}
509
510impl Translate {
511    /// Construct a new translation from the given parameters.
512    pub fn new(val: [f32; 3]) -> Self {
513        Self(Box::new(val))
514    }
515
516    #[cfg(feature = "nalgebra")]
517    /// Convert this transformation to a [`nalgebra::Matrix4`].
518    pub fn as_matrix(&self) -> Matrix4<f32> {
519        Matrix4::new_translation(&Vector3::from_row_slice(&*self.0))
520    }
521
522    #[cfg(feature = "nalgebra")]
523    /// Convert this transformation to a [`nalgebra::Matrix4`].
524    pub fn prepend_to_matrix(&self, mat: &mut Matrix4<f32>) {
525        mat.prepend_translation_mut(&Vector3::from_row_slice(&*self.0))
526    }
527
528    #[cfg(feature = "nalgebra")]
529    /// Convert this transformation to a [`nalgebra::Matrix4`].
530    pub fn append_to_matrix(&self, mat: &mut Matrix4<f32>) {
531        mat.append_translation_mut(&Vector3::from_row_slice(&*self.0))
532    }
533}