macaw/
conformal.rs

1use glam::Affine3A;
2use glam::Mat4;
3use glam::Vec3A;
4
5use crate::IsoTransform;
6use crate::Quat;
7use crate::Vec3;
8use crate::Vec3Ext;
9use crate::Vec4;
10use crate::Vec4Swizzles;
11
12/// Represents a transform with translation + rotation + uniform scale.
13/// Preserves local angles.
14/// Scale and rotation will be applied first, then translation.
15#[derive(Clone, Copy, PartialEq)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[cfg_attr(feature = "speedy", derive(speedy::Writable, speedy::Readable))]
18pub struct Conformal3 {
19    /// xyz = translation, w = uniform scale
20    pub translation_and_scale: Vec4,
21    pub rotation: Quat,
22}
23
24impl Conformal3 {
25    /// The identity transform: doesn't transform at all. Like multiplying with `1`.
26    pub const IDENTITY: Self = Self {
27        translation_and_scale: Vec4::W,
28        rotation: Quat::IDENTITY,
29    };
30
31    #[inline]
32    /// A transform that first rotates and scales around the origin and then moves all points by a set amount.
33    ///
34    /// Equivalent to `Conformal3::from_translation(translation) * (Conformal3::from_scale(rotation) * Conformal3::from_quat(scale))`.
35    ///
36    /// The given rotation should be normalized.
37    pub fn from_scale_rotation_translation(scale: f32, rotation: Quat, translation: Vec3) -> Self {
38        Self {
39            translation_and_scale: translation.extend(scale),
40            rotation,
41        }
42    }
43
44    /// A transform that first rotates around the origin and then moves all points by a set amount.
45    ///
46    /// Equivalent to `Conformal3::from_translation(translation) * Conformal3::from_quat(rotation)`.
47    ///
48    /// The given rotation should be normalized.
49    #[inline]
50    pub fn from_rotation_translation(rotation: Quat, translation: Vec3) -> Self {
51        Self {
52            translation_and_scale: translation.extend(1.0),
53            rotation,
54        }
55    }
56
57    /// A pure translation without any rotation or scale.
58    #[inline]
59    pub fn from_translation(translation: Vec3) -> Self {
60        Self {
61            translation_and_scale: translation.extend(1.0),
62            rotation: Quat::IDENTITY,
63        }
64    }
65
66    /// Returns this transform decomposed into scale, rotation, translation
67    #[inline]
68    pub fn to_scale_rotation_translation(self) -> (f32, Quat, Vec3) {
69        (self.scale(), self.rotation(), self.translation())
70    }
71
72    /// A pure rotation without any translation or scale.
73    #[inline]
74    pub fn from_quat(rotation: Quat) -> Self {
75        Self::from_scale_rotation_translation(1.0, rotation, Vec3::ZERO)
76    }
77
78    /// A pure scale without any translation or rotation.
79    #[inline]
80    pub fn from_scale(scale: f32) -> Self {
81        Self::from_scale_rotation_translation(scale, Quat::IDENTITY, Vec3::ZERO)
82    }
83
84    /// Returns the inverse of this transform. `my_transform * my_transform.inverse() = Conformal3::IDENITTY`
85    #[inline]
86    pub fn inverse(&self) -> Self {
87        let inv_scale = self.inv_scale();
88        let inv_rotation = self.rotation.inverse();
89        let inv_translation = inv_scale * (inv_rotation * -self.translation());
90        Self::from_scale_rotation_translation(inv_scale, inv_rotation, inv_translation)
91    }
92
93    /// Returns self normalized.
94    /// You generally don't need to call this unless you've multiplied A LOT of `Conformal3`.
95    #[inline]
96    #[must_use]
97    pub fn normalize(&self) -> Self {
98        let scale = self.scale();
99        let rotation = self.rotation().normalize();
100        let translation = self.translation();
101        Self::from_scale_rotation_translation(scale, rotation, translation)
102    }
103
104    /// Will attempt to create a `Conformal3` from an `Affine3A`. Assumes no shearing and uniform scaling.
105    /// If the affine transform contains shearing or non-uniform scaling it will be lost.
106    #[inline]
107    pub fn from_affine3a_lossy(transform: &crate::Affine3A) -> Self {
108        let (scale, rotation, translation) = transform.to_scale_rotation_translation();
109        Self {
110            translation_and_scale: translation.extend(scale.mean()),
111            rotation: rotation.normalize(),
112        }
113    }
114
115    /// Returns this transform as an `Affine3A`
116    #[inline]
117    pub fn to_affine3a(self) -> Affine3A {
118        Affine3A::from_scale_rotation_translation(
119            Vec3::splat(self.scale()),
120            self.rotation(),
121            self.translation(),
122        )
123    }
124
125    /// Returns this transform as a `Mat4`
126    #[inline]
127    pub fn to_mat4(self) -> Mat4 {
128        Mat4::from_scale_rotation_translation(
129            Vec3::splat(self.scale()),
130            self.rotation(),
131            self.translation(),
132        )
133    }
134
135    /// Transform a `Vec3` using translation, rotation, scale.
136    #[inline]
137    pub fn transform_point3(&self, value: Vec3) -> Vec3 {
138        self.translation() + self.scale() * (self.rotation() * value)
139    }
140
141    /// Transform a `Vec3A` using translation, rotation, scale.
142    #[inline]
143    pub fn transform_point3a(&self, value: Vec3A) -> Vec3A {
144        Vec3A::from(self.translation()) + self.scale() * (self.rotation() * value)
145    }
146
147    /// Transform a `Vec3` using only rotation and scale.
148    #[inline]
149    pub fn transform_vector3(&self, value: Vec3) -> Vec3 {
150        self.scale() * (self.rotation() * value)
151    }
152
153    /// Transform a `Vec3A` using only rotation and scale.
154    #[inline]
155    pub fn transform_vector3a(&self, value: Vec3A) -> Vec3A {
156        self.scale() * (self.rotation() * value)
157    }
158
159    /// Returns the rotation
160    #[inline]
161    pub fn rotation(&self) -> Quat {
162        self.rotation
163    }
164
165    /// Sets the rotation
166    #[inline]
167    pub fn set_rotation(&mut self, rotation: Quat) {
168        self.rotation = rotation;
169    }
170
171    /// Returns the translation
172    #[inline]
173    pub fn translation(&self) -> Vec3 {
174        self.translation_and_scale.xyz()
175    }
176
177    /// Returns the translation and scale as a `Vec4`
178    #[inline]
179    pub fn translation_and_scale(&self) -> Vec4 {
180        self.translation_and_scale
181    }
182
183    /// Sets the translation
184    #[inline]
185    pub fn set_translation(&mut self, translation: Vec3) {
186        let scale = self.scale();
187        self.translation_and_scale = translation.extend(scale);
188    }
189
190    /// Returns the scale
191    #[inline]
192    pub fn scale(&self) -> f32 {
193        self.translation_and_scale.w
194    }
195
196    /// Returns the scale inverse
197    #[inline]
198    pub fn inv_scale(&self) -> f32 {
199        if self.scale() == 0.0 {
200            f32::INFINITY
201        } else {
202            1.0 / self.scale()
203        }
204    }
205
206    /// Sets the scale
207    #[inline]
208    pub fn set_scale(&mut self, scale: f32) {
209        self.translation_and_scale.w = scale;
210    }
211
212    /// Builds a `Conformal3` from an `IsoTransform` (rotation, translation).
213    #[inline]
214    pub fn from_iso_transform(t: IsoTransform) -> Self {
215        Self::from_rotation_translation(t.rotation(), t.translation())
216    }
217
218    /// Truncates a `Conformal3` to an `IsoTransform` (rotation, translation).
219    pub fn to_iso_transform(self) -> IsoTransform {
220        IsoTransform::from_rotation_translation(self.rotation, self.translation())
221    }
222
223    /// Creates a right-handed view transform using a camera position,
224    /// a point to look at, and an up direction.
225    ///
226    /// The result transforms from world coordinates to view coordinates.
227    ///
228    /// For a view coordinate system with `+X=right`, `+Y=up` and `+Z=back`.
229    ///
230    /// Will return [`None`] if any argument is zero, non-finite, or if forward and up are colinear.
231    #[cfg(not(target_arch = "spirv"))] // TODO: large Options in rust-gpu
232    #[inline]
233    pub fn look_at_rh(eye: Vec3, target: Vec3, up: Vec3) -> Option<Self> {
234        IsoTransform::look_at_rh(eye, target, up).map(Self::from_iso_transform)
235    }
236
237    /// Returns `true` if, and only if, all components are finite.
238    ///
239    /// If any component is either `NaN`, positive or negative infinity, this will return `false`.
240    pub fn is_finite(&self) -> bool {
241        self.translation_and_scale.is_finite() && self.rotation.is_finite()
242    }
243}
244
245impl core::ops::Mul for &Conformal3 {
246    type Output = Conformal3;
247
248    #[inline]
249    fn mul(self, rhs: &Conformal3) -> Conformal3 {
250        let translation = self.transform_point3(rhs.translation());
251        let rotation = self.rotation() * rhs.rotation();
252        let scale = self.scale() * rhs.scale();
253        Conformal3::from_scale_rotation_translation(scale, rotation, translation)
254    }
255}
256
257impl core::ops::Mul<Conformal3> for &Conformal3 {
258    type Output = Conformal3;
259
260    #[inline]
261    fn mul(self, rhs: Conformal3) -> Conformal3 {
262        self.mul(&rhs)
263    }
264}
265
266impl core::ops::Mul for Conformal3 {
267    type Output = Self;
268
269    #[inline]
270    fn mul(self, rhs: Self) -> Self {
271        (&self).mul(&rhs)
272    }
273}
274
275impl core::ops::Mul<Conformal3> for IsoTransform {
276    type Output = Conformal3;
277
278    #[inline]
279    fn mul(self, rhs: Conformal3) -> Conformal3 {
280        Conformal3::from_iso_transform(self).mul(rhs)
281    }
282}
283
284impl core::ops::Mul<IsoTransform> for Conformal3 {
285    type Output = Self;
286
287    #[inline]
288    fn mul(self, rhs: IsoTransform) -> Self {
289        self.mul(Self::from_iso_transform(rhs))
290    }
291}
292
293/// Identity transform
294impl Default for Conformal3 {
295    /// Identity transform
296    #[inline]
297    fn default() -> Self {
298        Self::IDENTITY
299    }
300}
301
302impl From<Conformal3> for Mat4 {
303    #[inline]
304    fn from(c: Conformal3) -> Self {
305        c.to_mat4()
306    }
307}
308
309impl From<Conformal3> for crate::Affine3A {
310    #[inline]
311    fn from(c: Conformal3) -> Self {
312        c.to_affine3a()
313    }
314}
315
316impl From<IsoTransform> for Conformal3 {
317    #[inline]
318    fn from(c: IsoTransform) -> Self {
319        Self::from_iso_transform(c)
320    }
321}
322
323#[cfg(feature = "std")]
324impl core::fmt::Debug for Conformal3 {
325    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
326        let (axis, angle) = self.rotation().to_axis_angle();
327        let translation = self.translation();
328        let scale = self.scale();
329        f.debug_struct("Conformal3")
330            .field(
331                "translation",
332                &format!("[{} {} {}]", translation[0], translation[1], translation[2]),
333            )
334            .field(
335                "rotation",
336                &format!(
337                    "{:.1}° around [{} {} {}]",
338                    angle.to_degrees(),
339                    axis[0],
340                    axis[1],
341                    axis[2],
342                ),
343            )
344            .field("scale", &format!("{}", scale))
345            .finish()
346    }
347}
348
349#[cfg(test)]
350mod test {
351    use super::*;
352
353    fn approx_eq_transform(a: Conformal3, b: Conformal3) -> bool {
354        let max_abs_diff = 1e-6;
355        a.translation().abs_diff_eq(b.translation(), max_abs_diff)
356            && a.rotation().abs_diff_eq(b.rotation(), max_abs_diff)
357            && ((a.scale() - b.scale()).abs() < max_abs_diff)
358    }
359
360    macro_rules! assert_approx_eq_transform {
361        ($a: expr, $b: expr) => {
362            assert!(approx_eq_transform($a, $b), "{:#?} != {:#?}", $a, $b,);
363        };
364    }
365
366    #[test]
367    fn test_inverse() {
368        use crate::Conformal3;
369
370        let transform = Conformal3::from_scale_rotation_translation(
371            2.0,
372            Quat::from_rotation_y(std::f32::consts::PI),
373            Vec3::ONE,
374        );
375        let identity = transform * transform.inverse();
376        assert_approx_eq_transform!(identity, Conformal3::IDENTITY);
377
378        let transform = Conformal3::from_scale_rotation_translation(
379            10.0,
380            Quat::from_axis_angle(Vec3::ONE.normalize(), 1.234),
381            Vec3::new(1.0, 2.0, 3.0),
382        );
383        let identity = transform * transform.inverse();
384        assert_approx_eq_transform!(identity, Conformal3::IDENTITY);
385    }
386}