parry3d/query/sat/
sat_cuboid_support_map.rs

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