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}