parry2d/query/sat/
sat_cuboid_triangle.rs

1#[cfg(feature = "dim3")]
2use crate::approx::AbsDiffEq;
3use crate::math::{Isometry, Real, Vector};
4#[cfg(feature = "dim3")]
5use crate::query::sat;
6#[cfg(feature = "dim2")]
7use crate::query::sat::support_map_support_map_compute_separation;
8use crate::shape::{Cuboid, SupportMap, Triangle};
9
10/// Finds the best separating axis by testing all edge-edge combinations between a cuboid and a triangle (3D only).
11///
12/// In 3D collision detection, when a box and triangle intersect, the contact may occur along an
13/// axis perpendicular to an edge from each shape. This function tests all 3×3 = 9 such axes
14/// (cross products of cuboid edges with triangle edges) to find the one with maximum separation.
15///
16/// # Parameters
17///
18/// - `cube1`: The cuboid (in its local coordinate frame)
19/// - `triangle2`: The triangle
20/// - `pos12`: The position of the triangle relative to the cuboid
21///
22/// # Returns
23///
24/// A tuple containing:
25/// - `Real`: The maximum separation found across all edge-edge axes
26///   - **Positive**: Shapes are separated
27///   - **Negative**: Shapes are overlapping (penetration depth)
28/// - `Vector<Real>`: The axis direction that gives this separation
29///
30/// # The 9 Axes Tested
31///
32/// The function tests cross products between:
33/// - 3 cuboid edge directions: X, Y, Z (aligned with cuboid axes)
34/// - 3 triangle edge vectors: AB, BC, CA
35///
36/// This gives 3×3 = 9 potential separating axes. Each axis is normalized before testing,
37/// and degenerate axes (near-zero length, indicating parallel edges) are skipped.
38///
39/// # Example
40///
41/// ```rust
42/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
43/// use parry3d::shape::{Cuboid, Triangle};
44/// use parry3d::query::sat::cuboid_triangle_find_local_separating_edge_twoway;
45/// use nalgebra::{Point3, Vector3, Isometry3};
46///
47/// let cube = Cuboid::new(Vector3::new(1.0, 1.0, 1.0));
48/// let triangle = Triangle::new(
49///     Point3::origin(),
50///     Point3::new(1.0, 0.0, 0.0),
51///     Point3::new(0.0, 1.0, 0.0)
52/// );
53///
54/// // Position triangle near the cube
55/// let pos12 = Isometry3::translation(2.0, 0.0, 0.0);
56///
57/// let (separation, axis) = cuboid_triangle_find_local_separating_edge_twoway(
58///     &cube,
59///     &triangle,
60///     &pos12
61/// );
62///
63/// println!("Edge-edge separation: {} along {}", separation, axis);
64/// # }
65/// ```
66///
67/// # Usage in Complete SAT
68///
69/// For a complete cuboid-triangle SAT test, you must also test:
70/// 1. Cuboid face normals (X, Y, Z axes)
71/// 2. Triangle face normal (perpendicular to the triangle plane)
72/// 3. Edge-edge axes (this function)
73#[cfg(feature = "dim3")]
74#[inline(always)]
75pub fn cuboid_triangle_find_local_separating_edge_twoway(
76    cube1: &Cuboid,
77    triangle2: &Triangle,
78    pos12: &Isometry<Real>,
79) -> (Real, Vector<Real>) {
80    // NOTE: everything in this method will be expressed
81    // in the local-space of the first triangle. So we
82    // don't bother adding 2_1 suffixes (e.g. `a2_1`) to everything in
83    // order to keep the code more readable.
84    let a = pos12 * triangle2.a;
85    let b = pos12 * triangle2.b;
86    let c = pos12 * triangle2.c;
87
88    let ab = b - a;
89    let bc = c - b;
90    let ca = a - c;
91
92    // We have 3 * 3 = 9 axes to test.
93    let axes = [
94        // Vector::{x, y ,z}().cross(ab)
95        Vector::new(0.0, -ab.z, ab.y),
96        Vector::new(ab.z, 0.0, -ab.x),
97        Vector::new(-ab.y, ab.x, 0.0),
98        // Vector::{x, y ,z}().cross(bc)
99        Vector::new(0.0, -bc.z, bc.y),
100        Vector::new(bc.z, 0.0, -bc.x),
101        Vector::new(-bc.y, bc.x, 0.0),
102        // Vector::{x, y ,z}().cross(ca)
103        Vector::new(0.0, -ca.z, ca.y),
104        Vector::new(ca.z, 0.0, -ca.x),
105        Vector::new(-ca.y, ca.x, 0.0),
106    ];
107
108    let tri_dots = [
109        (axes[0].dot(&a.coords), axes[0].dot(&c.coords)),
110        (axes[1].dot(&a.coords), axes[1].dot(&c.coords)),
111        (axes[2].dot(&a.coords), axes[2].dot(&c.coords)),
112        (axes[3].dot(&a.coords), axes[3].dot(&c.coords)),
113        (axes[4].dot(&a.coords), axes[4].dot(&c.coords)),
114        (axes[5].dot(&a.coords), axes[5].dot(&c.coords)),
115        (axes[6].dot(&a.coords), axes[6].dot(&b.coords)),
116        (axes[7].dot(&a.coords), axes[7].dot(&b.coords)),
117        (axes[8].dot(&a.coords), axes[8].dot(&b.coords)),
118    ];
119
120    let mut best_sep = -Real::MAX;
121    let mut best_axis = axes[0];
122
123    for (i, axis) in axes.iter().enumerate() {
124        let axis_norm_squared = axis.norm_squared();
125
126        if axis_norm_squared > Real::default_epsilon() {
127            let axis_norm = na::ComplexField::sqrt(axis_norm_squared);
128
129            // NOTE: for both axis and -axis, the dot1 will have the same
130            // value because of the cuboid's symmetry.
131            let local_pt1 = cube1.local_support_point(axis);
132            let dot1 = local_pt1.coords.dot(axis) / axis_norm;
133
134            let (dot2_min, dot2_max) = crate::utils::sort2(tri_dots[i].0, tri_dots[i].1);
135
136            let separation_a = dot2_min / axis_norm - dot1; // separation on axis
137            let separation_b = -dot2_max / axis_norm - dot1; // separation on -axis
138
139            if separation_a > best_sep {
140                best_sep = separation_a;
141                best_axis = *axis / axis_norm;
142            }
143
144            if separation_b > best_sep {
145                best_sep = separation_b;
146                best_axis = -*axis / axis_norm;
147            }
148        }
149    }
150
151    (best_sep, best_axis)
152}
153
154/// Finds the best separating axis by testing the edge normals of a triangle against a support map shape (2D only).
155///
156/// In 2D, a triangle has three edges, each with an associated outward-pointing normal.
157/// This function tests all three edge normals to find which gives the maximum separation
158/// from the support map shape.
159///
160/// # Parameters
161///
162/// - `triangle1`: The triangle whose edge normals will be tested
163/// - `shape2`: Any convex shape implementing [`SupportMap`]
164/// - `pos12`: The position of `shape2` relative to `triangle1`
165///
166/// # Returns
167///
168/// A tuple containing:
169/// - `Real`: The maximum separation found among the triangle's edge normals
170///   - **Positive**: Shapes are separated
171///   - **Negative**: Shapes are overlapping
172/// - `Vector<Real>`: The edge normal direction that gives this separation
173///
174/// # 2D vs 3D
175///
176/// In 2D, triangles are true polygons with edges that have normals. In 3D, triangles are
177/// planar surfaces with a face normal (see the 3D version of this function).
178///
179/// # Example
180///
181/// ```rust
182/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
183/// use parry2d::shape::{Triangle, Ball};
184/// use parry2d::query::sat::triangle_support_map_find_local_separating_normal_oneway;
185/// use nalgebra::{Point2, Isometry2};
186///
187/// let triangle = Triangle::new(
188///     Point2::origin(),
189///     Point2::new(2.0, 0.0),
190///     Point2::new(1.0, 2.0)
191/// );
192/// let sphere = Ball::new(0.5);
193///
194/// let pos12 = Isometry2::translation(3.0, 1.0);
195///
196/// let (separation, normal) = triangle_support_map_find_local_separating_normal_oneway(
197///     &triangle,
198///     &sphere,
199///     &pos12
200/// );
201///
202/// if separation > 0.0 {
203///     println!("Separated by {} along edge normal {}", separation, normal);
204/// }
205/// # }
206/// ```
207#[cfg(feature = "dim2")]
208pub fn triangle_support_map_find_local_separating_normal_oneway(
209    triangle1: &Triangle,
210    shape2: &impl SupportMap,
211    pos12: &Isometry<Real>,
212) -> (Real, Vector<Real>) {
213    let mut best_sep = -Real::MAX;
214    let mut best_normal = Vector::zeros();
215
216    for edge in &triangle1.edges() {
217        if let Some(normal) = edge.normal() {
218            let sep = support_map_support_map_compute_separation(triangle1, shape2, pos12, &normal);
219
220            if sep > best_sep {
221                best_sep = sep;
222                best_normal = *normal;
223            }
224        }
225    }
226
227    (best_sep, best_normal)
228}
229
230/// Finds the best separating axis by testing a triangle's normals against a cuboid (2D only).
231///
232/// This is a specialized version of [`triangle_support_map_find_local_separating_normal_oneway`]
233/// for the specific case of a triangle and cuboid. In 2D, it tests the three edge normals of
234/// the triangle.
235///
236/// # Parameters
237///
238/// - `triangle1`: The triangle whose edge normals will be tested
239/// - `shape2`: The cuboid
240/// - `pos12`: The position of the cuboid relative to the triangle
241///
242/// # Returns
243///
244/// A tuple containing the maximum separation and the corresponding edge normal direction.
245///
246/// See [`triangle_support_map_find_local_separating_normal_oneway`] for more details and examples.
247#[cfg(feature = "dim2")]
248pub fn triangle_cuboid_find_local_separating_normal_oneway(
249    triangle1: &Triangle,
250    shape2: &Cuboid,
251    pos12: &Isometry<Real>,
252) -> (Real, Vector<Real>) {
253    triangle_support_map_find_local_separating_normal_oneway(triangle1, shape2, pos12)
254}
255
256/// Finds the best separating axis by testing a triangle's face normal against a cuboid (3D only).
257///
258/// In 3D, a triangle is a planar surface with a single face normal (perpendicular to the plane).
259/// This function tests both directions of this normal (+normal and -normal) to find the maximum
260/// separation from the cuboid.
261///
262/// # How It Works
263///
264/// The function uses the triangle's face normal and one of its vertices (point A) to represent
265/// the triangle as a point-with-normal. It then delegates to
266/// [`point_cuboid_find_local_separating_normal_oneway`](super::point_cuboid_find_local_separating_normal_oneway)
267/// which efficiently handles this case.
268///
269/// # Parameters
270///
271/// - `triangle1`: The triangle whose face normal will be tested
272/// - `shape2`: The cuboid
273/// - `pos12`: The position of the cuboid relative to the triangle
274///
275/// # Returns
276///
277/// A tuple containing:
278/// - `Real`: The separation distance along the triangle's face normal
279///   - **Positive**: Shapes are separated
280///   - **Negative**: Shapes are overlapping
281/// - `Vector<Real>`: The face normal direction (or its negation) that gives this separation
282///
283/// # Example
284///
285/// ```rust
286/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
287/// use parry3d::shape::{Triangle, Cuboid};
288/// use parry3d::query::sat::triangle_cuboid_find_local_separating_normal_oneway;
289/// use nalgebra::{Point3, Vector3, Isometry3};
290///
291/// // Horizontal triangle in the XY plane
292/// let triangle = Triangle::new(
293///     Point3::origin(),
294///     Point3::new(2.0, 0.0, 0.0),
295///     Point3::new(1.0, 2.0, 0.0)
296/// );
297/// let cube = Cuboid::new(Vector3::new(1.0, 1.0, 1.0));
298///
299/// // Position cube above the triangle
300/// let pos12 = Isometry3::translation(1.0, 1.0, 2.0);
301///
302/// let (separation, normal) = triangle_cuboid_find_local_separating_normal_oneway(
303///     &triangle,
304///     &cube,
305///     &pos12
306/// );
307///
308/// println!("Separation along triangle normal: {}", separation);
309/// # }
310/// ```
311///
312/// # 2D vs 3D
313///
314/// - **2D version**: Tests three edge normals (one per triangle edge)
315/// - **3D version** (this function): Tests one face normal (perpendicular to triangle plane)
316#[cfg(feature = "dim3")]
317#[inline(always)]
318pub fn triangle_cuboid_find_local_separating_normal_oneway(
319    triangle1: &Triangle,
320    shape2: &Cuboid,
321    pos12: &Isometry<Real>,
322) -> (Real, Vector<Real>) {
323    sat::point_cuboid_find_local_separating_normal_oneway(
324        triangle1.a,
325        triangle1.normal(),
326        shape2,
327        pos12,
328    )
329}