parry2d/query/nonlinear_shape_cast/
nonlinear_shape_cast.rs

1use crate::math::Real;
2use crate::query::{
3    DefaultQueryDispatcher, NonlinearRigidMotion, QueryDispatcher, ShapeCastHit, Unsupported,
4};
5use crate::shape::Shape;
6
7/// Computes when two shapes moving with translation and rotation will first collide.
8///
9/// This function performs **nonlinear shape casting** - finding the time of impact (TOI) for
10/// two shapes that are both translating and rotating. Unlike linear shape casting which only
11/// handles straight-line motion, this accounts for the curved trajectories created by rotation.
12///
13/// # What This Function Does
14///
15/// Given two shapes with complete rigid body motion (position, linear velocity, and angular
16/// velocity), this function finds:
17/// - **When** they will first touch (time of impact)
18/// - **Where** they touch (witness points)
19/// - **How** they touch (contact normals)
20///
21/// The function solves a complex nonlinear root-finding problem to determine the exact moment
22/// when the shapes' minimum distance reaches zero (or a target threshold).
23///
24/// # How It Differs from Linear Shape Casting
25///
26/// | Feature | Linear (`cast_shapes`) | Nonlinear (`cast_shapes_nonlinear`) |
27/// |---------|------------------------|--------------------------------------|
28/// | **Motion type** | Translation only | Translation + rotation |
29/// | **Trajectory** | Straight line | Curved (helical path) |
30/// | **Input** | Position + velocity | Full rigid motion (position + velocities + angular vel) |
31/// | **Use case** | Sliding, sweeping | Spinning, tumbling, realistic physics |
32/// | **Performance** | Faster | Slower (more complex) |
33///
34/// # Arguments
35///
36/// * `motion1` - Complete motion description for shape 1 ([`NonlinearRigidMotion`])
37///   - Starting position and orientation
38///   - Linear velocity (translation)
39///   - Angular velocity (rotation)
40///   - Local center of rotation
41/// * `g1` - The first shape geometry
42/// * `motion2` - Complete motion description for shape 2
43/// * `g2` - The second shape geometry
44/// * `start_time` - Beginning of time interval to check (typically `0.0`)
45/// * `end_time` - End of time interval to check (e.g., your physics timestep)
46/// * `stop_at_penetration` - Controls behavior when shapes start penetrating:
47///   - `true`: Returns immediately with `time_of_impact = start_time`
48///   - `false`: Checks if they're separating; if so, looks for later impacts
49///
50/// # The `stop_at_penetration` Parameter
51///
52/// This parameter is crucial for handling initially-penetrating shapes:
53///
54/// - **`true` (recommended for most cases)**: If shapes overlap at `start_time`, immediately
55///   return a hit at `start_time`. This is safer and prevents tunneling.
56///
57/// - **`false` (advanced)**: If shapes overlap at `start_time` BUT are moving apart (separating
58///   velocity), ignore this initial penetration and search for a later collision that could
59///   cause tunneling. Use this when you have external penetration resolution and only care
60///   about future impacts.
61///
62/// # Returns
63///
64/// * `Ok(Some(hit))` - Collision detected, see [`ShapeCastHit`] for impact details
65///   - `time_of_impact`: When collision occurs (in `[start_time, end_time]`)
66///   - `witness1`, `witness2`: Contact points on each shape (local space)
67///   - `normal1`, `normal2`: Contact normals on each shape (local space)
68///   - `status`: Algorithm convergence status
69/// * `Ok(None)` - No collision within the time interval
70/// * `Err(Unsupported)` - This shape pair is not supported for nonlinear casting
71///
72/// # Example: Basic Spinning Collision
73///
74/// ```rust
75/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
76/// use parry3d::query::{cast_shapes_nonlinear, NonlinearRigidMotion};
77/// use parry3d::shape::Ball;
78/// use nalgebra::{Isometry3, Point3, Vector3};
79///
80/// let ball1 = Ball::new(1.0);
81/// let ball2 = Ball::new(1.0);
82///
83/// // Ball 1: moving right AND spinning around Y axis
84/// let motion1 = NonlinearRigidMotion::new(
85///     Isometry3::translation(0.0, 0.0, 0.0), // start position
86///     Point3::origin(),                      // rotation center (local space)
87///     Vector3::new(2.0, 0.0, 0.0),          // moving right at speed 2
88///     Vector3::new(0.0, 10.0, 0.0),         // spinning around Y axis
89/// );
90///
91/// // Ball 2: stationary at x=10
92/// let motion2 = NonlinearRigidMotion::constant_position(
93///     Isometry3::translation(10.0, 0.0, 0.0)
94/// );
95///
96/// let result = cast_shapes_nonlinear(
97///     &motion1, &ball1,
98///     &motion2, &ball2,
99///     0.0,   // start at t=0
100///     10.0,  // check up to t=10
101///     true,  // stop if initially penetrating
102/// );
103///
104/// if let Ok(Some(hit)) = result {
105///     println!("Collision at time: {}", hit.time_of_impact);
106///     println!("Contact point on ball1: {:?}", hit.witness1);
107///     println!("Contact normal on ball1: {:?}", hit.normal1);
108/// }
109/// # }
110/// ```
111///
112/// # Example: Tumbling Box vs Stationary Wall
113///
114/// ```rust
115/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
116/// use parry3d::query::{cast_shapes_nonlinear, NonlinearRigidMotion};
117/// use parry3d::shape::Cuboid;
118/// use nalgebra::{Isometry3, Point3, Vector3};
119///
120/// // A cuboid tumbling through space
121/// let cube = Cuboid::new(Vector3::new(0.5, 0.5, 0.5));
122///
123/// // Large wall (very wide cuboid)
124/// let wall = Cuboid::new(Vector3::new(10.0, 10.0, 0.1));
125///
126/// // Cube falling and tumbling
127/// let motion_cube = NonlinearRigidMotion::new(
128///     Isometry3::translation(0.0, 5.0, 0.0),  // starting 5 units above
129///     Point3::origin(),                        // rotate around center
130///     Vector3::new(0.0, -2.0, 0.0),           // falling down
131///     Vector3::new(1.0, 0.5, 2.0),            // tumbling (complex rotation)
132/// );
133///
134/// // Wall is stationary
135/// let motion_wall = NonlinearRigidMotion::constant_position(
136///     Isometry3::translation(0.0, 0.0, 0.0)
137/// );
138///
139/// let result = cast_shapes_nonlinear(
140///     &motion_cube, &cube,
141///     &motion_wall, &wall,
142///     0.0,
143///     5.0,  // check 5 seconds of motion
144///     true,
145/// );
146///
147/// if let Ok(Some(hit)) = result {
148///     // Cube will hit wall while tumbling
149///     // Time depends on both falling speed and rotation
150///     println!("Tumbling cube hits wall at t = {}", hit.time_of_impact);
151/// }
152/// # }
153/// ```
154///
155/// # Example: Handling Initial Penetration
156///
157/// ```rust
158/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
159/// use parry3d::query::{cast_shapes_nonlinear, NonlinearRigidMotion};
160/// use parry3d::shape::Ball;
161/// use nalgebra::{Isometry3, Point3, Vector3};
162///
163/// let ball1 = Ball::new(2.0);
164/// let ball2 = Ball::new(2.0);
165///
166/// // Balls overlapping (3 units apart, but radii sum to 4)
167/// let motion1 = NonlinearRigidMotion::new(
168///     Isometry3::translation(0.0, 0.0, 0.0),
169///     Point3::origin(),
170///     Vector3::new(-1.0, 0.0, 0.0),  // moving AWAY from ball2
171///     Vector3::zeros(),
172/// );
173///
174/// let motion2 = NonlinearRigidMotion::constant_position(
175///     Isometry3::translation(3.0, 0.0, 0.0)
176/// );
177///
178/// // With stop_at_penetration = true
179/// let result_stop = cast_shapes_nonlinear(
180///     &motion1, &ball1, &motion2, &ball2,
181///     0.0, 10.0, true,  // STOP at penetration
182/// );
183/// // Returns Some(hit) with time_of_impact = 0.0
184///
185/// // With stop_at_penetration = false
186/// let result_continue = cast_shapes_nonlinear(
187///     &motion1, &ball1, &motion2, &ball2,
188///     0.0, 10.0, false,  // DON'T stop - they're separating
189/// );
190/// // Returns None - shapes are moving apart, no future impact
191/// # }
192/// ```
193///
194/// # When to Use This vs Linear Shape Casting
195///
196/// **Use `cast_shapes_nonlinear` when:**
197/// - Objects have significant angular velocity
198/// - Rotation accuracy matters (spinning blades, tumbling debris)
199/// - Physics simulation with full 6-DOF motion
200/// - Objects can rotate > 10-15 degrees during timestep
201///
202/// **Use `cast_shapes` (linear) when:**
203/// - Objects don't rotate (angular velocity ≈ 0)
204/// - Rotation is negligible for your timestep
205/// - Performance is critical
206/// - Implementing simple sweep/slide mechanics
207///
208/// # Performance Considerations
209///
210/// Nonlinear shape casting is significantly more expensive than linear:
211/// - **Iterative root-finding**: Multiple evaluations to converge
212/// - **Rotational transforms**: Matrix operations at each evaluation
213/// - **Complex geometry**: Shape changes orientation during motion
214///
215/// Typical cost: 3-10x slower than linear shape casting, depending on:
216/// - Shape complexity (balls faster than meshes)
217/// - Angular velocity magnitude (faster rotation = more iterations)
218/// - Time interval length (longer intervals may need more precision)
219///
220/// # Algorithm Notes
221///
222/// The implementation uses conservative root-finding algorithms that:
223/// - Guarantee no false negatives (won't miss collisions)
224/// - May require multiple iterations to converge
225/// - Handle degenerate cases (parallel surfaces, grazing contact)
226/// - Account for numerical precision limits
227///
228/// # Common Pitfalls
229///
230/// 1. **Units**: Ensure time, velocity, and angular velocity units match
231///    - If time is in seconds, velocity should be units/second
232///    - Angular velocity in radians/second (not degrees!)
233///
234/// 2. **Time interval**: Keep `end_time - start_time` reasonable
235///    - Very large intervals may miss fast rotations
236///    - Typical: use your physics timestep (e.g., 1/60 second)
237///
238/// 3. **Rotation center**: `local_center` in [`NonlinearRigidMotion`] must be in
239///    the shape's local coordinate system, not world space
240///
241/// 4. **Initial penetration**: Understand `stop_at_penetration` behavior for your use case
242///
243/// # See Also
244///
245/// - [`NonlinearRigidMotion`] - Describes an object's complete motion
246/// - [`cast_shapes`](crate::query::cast_shapes) - Linear shape casting (no rotation)
247/// - [`ShapeCastHit`] - Result structure
248/// - [`contact`](crate::query::contact::contact()) - Static contact queries (no motion)
249pub fn cast_shapes_nonlinear(
250    motion1: &NonlinearRigidMotion,
251    g1: &dyn Shape,
252    motion2: &NonlinearRigidMotion,
253    g2: &dyn Shape,
254    start_time: Real,
255    end_time: Real,
256    stop_at_penetration: bool,
257) -> Result<Option<ShapeCastHit>, Unsupported> {
258    DefaultQueryDispatcher.cast_shapes_nonlinear(
259        motion1,
260        g1,
261        motion2,
262        g2,
263        start_time,
264        end_time,
265        stop_at_penetration,
266    )
267}