1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
//! Soft constraints are spring-like constraints that dampen constraint responses
//! using intuitive tuning parameters, a damping ratio and a frequency in Hertz.
//!
//! The following section contains an overview of soft constraints
//! and their mathematical background.
//!
#![doc = include_str!("README.md")]
use bevy::reflect::Reflect;
use crate::{Scalar, TAU};
/// Soft constraint tuning parameters used for dampening
/// constraint response and controlling stiffness.
#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
pub struct SoftnessParameters {
/// 2x the damping ratio (zeta ζ). Controls the amount of oscillation.
///
/// This is stored as two times the damping ratio to avoid
/// unnecessary computations in [`SoftnessParameters::compute_coefficients`].
double_damping_ratio: Scalar,
/// The angular frequency (omega ω). Controls the rate of oscillation.
angular_frequency: Scalar,
}
impl SoftnessParameters {
/// Creates a new [`SoftnessParameters`] configuration based on
/// a given damping ratio and a frequency in Hertz.
///
/// The damping ratio (zeta ζ) controls the amount of oscillation,
/// and the frequency controls the constraint's cycles per second.
#[inline]
pub fn new(damping_ratio: Scalar, frequency_hz: Scalar) -> Self {
Self {
double_damping_ratio: 2.0 * damping_ratio,
angular_frequency: TAU * frequency_hz,
}
}
/// Returns the damping ratio that controls the amount of oscillation.
#[inline]
pub fn damping_ratio(self) -> Scalar {
self.double_damping_ratio * 0.5
}
/// Returns the frequency that controls the rate of oscillation.
#[inline]
pub fn frequency(self) -> Scalar {
self.angular_frequency / TAU
}
/// Returns the angular frequency that controls the rate of oscillation.
/// This is the [`frequency`](Self::frequency) multiplied by `2.0 * PI`.
#[inline]
pub const fn angular_frequency(self) -> Scalar {
self.angular_frequency
}
/// Computes [`SoftnessCoefficients`] based on the parameters in `self` and the time step.
#[inline]
pub fn compute_coefficients(self, delta_secs: Scalar) -> SoftnessCoefficients {
// Largely based on Erin Catto's Solver2D and Box2D: https://box2d.org/posts/2024/02/solver2d#soft-constraints
// See /docs/soft_constraint.md for in-depth explanation and derivation.
// Expressions shared by computations.
let a1 = self.double_damping_ratio + self.angular_frequency * delta_secs;
let a2 = self.angular_frequency * delta_secs * a1;
let a3 = 1.0 / (1.0 + a2);
// The coefficients used for soft constraints.
SoftnessCoefficients {
bias: self.angular_frequency / a1,
impulse_scale: a3,
mass_scale: a2 * a3,
}
}
}
/// Coefficients used by soft constraints.
#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
pub struct SoftnessCoefficients {
/// The bias coefficient used for scaling how strongly impulses
/// are biased based on the separation distance.
pub bias: Scalar,
/// The mass coefficient used for scaling the effective mass
/// "seen" by the constraint.
pub mass_scale: Scalar,
/// The impulse coefficient used for scaling the accumulated impulse
/// that is subtracted from the total impulse to prevent
/// the total impulse from becoming too large.
pub impulse_scale: Scalar,
}