parry3d/query/sat/sat_cuboid_support_map.rs
1use crate::math::{Pose, Real, Vector, VectorExt, DIM};
2use crate::shape::{Cuboid, SupportMap};
3
4/// Computes the separation distance between a cuboid and a convex support map shape along a given axis.
5///
6/// This function tests both the positive and negative directions of the axis to find which
7/// orientation gives the actual separation between the shapes. This is necessary because we don't
8/// know in advance which direction will show the true separation.
9///
10/// # Parameters
11///
12/// - `cube1`: The cuboid
13/// - `shape2`: Any convex shape implementing [`SupportMap`] (sphere, capsule, convex mesh, etc.)
14/// - `pos12`: The position of `shape2` relative to `cube1`
15/// - `axis1`: The unit direction vector (in `cube1`'s local space) to test
16///
17/// # Returns
18///
19/// A tuple containing:
20/// - `Real`: The separation distance along the better of the two axis directions
21/// - **Positive**: Shapes are separated
22/// - **Negative**: Shapes are overlapping
23/// - `Vector`: The axis direction (either `axis1` or `-axis1`) that gives this separation
24///
25/// # Why Test Both Directions?
26///
27/// When testing separation along an axis, we need to check both `axis1` and `-axis1` because:
28/// - The shapes might be oriented such that one direction shows separation while the other shows overlap
29/// - We want to find the direction that gives the maximum (least negative) separation
30///
31/// For symmetric shapes like sphere vs sphere, both directions give the same result, but for
32/// asymmetric configurations, testing both is necessary for correctness.
33///
34/// # Example
35///
36/// ```rust
37/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
38/// use parry3d::shape::{Cuboid, Ball};
39/// use parry3d::query::sat::cuboid_support_map_compute_separation_wrt_local_line;
40/// use parry3d::math::{Pose, Vector};
41///
42/// let cube = Cuboid::new(Vector::splat(1.0));
43/// let sphere = Ball::new(0.5);
44///
45/// // Position sphere near the cube
46/// let pos12 = Pose::translation(2.0, 0.0, 0.0);
47///
48/// // Test separation along an arbitrary axis
49/// let axis = Vector::new(1.0, 1.0, 0.0).normalize();
50/// let (separation, chosen_axis) = cuboid_support_map_compute_separation_wrt_local_line(
51/// &cube,
52/// &sphere,
53/// &pos12,
54/// axis
55/// );
56///
57/// println!("Separation: {} along axis: {:?}", separation, chosen_axis);
58/// # }
59/// ```
60///
61/// # Performance Note
62///
63/// This is a relatively expensive operation as it computes support points in both directions.
64/// For specific shape pairs (like cuboid-cuboid), there are optimized versions that avoid this
65/// double computation.
66#[cfg(feature = "dim3")]
67pub fn cuboid_support_map_compute_separation_wrt_local_line(
68 cube1: &Cuboid,
69 shape2: &impl SupportMap,
70 pos12: &Pose,
71 axis1: Vector,
72) -> (Real, Vector) {
73 let axis1_2 = pos12.rotation.inverse() * axis1;
74 let separation1 = {
75 let axis2 = -axis1_2;
76 let local_pt1 = cube1.local_support_point_toward(axis1);
77 let local_pt2 = shape2.local_support_point_toward(axis2);
78 let pt2 = pos12 * local_pt2;
79 (pt2 - local_pt1).dot(axis1)
80 };
81
82 let separation2 = {
83 let axis2 = axis1_2;
84 let local_pt1 = cube1.local_support_point_toward(-axis1);
85 let local_pt2 = shape2.local_support_point_toward(axis2);
86 let pt2 = pos12 * local_pt2;
87 (pt2 - local_pt1).dot(-axis1)
88 };
89
90 if separation1 > separation2 {
91 (separation1, axis1)
92 } else {
93 (separation2, -axis1)
94 }
95}
96
97/// Finds the best separating axis by testing edge-edge combinations between a cuboid and a support map shape.
98///
99/// This function is used in 3D SAT implementations where edge-edge contact is possible. It tests
100/// a precomputed set of axes (typically cross products of edges from both shapes) to find the
101/// axis with maximum separation.
102///
103/// # Parameters
104///
105/// - `cube1`: The cuboid
106/// - `shape2`: Any convex shape implementing [`SupportMap`]
107/// - `axes`: A slice of axis directions to test (not necessarily unit length)
108/// - `pos12`: The position of `shape2` relative to `cube1`
109///
110/// # Returns
111///
112/// A tuple containing:
113/// - `Real`: The maximum separation found across all tested axes
114/// - **Positive**: Shapes are separated
115/// - **Negative**: Shapes are overlapping (minimum penetration)
116/// - `Vector`: The axis direction that gives this separation (normalized)
117///
118/// # Why Precomputed Axes?
119///
120/// The caller typically computes the candidate axes as cross products of edges:
121/// - Cuboid has 3 edge directions (X, Y, Z)
122/// - The other shape's edges depend on its geometry
123/// - Cross products of these edges give potential separating axes
124///
125/// This function handles the actual separation testing for those precomputed axes.
126///
127/// # Example
128///
129/// ```rust
130/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
131/// use parry3d::shape::{Cuboid, Capsule};
132/// use parry3d::query::sat::cuboid_support_map_find_local_separating_edge_twoway;
133/// use parry3d::math::{Pose, Vector};
134///
135/// let cube = Cuboid::new(Vector::splat(1.0));
136/// let capsule = Capsule::new(Vector::new(0.0, -1.0, 0.0), Vector::new(0.0, 1.0, 0.0), 0.5);
137///
138/// // Position capsule near the cube
139/// let pos12 = Pose::translation(2.0, 0.0, 0.0);
140///
141/// // Compute edge cross products
142/// let capsule_dir = Vector::Y; // capsule's axis direction
143/// let axes = [
144/// Vector::X.cross(capsule_dir), // cube X × capsule axis
145/// Vector::Y.cross(capsule_dir), // cube Y × capsule axis
146/// Vector::Z.cross(capsule_dir), // cube Z × capsule axis
147/// ];
148///
149/// let (separation, axis) = cuboid_support_map_find_local_separating_edge_twoway(
150/// &cube,
151/// &capsule,
152/// &axes,
153/// &pos12
154/// );
155///
156/// println!("Best edge-edge separation: {} along {:?}", separation, axis);
157/// # }
158/// ```
159///
160/// # Implementation Details
161///
162/// - Axes with near-zero length are skipped (they represent parallel or degenerate edges)
163/// - Each axis is normalized before computing separation
164/// - The function tests both positive and negative directions of each axis using
165/// `cuboid_support_map_compute_separation_wrt_local_line`
166#[cfg(feature = "dim3")]
167pub fn cuboid_support_map_find_local_separating_edge_twoway(
168 cube1: &Cuboid,
169 shape2: &impl SupportMap,
170 axes: &[Vector],
171 pos12: &Pose,
172) -> (Real, Vector) {
173 let mut best_separation = -Real::MAX;
174 let mut best_dir = Vector::ZERO;
175
176 for axis1 in axes {
177 if let Some(axis1) = (*axis1).try_normalize() {
178 let (separation, axis1) =
179 cuboid_support_map_compute_separation_wrt_local_line(cube1, shape2, pos12, axis1);
180
181 if separation > best_separation {
182 best_separation = separation;
183 best_dir = axis1;
184 }
185 }
186 }
187
188 (best_separation, best_dir)
189}
190
191/// Finds the best separating axis by testing the face normals of a cuboid against a support map shape.
192///
193/// This function tests all face normals (X, Y, and Z axes in both positive and negative directions)
194/// of the cuboid to find which direction gives the maximum separation from the support map shape.
195///
196/// # Parameters
197///
198/// - `cube1`: The cuboid whose face normals will be tested
199/// - `shape2`: Any convex shape implementing [`SupportMap`]
200/// - `pos12`: The position of `shape2` relative to `cube1`
201///
202/// # Returns
203///
204/// A tuple containing:
205/// - `Real`: The maximum separation found among the cuboid's face normals
206/// - **Positive**: Shapes are separated
207/// - **Negative**: Shapes are overlapping
208/// - `Vector`: The face normal direction that gives this separation
209///
210/// # Usage in Complete SAT
211///
212/// This function tests only the face normals of the cuboid. For a complete SAT collision test
213/// between a cuboid and another shape, you typically need to:
214///
215/// 1. Test cuboid's face normals (this function)
216/// 2. Test the other shape's face normals (if applicable)
217/// 3. Test edge-edge cross products in 3D (if both shapes have edges)
218///
219/// # Example
220///
221/// ```rust
222/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
223/// use parry2d::shape::{Cuboid, Ball};
224/// use parry2d::query::sat::cuboid_support_map_find_local_separating_normal_oneway;
225/// use parry2d::math::{Pose, Vector};
226///
227/// let cube = Cuboid::new(Vector::new(1.0, 1.0));
228/// let sphere = Ball::new(0.5);
229///
230/// // Position sphere to the right of the cube
231/// let pos12 = Pose::translation(2.0, 0.0);
232///
233/// let (separation, normal) = cuboid_support_map_find_local_separating_normal_oneway(
234/// &cube,
235/// &sphere,
236/// &pos12
237/// );
238///
239/// if separation > 0.0 {
240/// println!("Shapes separated by {} along normal {}", separation, normal);
241/// } else {
242/// println!("Shapes overlapping by {} along normal {}", -separation, normal);
243/// }
244/// # }
245/// ```
246///
247/// # Performance
248///
249/// This function is more efficient than `cuboid_support_map_compute_separation_wrt_local_line`
250/// for face normals because it can directly compute the separation without testing both directions.
251/// It tests 2×DIM axes (where DIM is 2 or 3).
252pub fn cuboid_support_map_find_local_separating_normal_oneway<S: SupportMap>(
253 cube1: &Cuboid,
254 shape2: &S,
255 pos12: &Pose,
256) -> (Real, Vector) {
257 let mut best_separation = -Real::MAX;
258 let mut best_dir = Vector::ZERO;
259
260 for i in 0..DIM {
261 for sign in &[-1.0, 1.0] {
262 let axis1 = Vector::ith(i, *sign);
263 let pt2 = shape2.support_point_toward(pos12, -axis1);
264 let separation = pt2[i] * *sign - cube1.half_extents[i];
265
266 if separation > best_separation {
267 best_separation = separation;
268 best_dir = axis1;
269 }
270 }
271 }
272
273 (best_separation, best_dir)
274}