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