Merge pull request #581 from dimforge/solver-pick
feat: rework solver parameters to make it easy to recover the old behaviors
This commit is contained in:
@@ -44,10 +44,12 @@ pub struct IntegrationParameters {
|
|||||||
pub max_penetration_correction: Real,
|
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.002`).
|
||||||
pub prediction_distance: Real,
|
pub prediction_distance: Real,
|
||||||
/// Number of iterations performed to solve friction constraints at solver iteration (default: `2`).
|
|
||||||
pub num_friction_iteration_per_solver_iteration: usize,
|
|
||||||
/// 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`).
|
||||||
|
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,
|
||||||
/// Minimum number of dynamic bodies in each active island (default: `128`).
|
/// Minimum number of dynamic bodies in each active island (default: `128`).
|
||||||
pub min_island_size: usize,
|
pub min_island_size: usize,
|
||||||
/// Maximum number of substeps performed by the solver (default: `1`).
|
/// Maximum number of substeps performed by the solver (default: `1`).
|
||||||
@@ -55,6 +57,52 @@ pub struct IntegrationParameters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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_internal_pgs_iterations;
|
||||||
|
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).
|
/// The inverse of the time-stepping length, i.e. the steps per seconds (Hz).
|
||||||
///
|
///
|
||||||
/// This is zero if `self.dt` is zero.
|
/// This is zero if `self.dt` is zero.
|
||||||
@@ -154,7 +202,8 @@ impl Default for IntegrationParameters {
|
|||||||
allowed_linear_error: 0.001,
|
allowed_linear_error: 0.001,
|
||||||
max_penetration_correction: Real::MAX,
|
max_penetration_correction: Real::MAX,
|
||||||
prediction_distance: 0.002,
|
prediction_distance: 0.002,
|
||||||
num_friction_iteration_per_solver_iteration: 2,
|
num_internal_pgs_iterations: 1,
|
||||||
|
num_additional_friction_iterations: 4,
|
||||||
num_solver_iterations: NonZeroUsize::new(4).unwrap(),
|
num_solver_iterations: NonZeroUsize::new(4).unwrap(),
|
||||||
// TODO: what is the optimal value for min_island_size?
|
// TODO: what is the optimal value for min_island_size?
|
||||||
// It should not be too big so that we don't end up with
|
// It should not be too big so that we don't end up with
|
||||||
|
|||||||
@@ -118,8 +118,11 @@ impl RopeJoint {
|
|||||||
|
|
||||||
/// The maximum distance allowed between the attached objects.
|
/// The maximum distance allowed between the attached objects.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn max_distance(&self) -> Option<Real> {
|
pub fn max_distance(&self) -> Real {
|
||||||
self.data.limits(JointAxis::X).map(|l| l.max)
|
self.data
|
||||||
|
.limits(JointAxis::X)
|
||||||
|
.map(|l| l.max)
|
||||||
|
.unwrap_or(Real::MAX)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the maximum allowed distance between the attached objects.
|
/// Sets the maximum allowed distance between the attached objects.
|
||||||
@@ -146,8 +149,6 @@ pub struct RopeJointBuilder(pub RopeJoint);
|
|||||||
|
|
||||||
impl RopeJointBuilder {
|
impl RopeJointBuilder {
|
||||||
/// Creates a new builder for rope joints.
|
/// Creates a new builder for rope joints.
|
||||||
///
|
|
||||||
/// This axis is expressed in the local-space of both rigid-bodies.
|
|
||||||
pub fn new(max_dist: Real) -> Self {
|
pub fn new(max_dist: Real) -> Self {
|
||||||
Self(RopeJoint::new(max_dist))
|
Self(RopeJoint::new(max_dist))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::math::{Point, Real};
|
|||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
/// A spring-damper joint, applies a force proportional to the distance between two objects.
|
/// A spring-damper joint, applies a force proportional to the distance between two objects.
|
||||||
///
|
///
|
||||||
/// The spring is integrated implicitly, implying that an even undamped spring will still be subject to some
|
/// The spring is integrated implicitly, implying that even an undamped spring will be subject to some
|
||||||
/// amount of numerical damping (so it will eventually come to a rest). More solver iterations, or smaller
|
/// amount of numerical damping (so it will eventually come to a rest). More solver iterations, or smaller
|
||||||
/// timesteps, will lower the effect of numerical damping, providing a more realistic result.
|
/// timesteps, will lower the effect of numerical damping, providing a more realistic result.
|
||||||
pub struct SpringJoint {
|
pub struct SpringJoint {
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ impl RigidBody {
|
|||||||
/// and every rigid-body interacting directly or indirectly with it (through joints
|
/// and every rigid-body interacting directly or indirectly with it (through joints
|
||||||
/// or contacts). This implies a performance hit.
|
/// or contacts). This implies a performance hit.
|
||||||
///
|
///
|
||||||
/// The default value is 0, meaning [`IntegrationParameters::num_solver_iterations`] will
|
/// The default value is 0, meaning exactly [`IntegrationParameters::num_solver_iterations`] will
|
||||||
/// be used as number of solver iterations for this body.
|
/// be used as number of solver iterations for this body.
|
||||||
pub fn set_additional_solver_iterations(&mut self, additional_iterations: usize) {
|
pub fn set_additional_solver_iterations(&mut self, additional_iterations: usize) {
|
||||||
self.additional_solver_iterations = additional_iterations;
|
self.additional_solver_iterations = additional_iterations;
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ impl ParallelVelocitySolver {
|
|||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
for i in 0..params.num_velocity_iterations_per_small_step {
|
for i in 0..params.num_velocity_iterations_per_small_step {
|
||||||
let solve_friction = params.num_friction_iteration_per_solver_iteration + i
|
let solve_friction = params.num_additional_friction_iterations + i
|
||||||
>= params.num_velocity_iterations_per_small_step;
|
>= params.num_velocity_iterations_per_small_step;
|
||||||
// Solve joints.
|
// Solve joints.
|
||||||
solve!(
|
solve!(
|
||||||
@@ -156,11 +156,10 @@ impl ParallelVelocitySolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Solve the remaining friction iterations.
|
// Solve the remaining friction iterations.
|
||||||
let remaining_friction_iterations = if params
|
let remaining_friction_iterations = if params.num_additional_friction_iterations
|
||||||
.num_friction_iteration_per_solver_iteration
|
|
||||||
> params.num_velocity_iterations_per_small_step
|
> params.num_velocity_iterations_per_small_step
|
||||||
{
|
{
|
||||||
params.num_friction_iteration_per_solver_iteration
|
params.num_additional_friction_iterations
|
||||||
- params.num_velocity_iterations_per_small_step
|
- params.num_velocity_iterations_per_small_step
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
|
|||||||
@@ -152,14 +152,14 @@ impl VelocitySolver {
|
|||||||
pub fn solve_constraints(
|
pub fn solve_constraints(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: &IntegrationParameters,
|
params: &IntegrationParameters,
|
||||||
num_solver_iterations: usize,
|
num_substeps: usize,
|
||||||
bodies: &mut RigidBodySet,
|
bodies: &mut RigidBodySet,
|
||||||
multibodies: &mut MultibodyJointSet,
|
multibodies: &mut MultibodyJointSet,
|
||||||
contact_constraints: &mut SolverConstraintsSet<ContactConstraintTypes>,
|
contact_constraints: &mut SolverConstraintsSet<ContactConstraintTypes>,
|
||||||
joint_constraints: &mut SolverConstraintsSet<JointConstraintTypes>,
|
joint_constraints: &mut SolverConstraintsSet<JointConstraintTypes>,
|
||||||
) {
|
) {
|
||||||
for small_step_id in 0..num_solver_iterations {
|
for substep_id in 0..num_substeps {
|
||||||
let is_last_small_step = small_step_id == num_solver_iterations - 1;
|
let is_last_substep = substep_id == num_substeps - 1;
|
||||||
|
|
||||||
for (solver_vels, incr) in self
|
for (solver_vels, incr) in self
|
||||||
.solver_vels
|
.solver_vels
|
||||||
@@ -176,28 +176,27 @@ impl VelocitySolver {
|
|||||||
* Update & solve constraints.
|
* Update & solve constraints.
|
||||||
*/
|
*/
|
||||||
joint_constraints.update(¶ms, multibodies, &self.solver_bodies);
|
joint_constraints.update(¶ms, multibodies, &self.solver_bodies);
|
||||||
contact_constraints.update(¶ms, small_step_id, multibodies, &self.solver_bodies);
|
contact_constraints.update(¶ms, substep_id, multibodies, &self.solver_bodies);
|
||||||
|
|
||||||
joint_constraints.solve(&mut self.solver_vels, &mut self.generic_solver_vels);
|
for _ in 0..params.num_internal_pgs_iterations {
|
||||||
contact_constraints
|
joint_constraints.solve(&mut self.solver_vels, &mut self.generic_solver_vels);
|
||||||
.solve_restitution(&mut self.solver_vels, &mut self.generic_solver_vels);
|
contact_constraints
|
||||||
|
.solve_restitution(&mut self.solver_vels, &mut self.generic_solver_vels);
|
||||||
let num_friction_iterations = if is_last_small_step {
|
|
||||||
params.num_friction_iteration_per_solver_iteration * num_solver_iterations
|
|
||||||
- (num_solver_iterations - 1)
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
};
|
|
||||||
|
|
||||||
for _ in 0..num_friction_iterations {
|
|
||||||
contact_constraints
|
contact_constraints
|
||||||
.solve_friction(&mut self.solver_vels, &mut self.generic_solver_vels);
|
.solve_friction(&mut self.solver_vels, &mut self.generic_solver_vels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if is_last_substep {
|
||||||
|
for _ in 0..params.num_additional_friction_iterations {
|
||||||
|
contact_constraints
|
||||||
|
.solve_friction(&mut self.solver_vels, &mut self.generic_solver_vels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Integrate positions.
|
* Integrate positions.
|
||||||
*/
|
*/
|
||||||
self.integrate_positions(¶ms, is_last_small_step, bodies, multibodies);
|
self.integrate_positions(¶ms, is_last_substep, bodies, multibodies);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Resolution without bias.
|
* Resolution without bias.
|
||||||
@@ -211,7 +210,7 @@ impl VelocitySolver {
|
|||||||
pub fn integrate_positions(
|
pub fn integrate_positions(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: &IntegrationParameters,
|
params: &IntegrationParameters,
|
||||||
is_last_small_step: bool,
|
is_last_substep: bool,
|
||||||
bodies: &mut RigidBodySet,
|
bodies: &mut RigidBodySet,
|
||||||
multibodies: &mut MultibodyJointSet,
|
multibodies: &mut MultibodyJointSet,
|
||||||
) {
|
) {
|
||||||
@@ -241,9 +240,9 @@ impl VelocitySolver {
|
|||||||
multibody.velocities.copy_from(&solver_vels);
|
multibody.velocities.copy_from(&solver_vels);
|
||||||
multibody.integrate(params.dt);
|
multibody.integrate(params.dt);
|
||||||
// PERF: we could have a mode where it doesn’t write back to the `bodies` yet.
|
// PERF: we could have a mode where it doesn’t write back to the `bodies` yet.
|
||||||
multibody.forward_kinematics(bodies, !is_last_small_step);
|
multibody.forward_kinematics(bodies, !is_last_substep);
|
||||||
|
|
||||||
if !is_last_small_step {
|
if !is_last_substep {
|
||||||
// These are very expensive and not needed if we don’t
|
// These are very expensive and not needed if we don’t
|
||||||
// have to run another step.
|
// have to run another step.
|
||||||
multibody.update_dynamics(params.dt, bodies);
|
multibody.update_dynamics(params.dt, bodies);
|
||||||
@@ -260,7 +259,7 @@ impl VelocitySolver {
|
|||||||
pub fn writeback_bodies(
|
pub fn writeback_bodies(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: &IntegrationParameters,
|
params: &IntegrationParameters,
|
||||||
num_solver_iterations: usize,
|
num_substeps: usize,
|
||||||
islands: &IslandManager,
|
islands: &IslandManager,
|
||||||
island_id: usize,
|
island_id: usize,
|
||||||
bodies: &mut RigidBodySet,
|
bodies: &mut RigidBodySet,
|
||||||
@@ -302,9 +301,9 @@ impl VelocitySolver {
|
|||||||
// solver integrating at intermediate sub-steps. Should we just switch
|
// solver integrating at intermediate sub-steps. Should we just switch
|
||||||
// to interpolation?
|
// to interpolation?
|
||||||
rb.integrated_vels.linvel =
|
rb.integrated_vels.linvel =
|
||||||
solver_body.integrated_vels.linvel / num_solver_iterations as Real;
|
solver_body.integrated_vels.linvel / num_substeps as Real;
|
||||||
rb.integrated_vels.angvel =
|
rb.integrated_vels.angvel =
|
||||||
solver_body.integrated_vels.angvel / num_solver_iterations as Real;
|
solver_body.integrated_vels.angvel / num_substeps as Real;
|
||||||
rb.vels = new_vels;
|
rb.vels = new_vels;
|
||||||
rb.pos.next_position = solver_body.position;
|
rb.pos.next_position = solver_body.position;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,12 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum RapierSolverType {
|
||||||
|
SmallStepsPgs,
|
||||||
|
StandardPgs,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
pub struct TestbedState {
|
pub struct TestbedState {
|
||||||
pub running: RunMode,
|
pub running: RunMode,
|
||||||
@@ -121,6 +127,7 @@ pub struct TestbedState {
|
|||||||
pub example_names: Vec<&'static str>,
|
pub example_names: Vec<&'static str>,
|
||||||
pub selected_example: usize,
|
pub selected_example: usize,
|
||||||
pub selected_backend: usize,
|
pub selected_backend: usize,
|
||||||
|
pub solver_type: RapierSolverType,
|
||||||
pub physx_use_two_friction_directions: bool,
|
pub physx_use_two_friction_directions: bool,
|
||||||
pub snapshot: Option<PhysicsSnapshot>,
|
pub snapshot: Option<PhysicsSnapshot>,
|
||||||
nsteps: usize,
|
nsteps: usize,
|
||||||
@@ -204,6 +211,7 @@ impl TestbedApp {
|
|||||||
example_names: Vec::new(),
|
example_names: Vec::new(),
|
||||||
selected_example: 0,
|
selected_example: 0,
|
||||||
selected_backend: RAPIER_BACKEND,
|
selected_backend: RAPIER_BACKEND,
|
||||||
|
solver_type: RapierSolverType::SmallStepsPgs,
|
||||||
physx_use_two_friction_directions: true,
|
physx_use_two_friction_directions: true,
|
||||||
nsteps: 1,
|
nsteps: 1,
|
||||||
camera_locked: false,
|
camera_locked: false,
|
||||||
@@ -1189,6 +1197,14 @@ fn update_testbed(
|
|||||||
|
|
||||||
if state.selected_example != prev_example {
|
if state.selected_example != prev_example {
|
||||||
harness.physics.integration_parameters = IntegrationParameters::default();
|
harness.physics.integration_parameters = IntegrationParameters::default();
|
||||||
|
|
||||||
|
match state.solver_type {
|
||||||
|
RapierSolverType::SmallStepsPgs => {} // It’s already the default.
|
||||||
|
RapierSolverType::StandardPgs => harness
|
||||||
|
.physics
|
||||||
|
.integration_parameters
|
||||||
|
.switch_to_standard_pgs_solver(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let selected_example = state.selected_example;
|
let selected_example = state.selected_example;
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use std::num::NonZeroUsize;
|
|||||||
use crate::debug_render::DebugRenderPipelineResource;
|
use crate::debug_render::DebugRenderPipelineResource;
|
||||||
use crate::harness::Harness;
|
use crate::harness::Harness;
|
||||||
use crate::testbed::{
|
use crate::testbed::{
|
||||||
RunMode, TestbedActionFlags, TestbedState, TestbedStateFlags, PHYSX_BACKEND_PATCH_FRICTION,
|
RapierSolverType, RunMode, TestbedActionFlags, TestbedState, TestbedStateFlags,
|
||||||
PHYSX_BACKEND_TWO_FRICTION_DIR,
|
PHYSX_BACKEND_PATCH_FRICTION, PHYSX_BACKEND_TWO_FRICTION_DIR,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::PhysicsState;
|
use crate::PhysicsState;
|
||||||
@@ -107,6 +107,34 @@ pub fn update_ui(
|
|||||||
integration_parameters.num_solver_iterations =
|
integration_parameters.num_solver_iterations =
|
||||||
NonZeroUsize::new(num_iterations).unwrap();
|
NonZeroUsize::new(num_iterations).unwrap();
|
||||||
} else {
|
} else {
|
||||||
|
let mut changed = false;
|
||||||
|
egui::ComboBox::from_label("solver type")
|
||||||
|
.width(150.0)
|
||||||
|
.selected_text(format!("{:?}", state.solver_type))
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
let solver_types = [
|
||||||
|
RapierSolverType::SmallStepsPgs,
|
||||||
|
RapierSolverType::StandardPgs,
|
||||||
|
];
|
||||||
|
for sty in solver_types {
|
||||||
|
changed = ui
|
||||||
|
.selectable_value(&mut state.solver_type, sty, format!("{sty:?}"))
|
||||||
|
.changed()
|
||||||
|
|| changed;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
match state.solver_type {
|
||||||
|
RapierSolverType::SmallStepsPgs => {
|
||||||
|
integration_parameters.switch_to_small_steps_pgs_solver()
|
||||||
|
}
|
||||||
|
RapierSolverType::StandardPgs => {
|
||||||
|
integration_parameters.switch_to_standard_pgs_solver()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut num_iterations = integration_parameters.num_solver_iterations.get();
|
let mut num_iterations = integration_parameters.num_solver_iterations.get();
|
||||||
ui.add(Slider::new(&mut num_iterations, 1..=40).text("num solver iters."));
|
ui.add(Slider::new(&mut num_iterations, 1..=40).text("num solver iters."));
|
||||||
integration_parameters.num_solver_iterations =
|
integration_parameters.num_solver_iterations =
|
||||||
@@ -114,10 +142,17 @@ pub fn update_ui(
|
|||||||
|
|
||||||
ui.add(
|
ui.add(
|
||||||
Slider::new(
|
Slider::new(
|
||||||
&mut integration_parameters.num_friction_iteration_per_solver_iteration,
|
&mut integration_parameters.num_internal_pgs_iterations,
|
||||||
1..=40,
|
1..=40,
|
||||||
)
|
)
|
||||||
.text("frict. iters. per solver iters."),
|
.text("num internal PGS iters."),
|
||||||
|
);
|
||||||
|
ui.add(
|
||||||
|
Slider::new(
|
||||||
|
&mut integration_parameters.num_additional_friction_iterations,
|
||||||
|
1..=40,
|
||||||
|
)
|
||||||
|
.text("num additional frict. iters."),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user