jagua_rs/geometry/
allowed_orientations.rs1use std::f32::consts::{PI, TAU};
2
3use anyhow::{Result, ensure};
4
5use crate::geometry::geo_enums::RotationRange;
6use crate::geometry::{DTransformation, normalize_rotation};
7
8#[derive(Clone, Debug, PartialEq)]
15pub struct AllowedOrientations {
16 rotations: RotationRange,
17 rotations_after_reflection: Option<RotationRange>,
18}
19
20impl AllowedOrientations {
21 pub const MAX_ANGLES: usize = 65_536;
24
25 pub fn new(rotations: RotationRange, mut reflection_axes: Vec<f32>) -> Result<Self> {
29 ensure!(
30 reflection_axes.len() <= Self::MAX_ANGLES,
31 "reflection axis list exceeds the count limit"
32 );
33 let rotations = match rotations {
34 RotationRange::Discrete(angles) => discrete_rotations(angles)?,
35 RotationRange::None => RotationRange::None,
36 RotationRange::Continuous => RotationRange::Continuous,
37 };
38 ensure!(
39 reflection_axes.iter().all(|a| a.is_finite()),
40 "reflection axes must be finite"
41 );
42 for axis in &mut reflection_axes {
43 *axis = axis.rem_euclid(PI) % PI;
44 }
45 reflection_axes.sort_by(f32::total_cmp);
46 reflection_axes.dedup();
47 let rotations_after_reflection = if reflection_axes.is_empty() {
48 None
49 } else {
50 Some(match &rotations {
51 RotationRange::Continuous => RotationRange::Continuous,
52 RotationRange::None => {
53 discrete_rotations(reflection_axes.iter().map(|a| 2.0 * a).collect())?
54 }
55 RotationRange::Discrete(angles) => {
56 ensure!(
57 angles.len() <= Self::MAX_ANGLES / reflection_axes.len(),
58 "reflected orientation expansion exceeds the count limit"
59 );
60 discrete_rotations(
61 reflection_axes
62 .iter()
63 .flat_map(|a| angles.iter().map(move |r| r + 2.0 * a))
64 .collect(),
65 )?
66 }
67 })
68 };
69 Ok(Self {
70 rotations,
71 rotations_after_reflection,
72 })
73 }
74
75 #[must_use]
78 pub fn rotations(&self) -> &RotationRange {
79 &self.rotations
80 }
81
82 #[must_use]
86 pub fn rotations_after_reflection(&self) -> Option<&RotationRange> {
87 self.rotations_after_reflection.as_ref()
88 }
89
90 #[must_use]
93 pub fn allows(&self, transformation: &DTransformation) -> bool {
94 let rotation = transformation.rotation();
95 if !rotation.is_finite() {
96 return false;
97 }
98 let rotation = normalize_rotation(rotation);
99 let matches = |r: f32| {
100 let distance = (rotation - r).abs();
101 distance.min(TAU - distance) <= 4.0 * f32::EPSILON * TAU
102 };
103 let rotations = if transformation.reflected {
104 self.rotations_after_reflection()
105 } else {
106 Some(self.rotations())
107 };
108 match rotations {
109 None => false,
110 Some(RotationRange::Continuous) => true,
111 Some(RotationRange::None) => matches(0.0),
112 Some(RotationRange::Discrete(angles)) => angles.iter().copied().any(matches),
113 }
114 }
115}
116
117fn discrete_rotations(mut angles: Vec<f32>) -> Result<RotationRange> {
118 ensure!(
119 angles.len() <= AllowedOrientations::MAX_ANGLES,
120 "rotation angle list exceeds the count limit"
121 );
122 ensure!(
123 angles.iter().all(|a| a.is_finite()),
124 "rotations must be finite"
125 );
126 for angle in &mut angles {
127 *angle = normalize_rotation(*angle);
128 }
129 angles.sort_by(f32::total_cmp);
130 angles.dedup();
131 Ok(if angles.is_empty() || angles == [0.0] {
132 RotationRange::None
133 } else {
134 RotationRange::Discrete(angles)
135 })
136}