feat: add IntegrationParameters::length_unit to adjust internal threshold based on user-defined length units
This commit is contained in:
committed by
Sébastien Crozet
parent
6635d49c8b
commit
c079452a47
@@ -49,12 +49,33 @@ pub struct IntegrationParameters {
|
|||||||
/// (default `1.0`).
|
/// (default `1.0`).
|
||||||
pub warmstart_coefficient: Real,
|
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`).
|
/// 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.
|
/// 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`).
|
/// 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`).
|
/// The number of solver iterations run by the constraints solver for calculating forces (default: `4`).
|
||||||
pub num_solver_iterations: NonZeroUsize,
|
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: `4`).
|
||||||
@@ -157,6 +178,22 @@ impl IntegrationParameters {
|
|||||||
* self.joint_damping_ratio)
|
* 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
|
/// Initialize the simulation paramaters with settings matching the TGS-soft solver
|
||||||
/// with warmstarting.
|
/// with warmstarting.
|
||||||
///
|
///
|
||||||
@@ -180,21 +217,22 @@ impl IntegrationParameters {
|
|||||||
// However we don't want it to be too small and end up with
|
// However we don't want it to be too small and end up with
|
||||||
// tons of islands, reducing SIMD parallelism opportunities.
|
// tons of islands, reducing SIMD parallelism opportunities.
|
||||||
min_island_size: 128,
|
min_island_size: 128,
|
||||||
allowed_linear_error: 0.001,
|
normalized_allowed_linear_error: 0.001,
|
||||||
max_penetration_correction: Real::MAX,
|
normalized_max_penetration_correction: Real::MAX,
|
||||||
prediction_distance: 0.002,
|
normalized_prediction_distance: 0.002,
|
||||||
max_ccd_substeps: 1,
|
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.
|
/// **without** warmstarting.
|
||||||
///
|
///
|
||||||
/// The [`IntegrationParameters::tgs_soft()`] configuration should be preferred unless
|
/// The [`IntegrationParameters::tgs_soft()`] configuration should be preferred unless
|
||||||
/// warmstarting proves to be undesirable for your use-case.
|
/// warmstarting proves to be undesirable for your use-case.
|
||||||
pub fn tgs_soft_without_warmstart() -> Self {
|
pub fn tgs_soft_without_warmstart() -> Self {
|
||||||
Self {
|
Self {
|
||||||
erp: 0.8,
|
erp: 0.6,
|
||||||
damping_ratio: 1.0,
|
damping_ratio: 1.0,
|
||||||
warmstart_coefficient: 0.0,
|
warmstart_coefficient: 0.0,
|
||||||
num_additional_friction_iterations: 4,
|
num_additional_friction_iterations: 4,
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ impl IslandManager {
|
|||||||
pub(crate) fn update_active_set_with_contacts(
|
pub(crate) fn update_active_set_with_contacts(
|
||||||
&mut self,
|
&mut self,
|
||||||
dt: Real,
|
dt: Real,
|
||||||
|
length_unit: Real,
|
||||||
bodies: &mut RigidBodySet,
|
bodies: &mut RigidBodySet,
|
||||||
colliders: &ColliderSet,
|
colliders: &ColliderSet,
|
||||||
narrow_phase: &NarrowPhase,
|
narrow_phase: &NarrowPhase,
|
||||||
@@ -181,7 +182,7 @@ impl IslandManager {
|
|||||||
let sq_linvel = rb.vels.linvel.norm_squared();
|
let sq_linvel = rb.vels.linvel.norm_squared();
|
||||||
let sq_angvel = rb.vels.angvel.gdot(rb.vels.angvel);
|
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 {
|
if rb.activation.time_since_can_sleep >= rb.activation.time_until_sleep {
|
||||||
// Mark them as sleeping for now. This will
|
// 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) {
|
fn update_energy(
|
||||||
if sq_linvel < activation.linear_threshold * activation.linear_threshold.abs()
|
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()
|
&& sq_angvel < activation.angular_threshold * activation.angular_threshold.abs()
|
||||||
{
|
{
|
||||||
activation.time_since_can_sleep += dt;
|
activation.time_since_can_sleep += dt;
|
||||||
|
|||||||
@@ -1498,7 +1498,7 @@ impl RigidBodyBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !self.can_sleep {
|
if !self.can_sleep {
|
||||||
rb.activation.linear_threshold = -1.0;
|
rb.activation.normalized_linear_threshold = -1.0;
|
||||||
rb.activation.angular_threshold = -1.0;
|
rb.activation.angular_threshold = -1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -995,7 +995,10 @@ impl RigidBodyDominance {
|
|||||||
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
||||||
pub struct RigidBodyActivation {
|
pub struct RigidBodyActivation {
|
||||||
/// The threshold linear velocity bellow which the body can fall asleep.
|
/// 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.
|
/// The angular linear velocity bellow which the body can fall asleep.
|
||||||
pub angular_threshold: Real,
|
pub angular_threshold: Real,
|
||||||
/// The amount of time the rigid-body must remain below the thresholds to be put to sleep.
|
/// 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 {
|
impl RigidBodyActivation {
|
||||||
/// The default linear velocity bellow which a body can be put to sleep.
|
/// 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
|
0.4
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1032,7 +1035,7 @@ impl RigidBodyActivation {
|
|||||||
/// Create a new rb_activation status initialised with the default rb_activation threshold and is active.
|
/// Create a new rb_activation status initialised with the default rb_activation threshold and is active.
|
||||||
pub fn active() -> Self {
|
pub fn active() -> Self {
|
||||||
RigidBodyActivation {
|
RigidBodyActivation {
|
||||||
linear_threshold: Self::default_linear_threshold(),
|
normalized_linear_threshold: Self::default_normalized_linear_threshold(),
|
||||||
angular_threshold: Self::default_angular_threshold(),
|
angular_threshold: Self::default_angular_threshold(),
|
||||||
time_until_sleep: Self::default_time_until_sleep(),
|
time_until_sleep: Self::default_time_until_sleep(),
|
||||||
time_since_can_sleep: 0.0,
|
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.
|
/// Create a new rb_activation status initialised with the default rb_activation threshold and is inactive.
|
||||||
pub fn inactive() -> Self {
|
pub fn inactive() -> Self {
|
||||||
RigidBodyActivation {
|
RigidBodyActivation {
|
||||||
linear_threshold: Self::default_linear_threshold(),
|
normalized_linear_threshold: Self::default_normalized_linear_threshold(),
|
||||||
angular_threshold: Self::default_angular_threshold(),
|
angular_threshold: Self::default_angular_threshold(),
|
||||||
time_until_sleep: Self::default_time_until_sleep(),
|
time_until_sleep: Self::default_time_until_sleep(),
|
||||||
time_since_can_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.
|
/// Create a new activation status that prevents the rigid-body from sleeping.
|
||||||
pub fn cannot_sleep() -> Self {
|
pub fn cannot_sleep() -> Self {
|
||||||
RigidBodyActivation {
|
RigidBodyActivation {
|
||||||
linear_threshold: -1.0,
|
normalized_linear_threshold: -1.0,
|
||||||
angular_threshold: -1.0,
|
angular_threshold: -1.0,
|
||||||
..Self::active()
|
..Self::active()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -310,8 +310,8 @@ impl OneBodyConstraintBuilder {
|
|||||||
{
|
{
|
||||||
let rhs_wo_bias = info.normal_rhs_wo_bias + dist.max(0.0) * inv_dt;
|
let rhs_wo_bias = info.normal_rhs_wo_bias + dist.max(0.0) * inv_dt;
|
||||||
let rhs_bias = erp_inv_dt
|
let rhs_bias = erp_inv_dt
|
||||||
* (dist + params.allowed_linear_error)
|
* (dist + params.allowed_linear_error())
|
||||||
.clamp(-params.max_penetration_correction, 0.0);
|
.clamp(-params.max_penetration_correction(), 0.0);
|
||||||
let new_rhs = rhs_wo_bias + rhs_bias;
|
let new_rhs = rhs_wo_bias + rhs_bias;
|
||||||
is_fast_contact = is_fast_contact || (-new_rhs * params.dt > ccd_thickness * 0.5);
|
is_fast_contact = is_fast_contact || (-new_rhs * params.dt > ccd_thickness * 0.5);
|
||||||
|
|
||||||
|
|||||||
@@ -265,9 +265,9 @@ impl SimdOneBodyConstraintBuilder {
|
|||||||
let cfm_factor = SimdReal::splat(params.cfm_factor());
|
let cfm_factor = SimdReal::splat(params.cfm_factor());
|
||||||
let dt = SimdReal::splat(params.dt);
|
let dt = SimdReal::splat(params.dt);
|
||||||
let inv_dt = SimdReal::splat(params.inv_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 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 warmstart_coeff = SimdReal::splat(params.warmstart_coefficient);
|
||||||
|
|
||||||
let rb2 = gather![|ii| &bodies[constraint.solver_vel2[ii]]];
|
let rb2 = gather![|ii| &bodies[constraint.solver_vel2[ii]]];
|
||||||
|
|||||||
@@ -410,8 +410,8 @@ impl TwoBodyConstraintBuilder {
|
|||||||
{
|
{
|
||||||
let rhs_wo_bias = info.normal_rhs_wo_bias + dist.max(0.0) * inv_dt;
|
let rhs_wo_bias = info.normal_rhs_wo_bias + dist.max(0.0) * inv_dt;
|
||||||
let rhs_bias = erp_inv_dt
|
let rhs_bias = erp_inv_dt
|
||||||
* (dist + params.allowed_linear_error)
|
* (dist + params.allowed_linear_error())
|
||||||
.clamp(-params.max_penetration_correction, 0.0);
|
.clamp(-params.max_penetration_correction(), 0.0);
|
||||||
let new_rhs = rhs_wo_bias + rhs_bias;
|
let new_rhs = rhs_wo_bias + rhs_bias;
|
||||||
is_fast_contact = is_fast_contact || (-new_rhs * params.dt > ccd_thickness * 0.5);
|
is_fast_contact = is_fast_contact || (-new_rhs * params.dt > ccd_thickness * 0.5);
|
||||||
|
|
||||||
|
|||||||
@@ -255,9 +255,9 @@ impl TwoBodyConstraintBuilderSimd {
|
|||||||
let cfm_factor = SimdReal::splat(params.cfm_factor());
|
let cfm_factor = SimdReal::splat(params.cfm_factor());
|
||||||
let dt = SimdReal::splat(params.dt);
|
let dt = SimdReal::splat(params.dt);
|
||||||
let inv_dt = SimdReal::splat(params.inv_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 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 warmstart_coeff = SimdReal::splat(params.warmstart_coefficient);
|
||||||
|
|
||||||
let rb1 = gather![|ii| &bodies[constraint.solver_vel1[ii]]];
|
let rb1 = gather![|ii| &bodies[constraint.solver_vel1[ii]]];
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ impl PhysicsPipeline {
|
|||||||
self.broadphase_collider_pairs.clear();
|
self.broadphase_collider_pairs.clear();
|
||||||
broad_phase.update(
|
broad_phase.update(
|
||||||
integration_parameters.dt,
|
integration_parameters.dt,
|
||||||
integration_parameters.prediction_distance,
|
integration_parameters.prediction_distance(),
|
||||||
colliders,
|
colliders,
|
||||||
bodies,
|
bodies,
|
||||||
modified_colliders,
|
modified_colliders,
|
||||||
@@ -143,7 +143,7 @@ impl PhysicsPipeline {
|
|||||||
events,
|
events,
|
||||||
);
|
);
|
||||||
narrow_phase.compute_contacts(
|
narrow_phase.compute_contacts(
|
||||||
integration_parameters.prediction_distance,
|
integration_parameters.prediction_distance(),
|
||||||
integration_parameters.dt,
|
integration_parameters.dt,
|
||||||
bodies,
|
bodies,
|
||||||
colliders,
|
colliders,
|
||||||
@@ -174,6 +174,7 @@ impl PhysicsPipeline {
|
|||||||
self.counters.stages.island_construction_time.resume();
|
self.counters.stages.island_construction_time.resume();
|
||||||
islands.update_active_set_with_contacts(
|
islands.update_active_set_with_contacts(
|
||||||
integration_parameters.dt,
|
integration_parameters.dt,
|
||||||
|
integration_parameters.length_unit,
|
||||||
bodies,
|
bodies,
|
||||||
colliders,
|
colliders,
|
||||||
narrow_phase,
|
narrow_phase,
|
||||||
|
|||||||
@@ -1355,15 +1355,15 @@ fn update_testbed(
|
|||||||
{
|
{
|
||||||
if state.flags.contains(TestbedStateFlags::SLEEP) {
|
if state.flags.contains(TestbedStateFlags::SLEEP) {
|
||||||
for (_, body) in harness.physics.bodies.iter_mut() {
|
for (_, body) in harness.physics.bodies.iter_mut() {
|
||||||
body.activation_mut().linear_threshold =
|
body.activation_mut().normalized_linear_threshold =
|
||||||
RigidBodyActivation::default_linear_threshold();
|
RigidBodyActivation::default_normalized_linear_threshold();
|
||||||
body.activation_mut().angular_threshold =
|
body.activation_mut().angular_threshold =
|
||||||
RigidBodyActivation::default_angular_threshold();
|
RigidBodyActivation::default_angular_threshold();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (_, body) in harness.physics.bodies.iter_mut() {
|
for (_, body) in harness.physics.bodies.iter_mut() {
|
||||||
body.wake_up(true);
|
body.wake_up(true);
|
||||||
body.activation_mut().linear_threshold = -1.0;
|
body.activation_mut().normalized_linear_threshold = -1.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user