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}