Skip to main content

avian3d/dynamics/solver/contact/
mod.rs

1//! Constraints and other types used for solving contacts.
2
3mod normal_part;
4mod tangent_part;
5
6pub use normal_part::ContactNormalPart;
7pub use tangent_part::ContactTangentPart;
8
9use core::cmp::Ordering;
10
11use crate::{
12    collision::contact_types::ContactId, dynamics::solver::ContactSoftnessCoefficients, prelude::*,
13};
14#[cfg(feature = "serialize")]
15use bevy::reflect::{ReflectDeserialize, ReflectSerialize};
16use bevy::{reflect::Reflect, utils::default};
17
18use super::solver_body::{SolverBody, SolverBodyIndex, SolverBodyInertia};
19
20// TODO: One-body constraint version
21/// Data and logic for solving a single contact point for a [`ContactConstraint`].
22#[derive(Clone, Debug, PartialEq, Reflect)]
23#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
24#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
25#[reflect(Debug, PartialEq)]
26pub struct ContactConstraintPoint {
27    /// The normal part of the contact constraint.
28    pub normal_part: ContactNormalPart,
29
30    /// The tangential friction part of the contact constraint.
31    ///
32    /// `None` if the coefficient of friction is zero.
33    pub tangent_part: Option<ContactTangentPart>,
34
35    /// The world-space contact point relative to the center of mass of the first body.
36    pub anchor1: Vector,
37
38    /// The world-space contact point relative to the center of mass of the second body.
39    pub anchor2: Vector,
40
41    /// The pre-solve relative velocity of the bodies along the normal at the contact point.
42    pub normal_speed: f32,
43
44    /// The pre-solve separation distance between the bodies.
45    ///
46    /// A negative separation indicates penetration.
47    pub initial_separation: f32,
48}
49
50/// A contact constraint used for resolving inter-penetration between two bodies.
51///
52/// Each constraint corresponds to a [`ContactManifold`] indicated by the `manifold_index`.
53/// The contact points are stored in `points`, and they all share the same `normal`.
54#[derive(Clone, Debug, PartialEq, Reflect)]
55#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
56#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
57#[reflect(Debug, PartialEq)]
58pub struct ContactConstraint {
59    /// The [`SolverBodyIndex`] of the first body in the contact,
60    /// or [`SolverBodyIndex::INVALID`] if it is static or sleeping.
61    pub body_index1: SolverBodyIndex,
62    /// The [`SolverBodyIndex`] of the second body in the contact,
63    /// or [`SolverBodyIndex::INVALID`] if it is static or sleeping.
64    pub body_index2: SolverBodyIndex,
65    /// The relative dominance of the bodies.
66    ///
67    /// If the relative dominance is positive, the first body is dominant
68    /// and is considered to have infinite mass.
69    pub relative_dominance: i16,
70    /// The combined coefficient of dynamic [friction](Friction) of the bodies.
71    pub friction: f32,
72    /// The combined coefficient of [restitution](Restitution) of the bodies.
73    pub restitution: f32,
74    /// The desired relative linear speed of the bodies along the surface,
75    /// expressed in world space as `tangent_speed2 - tangent_speed1`.
76    ///
77    /// Defaults to zero. If set to a non-zero value, this can be used to simulate effects
78    /// such as conveyor belts.
79    #[cfg(feature = "2d")]
80    pub tangent_speed: f32,
81    /// The desired relative linear velocity of the bodies along the surface,
82    /// expressed in world space as `tangent_velocity2 - tangent_velocity1`.
83    ///
84    /// Defaults to zero. If set to a non-zero value, this can be used to simulate effects
85    /// such as conveyor belts.
86    #[cfg(feature = "3d")]
87    pub tangent_velocity: Vector,
88    /// The world-space contact normal shared by all points in the contact manifold.
89    pub normal: Vector,
90    /// The first world-space tangent direction shared by all points in the contact manifold.
91    #[cfg(feature = "3d")]
92    pub tangent1: Vector,
93    /// The contact points in the manifold. Each point shares the same `normal`.
94    // TODO: Use a `SmallVec`
95    pub points: Vec<ContactConstraintPoint>,
96    /// The stable identifier of the [`ContactEdge`] in the [`ContactGraph`].
97    ///
98    /// [`ContactEdge`]: crate::collision::contact_types::ContactEdge
99    pub contact_id: ContactId,
100    /// The index of the contact manifold in the [`ContactPair`].
101    pub manifold_index: usize,
102}
103
104impl ContactConstraint {
105    /// Generates a new [`ContactConstraint`] from the given bodies and contact manifold.
106    pub(super) fn generate(
107        body_index1: SolverBodyIndex,
108        body_index2: SolverBodyIndex,
109        inertia1: &SolverBodyInertia,
110        inertia2: &SolverBodyInertia,
111        linear_velocity1: Vector,
112        linear_velocity2: Vector,
113        contact_id: ContactId,
114        manifold: &ContactManifold,
115        manifold_index: usize,
116        warm_start_enabled: bool,
117        softness: &ContactSoftnessCoefficients,
118    ) -> Self {
119        // Compute the relative dominance of the bodies.
120        let relative_dominance = inertia1.dominance() - inertia2.dominance();
121
122        // Compute the inverse mass and angular inertia, taking into account the relative dominance.
123        let (inv_mass1, i1, inv_mass2, i2) = match relative_dominance.cmp(&0) {
124            Ordering::Equal => (
125                inertia1.effective_inv_mass(),
126                inertia1.effective_inv_angular_inertia(),
127                inertia2.effective_inv_mass(),
128                inertia2.effective_inv_angular_inertia(),
129            ),
130            Ordering::Greater => (
131                Vector::ZERO,
132                SymmetricTensor::ZERO,
133                inertia2.effective_inv_mass(),
134                inertia2.effective_inv_angular_inertia(),
135            ),
136            Ordering::Less => (
137                inertia1.effective_inv_mass(),
138                inertia1.effective_inv_angular_inertia(),
139                Vector::ZERO,
140                SymmetricTensor::ZERO,
141            ),
142        };
143
144        let softness = if relative_dominance != 0 {
145            softness.non_dynamic
146        } else {
147            softness.dynamic
148        };
149
150        let effective_inverse_mass_sum = inv_mass1 + inv_mass2;
151
152        let tangents =
153            compute_tangent_directions(manifold.normal, linear_velocity1, linear_velocity2);
154
155        let mut points = Vec::with_capacity(manifold.points.len());
156
157        for point in manifold.points.iter() {
158            // Use fixed world-space anchors.
159            // This improves rolling behavior for shapes like balls and capsules.
160            let anchor1 = point.anchor1;
161            let anchor2 = point.anchor2;
162
163            let point = ContactConstraintPoint {
164                // TODO: Apply warm starting scale here instead of in `warm_start`?
165                normal_part: ContactNormalPart::generate(
166                    effective_inverse_mass_sum,
167                    &i1,
168                    &i2,
169                    anchor1,
170                    anchor2,
171                    manifold.normal,
172                    warm_start_enabled.then_some(point.warm_start_normal_impulse),
173                    softness,
174                ),
175                // There should only be a friction part if the coefficient of friction is non-negative.
176                tangent_part: (manifold.friction > 0.0).then_some(ContactTangentPart::generate(
177                    effective_inverse_mass_sum,
178                    &i1,
179                    &i2,
180                    anchor1,
181                    anchor2,
182                    tangents,
183                    warm_start_enabled.then_some(point.warm_start_tangent_impulse),
184                )),
185                anchor1,
186                anchor2,
187                normal_speed: point.normal_speed,
188                initial_separation: -point.penetration - (anchor2 - anchor1).dot(manifold.normal),
189            };
190
191            points.push(point);
192        }
193
194        ContactConstraint {
195            body_index1,
196            body_index2,
197            relative_dominance,
198            friction: manifold.friction,
199            restitution: manifold.restitution,
200            #[cfg(feature = "2d")]
201            tangent_speed: manifold.tangent_speed,
202            #[cfg(feature = "3d")]
203            tangent_velocity: manifold.tangent_velocity,
204            normal: manifold.normal,
205            #[cfg(feature = "3d")]
206            tangent1: tangents[0],
207            points,
208            contact_id,
209            manifold_index,
210        }
211    }
212
213    /// Warm starts the contact constraint by applying the impulses from the previous frame or substep.
214    pub fn warm_start(
215        &self,
216        body1: &mut SolverBody,
217        body2: &mut SolverBody,
218        inertia1: &SolverBodyInertia,
219        inertia2: &SolverBodyInertia,
220        warm_start_coefficient: f32,
221    ) {
222        let inv_mass1 = inertia1.effective_inv_mass();
223        let inv_mass2 = inertia2.effective_inv_mass();
224        let inv_angular_inertia1 = inertia1.effective_inv_angular_inertia();
225        let inv_angular_inertia2 = inertia2.effective_inv_angular_inertia();
226
227        let tangent_directions = self.tangent_directions();
228
229        for point in self.points.iter() {
230            // Fixed anchors
231            let r1 = point.anchor1;
232            let r2 = point.anchor2;
233
234            let tangent_impulse = point
235                .tangent_part
236                .as_ref()
237                .map_or(default(), |part| part.impulse);
238
239            #[cfg(feature = "2d")]
240            let p = warm_start_coefficient
241                * (point.normal_part.impulse * self.normal
242                    + tangent_impulse * tangent_directions[0]);
243            #[cfg(feature = "3d")]
244            let p = warm_start_coefficient
245                * (point.normal_part.impulse * self.normal
246                    + tangent_impulse.x * tangent_directions[0]
247                    + tangent_impulse.y * tangent_directions[1]);
248
249            body1.linear_velocity -= p * inv_mass1;
250            body1.angular_velocity -= inv_angular_inertia1 * cross(r1, p);
251
252            body2.linear_velocity += p * inv_mass2;
253            body2.angular_velocity += inv_angular_inertia2 * cross(r2, p);
254        }
255    }
256
257    /// Solves the [`ContactConstraint`], applying an impulse to the given bodies.
258    pub fn solve<const USE_BIAS: bool>(
259        &mut self,
260        body1: &mut SolverBody,
261        body2: &mut SolverBody,
262        inertia1: &SolverBodyInertia,
263        inertia2: &SolverBodyInertia,
264        delta_secs: f32,
265        max_overlap_solve_speed: f32,
266    ) {
267        let inv_mass1 = inertia1.effective_inv_mass();
268        let inv_mass2 = inertia2.effective_inv_mass();
269        let inv_angular_inertia1 = inertia1.effective_inv_angular_inertia();
270        let inv_angular_inertia2 = inertia2.effective_inv_angular_inertia();
271
272        let delta_translation = body2.delta_position - body1.delta_position;
273
274        // Normal impulses
275        for point in self.points.iter_mut() {
276            let r1 = body1.delta_rotation * point.anchor1;
277            let r2 = body2.delta_rotation * point.anchor2;
278
279            // Compute current separation.
280            let delta_separation = delta_translation + (r2 - r1);
281            let separation = delta_separation.dot(self.normal) + point.initial_separation;
282
283            // Fixed anchors
284            let r1 = point.anchor1;
285            let r2 = point.anchor2;
286
287            // Relative velocity at contact
288            let relative_velocity = body2.velocity_at_point(r2) - body1.velocity_at_point(r1);
289
290            // Compute the incremental impulse. The clamping and impulse accumulation is handled by the method.
291            let impulse_magnitude = point.normal_part.solve_impulse::<USE_BIAS>(
292                separation,
293                relative_velocity,
294                self.normal,
295                max_overlap_solve_speed,
296                delta_secs,
297            );
298
299            let impulse = impulse_magnitude * self.normal;
300
301            // Apply the impulse.
302            body1.linear_velocity -= impulse * inv_mass1;
303            body1.angular_velocity -= inv_angular_inertia1 * cross(r1, impulse);
304
305            body2.linear_velocity += impulse * inv_mass2;
306            body2.angular_velocity += inv_angular_inertia2 * cross(r2, impulse);
307        }
308
309        // Friction impulses, only during the relaxation stage.
310        // Applying friction during the bias stage does not meaningfully improve quality for the cost.
311        if !USE_BIAS {
312            let tangent_directions = self.tangent_directions();
313
314            for point in self.points.iter_mut() {
315                let Some(ref mut friction_part) = point.tangent_part else {
316                    continue;
317                };
318
319                // Fixed anchors
320                let r1 = point.anchor1;
321                let r2 = point.anchor2;
322
323                // Relative velocity at contact point
324                let relative_velocity = body2.velocity_at_point(r2) - body1.velocity_at_point(r1);
325
326                // Compute the incremental impulse. The clamping and impulse accumulation is handled by the method.
327                let impulse = friction_part.solve_impulse(
328                    tangent_directions,
329                    relative_velocity,
330                    #[cfg(feature = "2d")]
331                    self.tangent_speed,
332                    #[cfg(feature = "3d")]
333                    self.tangent_velocity,
334                    self.friction,
335                    point.normal_part.impulse,
336                );
337
338                // Apply the impulse.
339                body1.linear_velocity -= impulse * inv_mass1;
340                body1.angular_velocity -= inv_angular_inertia1 * cross(r1, impulse);
341
342                body2.linear_velocity += impulse * inv_mass2;
343                body2.angular_velocity += inv_angular_inertia2 * cross(r2, impulse);
344            }
345        }
346    }
347
348    /// Applies [restitution](`Restitution`) for the given bodies if the relative speed
349    /// along the contact normal exceeds the given `threshold`.
350    pub fn apply_restitution(
351        &mut self,
352        body1: &mut SolverBody,
353        body2: &mut SolverBody,
354        inertia1: &SolverBodyInertia,
355        inertia2: &SolverBodyInertia,
356        threshold: f32,
357    ) {
358        let inv_mass1 = inertia1.effective_inv_mass();
359        let inv_mass2 = inertia2.effective_inv_mass();
360        let inv_angular_inertia1 = inertia1.effective_inv_angular_inertia();
361        let inv_angular_inertia2 = inertia2.effective_inv_angular_inertia();
362
363        for point in self.points.iter_mut() {
364            // Skip restitution for speeds below the threshold.
365            // We also skip contacts that don't apply an impulse to account for speculative contacts.
366            if point.normal_speed > -threshold || point.normal_part.total_impulse == 0.0 {
367                continue;
368            }
369
370            // Fixed anchors
371            let r1 = point.anchor1;
372            let r2 = point.anchor2;
373
374            // Relative velocity at contact point
375            let relative_velocity = body2.velocity_at_point(r2) - body1.velocity_at_point(r1);
376            let normal_speed = relative_velocity.dot(self.normal);
377
378            // Compute the incremental normal impulse to account for restitution.
379            let mut impulse = -point.normal_part.effective_mass
380                * (normal_speed + self.restitution * point.normal_speed);
381
382            // Clamp the accumulated impulse.
383            let new_impulse = (point.normal_part.impulse + impulse).max(0.0);
384            impulse = new_impulse - point.normal_part.impulse;
385            point.normal_part.impulse = new_impulse;
386
387            // Add the incremental impulse instead of the full impulse because this is not a substep.
388            point.normal_part.total_impulse += impulse;
389
390            // Apply the impulse.
391            let impulse = impulse * self.normal;
392
393            body1.linear_velocity -= impulse * inv_mass1;
394            body1.angular_velocity -= inv_angular_inertia1 * cross(r1, impulse);
395
396            body2.linear_velocity += impulse * inv_mass2;
397            body2.angular_velocity += inv_angular_inertia2 * cross(r2, impulse);
398        }
399    }
400
401    /// Returns the tangent directions for the contact constraint.
402    #[inline(always)]
403    pub fn tangent_directions(&self) -> [Vector; DIM - 1] {
404        #[cfg(feature = "2d")]
405        {
406            [Vector::new(self.normal.y, -self.normal.x)]
407        }
408        #[cfg(feature = "3d")]
409        {
410            // Note: The order is flipped here so that we use `-normal`.
411            [self.tangent1, self.tangent1.cross(self.normal)]
412        }
413    }
414}
415
416/// Computes `DIM - 1` tangent directions.
417#[allow(unused_variables)]
418#[inline(always)]
419fn compute_tangent_directions(
420    normal: Vector,
421    velocity1: Vector,
422    velocity2: Vector,
423) -> [Vector; DIM - 1] {
424    #[cfg(feature = "2d")]
425    {
426        [Vector::new(normal.y, -normal.x)]
427    }
428    #[cfg(feature = "3d")]
429    {
430        let force_direction = -normal;
431        let relative_velocity = velocity1 - velocity2;
432        let tangent_velocity =
433            relative_velocity - force_direction * force_direction.dot(relative_velocity);
434
435        let tangent = tangent_velocity
436            .try_normalize()
437            .unwrap_or(force_direction.any_orthonormal_vector());
438        let bitangent = force_direction.cross(tangent);
439        [tangent, bitangent]
440    }
441}