parry2d/shape/
triangle_pseudo_normals.rs1use 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}
29
30#[cfg(feature = "alloc")]
31impl NormalConstraints for TrianglePseudoNormals {
32 fn project_local_normal_mut(&self, dir: &mut Vector) -> bool {
35 let dots = Vector3::new(
37 dir.dot(self.edges[0]),
38 dir.dot(self.edges[1]),
39 dir.dot(self.edges[2]),
40 );
41 let closest_edge = self.edges[dots.max_position()];
42 crate::shape::pseudo_normals::project_into_cone(self.face, closest_edge, dir)
43 }
44}
45
46#[cfg(test)]
47#[cfg(all(feature = "dim3", feature = "alloc"))]
48mod test {
49 use super::NormalConstraints;
50 use crate::math::{Real, Vector};
51 use crate::shape::TrianglePseudoNormals;
52
53 fn bisector(v1: Vector, v2: Vector) -> Vector {
54 (v1 + v2).normalize()
55 }
56
57 fn bisector_y(v: Vector) -> Vector {
58 bisector(v, Vector::Y)
59 }
60
61 #[test]
62 fn trivial_pseudo_normals_projection() {
63 let pn = TrianglePseudoNormals {
64 face: Vector::Y,
65 edges: [Vector::Y; 3],
66 };
67
68 assert_eq!(
69 pn.project_local_normal(Vector::new(1.0, 1.0, 1.0)),
70 Some(Vector::Y)
71 );
72 assert!(pn.project_local_normal(-Vector::Y).is_none());
73 }
74
75 #[test]
76 fn edge_pseudo_normals_projection_strictly_positive() {
77 let bisector = |v1: Vector, v2: Vector| (v1 + v2).normalize();
78 let bisector_y = |v: Vector| bisector(v, Vector::Y);
79
80 let cones_ref_dir = [
82 -Vector::Z,
83 -Vector::X,
84 Vector::new(1.0, 0.0, 1.0).normalize(),
85 ];
86 let cones_ends = cones_ref_dir.map(bisector_y);
87 let cones_axes = cones_ends.map(bisector_y);
88
89 let pn = TrianglePseudoNormals {
90 face: Vector::Y,
91 edges: cones_axes.map(|v| v.normalize()),
92 };
93
94 for i in 0..3 {
95 assert!(pn
96 .project_local_normal(cones_ends[i])
97 .unwrap()
98 .abs_diff_eq(cones_ends[i], 1.0e-5));
99 assert_eq!(pn.project_local_normal(cones_axes[i]), Some(cones_axes[i]));
100
101 let subdivs = 100;
103
104 for k in 1..100 {
105 let v = Vector::Y
106 .lerp(cones_ends[i], k as Real / (subdivs as Real))
107 .normalize();
108 assert_eq!(pn.project_local_normal(v).unwrap(), v);
109 }
110
111 for k in 1..subdivs {
113 let v = cones_ref_dir[i]
114 .lerp(cones_ends[i], k as Real / (subdivs as Real))
115 .normalize();
116 assert!(pn
117 .project_local_normal(v)
118 .unwrap()
119 .abs_diff_eq(cones_ends[i], 1.0e-5));
120 }
121
122 for k in 1..subdivs {
124 let v = cones_ref_dir[i]
125 .lerp(-Vector::Y, k as Real / (subdivs as Real))
126 .normalize();
127 assert!(pn.project_local_normal(v).is_none(),);
128 }
129 }
130 }
131
132 #[test]
133 fn edge_pseudo_normals_projection_negative() {
134 let cones_ref_dir = [
136 -Vector::Z,
137 -Vector::X,
138 Vector::new(1.0, 0.0, 1.0).normalize(),
139 ];
140 let cones_ends = cones_ref_dir.map(|v| bisector(v, -Vector::Y));
141 let cones_axes = [
142 bisector(bisector_y(cones_ref_dir[0]), cones_ref_dir[0]),
143 bisector(bisector_y(cones_ref_dir[1]), cones_ref_dir[1]),
144 bisector(bisector_y(cones_ref_dir[2]), cones_ref_dir[2]),
145 ];
146
147 let pn = TrianglePseudoNormals {
148 face: Vector::Y,
149 edges: cones_axes.map(|v| v.normalize()),
150 };
151
152 for i in 0..3 {
153 assert_eq!(pn.project_local_normal(cones_axes[i]), Some(cones_axes[i]));
154
155 let subdivs = 100;
157
158 for k in 1..subdivs {
159 let v = Vector::Y
160 .lerp(cones_ends[i], k as Real / (subdivs as Real))
161 .normalize();
162 assert_eq!(pn.project_local_normal(v).unwrap(), v);
163 }
164
165 for k in 1..subdivs {
168 let v = (-Vector::Y)
169 .lerp(cones_ends[i], k as Real / (subdivs as Real))
170 .normalize();
171 assert!(pn.project_local_normal(v).is_none());
172 }
173 }
174 }
175}