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