Skip to main content

jagua_rs/geometry/
d_transformation.rs

1use std::borrow::Borrow;
2use std::f32::consts::PI;
3use std::fmt::Display;
4
5use crate::geometry::Transformation;
6use ordered_float::NotNan;
7
8#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy, Default)]
9/// A rigid transformation: optional reflection across the x-axis, then rotation, then translation.
10/// The reflection axis is in the transformation's input coordinate system.
11pub struct DTransformation {
12    /// Reflect across the x-axis before rotating (negate the local y coordinate).
13    pub reflected: bool,
14    /// The rotation in radians
15    pub rotation: NotNan<f32>,
16    /// The translation in the x and y-axis
17    pub translation: (NotNan<f32>, NotNan<f32>),
18}
19
20impl DTransformation {
21    #[must_use]
22    pub fn new(rotation: f32, translation: (f32, f32)) -> Self {
23        Self {
24            reflected: false,
25            rotation: NotNan::new(rotation).expect("rotation is NaN"),
26            translation: (
27                NotNan::new(translation.0).expect("translation.0 is NaN"),
28                NotNan::new(translation.1).expect("translation.1 is NaN"),
29            ),
30        }
31    }
32
33    #[must_use]
34    pub const fn empty() -> Self {
35        const _0: NotNan<f32> = unsafe { NotNan::new_unchecked(0.0) };
36        Self {
37            reflected: false,
38            rotation: _0,
39            translation: (_0, _0),
40        }
41    }
42
43    /// Sets the canonical x-axis reflection applied before rotation and translation.
44    #[must_use]
45    pub fn with_reflection(mut self, reflected: bool) -> Self {
46        self.reflected = reflected;
47        self
48    }
49
50    #[must_use]
51    pub fn rotation(&self) -> f32 {
52        self.rotation.into()
53    }
54
55    #[must_use]
56    pub fn translation(&self) -> (f32, f32) {
57        (self.translation.0.into(), self.translation.1.into())
58    }
59
60    #[must_use]
61    pub fn compose(&self) -> Transformation {
62        self.into()
63    }
64}
65
66impl<T> From<T> for DTransformation
67where
68    T: Borrow<Transformation>,
69{
70    fn from(t: T) -> Self {
71        t.borrow().decompose()
72    }
73}
74
75impl Display for DTransformation {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        write!(
78            f,
79            "reflected: {}, r: {:.3}°, t: ({:.3}, {:.3})",
80            self.reflected,
81            self.rotation.to_degrees(),
82            self.translation.0.into_inner(),
83            self.translation.1.into_inner()
84        )
85    }
86}
87
88/// Normalizes a rotation angle to the range [0, 2π).
89#[must_use]
90pub fn normalize_rotation(r: f32) -> f32 {
91    // rem_euclid can round a tiny negative remainder up to the modulus.
92    r.rem_euclid(2.0 * PI) % (2.0 * PI)
93}