parry3d/query/sat/sat_triangle_segment.rs
1use crate::math::{Isometry, Real, Vector};
2use crate::query::sat;
3use crate::shape::{Segment, SupportMap, Triangle};
4use na::Unit;
5
6/// Finds the best separating axis by testing a triangle's face normal against a segment (3D only).
7///
8/// In 3D, a triangle has a face normal (perpendicular to its plane). This function tests both
9/// directions of this normal (+normal and -normal) to find the maximum separation from the segment.
10///
11/// # How It Works
12///
13/// The function computes support points on the segment in both the positive and negative normal
14/// directions, then measures which direction gives greater separation from the triangle's surface.
15///
16/// # Parameters
17///
18/// - `triangle1`: The triangle whose face normal will be tested
19/// - `segment2`: The line segment
20/// - `pos12`: The position of the segment relative to the triangle
21///
22/// # Returns
23///
24/// A tuple containing:
25/// - `Real`: The separation distance along the triangle's face normal
26/// - **Positive**: Shapes are separated
27/// - **Negative**: Shapes are overlapping
28/// - **Very negative** if the triangle has no normal (degenerate triangle)
29/// - `Vector<Real>`: The face normal direction (or its negation) that gives this separation
30///
31/// # Degenerate Triangles
32///
33/// If the triangle is degenerate (all three points are collinear), it has no valid normal.
34/// In this case, the function returns `-Real::MAX` for separation and a zero vector for the normal.
35///
36/// # Example
37///
38/// ```rust
39/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
40/// use parry3d::shape::{Triangle, Segment};
41/// use parry3d::query::sat::triangle_segment_find_local_separating_normal_oneway;
42/// use nalgebra::{Point3, Isometry3};
43///
44/// // Triangle in the XY plane
45/// let triangle = Triangle::new(
46/// Point3::origin(),
47/// Point3::new(2.0, 0.0, 0.0),
48/// Point3::new(1.0, 2.0, 0.0)
49/// );
50///
51/// // Vertical segment above the triangle
52/// let segment = Segment::new(
53/// Point3::new(1.0, 1.0, 1.0),
54/// Point3::new(1.0, 1.0, 3.0)
55/// );
56///
57/// let pos12 = Isometry3::identity();
58///
59/// let (separation, normal) = triangle_segment_find_local_separating_normal_oneway(
60/// &triangle,
61/// &segment,
62/// &pos12
63/// );
64///
65/// if separation > 0.0 {
66/// println!("Separated by {} along triangle normal", separation);
67/// }
68/// # }
69/// ```
70///
71/// # Usage in Complete SAT
72///
73/// For a complete triangle-segment collision test, you must also test:
74/// 1. Triangle face normal (this function)
75/// 2. Segment normal (in 2D) or edge-edge axes (in 3D)
76/// 3. Edge-edge cross products (see [`segment_triangle_find_local_separating_edge_twoway`])
77pub fn triangle_segment_find_local_separating_normal_oneway(
78 triangle1: &Triangle,
79 segment2: &Segment,
80 pos12: &Isometry<Real>,
81) -> (Real, Vector<Real>) {
82 if let Some(dir) = triangle1.normal() {
83 let p2a = segment2.support_point_toward(pos12, &-dir);
84 let p2b = segment2.support_point_toward(pos12, &dir);
85 let sep_a = (p2a - triangle1.a).dot(&dir);
86 let sep_b = -(p2b - triangle1.a).dot(&dir);
87
88 if sep_a >= sep_b {
89 (sep_a, *dir)
90 } else {
91 (sep_b, -*dir)
92 }
93 } else {
94 (-Real::MAX, Vector::zeros())
95 }
96}
97
98/// Finds the best separating axis by testing edge-edge combinations between a segment and a triangle (3D only).
99///
100/// In 3D, when a line segment and triangle collide, the contact might occur along an axis
101/// perpendicular to both the segment and one of the triangle's edges. This function tests all
102/// such axes (cross products) to find the one with maximum separation.
103///
104/// # Parameters
105///
106/// - `segment1`: The line segment
107/// - `triangle2`: The triangle
108/// - `pos12`: The position of the triangle relative to the segment
109///
110/// # Returns
111///
112/// A tuple containing:
113/// - `Real`: The maximum separation found across all edge-edge axes
114/// - **Positive**: Shapes are separated
115/// - **Negative**: Shapes are overlapping
116/// - `Vector<Real>`: The axis direction that gives this separation
117///
118/// # The Axes Tested
119///
120/// The function computes cross products between:
121/// - The segment's direction (B - A)
122/// - Each of the 3 triangle edges (AB, BC, CA)
123///
124/// This gives 3 base axes. The function tests both each axis and its negation (6 total),
125/// finding which gives the maximum separation.
126///
127/// # Example
128///
129/// ```rust
130/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
131/// use parry3d::shape::{Segment, Triangle};
132/// use parry3d::query::sat::segment_triangle_find_local_separating_edge_twoway;
133/// use nalgebra::{Point3, Isometry3};
134///
135/// let segment = Segment::new(
136/// Point3::origin(),
137/// Point3::new(0.0, 0.0, 2.0)
138/// );
139///
140/// let triangle = Triangle::new(
141/// Point3::new(1.0, 0.0, 1.0),
142/// Point3::new(3.0, 0.0, 1.0),
143/// Point3::new(2.0, 2.0, 1.0)
144/// );
145///
146/// let pos12 = Isometry3::identity();
147///
148/// let (separation, axis) = segment_triangle_find_local_separating_edge_twoway(
149/// &segment,
150/// &triangle,
151/// &pos12
152/// );
153///
154/// if separation > 0.0 {
155/// println!("Separated by {} along edge-edge axis", separation);
156/// }
157/// # }
158/// ```
159///
160/// # Implementation Details
161///
162/// - Axes with near-zero length (parallel edges) are skipped
163/// - The function uses [`support_map_support_map_compute_separation`](super::support_map_support_map_compute_separation)
164/// to compute the actual separation along each axis
165/// - Both positive and negative directions are tested for each cross product
166///
167/// # Usage in Complete SAT
168///
169/// For a complete segment-triangle collision test, you must also test:
170/// 1. Triangle face normal ([`triangle_segment_find_local_separating_normal_oneway`])
171/// 2. Segment-specific axes (depends on whether the segment has associated normals)
172/// 3. Edge-edge cross products (this function)
173pub fn segment_triangle_find_local_separating_edge_twoway(
174 segment1: &Segment,
175 triangle2: &Triangle,
176 pos12: &Isometry<Real>,
177) -> (Real, Vector<Real>) {
178 let x2 = pos12 * (triangle2.b - triangle2.a);
179 let y2 = pos12 * (triangle2.c - triangle2.b);
180 let z2 = pos12 * (triangle2.a - triangle2.c);
181 let dir1 = segment1.scaled_direction();
182
183 let crosses1 = [dir1.cross(&x2), dir1.cross(&y2), dir1.cross(&z2)];
184 let axes1 = [
185 crosses1[0],
186 crosses1[1],
187 crosses1[2],
188 -crosses1[0],
189 -crosses1[1],
190 -crosses1[2],
191 ];
192 let mut max_separation = -Real::MAX;
193 let mut sep_dir = axes1[0];
194
195 for axis1 in &axes1 {
196 if let Some(axis1) = Unit::try_new(*axis1, 0.0) {
197 let sep =
198 sat::support_map_support_map_compute_separation(segment1, triangle2, pos12, &axis1);
199
200 if sep > max_separation {
201 max_separation = sep;
202 sep_dir = *axis1;
203 }
204 }
205 }
206
207 (max_separation, sep_dir)
208}