avian3d/dynamics/rigid_body/
physics_material.rs

1use crate::prelude::*;
2use bevy::prelude::*;
3
4/// Determines how coefficients are combined for [`Restitution`] and [`Friction`].
5/// The default is `Average`.
6///
7/// When combine rules clash with each other, the following priority order is used:
8/// `Max > Multiply > Min > GeometricMean > Average`.
9#[derive(Reflect, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
10#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
11#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
12#[reflect(Debug, PartialEq)]
13pub enum CoefficientCombine {
14    /// Coefficients are combined by computing their average `(a + b) / 2.0`.
15    Average = 1,
16    /// Coefficients are combined by computing their geometric mean `sqrt(a * b)`.
17    GeometricMean = 2,
18    /// Coefficients are combined by choosing the smaller coefficient `min(a, b)`.
19    Min = 3,
20    /// Coefficients are combined by computing their product `a * b`.
21    Multiply = 4,
22    /// Coefficients are combined by choosing the larger coefficient `max(a, b)`.
23    Max = 5,
24}
25
26impl CoefficientCombine {
27    /// Combines two coefficients according to the combine rule.
28    pub fn mix(&self, a: Scalar, b: Scalar) -> Scalar {
29        match self {
30            CoefficientCombine::Average => (a + b) * 0.5,
31            CoefficientCombine::GeometricMean => (a * b).sqrt(),
32            CoefficientCombine::Min => a.min(b),
33            CoefficientCombine::Multiply => a * b,
34            CoefficientCombine::Max => a.max(b),
35        }
36    }
37}
38
39/// A resource for the default [`Friction`] to use for physics objects.
40///
41/// Friction can be set for individual colliders and rigid bodies using the [`Friction`] component.
42///
43/// Defaults to dynamic and static friction coefficients of `0.5` with a combine rule of [`CoefficientCombine::Average`].
44#[derive(Resource, Clone, Copy, Debug, Default, Deref, DerefMut, PartialEq, Reflect)]
45#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
46#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
47#[reflect(Debug, Default, PartialEq)]
48pub struct DefaultFriction(pub Friction);
49
50/// A resource for the default [`Restitution`] to use for physics objects.
51///
52/// Restitution can be set for individual colliders and rigid bodies using the [`Restitution`] component.
53///
54/// Defaults to a coefficient of `0.0` with a combine rule of [`CoefficientCombine::Average`].
55#[derive(Resource, Clone, Copy, Debug, Default, Deref, DerefMut, PartialEq, Reflect)]
56#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
57#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
58#[reflect(Debug, Default, PartialEq)]
59pub struct DefaultRestitution(pub Restitution);
60
61/// A component for [dry friction], controlling how strongly a [rigid body](RigidBody) or [collider](Collider)
62/// opposes sliding along other surfaces while in contact.
63///
64/// For surfaces that are at rest relative to each other, **static friction** is used.
65/// Once the static friction is overcome, the bodies will start sliding, and **dynamic friction** is applied instead.
66/// The friction force is proportional to the normal force of the contact, following the [Coulomb friction model].
67///
68/// The friction coefficients should typically be between 0 and 1, where 0 corresponds to no friction at all, and 1 corresponds to high friction.
69/// However, any non-negative value is allowed.
70///
71/// If a collider does not have [`Friction`] specified, the [`Friction`] of its rigid body entity will be used instead.
72/// If that is not specified either, collisions use the [`DefaultFriction`] resource. The default dynamic and static friction
73/// coefficients are set to `0.5`.
74///
75/// [dry friction]: https://en.wikipedia.org/wiki/Friction#Dry_friction
76/// [Coulomb friction model]: https://en.wikipedia.org/wiki/Friction#Dry_friction
77///
78/// # Combine Rule
79///
80/// When two bodies collide, their coefficients are combined using the specified [`CoefficientCombine`] rule.
81/// In the case of clashing rules, the following priority order is used: `Max > Multiply > Min > GeometricMean > Average`.
82///
83/// By default, friction uses [`CoefficientCombine::Average`], computing the average `(a + b) / 2.0`.
84///
85/// # Usage
86///
87/// Create a new [`Friction`] component with dynamic and static friction coefficients of 0.4:
88///
89/// ```ignore
90/// Friction::new(0.4)
91/// ```
92///
93/// Set the other friction coefficient:
94///
95/// ```ignore
96/// // 0.4 static and 0.6 dynamic
97/// Friction::new(0.4).with_dynamic_coefficient(0.6)
98/// // 0.4 dynamic and 0.6 static
99/// Friction::new(0.4).with_static_coefficient(0.6)
100/// ```
101///
102/// Configure how the friction coefficients of two [`Friction`] components are combined with [`CoefficientCombine`]:
103///
104/// ```ignore
105/// Friction::new(0.4).with_combine_rule(CoefficientCombine::Multiply)
106/// ```
107///
108/// Combine the properties of two [`Friction`] components:
109///
110/// ```
111#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
112#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
113/// #
114/// let first = Friction::new(0.8).with_combine_rule(CoefficientCombine::Average);
115/// let second = Friction::new(0.5).with_combine_rule(CoefficientCombine::Multiply);
116///
117/// // `CoefficientCombine::Multiply` has higher priority, so the coefficients are multiplied
118/// assert_eq!(
119///     first.combine(second),
120///     Friction::new(0.4).with_combine_rule(CoefficientCombine::Multiply)
121/// );
122/// ```
123///
124/// # Accuracy
125///
126/// Avian attempts to simulate friction accurately, but [Coulomb friction][Coulomb friction model] is still a simplification of real-world friction.
127/// Each collision typically has only a small number of contact points, so friction cannot consider the entire surface perfectly.
128/// Still, friction should be reasonably accurate for most cases, particularly for game purposes.
129///
130/// It is worth noting that in real life, friction coefficients can vary greatly based on material combinations, surface roughness,
131/// and numerous other factors, and they are not uniform across surfaces. For game purposes however, it is impractical to consider
132/// all of these factors, so instead, material interactions are controlled using simple [`CoefficientCombine`] rules.
133#[derive(Reflect, Clone, Copy, Component, Debug, PartialEq, PartialOrd)]
134#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
135#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
136#[reflect(Debug, Component, PartialEq)]
137pub struct Friction {
138    /// Coefficient of dynamic friction. Applied when bodies are sliding relative to each other.
139    ///
140    /// Defaults to `0.5`.
141    pub dynamic_coefficient: Scalar,
142    /// Coefficient of static friction. Applied when bodies are at rest relative to each other.
143    ///
144    /// Defaults to `0.5`.
145    pub static_coefficient: Scalar,
146    /// The rule used for computing the combined coefficients of friction when two bodies collide.
147    ///
148    /// Defaults to [`CoefficientCombine::Average`].
149    pub combine_rule: CoefficientCombine,
150}
151
152impl Default for Friction {
153    /// The default [`Friction`] with dynamic and static friction coefficients of `0.5` and a combine rule of [`CoefficientCombine::Average`].
154    fn default() -> Self {
155        Self {
156            dynamic_coefficient: 0.5,
157            static_coefficient: 0.5,
158            combine_rule: CoefficientCombine::Average,
159        }
160    }
161}
162
163impl Friction {
164    /// Zero dynamic and static friction and [`CoefficientCombine::Average`].
165    pub const ZERO: Self = Self {
166        dynamic_coefficient: 0.0,
167        static_coefficient: 0.0,
168        combine_rule: CoefficientCombine::Average,
169    };
170
171    /// Creates a new [`Friction`] component with the same dynamic and static friction coefficients.
172    pub fn new(friction_coefficient: Scalar) -> Self {
173        Self {
174            dynamic_coefficient: friction_coefficient,
175            static_coefficient: friction_coefficient,
176            ..default()
177        }
178    }
179
180    /// Sets the [`CoefficientCombine`] rule used.
181    pub fn with_combine_rule(&self, combine_rule: CoefficientCombine) -> Self {
182        Self {
183            combine_rule,
184            ..*self
185        }
186    }
187
188    /// Sets the coefficient of dynamic friction.
189    pub fn with_dynamic_coefficient(&self, coefficient: Scalar) -> Self {
190        Self {
191            dynamic_coefficient: coefficient,
192            ..*self
193        }
194    }
195
196    /// Sets the coefficient of static friction.
197    pub fn with_static_coefficient(&self, coefficient: Scalar) -> Self {
198        Self {
199            static_coefficient: coefficient,
200            ..*self
201        }
202    }
203
204    /// Combines the properties of two [`Friction`] components.
205    pub fn combine(&self, other: Self) -> Self {
206        // Choose rule with higher priority
207        let rule = self.combine_rule.max(other.combine_rule);
208
209        Self {
210            dynamic_coefficient: rule.mix(self.dynamic_coefficient, other.dynamic_coefficient),
211            static_coefficient: rule.mix(self.static_coefficient, other.static_coefficient),
212            combine_rule: rule,
213        }
214    }
215}
216
217impl From<Scalar> for Friction {
218    fn from(coefficient: Scalar) -> Self {
219        Self {
220            dynamic_coefficient: coefficient,
221            static_coefficient: coefficient,
222            ..default()
223        }
224    }
225}
226
227/// A component for [restitution], controlling how bouncy a [rigid body](RigidBody) or [collider](Collider) is.
228///
229/// The coefficient should be between 0 and 1, where 0 corresponds to a **perfectly inelastic** collision with zero bounce,
230/// and 1 corresponds to a **perfectly elastic** collision that tries to preserve all kinetic energy.
231/// Values larger than 1 can result in unstable or explosive behavior.
232///
233/// If a collider does not have [`Restitution`] specified, the [`Restitution`] of its rigid body entity will be used instead.
234/// If that is not specified either, collisions use the [`DefaultRestitution`] resource. The default restitution is set to 0,
235/// meaning that objects are not bouncy by default.
236///
237/// [restitution]: https://en.wikipedia.org/wiki/Coefficient_of_restitution
238///
239/// # Combine Rule
240///
241/// When two bodies collide, their coefficients are combined using the specified [`CoefficientCombine`] rule.
242/// In the case of clashing rules, the following priority order is used: `Max > Multiply > Min > GeometricMean > Average`.
243///
244/// By default, restitution uses [`CoefficientCombine::Average`], computing the average `(a + b) / 2.0`.
245///
246/// # Usage
247///
248/// Create a new [`Restitution`] component with a restitution coefficient of `0.4`:
249///
250/// ```ignore
251/// Restitution::new(0.4)
252/// ```
253///
254/// Configure how two restitution coefficients are combined with [`CoefficientCombine`]:
255///
256/// ```ignore
257/// Restitution::new(0.4).with_combine_rule(CoefficientCombine::Max)
258/// ```
259///
260/// Combine the properties of two [`Restitution`] components:
261///
262/// ```
263#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
264#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
265/// #
266/// let first = Restitution::new(0.8).with_combine_rule(CoefficientCombine::Average);
267/// let second = Restitution::new(0.5).with_combine_rule(CoefficientCombine::Multiply);
268///
269/// // `CoefficientCombine::Multiply` has higher priority, so the coefficients are multiplied
270/// assert_eq!(
271///     first.combine(second),
272///     Restitution::new(0.4).with_combine_rule(CoefficientCombine::Multiply)
273/// );
274/// ```
275///
276/// # Accuracy
277///
278/// Restitution is not guaranteed to be entirely accurate, especially for fast-moving bodies or when there are multiple contact points.
279///
280/// - Even with a coefficient of 1, some kinetic energy can be lost over long periods of time for bouncing objects.
281///   This can be caused by [friction](Friction), [damping](LinearDamping), or simulation inaccuracies.
282///
283/// - Collisions can have more or less bounce than expected, especially when objects are moving very fast.
284///   This is largely due to the the sequential solver and [speculative collision](dynamics::ccd#speculative-collision).
285///   For more accurate restitution, consider disabling speculative collision and using [`SweptCcd`] instead.
286///
287/// - An object falling flat on the ground with multiple contact points may tip over on one side or corner a bit.
288///   This is because contact points are solved sequentially, and the order of contact points affects the result.
289///   Configuring [`SolverConfig::restitution_iterations`](dynamics::solver::SolverConfig::restitution_iterations) may help mitigate this.
290///
291/// - When collision velocity is small, collisions are treated as inelastic to prevent jitter. The velocity threshold can be configured
292///   using [`SolverConfig::restitution_threshold`](dynamics::solver::SolverConfig::restitution_threshold).
293///
294/// For game purposes however, restitution should still be reasonably accurate.
295///
296/// It is worth noting that in real life, restitution coefficients can vary greatly based on material combinations
297/// and numerous other factors, and they are not uniform across surfaces. For game purposes however, it is impractical to consider
298/// all of these factors, so instead, material interactions are controlled using simple [`CoefficientCombine`] rules.
299#[doc(alias = "Bounciness")]
300#[doc(alias = "Elasticity")]
301#[derive(Reflect, Clone, Copy, Component, Debug, PartialEq, PartialOrd)]
302#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
303#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
304#[reflect(Debug, Component, PartialEq)]
305pub struct Restitution {
306    /// The [coefficient of restitution](https://en.wikipedia.org/wiki/Coefficient_of_restitution).
307    ///
308    /// This should be between 0 and 1, where 0 corresponds to a **perfectly inelastic** collision with zero bounce,
309    /// and 1 corresponds to a **perfectly elastic** collision that tries to preserve all kinetic energy.
310    /// Values larger than 1 can result in unstable or explosive behavior.
311    ///
312    /// Defaults to `0.0`.
313    pub coefficient: Scalar,
314    /// The rule used for computing the combined coefficient of restitution when two bodies collide.
315    ///
316    /// Defaults to [`CoefficientCombine::Average`].
317    pub combine_rule: CoefficientCombine,
318}
319
320impl Default for Restitution {
321    /// The default [`Restitution`] with a coefficient of `0.0` and a combine rule of [`CoefficientCombine::Average`].
322    fn default() -> Self {
323        Self {
324            coefficient: 0.0,
325            combine_rule: CoefficientCombine::Average,
326        }
327    }
328}
329
330impl Restitution {
331    /// A restitution coefficient of `0.0` and a combine rule of [`CoefficientCombine::Average`].
332    ///
333    /// This is equivalent to [`Restitution::PERFECTLY_INELASTIC`].
334    pub const ZERO: Self = Self {
335        coefficient: 0.0,
336        combine_rule: CoefficientCombine::Average,
337    };
338
339    /// A restitution coefficient of `0.0`, which corresponds to a perfectly inelastic collision.
340    ///
341    /// Uses [`CoefficientCombine::Average`].
342    pub const PERFECTLY_INELASTIC: Self = Self {
343        coefficient: 0.0,
344        combine_rule: CoefficientCombine::Average,
345    };
346
347    /// A restitution coefficient of `1.0`, which corresponds to a perfectly elastic collision.
348    ///
349    /// Uses [`CoefficientCombine::Average`].
350    pub const PERFECTLY_ELASTIC: Self = Self {
351        coefficient: 1.0,
352        combine_rule: CoefficientCombine::Average,
353    };
354
355    /// Creates a new [`Restitution`] component with the given restitution coefficient.
356    pub fn new(coefficient: Scalar) -> Self {
357        Self {
358            coefficient,
359            combine_rule: CoefficientCombine::Average,
360        }
361    }
362
363    /// Sets the [`CoefficientCombine`] rule used.
364    pub fn with_combine_rule(&self, combine_rule: CoefficientCombine) -> Self {
365        Self {
366            combine_rule,
367            ..*self
368        }
369    }
370
371    /// Combines the properties of two [`Restitution`] components.
372    pub fn combine(&self, other: Self) -> Self {
373        // Choose rule with higher priority
374        let rule = self.combine_rule.max(other.combine_rule);
375
376        Self {
377            coefficient: rule.mix(self.coefficient, other.coefficient),
378            combine_rule: rule,
379        }
380    }
381}
382
383impl From<Scalar> for Restitution {
384    fn from(coefficient: Scalar) -> Self {
385        Self {
386            coefficient,
387            ..default()
388        }
389    }
390}
391
392#[cfg(test)]
393mod tests {
394    use crate::prelude::*;
395    use approx::assert_relative_eq;
396
397    // TODO: Test `CoefficientCombine` directly
398    #[test]
399    fn coefficient_combine_works() {
400        let r1 = Restitution::new(0.3).with_combine_rule(CoefficientCombine::Average);
401
402        // (0.3 + 0.7) / 2.0 == 0.5
403        let average_result =
404            r1.combine(Restitution::new(0.7).with_combine_rule(CoefficientCombine::Average));
405        let average_expected = Restitution::new(0.5).with_combine_rule(CoefficientCombine::Average);
406        assert_relative_eq!(
407            average_result.coefficient,
408            average_expected.coefficient,
409            epsilon = 0.0001
410        );
411        assert_eq!(average_result.combine_rule, average_expected.combine_rule);
412
413        // (0.3 * 0.7).sqrt() == 0.4582575694
414        let geometric_mean_result =
415            r1.combine(Restitution::new(0.7).with_combine_rule(CoefficientCombine::GeometricMean));
416        let geometric_mean_expected =
417            Restitution::new(0.458_257_56).with_combine_rule(CoefficientCombine::GeometricMean);
418        assert_relative_eq!(
419            geometric_mean_result.coefficient,
420            geometric_mean_expected.coefficient,
421            epsilon = 0.0001
422        );
423        assert_eq!(
424            geometric_mean_result.combine_rule,
425            geometric_mean_expected.combine_rule
426        );
427
428        // 0.3.min(0.7) == 0.3
429        assert_eq!(
430            r1.combine(Restitution::new(0.7).with_combine_rule(CoefficientCombine::Min)),
431            Restitution::new(0.3).with_combine_rule(CoefficientCombine::Min)
432        );
433
434        // 0.3 * 0.7 == 0.21
435        let multiply_result =
436            r1.combine(Restitution::new(0.7).with_combine_rule(CoefficientCombine::Multiply));
437        let multiply_expected =
438            Restitution::new(0.21).with_combine_rule(CoefficientCombine::Multiply);
439        assert_relative_eq!(
440            multiply_result.coefficient,
441            multiply_expected.coefficient,
442            epsilon = 0.0001
443        );
444        assert_eq!(multiply_result.combine_rule, multiply_expected.combine_rule);
445
446        // 0.3.max(0.7) == 0.7
447        assert_eq!(
448            r1.combine(Restitution::new(0.7).with_combine_rule(CoefficientCombine::Max)),
449            Restitution::new(0.7).with_combine_rule(CoefficientCombine::Max)
450        );
451    }
452}