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}