1use crate::math::Vector;
2
3#[cfg(feature = "alloc")]
4use crate::{math::Vector3, query::details::NormalConstraints};
5
6#[derive(Clone, Debug)]
19pub struct TrianglePseudoNormals {
20 pub face: Vector,
22 pub edges: [Vector; 3],
28 pub two_sided: bool,
31}
32
33#[cfg(feature = "alloc")]
34impl NormalConstraints for TrianglePseudoNormals {
35 fn project_local_normal_mut(&self, dir: &mut Vector) -> bool {
38 if self.two_sided && dir.dot(self.face) < 0.0 {
39 let mut mirrored = -*dir;
41 let _ = self.project_front(&mut mirrored);
42 *dir = -mirrored;
43 return true;
44 }
45
46 self.project_front(dir)
47 }
48}
49
50#[cfg(feature = "alloc")]
51impl TrianglePseudoNormals {
52 fn project_front(&self, dir: &mut Vector) -> bool {
53 let dots = Vector3::new(
55 dir.dot(self.edges[0]),
56 dir.dot(self.edges[1]),
57 dir.dot(self.edges[2]),
58 );
59 let closest_edge = self.edges[dots.max_position()];
60 crate::shape::pseudo_normals::project_into_cone(self.face, closest_edge, dir)
61 }
62}
63
64#[cfg(test)]
65#[cfg(all(feature = "dim3", feature = "alloc"))]
66mod test {
67 use super::NormalConstraints;
68 use crate::math::{Real, Vector};
69 use crate::shape::TrianglePseudoNormals;
70
71 fn bisector(v1: Vector, v2: Vector) -> Vector {
72 (v1 + v2).normalize()
73 }
74
75 fn bisector_y(v: Vector) -> Vector {
76 bisector(v, Vector::Y)
77 }
78
79 #[test]
80 fn trivial_pseudo_normals_projection() {
81 let pn = TrianglePseudoNormals {
82 face: Vector::Y,
83 edges: [Vector::Y; 3],
84 two_sided: false,
85 };
86
87 assert_eq!(
88 pn.project_local_normal(Vector::new(1.0, 1.0, 1.0)),
89 Some(Vector::Y)
90 );
91 assert!(pn.project_local_normal(-Vector::Y).is_none());
92 }
93
94 #[test]
95 fn edge_pseudo_normals_projection_strictly_positive() {
96 let bisector = |v1: Vector, v2: Vector| (v1 + v2).normalize();
97 let bisector_y = |v: Vector| bisector(v, Vector::Y);
98
99 let cones_ref_dir = [
101 -Vector::Z,
102 -Vector::X,
103 Vector::new(1.0, 0.0, 1.0).normalize(),
104 ];
105 let cones_ends = cones_ref_dir.map(bisector_y);
106 let cones_axes = cones_ends.map(bisector_y);
107
108 let pn = TrianglePseudoNormals {
109 face: Vector::Y,
110 edges: cones_axes.map(|v| v.normalize()),
111 two_sided: false,
112 };
113
114 for i in 0..3 {
115 assert!(pn
116 .project_local_normal(cones_ends[i])
117 .unwrap()
118 .abs_diff_eq(cones_ends[i], 1.0e-5));
119 assert_eq!(pn.project_local_normal(cones_axes[i]), Some(cones_axes[i]));
120
121 let subdivs = 100;
123
124 for k in 1..100 {
125 let v = Vector::Y
126 .lerp(cones_ends[i], k as Real / (subdivs as Real))
127 .normalize();
128 assert_eq!(pn.project_local_normal(v).unwrap(), v);
129 }
130
131 for k in 1..subdivs {
133 let v = cones_ref_dir[i]
134 .lerp(cones_ends[i], k as Real / (subdivs as Real))
135 .normalize();
136 assert!(pn
137 .project_local_normal(v)
138 .unwrap()
139 .abs_diff_eq(cones_ends[i], 1.0e-5));
140 }
141
142 for k in 1..subdivs {
144 let v = cones_ref_dir[i]
145 .lerp(-Vector::Y, k as Real / (subdivs as Real))
146 .normalize();
147 assert!(pn.project_local_normal(v).is_none(),);
148 }
149 }
150 }
151
152 #[test]
153 fn edge_pseudo_normals_projection_negative() {
154 let cones_ref_dir = [
156 -Vector::Z,
157 -Vector::X,
158 Vector::new(1.0, 0.0, 1.0).normalize(),
159 ];
160 let cones_ends = cones_ref_dir.map(|v| bisector(v, -Vector::Y));
161 let cones_axes = [
162 bisector(bisector_y(cones_ref_dir[0]), cones_ref_dir[0]),
163 bisector(bisector_y(cones_ref_dir[1]), cones_ref_dir[1]),
164 bisector(bisector_y(cones_ref_dir[2]), cones_ref_dir[2]),
165 ];
166
167 let pn = TrianglePseudoNormals {
168 face: Vector::Y,
169 edges: cones_axes.map(|v| v.normalize()),
170 two_sided: false,
171 };
172
173 for i in 0..3 {
174 assert_eq!(pn.project_local_normal(cones_axes[i]), Some(cones_axes[i]));
175
176 let subdivs = 100;
178
179 for k in 1..subdivs {
180 let v = Vector::Y
181 .lerp(cones_ends[i], k as Real / (subdivs as Real))
182 .normalize();
183 assert_eq!(pn.project_local_normal(v).unwrap(), v);
184 }
185
186 for k in 1..subdivs {
189 let v = (-Vector::Y)
190 .lerp(cones_ends[i], k as Real / (subdivs as Real))
191 .normalize();
192 assert!(pn.project_local_normal(v).is_none());
193 }
194 }
195 }
196
197 #[test]
198 fn two_sided_pseudo_normals_mirror_the_cone() {
199 let one_sided = TrianglePseudoNormals {
200 face: Vector::Y,
201 edges: [Vector::Y; 3],
202 two_sided: false,
203 };
204 let two_sided = TrianglePseudoNormals {
205 two_sided: true,
206 ..one_sided.clone()
207 };
208
209 let front = Vector::new(1.0, 1.0, 1.0);
211 assert_eq!(two_sided.project_local_normal(front), Some(Vector::Y));
212
213 let back = Vector::new(1.0, -1.0, 1.0);
215 assert!(one_sided.project_local_normal(back).is_none());
216 assert_eq!(two_sided.project_local_normal(back), Some(-Vector::Y));
217
218 let cone_axes = [
220 bisector_y(-Vector::Z),
221 bisector_y(-Vector::X),
222 bisector_y(Vector::new(1.0, 0.0, 1.0).normalize()),
223 ];
224 let pn = TrianglePseudoNormals {
225 face: Vector::Y,
226 edges: cone_axes,
227 two_sided: true,
228 };
229 for dir in [
230 Vector::new(0.3, 1.0, -0.8).normalize(),
231 Vector::new(-1.0, 0.2, 0.1).normalize(),
232 Vector::new(0.9, 0.5, 0.9).normalize(),
233 ] {
234 let front = pn.project_local_normal(dir).unwrap();
235 let back = pn.project_local_normal(-dir).unwrap();
236 assert!(back.abs_diff_eq(-front, 1.0e-6));
237 }
238 }
239}