From 3ddf2441ea6c43aa98718e0ce8650c3b804062d4 Mon Sep 17 00:00:00 2001 From: Sébastien Crozet Date: Sun, 14 Apr 2024 15:53:35 +0200 Subject: feat: add exact mlcp solver for pais of 2 constraints --- src/dynamics/integration_parameters.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/dynamics/integration_parameters.rs') diff --git a/src/dynamics/integration_parameters.rs b/src/dynamics/integration_parameters.rs index 13b3fde..2788349 100644 --- a/src/dynamics/integration_parameters.rs +++ b/src/dynamics/integration_parameters.rs @@ -1,6 +1,9 @@ use crate::math::Real; use std::num::NonZeroUsize; +pub(crate) static BLOCK_SOLVER_ENABLED: bool = cfg!(feature = "dim2"); +pub(crate) static DISABLE_FRICTION_LIMIT_REAPPLY: bool = false; + /// Parameters for a time-step of the physics engine. #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] @@ -50,6 +53,8 @@ pub struct IntegrationParameters { pub num_additional_friction_iterations: usize, /// Number of internal Project Gauss Seidel (PGS) iterations run at each solver iteration (default: `1`). pub num_internal_pgs_iterations: usize, + /// The maximum number of stabilization iterations run at each solver iterations (default: `10`). + pub max_internal_stabilization_iterations: usize, /// Minimum number of dynamic bodies in each active island (default: `128`). pub min_island_size: usize, /// Maximum number of substeps performed by the solver (default: `1`). @@ -194,7 +199,7 @@ impl Default for IntegrationParameters { Self { dt: 1.0 / 60.0, min_ccd_dt: 1.0 / 60.0 / 100.0, - erp: 0.6, + erp: 0.8, damping_ratio: 1.0, joint_erp: 1.0, joint_damping_ratio: 1.0, @@ -202,6 +207,7 @@ impl Default for IntegrationParameters { max_penetration_correction: Real::MAX, prediction_distance: 0.002, num_internal_pgs_iterations: 1, + max_internal_stabilization_iterations: 10, num_additional_friction_iterations: 4, num_solver_iterations: NonZeroUsize::new(4).unwrap(), // TODO: what is the optimal value for min_island_size? -- cgit From de5e871cd1a76d424c2fa49da776c14ddd6f533b Mon Sep 17 00:00:00 2001 From: Sébastien Crozet Date: Sun, 14 Apr 2024 18:59:08 +0200 Subject: chore: rework vertical stacks demo --- src/dynamics/integration_parameters.rs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/dynamics/integration_parameters.rs') diff --git a/src/dynamics/integration_parameters.rs b/src/dynamics/integration_parameters.rs index 2788349..093ebe9 100644 --- a/src/dynamics/integration_parameters.rs +++ b/src/dynamics/integration_parameters.rs @@ -1,6 +1,8 @@ use crate::math::Real; use std::num::NonZeroUsize; +// TODO: enabling the block solver in 3d introduces a lot of jitters in +// the 3D domino demo. So for now we dont enable it in 3D. pub(crate) static BLOCK_SOLVER_ENABLED: bool = cfg!(feature = "dim2"); pub(crate) static DISABLE_FRICTION_LIMIT_REAPPLY: bool = false; -- cgit From f58b4f7c195ab7acf0778ea65c46ebf37ac8188c Mon Sep 17 00:00:00 2001 From: Sébastien Crozet Date: Sun, 21 Apr 2024 18:55:11 +0200 Subject: feat: add warmstarting to contact constraints resolution --- src/dynamics/integration_parameters.rs | 130 ++++++++++++++++----------------- 1 file changed, 65 insertions(+), 65 deletions(-) (limited to 'src/dynamics/integration_parameters.rs') diff --git a/src/dynamics/integration_parameters.rs b/src/dynamics/integration_parameters.rs index 093ebe9..80ca92c 100644 --- a/src/dynamics/integration_parameters.rs +++ b/src/dynamics/integration_parameters.rs @@ -4,7 +4,6 @@ use std::num::NonZeroUsize; // TODO: enabling the block solver in 3d introduces a lot of jitters in // the 3D domino demo. So for now we dont enable it in 3D. pub(crate) static BLOCK_SOLVER_ENABLED: bool = cfg!(feature = "dim2"); -pub(crate) static DISABLE_FRICTION_LIMIT_REAPPLY: bool = false; /// Parameters for a time-step of the physics engine. #[derive(Copy, Clone, Debug)] @@ -26,12 +25,12 @@ pub struct IntegrationParameters { /// 0-1: multiplier for how much of the constraint violation (e.g. contact penetration) /// will be compensated for during the velocity solve. - /// (default `0.8`). + /// (default `0.1`). pub erp: Real, /// 0-1: the damping ratio used by the springs for Baumgarte constraints stabilization. /// Lower values make the constraints more compliant (more "springy", allowing more visible penetrations /// before stabilization). - /// (default `0.25`). + /// (default `20.0`). pub damping_ratio: Real, /// 0-1: multiplier for how much of the joint violation @@ -40,14 +39,21 @@ pub struct IntegrationParameters { pub joint_erp: Real, /// The fraction of critical damping applied to the joint for constraints regularization. - /// (default `0.25`). + /// (default `1.0`). pub joint_damping_ratio: Real, - /// Amount of penetration the engine wont attempt to correct (default: `0.001m`). + /// The coefficient in `[0, 1]` applied to warmstart impulses, i.e., impulses that are used as the + /// initial solution (instead of 0) at the next simulation step. + /// + /// This should generally be set to 1. Can be set to 0 if using a large [`Self::erp`] value. + /// (default `1.0`). + pub warmstart_coefficient: Real, + + /// Amount of penetration the engine won’t attempt to correct (default: `0.001m`). pub allowed_linear_error: Real, /// Maximum amount of penetration the solver will attempt to resolve in one timestep. pub max_penetration_correction: Real, - /// The maximal distance separating two objects that will generate predictive contacts (default: `0.002`). + /// The maximal distance separating two objects that will generate predictive contacts (default: `0.002m`). pub prediction_distance: Real, /// The number of solver iterations run by the constraints solver for calculating forces (default: `4`). pub num_solver_iterations: NonZeroUsize, @@ -55,8 +61,8 @@ pub struct IntegrationParameters { pub num_additional_friction_iterations: usize, /// Number of internal Project Gauss Seidel (PGS) iterations run at each solver iteration (default: `1`). pub num_internal_pgs_iterations: usize, - /// The maximum number of stabilization iterations run at each solver iterations (default: `10`). - pub max_internal_stabilization_iterations: usize, + /// The number of stabilization iterations run at each solver iterations (default: `2`). + pub num_internal_stabilization_iterations: usize, /// Minimum number of dynamic bodies in each active island (default: `128`). pub min_island_size: usize, /// Maximum number of substeps performed by the solver (default: `1`). @@ -64,51 +70,6 @@ pub struct IntegrationParameters { } impl IntegrationParameters { - /// Configures the integration parameters to match the old PGS solver - /// from Rapier version <= 0.17. - /// - /// This solver was slightly faster than the new one but resulted - /// in less stable joints and worse convergence rates. - /// - /// This should only be used for comparison purpose or if you are - /// experiencing problems with the new solver. - /// - /// NOTE: this does not affect any [`RigidBody::additional_solver_iterations`] that will - /// still create solver iterations based on the new "small-steps" PGS solver. - /// NOTE: this resets [`Self::erp`], [`Self::damping_ratio`], [`Self::joint_erp`], - /// [`Self::joint_damping_ratio`] to their former default values. - pub fn switch_to_standard_pgs_solver(&mut self) { - self.num_internal_pgs_iterations *= self.num_solver_iterations.get(); - self.num_solver_iterations = NonZeroUsize::new(1).unwrap(); - self.erp = 0.8; - self.damping_ratio = 0.25; - self.joint_erp = 1.0; - self.joint_damping_ratio = 1.0; - } - - /// Configures the integration parameters to match the new "small-steps" PGS solver - /// from Rapier version >= 0.18. - /// - /// The "small-steps" PGS solver is the default one given by [`Self::default()`] so - /// calling this function is generally not needed unless - /// [`Self::switch_to_standard_pgs_solver()`] was called. - /// - /// This solver results in more stable joints and significantly better convergence - /// rates but is slightly slower in its default settings. - /// - /// NOTE: this resets [`Self::erp`], [`Self::damping_ratio`], [`Self::joint_erp`], - /// [`Self::joint_damping_ratio`] to their default values. - pub fn switch_to_small_steps_pgs_solver(&mut self) { - self.num_solver_iterations = NonZeroUsize::new(self.num_internal_pgs_iterations).unwrap(); - self.num_internal_pgs_iterations = 1; - - let default = Self::default(); - self.erp = default.erp; - self.damping_ratio = default.damping_ratio; - self.joint_erp = default.joint_erp; - self.joint_damping_ratio = default.joint_damping_ratio; - } - /// The inverse of the time-stepping length, i.e. the steps per seconds (Hz). /// /// This is zero if `self.dt` is zero. @@ -161,7 +122,7 @@ impl IntegrationParameters { // let damping = 4.0 * damping_ratio * damping_ratio * projected_mass // / (dt * inv_erp_minus_one); // let cfm = 1.0 / (dt * dt * stiffness + dt * damping); - // NOTE: This simplies to cfm = cfm_coefff / projected_mass: + // NOTE: This simplifies to cfm = cfm_coeff / projected_mass: let cfm_coeff = inv_erp_minus_one * inv_erp_minus_one / ((1.0 + inv_erp_minus_one) * 4.0 * self.damping_ratio * self.damping_ratio); @@ -180,13 +141,14 @@ impl IntegrationParameters { // new_impulse = cfm_factor * (old_impulse - m * delta_vel) // // The value returned by this function is this cfm_factor that can be used directly - // in the constraints solver. + // in the constraint solver. 1.0 / (1.0 + cfm_coeff) } /// The CFM (constraints force mixing) coefficient applied to all joints for constraints regularization pub fn joint_cfm_coeff(&self) -> Real { // Compute CFM assuming a critically damped spring multiplied by the damping ratio. + // The logic is similar to `Self::cfm_factor`. let inv_erp_minus_one = 1.0 / self.joint_erp - 1.0; inv_erp_minus_one * inv_erp_minus_one / ((1.0 + inv_erp_minus_one) @@ -194,23 +156,23 @@ impl IntegrationParameters { * self.joint_damping_ratio * self.joint_damping_ratio) } -} -impl Default for IntegrationParameters { - fn default() -> Self { + /// Initialize the simulation paramaters with settings matching the TGS-soft solver + /// with warmstarting. + /// + /// This is the default configuration, equivalent to [`IntegrationParameters::default()`]. + pub fn tgs_soft() -> Self { Self { dt: 1.0 / 60.0, min_ccd_dt: 1.0 / 60.0 / 100.0, - erp: 0.8, - damping_ratio: 1.0, + erp: 0.1, + damping_ratio: 20.0, joint_erp: 1.0, joint_damping_ratio: 1.0, - allowed_linear_error: 0.001, - max_penetration_correction: Real::MAX, - prediction_distance: 0.002, + warmstart_coefficient: 1.0, num_internal_pgs_iterations: 1, - max_internal_stabilization_iterations: 10, - num_additional_friction_iterations: 4, + num_internal_stabilization_iterations: 2, + num_additional_friction_iterations: 0, num_solver_iterations: NonZeroUsize::new(4).unwrap(), // TODO: what is the optimal value for min_island_size? // It should not be too big so that we don't end up with @@ -218,7 +180,45 @@ impl Default for IntegrationParameters { // However we don't want it to be too small and end up with // tons of islands, reducing SIMD parallelism opportunities. min_island_size: 128, + allowed_linear_error: 0.001, + max_penetration_correction: Real::MAX, + prediction_distance: 0.002, max_ccd_substeps: 1, } } + + /// Initialize the simulation paramaters with settings matching the TGS-soft solver + /// **without** warmstarting. + /// + /// The [`IntegrationParameters::tgs_soft()`] configuration should be preferred unless + /// warmstarting proves to be undesirable for your use-case. + pub fn tgs_soft_without_warmstart() -> Self { + Self { + erp: 0.8, + damping_ratio: 1.0, + warmstart_coefficient: 0.0, + num_additional_friction_iterations: 4, + ..Self::tgs_soft() + } + } + + /// Initializes the integration parameters to match the legacy PGS solver from Rapier version <= 0.17. + /// + /// This exists mainly for testing and comparison purpose. + pub fn pgs_legacy() -> Self { + Self { + erp: 0.8, + damping_ratio: 0.25, + warmstart_coefficient: 0.0, + num_additional_friction_iterations: 4, + num_solver_iterations: NonZeroUsize::new(1).unwrap(), + ..Self::tgs_soft() + } + } +} + +impl Default for IntegrationParameters { + fn default() -> Self { + Self::tgs_soft() + } } -- cgit From c079452a478bb2f5d976cbba162e7f92252b505d Mon Sep 17 00:00:00 2001 From: Sébastien Crozet Date: Wed, 24 Apr 2024 22:37:21 +0200 Subject: feat: add IntegrationParameters::length_unit to adjust internal threshold based on user-defined length units MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dynamics/integration_parameters.rs | 54 +++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 8 deletions(-) (limited to 'src/dynamics/integration_parameters.rs') diff --git a/src/dynamics/integration_parameters.rs b/src/dynamics/integration_parameters.rs index 80ca92c..b883b0e 100644 --- a/src/dynamics/integration_parameters.rs +++ b/src/dynamics/integration_parameters.rs @@ -49,12 +49,33 @@ pub struct IntegrationParameters { /// (default `1.0`). pub warmstart_coefficient: Real, + /// The approximate size of most dynamic objects in the scale. + /// + /// This value is used internally to estimate some length-based tolerance. In particular, the + /// values [`IntegrationParametres::allowed_linear_error`], + /// [`IntegrationParametres::max_penetration_correction`], + /// [`IntegrationParametres::prediction_distance`], [`RigidBodyActivation::linear_threshold`] + /// are scaled by this value implicitly. + /// + /// This value can be understood as the number of units-per-meter in your physical world compared + /// to a human-sized world in meter. For example, in a 2d game, if your typical object size is 100 + /// pixels, set the `[`Self::length_unit`]` parameter to 100.0. The physics engine will interpret + /// it as if 100 pixels is equivalent to 1 meter in its various internal threshold. + /// (default `1.0`). + pub length_unit: Real, + /// Amount of penetration the engine won’t attempt to correct (default: `0.001m`). - pub allowed_linear_error: Real, + /// + /// This value is implicitly scaled by [`IntegrationParameters::length_unit`]. + pub normalized_allowed_linear_error: Real, /// Maximum amount of penetration the solver will attempt to resolve in one timestep. - pub max_penetration_correction: Real, + /// + /// This value is implicitly scaled by [`IntegrationParameters::length_unit`]. + pub normalized_max_penetration_correction: Real, /// The maximal distance separating two objects that will generate predictive contacts (default: `0.002m`). - pub prediction_distance: Real, + /// + /// This value is implicitly scaled by [`IntegrationParameters::length_unit`]. + pub normalized_prediction_distance: Real, /// The number of solver iterations run by the constraints solver for calculating forces (default: `4`). pub num_solver_iterations: NonZeroUsize, /// Number of addition friction resolution iteration run during the last solver sub-step (default: `4`). @@ -157,6 +178,22 @@ impl IntegrationParameters { * self.joint_damping_ratio) } + pub fn allowed_linear_error(&self) -> Real { + self.normalized_allowed_linear_error * self.length_unit + } + + pub fn max_penetration_correction(&self) -> Real { + if self.normalized_max_penetration_correction != Real::MAX { + self.normalized_max_penetration_correction * self.length_unit + } else { + Real::MAX + } + } + + pub fn prediction_distance(&self) -> Real { + self.normalized_prediction_distance * self.length_unit + } + /// Initialize the simulation paramaters with settings matching the TGS-soft solver /// with warmstarting. /// @@ -180,21 +217,22 @@ impl IntegrationParameters { // However we don't want it to be too small and end up with // tons of islands, reducing SIMD parallelism opportunities. min_island_size: 128, - allowed_linear_error: 0.001, - max_penetration_correction: Real::MAX, - prediction_distance: 0.002, + normalized_allowed_linear_error: 0.001, + normalized_max_penetration_correction: Real::MAX, + normalized_prediction_distance: 0.002, max_ccd_substeps: 1, + length_unit: 1.0, } } - /// Initialize the simulation paramaters with settings matching the TGS-soft solver + /// Initialize the simulation parameters with settings matching the TGS-soft solver /// **without** warmstarting. /// /// The [`IntegrationParameters::tgs_soft()`] configuration should be preferred unless /// warmstarting proves to be undesirable for your use-case. pub fn tgs_soft_without_warmstart() -> Self { Self { - erp: 0.8, + erp: 0.6, damping_ratio: 1.0, warmstart_coefficient: 0.0, num_additional_friction_iterations: 4, -- cgit From 0a9153e273dc0bdd4ba6443bd7f4dcfc671faac3 Mon Sep 17 00:00:00 2001 From: Sébastien Crozet Date: Sun, 28 Apr 2024 18:23:30 +0200 Subject: chore: clippy fixes --- src/dynamics/integration_parameters.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src/dynamics/integration_parameters.rs') diff --git a/src/dynamics/integration_parameters.rs b/src/dynamics/integration_parameters.rs index b883b0e..8b2ba3b 100644 --- a/src/dynamics/integration_parameters.rs +++ b/src/dynamics/integration_parameters.rs @@ -178,10 +178,16 @@ impl IntegrationParameters { * self.joint_damping_ratio) } + /// Amount of penetration the engine won’t attempt to correct (default: `0.001` multiplied by + /// [`Self::length_unit`]). pub fn allowed_linear_error(&self) -> Real { self.normalized_allowed_linear_error * self.length_unit } + /// Maximum amount of penetration the solver will attempt to resolve in one timestep. + /// + /// This is equal to [`Self::normalized_max_penetration_correction`] multiplied by + /// [`Self::length_unit`]. pub fn max_penetration_correction(&self) -> Real { if self.normalized_max_penetration_correction != Real::MAX { self.normalized_max_penetration_correction * self.length_unit @@ -190,11 +196,13 @@ impl IntegrationParameters { } } + /// The maximal distance separating two objects that will generate predictive contacts + /// (default: `0.002m` multiped by [`Self::length_unit`]). pub fn prediction_distance(&self) -> Real { self.normalized_prediction_distance * self.length_unit } - /// Initialize the simulation paramaters with settings matching the TGS-soft solver + /// Initialize the simulation parameters with settings matching the TGS-soft solver /// with warmstarting. /// /// This is the default configuration, equivalent to [`IntegrationParameters::default()`]. -- cgit From 425b2fc83d403d2cf6fa5f151479a112bdd05b92 Mon Sep 17 00:00:00 2001 From: Sébastien Crozet Date: Sun, 5 May 2024 14:51:48 +0200 Subject: chore: misc typo fixes --- src/dynamics/integration_parameters.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/dynamics/integration_parameters.rs') diff --git a/src/dynamics/integration_parameters.rs b/src/dynamics/integration_parameters.rs index 8b2ba3b..c662128 100644 --- a/src/dynamics/integration_parameters.rs +++ b/src/dynamics/integration_parameters.rs @@ -49,12 +49,12 @@ pub struct IntegrationParameters { /// (default `1.0`). pub warmstart_coefficient: Real, - /// The approximate size of most dynamic objects in the scale. + /// The approximate size of most dynamic objects in the scene. /// /// This value is used internally to estimate some length-based tolerance. In particular, the - /// values [`IntegrationParametres::allowed_linear_error`], - /// [`IntegrationParametres::max_penetration_correction`], - /// [`IntegrationParametres::prediction_distance`], [`RigidBodyActivation::linear_threshold`] + /// values [`IntegrationParameters::allowed_linear_error`], + /// [`IntegrationParameters::max_penetration_correction`], + /// [`IntegrationParameters::prediction_distance`], [`RigidBodyActivation::linear_threshold`] /// are scaled by this value implicitly. /// /// This value can be understood as the number of units-per-meter in your physical world compared -- cgit From fdd935dbf13d02b3b08a139baca8a96aa5a3247e Mon Sep 17 00:00:00 2001 From: Sébastien Crozet Date: Sat, 25 May 2024 11:05:00 +0200 Subject: feat: make the constraints regularization coefficients configurable with angular frequency instead of explicit ERP --- src/dynamics/integration_parameters.rs | 125 ++++++++++++++++++++++----------- 1 file changed, 83 insertions(+), 42 deletions(-) (limited to 'src/dynamics/integration_parameters.rs') diff --git a/src/dynamics/integration_parameters.rs b/src/dynamics/integration_parameters.rs index c662128..2d580e2 100644 --- a/src/dynamics/integration_parameters.rs +++ b/src/dynamics/integration_parameters.rs @@ -1,4 +1,5 @@ use crate::math::Real; +use na::RealField; use std::num::NonZeroUsize; // TODO: enabling the block solver in 3d introduces a lot of jitters in @@ -9,9 +10,9 @@ pub(crate) static BLOCK_SOLVER_ENABLED: bool = cfg!(feature = "dim2"); #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct IntegrationParameters { - /// The timestep length (default: `1.0 / 60.0`) + /// The timestep length (default: `1.0 / 60.0`). pub dt: Real, - /// Minimum timestep size when using CCD with multiple substeps (default `1.0 / 60.0 / 100.0`) + /// Minimum timestep size when using CCD with multiple substeps (default: `1.0 / 60.0 / 100.0`). /// /// When CCD with multiple substeps is enabled, the timestep is subdivided /// into smaller pieces. This timestep subdivision won't generate timestep @@ -23,20 +24,24 @@ pub struct IntegrationParameters { /// to numerical instabilities. pub min_ccd_dt: Real, - /// 0-1: multiplier for how much of the constraint violation (e.g. contact penetration) - /// will be compensated for during the velocity solve. - /// (default `0.1`). - pub erp: Real, - /// 0-1: the damping ratio used by the springs for Baumgarte constraints stabilization. - /// Lower values make the constraints more compliant (more "springy", allowing more visible penetrations - /// before stabilization). - /// (default `20.0`). - pub damping_ratio: Real, + /// > 0: the damping ratio used by the springs for contact constraint stabilization. + /// Lower values make the constraints more compliant (more "springy", allowing more visible + /// penetrations before stabilization). + /// (default `5.0`). + pub contact_damping_ratio: Real, - /// 0-1: multiplier for how much of the joint violation - /// will be compensated for during the velocity solve. - /// (default `1.0`). - pub joint_erp: Real, + /// > 0: the natural frequency used by the springs for contact constraint regularization. + /// Increasing this value will make it so that penetrations get fixed more quickly at the + /// expense of potential jitter effects due to overshooting. In order to make the simulation + /// look stiffer, it is recommended to increase the [`Self::damping_ratio`] instead of this + /// value. + /// (default: `30.0`). + pub contact_natural_frequency: Real, + + /// > 0: the natural frequency used by the springs for joint constraint regularization. + /// Increasing this value will make it so that penetrations get fixed more quickly. + /// (default: `1.0e6`). + pub joint_natural_frequency: Real, /// The fraction of critical damping applied to the joint for constraints regularization. /// (default `1.0`). @@ -45,7 +50,8 @@ pub struct IntegrationParameters { /// The coefficient in `[0, 1]` applied to warmstart impulses, i.e., impulses that are used as the /// initial solution (instead of 0) at the next simulation step. /// - /// This should generally be set to 1. Can be set to 0 if using a large [`Self::erp`] value. + /// This should generally be set to 1. + /// /// (default `1.0`). pub warmstart_coefficient: Real, @@ -53,7 +59,7 @@ pub struct IntegrationParameters { /// /// This value is used internally to estimate some length-based tolerance. In particular, the /// values [`IntegrationParameters::allowed_linear_error`], - /// [`IntegrationParameters::max_penetration_correction`], + /// [`IntegrationParameters::max_corrective_velocity`], /// [`IntegrationParameters::prediction_distance`], [`RigidBodyActivation::linear_threshold`] /// are scaled by this value implicitly. /// @@ -71,7 +77,7 @@ pub struct IntegrationParameters { /// Maximum amount of penetration the solver will attempt to resolve in one timestep. /// /// This value is implicitly scaled by [`IntegrationParameters::length_unit`]. - pub normalized_max_penetration_correction: Real, + pub normalized_max_corrective_velocity: Real, /// The maximal distance separating two objects that will generate predictive contacts (default: `0.002m`). /// /// This value is implicitly scaled by [`IntegrationParameters::length_unit`]. @@ -123,20 +129,53 @@ impl IntegrationParameters { } } - /// The ERP coefficient, multiplied by the inverse timestep length. + /// The contact’s spring angular frequency for constraints regularization. + pub fn angular_frequency(&self) -> Real { + self.contact_natural_frequency * Real::two_pi() + } + + /// The [`Self::erp`] coefficient, multiplied by the inverse timestep length. pub fn erp_inv_dt(&self) -> Real { - self.erp * self.inv_dt() + let ang_freq = self.angular_frequency(); + ang_freq / (self.dt * ang_freq + 2.0 * self.contact_damping_ratio) + } + + /// The effective Error Reduction Parameter applied for calculating regularization forces + /// on contacts. + /// + /// This parameter is computed automatically from [`Self::natural_frequency`], + /// [`Self::damping_ratio`] and the substep length. + pub fn erp(&self) -> Real { + self.dt * self.erp_inv_dt() + } + + /// The joint’s spring angular frequency for constraint regularization. + pub fn joint_angular_frequency(&self) -> Real { + self.joint_natural_frequency * Real::two_pi() } - /// The joint ERP coefficient, multiplied by the inverse timestep length. + /// The [`Self::joint_erp`] coefficient, multiplied by the inverse timestep length. pub fn joint_erp_inv_dt(&self) -> Real { - self.joint_erp * self.inv_dt() + let ang_freq = self.joint_angular_frequency(); + ang_freq / (self.dt * ang_freq + 2.0 * self.joint_damping_ratio) } - /// The CFM factor to be used in the constraints resolution. + /// The effective Error Reduction Parameter applied for calculating regularization forces + /// on joints. + /// + /// This parameter is computed automatically from [`Self::joint_natural_frequency`], + /// [`Self::joint_damping_ratio`] and the substep length. + pub fn joint_erp(&self) -> Real { + self.dt * self.joint_erp_inv_dt() + } + + /// The CFM factor to be used in the constraint resolution. + /// + /// This parameter is computed automatically from [`Self::natural_frequency`], + /// [`Self::damping_ratio`] and the substep length. pub fn cfm_factor(&self) -> Real { // Compute CFM assuming a critically damped spring multiplied by the damping ratio. - let inv_erp_minus_one = 1.0 / self.erp - 1.0; + let inv_erp_minus_one = 1.0 / self.erp() - 1.0; // let stiffness = 4.0 * damping_ratio * damping_ratio * projected_mass // / (dt * dt * inv_erp_minus_one * inv_erp_minus_one); @@ -145,7 +184,10 @@ impl IntegrationParameters { // let cfm = 1.0 / (dt * dt * stiffness + dt * damping); // NOTE: This simplifies to cfm = cfm_coeff / projected_mass: let cfm_coeff = inv_erp_minus_one * inv_erp_minus_one - / ((1.0 + inv_erp_minus_one) * 4.0 * self.damping_ratio * self.damping_ratio); + / ((1.0 + inv_erp_minus_one) + * 4.0 + * self.contact_damping_ratio + * self.contact_damping_ratio); // Furthermore, we use this coefficient inside of the impulse resolution. // Surprisingly, several simplifications happen there. @@ -166,11 +208,14 @@ impl IntegrationParameters { 1.0 / (1.0 + cfm_coeff) } - /// The CFM (constraints force mixing) coefficient applied to all joints for constraints regularization + /// The CFM (constraints force mixing) coefficient applied to all joints for constraints regularization. + /// + /// This parameter is computed automatically from [`Self::joint_natural_frequency`], + /// [`Self::joint_damping_ratio`] and the substep length. pub fn joint_cfm_coeff(&self) -> Real { // Compute CFM assuming a critically damped spring multiplied by the damping ratio. // The logic is similar to `Self::cfm_factor`. - let inv_erp_minus_one = 1.0 / self.joint_erp - 1.0; + let inv_erp_minus_one = 1.0 / self.joint_erp() - 1.0; inv_erp_minus_one * inv_erp_minus_one / ((1.0 + inv_erp_minus_one) * 4.0 @@ -186,11 +231,11 @@ impl IntegrationParameters { /// Maximum amount of penetration the solver will attempt to resolve in one timestep. /// - /// This is equal to [`Self::normalized_max_penetration_correction`] multiplied by + /// This is equal to [`Self::normalized_max_corrective_velocity`] multiplied by /// [`Self::length_unit`]. - pub fn max_penetration_correction(&self) -> Real { - if self.normalized_max_penetration_correction != Real::MAX { - self.normalized_max_penetration_correction * self.length_unit + pub fn max_corrective_velocity(&self) -> Real { + if self.normalized_max_corrective_velocity != Real::MAX { + self.normalized_max_corrective_velocity * self.length_unit } else { Real::MAX } @@ -210,9 +255,9 @@ impl IntegrationParameters { Self { dt: 1.0 / 60.0, min_ccd_dt: 1.0 / 60.0 / 100.0, - erp: 0.1, - damping_ratio: 20.0, - joint_erp: 1.0, + contact_natural_frequency: 30.0, + contact_damping_ratio: 5.0, + joint_natural_frequency: 1.0e6, joint_damping_ratio: 1.0, warmstart_coefficient: 1.0, num_internal_pgs_iterations: 1, @@ -226,7 +271,7 @@ impl IntegrationParameters { // tons of islands, reducing SIMD parallelism opportunities. min_island_size: 128, normalized_allowed_linear_error: 0.001, - normalized_max_penetration_correction: Real::MAX, + normalized_max_corrective_velocity: 10.0, normalized_prediction_distance: 0.002, max_ccd_substeps: 1, length_unit: 1.0, @@ -240,8 +285,7 @@ impl IntegrationParameters { /// warmstarting proves to be undesirable for your use-case. pub fn tgs_soft_without_warmstart() -> Self { Self { - erp: 0.6, - damping_ratio: 1.0, + contact_damping_ratio: 0.25, warmstart_coefficient: 0.0, num_additional_friction_iterations: 4, ..Self::tgs_soft() @@ -253,12 +297,9 @@ impl IntegrationParameters { /// This exists mainly for testing and comparison purpose. pub fn pgs_legacy() -> Self { Self { - erp: 0.8, - damping_ratio: 0.25, - warmstart_coefficient: 0.0, - num_additional_friction_iterations: 4, num_solver_iterations: NonZeroUsize::new(1).unwrap(), - ..Self::tgs_soft() + num_internal_pgs_iterations: 4, + ..Self::tgs_soft_without_warmstart() } } } -- cgit From cdec395d094bfcc1e5c6ea7d49972f7d2ae3e11b Mon Sep 17 00:00:00 2001 From: Sébastien Crozet Date: Sat, 25 May 2024 11:30:41 +0200 Subject: feat: rename cfm_factor, damping_ratio to contact_cfm_factor and contact_damping_ratio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dynamics/integration_parameters.rs | 40 +++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 17 deletions(-) (limited to 'src/dynamics/integration_parameters.rs') diff --git a/src/dynamics/integration_parameters.rs b/src/dynamics/integration_parameters.rs index 2d580e2..2de58ae 100644 --- a/src/dynamics/integration_parameters.rs +++ b/src/dynamics/integration_parameters.rs @@ -25,25 +25,31 @@ pub struct IntegrationParameters { pub min_ccd_dt: Real, /// > 0: the damping ratio used by the springs for contact constraint stabilization. - /// Lower values make the constraints more compliant (more "springy", allowing more visible + /// + /// Larger values make the constraints more compliant (allowing more visible /// penetrations before stabilization). /// (default `5.0`). pub contact_damping_ratio: Real, /// > 0: the natural frequency used by the springs for contact constraint regularization. + /// /// Increasing this value will make it so that penetrations get fixed more quickly at the /// expense of potential jitter effects due to overshooting. In order to make the simulation - /// look stiffer, it is recommended to increase the [`Self::damping_ratio`] instead of this + /// look stiffer, it is recommended to increase the [`Self::contact_damping_ratio`] instead of this /// value. /// (default: `30.0`). pub contact_natural_frequency: Real, /// > 0: the natural frequency used by the springs for joint constraint regularization. + /// /// Increasing this value will make it so that penetrations get fixed more quickly. /// (default: `1.0e6`). pub joint_natural_frequency: Real, /// The fraction of critical damping applied to the joint for constraints regularization. + /// + /// Larger values make the constraints more compliant (allowing more joint + /// drift before stabilization). /// (default `1.0`). pub joint_damping_ratio: Real, @@ -65,7 +71,7 @@ pub struct IntegrationParameters { /// /// This value can be understood as the number of units-per-meter in your physical world compared /// to a human-sized world in meter. For example, in a 2d game, if your typical object size is 100 - /// pixels, set the `[`Self::length_unit`]` parameter to 100.0. The physics engine will interpret + /// pixels, set the [`Self::length_unit`] parameter to 100.0. The physics engine will interpret /// it as if 100 pixels is equivalent to 1 meter in its various internal threshold. /// (default `1.0`). pub length_unit: Real, @@ -74,7 +80,7 @@ pub struct IntegrationParameters { /// /// This value is implicitly scaled by [`IntegrationParameters::length_unit`]. pub normalized_allowed_linear_error: Real, - /// Maximum amount of penetration the solver will attempt to resolve in one timestep. + /// Maximum amount of penetration the solver will attempt to resolve in one timestep (default: `10.0`). /// /// This value is implicitly scaled by [`IntegrationParameters::length_unit`]. pub normalized_max_corrective_velocity: Real, @@ -84,7 +90,7 @@ pub struct IntegrationParameters { pub normalized_prediction_distance: Real, /// The number of solver iterations run by the constraints solver for calculating forces (default: `4`). pub num_solver_iterations: NonZeroUsize, - /// Number of addition friction resolution iteration run during the last solver sub-step (default: `4`). + /// Number of addition friction resolution iteration run during the last solver sub-step (default: `0`). pub num_additional_friction_iterations: usize, /// Number of internal Project Gauss Seidel (PGS) iterations run at each solver iteration (default: `1`). pub num_internal_pgs_iterations: usize, @@ -130,23 +136,23 @@ impl IntegrationParameters { } /// The contact’s spring angular frequency for constraints regularization. - pub fn angular_frequency(&self) -> Real { + pub fn contact_angular_frequency(&self) -> Real { self.contact_natural_frequency * Real::two_pi() } - /// The [`Self::erp`] coefficient, multiplied by the inverse timestep length. - pub fn erp_inv_dt(&self) -> Real { - let ang_freq = self.angular_frequency(); + /// The [`Self::contact_erp`] coefficient, multiplied by the inverse timestep length. + pub fn contact_erp_inv_dt(&self) -> Real { + let ang_freq = self.contact_angular_frequency(); ang_freq / (self.dt * ang_freq + 2.0 * self.contact_damping_ratio) } /// The effective Error Reduction Parameter applied for calculating regularization forces /// on contacts. /// - /// This parameter is computed automatically from [`Self::natural_frequency`], - /// [`Self::damping_ratio`] and the substep length. - pub fn erp(&self) -> Real { - self.dt * self.erp_inv_dt() + /// This parameter is computed automatically from [`Self::contact_natural_frequency`], + /// [`Self::contact_damping_ratio`] and the substep length. + pub fn contact_erp(&self) -> Real { + self.dt * self.contact_erp_inv_dt() } /// The joint’s spring angular frequency for constraint regularization. @@ -171,11 +177,11 @@ impl IntegrationParameters { /// The CFM factor to be used in the constraint resolution. /// - /// This parameter is computed automatically from [`Self::natural_frequency`], - /// [`Self::damping_ratio`] and the substep length. - pub fn cfm_factor(&self) -> Real { + /// This parameter is computed automatically from [`Self::contact_natural_frequency`], + /// [`Self::contact_damping_ratio`] and the substep length. + pub fn contact_cfm_factor(&self) -> Real { // Compute CFM assuming a critically damped spring multiplied by the damping ratio. - let inv_erp_minus_one = 1.0 / self.erp() - 1.0; + let inv_erp_minus_one = 1.0 / self.contact_erp() - 1.0; // let stiffness = 4.0 * damping_ratio * damping_ratio * projected_mass // / (dt * dt * inv_erp_minus_one * inv_erp_minus_one); -- cgit