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