fenris_geometry/primitives/
triangle.rs1use crate::{
2 AxisAlignedBoundingBox, BoundedGeometry, ConvexPolygon3d, Distance, LineSegment, LineSegment2d, Orientation,
3 OrientationTestResult, SignedDistance, SignedDistanceResult,
4};
5use fenris_traits::Real;
6use nalgebra::allocator::Allocator;
7use nalgebra::Matrix3;
8use nalgebra::{DefaultAllocator, DimName, OPoint, OVector, Point2, Point3, Scalar, Vector3, U2, U3};
9use numeric_literals::replace_float_literals;
10use serde::{Deserialize, Serialize};
11use std::fmt::Debug;
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(bound(
15 serialize = "OPoint<T, D>: Serialize",
16 deserialize = "OPoint<T, D>: Deserialize<'de>"
17))]
18pub struct Triangle<T, D>(pub [OPoint<T, D>; 3])
19where
20 T: Scalar,
21 D: DimName,
22 DefaultAllocator: Allocator<T, D>;
23
24pub type Triangle2d<T> = Triangle<T, U2>;
30pub type Triangle3d<T> = Triangle<T, U3>;
31
32impl<T, D> Copy for Triangle<T, D>
33where
34 T: Scalar,
35 D: DimName,
36 DefaultAllocator: Allocator<T, D>,
37 OPoint<T, D>: Copy,
38{
39}
40
41impl<T, D> Triangle<T, D>
42where
43 T: Scalar,
44 D: DimName,
45 DefaultAllocator: Allocator<T, D>,
46{
47 pub fn swap_vertices(&mut self, i: usize, j: usize) {
48 self.0.swap(i, j);
49 }
50
51 pub fn edge(&self, index: usize) -> LineSegment<T, D> {
52 assert!(index < 3);
53 let a = self.0[(index + 0) % 3].clone();
54 let b = self.0[(index + 1) % 3].clone();
55 LineSegment::from_end_points(a, b)
56 }
57}
58
59impl<T, D> Triangle<T, D>
60where
61 T: Real,
62 D: DimName,
63 DefaultAllocator: Allocator<T, D>,
64{
65 pub fn centroid(&self) -> OPoint<T, D> {
66 let mut centroid = OVector::zeros();
67 for p in &self.0 {
68 centroid += &p.coords * T::from_f64(1.0 / 3.0).unwrap();
69 }
70 OPoint::from(centroid)
71 }
72
73 pub fn sides(&self) -> [OVector<T, D>; 3] {
75 let a = &self.0[0];
76 let b = &self.0[1];
77 let c = &self.0[2];
78 [b - a, c - b, a - c]
79 }
80}
81
82impl<T> Triangle2d<T>
83where
84 T: Real,
85{
86 pub fn orientation(&self) -> Orientation {
87 if self.signed_area() >= T::zero() {
88 Orientation::Counterclockwise
89 } else {
90 Orientation::Clockwise
91 }
92 }
93
94 #[replace_float_literals(T::from_f64(literal).expect("Literal must fit in T."))]
95 pub fn signed_area(&self) -> T {
96 let a = &self.0[0];
97 let b = &self.0[1];
98 let c = &self.0[2];
99 let ab = b - a;
100 let ac = c - a;
101 0.5 * ab.perp(&ac)
102 }
103
104 pub fn area(&self) -> T {
105 self.signed_area().abs()
106 }
107}
108
109impl<T> Triangle3d<T>
110where
111 T: Real,
112{
113 pub fn normal_dir(&self) -> Vector3<T> {
115 let a = &self.0[0];
116 let b = &self.0[1];
117 let c = &self.0[2];
118 let ab = b - a;
119 let ac = c - a;
120 let n = ab.cross(&ac);
121 n
122 }
123
124 pub fn normal(&self) -> Vector3<T> {
125 self.normal_dir().normalize()
126 }
127
128 pub fn orientation(&self) -> Orientation {
130 if self.signed_area() >= T::zero() {
131 Orientation::Clockwise
132 } else {
133 Orientation::Counterclockwise
134 }
135 }
136
137 #[replace_float_literals(T::from_f64(literal).expect("Literal must fit in T."))]
140 pub fn signed_area(&self) -> T {
141 let a = &self.0[0];
142 let b = &self.0[1];
143 let c = &self.0[2];
144 let ab = b - a;
145 let ac = c - a;
146 0.5 * ab.cross(&ac).norm()
147 }
148
149 pub fn area(&self) -> T {
150 self.signed_area().abs()
151 }
152
153 pub fn point_orientation(&self, point: &Point3<T>) -> OrientationTestResult {
162 let point_in_plane = &self.0[0];
164
165 let x = point;
166 let x0 = point_in_plane;
167 let n = self.normal_dir().normalize();
168 let projected_dist = n.dot(&(x - x0));
169
170 if projected_dist > T::zero() {
171 OrientationTestResult::Positive
172 } else if projected_dist < T::zero() {
173 OrientationTestResult::Negative
174 } else {
175 OrientationTestResult::Zero
176 }
177 }
178}
179
180impl<T, D> BoundedGeometry<T> for Triangle<T, D>
181where
182 T: Real,
183 D: DimName,
184 DefaultAllocator: Allocator<T, D>,
185{
186 type Dimension = D;
187
188 fn bounding_box(&self) -> AxisAlignedBoundingBox<T, D> {
189 AxisAlignedBoundingBox::from_points(&self.0).unwrap()
190 }
191}
192
193impl<T> Distance<T, Point2<T>> for Triangle2d<T>
194where
195 T: Real,
196{
197 fn distance(&self, point: &Point2<T>) -> T {
198 let sdf = self.query_signed_distance(point).unwrap();
199 T::max(T::zero(), sdf.signed_distance)
200 }
201}
202
203impl<T> SignedDistance<T, U2> for Triangle2d<T>
204where
205 T: Real,
206{
207 fn query_signed_distance(&self, point: &Point2<T>) -> Option<SignedDistanceResult<T, U2>> {
208 let mut inside = true;
210
211 let mut closest_segment = 0;
212 let mut closest_dist2 = T::max_value().unwrap();
214 let mut closest_point = Point2::origin();
215
216 for i in 0..3 {
218 let a = &self.0[i];
219 let b = &self.0[(i + 1) % 3];
220 let segment = LineSegment2d::from_end_points(*a, *b);
221 let normal_dir = segment.normal_dir();
223 let projected_point = segment.closest_point(point);
224 let d = &point.coords - &projected_point.coords;
225
226 if d.dot(&normal_dir) > T::zero() {
227 inside = false;
228 }
229
230 let dist2 = d.magnitude_squared();
231 if dist2 < closest_dist2 {
232 closest_segment = i;
233 closest_dist2 = dist2;
234 closest_point = projected_point;
235 }
236 }
237
238 let sign = if inside { T::from_f64(-1.0).unwrap() } else { T::one() };
239
240 Some(SignedDistanceResult {
241 feature_id: closest_segment,
242 point: closest_point,
243 signed_distance: sign * closest_dist2.sqrt(),
244 })
245 }
246}
247
248impl<T> Distance<T, Point3<T>> for Triangle3d<T>
249where
250 T: Real,
251{
252 #[replace_float_literals(T::from_f64(literal).unwrap())]
253 fn distance(&self, point: &OPoint<T, U3>) -> T {
254 self.closest_point(point).distance
255 }
256}
257
258impl<'a, T: Real> ConvexPolygon3d<'a, T> for Triangle3d<T> {
259 fn num_vertices(&self) -> usize {
260 3
261 }
262
263 fn get_vertex(&self, index: usize) -> Option<OPoint<T, U3>> {
264 self.0.get(index).copied()
265 }
266}
267
268impl<T: Real> Triangle3d<T> {
269 #[replace_float_literals(T::from_f64(literal).unwrap())]
271 pub fn compute_solid_angle(&self, p: &Point3<T>) -> T {
272 let [a, b, c] = self.0.clone().map(|v_i| v_i - p);
275 let abc_matrix = Matrix3::from_columns(&[a.clone(), b.clone(), c.clone()]);
276
277 let anorm = a.norm();
278 let bnorm = b.norm();
279 let cnorm = c.norm();
280
281 let denominator = anorm * bnorm * cnorm + a.dot(&b) * cnorm + b.dot(&c) * anorm + c.dot(&a) * bnorm;
282 let tan_omega_half = abc_matrix.determinant() / denominator;
283 2.0 * tan_omega_half.atan()
284 }
285}
286
287#[replace_float_literals(T::from_f64(literal).unwrap())]
288pub fn compute_winding_number_for_triangles_3d<T, I>(triangles: I, point: &Point3<T>) -> T
289where
290 T: Real,
291 I: IntoIterator<Item = Triangle3d<T>>,
292{
293 let angle_sum = triangles
294 .into_iter()
295 .map(|triangle| triangle.compute_solid_angle(point))
296 .reduce(|acc, angle| acc + angle)
297 .unwrap_or(T::zero());
298 angle_sum / (4.0 * T::pi())
299}