feat: add IntegrationParameters::length_unit to adjust internal threshold based on user-defined length units

This commit is contained in:
Sébastien Crozet
2024-04-24 22:37:21 +02:00
committed by Sébastien Crozet
parent 6635d49c8b
commit c079452a47
10 changed files with 80 additions and 30 deletions

View File

@@ -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 wont 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,

View File

@@ -150,6 +150,7 @@ impl IslandManager {
pub(crate) fn update_active_set_with_contacts(
&mut self,
dt: Real,
length_unit: Real,
bodies: &mut RigidBodySet,
colliders: &ColliderSet,
narrow_phase: &NarrowPhase,
@@ -181,7 +182,7 @@ impl IslandManager {
let sq_linvel = rb.vels.linvel.norm_squared();
let sq_angvel = rb.vels.angvel.gdot(rb.vels.angvel);
update_energy(&mut rb.activation, sq_linvel, sq_angvel, dt);
update_energy(length_unit, &mut rb.activation, sq_linvel, sq_angvel, dt);
if rb.activation.time_since_can_sleep >= rb.activation.time_until_sleep {
// Mark them as sleeping for now. This will
@@ -324,8 +325,15 @@ impl IslandManager {
}
}
fn update_energy(activation: &mut RigidBodyActivation, sq_linvel: Real, sq_angvel: Real, dt: Real) {
if sq_linvel < activation.linear_threshold * activation.linear_threshold.abs()
fn update_energy(
length_unit: Real,
activation: &mut RigidBodyActivation,
sq_linvel: Real,
sq_angvel: Real,
dt: Real,
) {
let linear_threshold = activation.normalized_linear_threshold * length_unit;
if sq_linvel < linear_threshold * linear_threshold.abs()
&& sq_angvel < activation.angular_threshold * activation.angular_threshold.abs()
{
activation.time_since_can_sleep += dt;

View File

@@ -1498,7 +1498,7 @@ impl RigidBodyBuilder {
}
if !self.can_sleep {
rb.activation.linear_threshold = -1.0;
rb.activation.normalized_linear_threshold = -1.0;
rb.activation.angular_threshold = -1.0;
}

View File

@@ -995,7 +995,10 @@ impl RigidBodyDominance {
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub struct RigidBodyActivation {
/// The threshold linear velocity bellow which the body can fall asleep.
pub linear_threshold: Real,
///
/// The value is "normalized", i.e., the actual threshold applied by the physics engine
/// is equal to this value multiplied by [`IntegrationParameters::length_unit`].
pub normalized_linear_threshold: Real,
/// The angular linear velocity bellow which the body can fall asleep.
pub angular_threshold: Real,
/// The amount of time the rigid-body must remain below the thresholds to be put to sleep.
@@ -1014,7 +1017,7 @@ impl Default for RigidBodyActivation {
impl RigidBodyActivation {
/// The default linear velocity bellow which a body can be put to sleep.
pub fn default_linear_threshold() -> Real {
pub fn default_normalized_linear_threshold() -> Real {
0.4
}
@@ -1032,7 +1035,7 @@ impl RigidBodyActivation {
/// Create a new rb_activation status initialised with the default rb_activation threshold and is active.
pub fn active() -> Self {
RigidBodyActivation {
linear_threshold: Self::default_linear_threshold(),
normalized_linear_threshold: Self::default_normalized_linear_threshold(),
angular_threshold: Self::default_angular_threshold(),
time_until_sleep: Self::default_time_until_sleep(),
time_since_can_sleep: 0.0,
@@ -1043,7 +1046,7 @@ impl RigidBodyActivation {
/// Create a new rb_activation status initialised with the default rb_activation threshold and is inactive.
pub fn inactive() -> Self {
RigidBodyActivation {
linear_threshold: Self::default_linear_threshold(),
normalized_linear_threshold: Self::default_normalized_linear_threshold(),
angular_threshold: Self::default_angular_threshold(),
time_until_sleep: Self::default_time_until_sleep(),
time_since_can_sleep: Self::default_time_until_sleep(),
@@ -1054,7 +1057,7 @@ impl RigidBodyActivation {
/// Create a new activation status that prevents the rigid-body from sleeping.
pub fn cannot_sleep() -> Self {
RigidBodyActivation {
linear_threshold: -1.0,
normalized_linear_threshold: -1.0,
angular_threshold: -1.0,
..Self::active()
}

View File

@@ -310,8 +310,8 @@ impl OneBodyConstraintBuilder {
{
let rhs_wo_bias = info.normal_rhs_wo_bias + dist.max(0.0) * inv_dt;
let rhs_bias = erp_inv_dt
* (dist + params.allowed_linear_error)
.clamp(-params.max_penetration_correction, 0.0);
* (dist + params.allowed_linear_error())
.clamp(-params.max_penetration_correction(), 0.0);
let new_rhs = rhs_wo_bias + rhs_bias;
is_fast_contact = is_fast_contact || (-new_rhs * params.dt > ccd_thickness * 0.5);

View File

@@ -265,9 +265,9 @@ impl SimdOneBodyConstraintBuilder {
let cfm_factor = SimdReal::splat(params.cfm_factor());
let dt = SimdReal::splat(params.dt);
let inv_dt = SimdReal::splat(params.inv_dt());
let allowed_lin_err = SimdReal::splat(params.allowed_linear_error);
let allowed_lin_err = SimdReal::splat(params.allowed_linear_error());
let erp_inv_dt = SimdReal::splat(params.erp_inv_dt());
let max_penetration_correction = SimdReal::splat(params.max_penetration_correction);
let max_penetration_correction = SimdReal::splat(params.max_penetration_correction());
let warmstart_coeff = SimdReal::splat(params.warmstart_coefficient);
let rb2 = gather![|ii| &bodies[constraint.solver_vel2[ii]]];

View File

@@ -410,8 +410,8 @@ impl TwoBodyConstraintBuilder {
{
let rhs_wo_bias = info.normal_rhs_wo_bias + dist.max(0.0) * inv_dt;
let rhs_bias = erp_inv_dt
* (dist + params.allowed_linear_error)
.clamp(-params.max_penetration_correction, 0.0);
* (dist + params.allowed_linear_error())
.clamp(-params.max_penetration_correction(), 0.0);
let new_rhs = rhs_wo_bias + rhs_bias;
is_fast_contact = is_fast_contact || (-new_rhs * params.dt > ccd_thickness * 0.5);

View File

@@ -255,9 +255,9 @@ impl TwoBodyConstraintBuilderSimd {
let cfm_factor = SimdReal::splat(params.cfm_factor());
let dt = SimdReal::splat(params.dt);
let inv_dt = SimdReal::splat(params.inv_dt());
let allowed_lin_err = SimdReal::splat(params.allowed_linear_error);
let allowed_lin_err = SimdReal::splat(params.allowed_linear_error());
let erp_inv_dt = SimdReal::splat(params.erp_inv_dt());
let max_penetration_correction = SimdReal::splat(params.max_penetration_correction);
let max_penetration_correction = SimdReal::splat(params.max_penetration_correction());
let warmstart_coeff = SimdReal::splat(params.warmstart_coefficient);
let rb1 = gather![|ii| &bodies[constraint.solver_vel1[ii]]];