Skip to main content

avian3d/dynamics/rigid_body/
physics_material.rs

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