Skip to content

Commit 1af3415

Browse files
committed
Add softness to joints
1 parent f4b7c3d commit 1af3415

18 files changed

+376
-89
lines changed

examples2d/joints2.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ pub fn init_world(testbed: &mut Testbed) {
4444
// Vertical joint.
4545
if i > 0 {
4646
let parent_handle = *body_handles.last().unwrap();
47-
let joint = RevoluteJointBuilder::new().local_anchor2(point![0.0, shift]);
47+
let joint = RevoluteJointBuilder::new().local_anchor2(point![0.0, shift]).natural_frequency(5.0 * (i+1) as f32).damping_ratio(0.1 * (i+1) as f32);
4848
impulse_joints.insert(parent_handle, child_handle, joint, true);
4949
}
5050

5151
// Horizontal joint.
5252
if k > 0 {
5353
let parent_index = body_handles.len() - numi;
5454
let parent_handle = body_handles[parent_index];
55-
let joint = RevoluteJointBuilder::new().local_anchor2(point![-shift, 0.0]);
55+
let joint = RevoluteJointBuilder::new().local_anchor2(point![-shift, 0.0]).natural_frequency(5.0 * (i+1) as f32).damping_ratio(0.1 * (i+1) as f32);
5656
impulse_joints.insert(parent_handle, child_handle, joint, true);
5757
}
5858

examples2d/pin_slot_joint2.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,5 @@ pub fn init_world(testbed: &mut Testbed) {
8181
* Set up the testbed.
8282
*/
8383
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
84-
testbed.set_character_body(character_handle);
8584
testbed.look_at(point![0.0, 1.0], 100.0);
8685
}

src/dynamics/integration_parameters.rs

Lines changed: 28 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -96,19 +96,6 @@ pub struct IntegrationParameters {
9696
/// (default: `30.0`).
9797
pub contact_natural_frequency: Real,
9898

99-
/// > 0: the natural frequency used by the springs for joint constraint regularization.
100-
///
101-
/// Increasing this value will make it so that penetrations get fixed more quickly.
102-
/// (default: `1.0e6`).
103-
pub joint_natural_frequency: Real,
104-
105-
/// The fraction of critical damping applied to the joint for constraints regularization.
106-
///
107-
/// Larger values make the constraints more compliant (allowing more joint
108-
/// drift before stabilization).
109-
/// (default `1.0`).
110-
pub joint_damping_ratio: Real,
111-
11299
/// The coefficient in `[0, 1]` applied to warmstart impulses, i.e., impulses that are used as the
113100
/// initial solution (instead of 0) at the next simulation step.
114101
///
@@ -212,26 +199,7 @@ impl IntegrationParameters {
212199
pub fn contact_erp(&self) -> Real {
213200
self.dt * self.contact_erp_inv_dt()
214201
}
215-
216-
/// The joint’s spring angular frequency for constraint regularization.
217-
pub fn joint_angular_frequency(&self) -> Real {
218-
self.joint_natural_frequency * Real::two_pi()
219-
}
220-
221-
/// The [`Self::joint_erp`] coefficient, multiplied by the inverse timestep length.
222-
pub fn joint_erp_inv_dt(&self) -> Real {
223-
let ang_freq = self.joint_angular_frequency();
224-
ang_freq / (self.dt * ang_freq + 2.0 * self.joint_damping_ratio)
225-
}
226-
227-
/// The effective Error Reduction Parameter applied for calculating regularization forces
228-
/// on joints.
229-
///
230-
/// This parameter is computed automatically from [`Self::joint_natural_frequency`],
231-
/// [`Self::joint_damping_ratio`] and the substep length.
232-
pub fn joint_erp(&self) -> Real {
233-
self.dt * self.joint_erp_inv_dt()
234-
}
202+
235203

236204
/// The CFM factor to be used in the constraint resolution.
237205
///
@@ -281,22 +249,39 @@ impl IntegrationParameters {
281249
///
282250
/// This parameter is computed automatically from [`Self::joint_natural_frequency`],
283251
/// [`Self::joint_damping_ratio`] and the substep length.
284-
pub fn joint_cfm_coeff(&self) -> Real {
285-
// Compute CFM assuming a critically damped spring multiplied by the damping ratio.
286-
// The logic is similar to `Self::contact_cfm_factor`.
287-
let joint_erp = self.joint_erp();
252+
253+
254+
/// The joint's spring angular frequency for constraint regularization, using per-joint parameter.
255+
pub fn joint_angular_frequency_with_override(&self, natural_frequency: Real) -> Real {
256+
natural_frequency * Real::two_pi()
257+
}
258+
259+
/// The joint ERP coefficient (multiplied by inverse timestep), using per-joint parameters.
260+
pub fn joint_erp_inv_dt_with_override(
261+
&self,
262+
natural_frequency: Real,
263+
damping_ratio: Real,
264+
) -> Real {
265+
let ang_freq = self.joint_angular_frequency_with_override(natural_frequency);
266+
ang_freq / (self.dt * ang_freq + 2.0 * damping_ratio)
267+
}
268+
269+
/// The joint CFM coefficient for constraint regularization, using per-joint parameters.
270+
pub fn joint_cfm_coeff_with_override(
271+
&self,
272+
natural_frequency: Real,
273+
damping_ratio: Real,
274+
) -> Real {
275+
let erp_inv_dt = self.joint_erp_inv_dt_with_override(natural_frequency, damping_ratio);
276+
let joint_erp = self.dt * erp_inv_dt;
288277
if joint_erp == 0.0 {
289278
return 0.0;
290279
}
291280
let inv_erp_minus_one = 1.0 / joint_erp - 1.0;
292-
inv_erp_minus_one * inv_erp_minus_one
293-
/ ((1.0 + inv_erp_minus_one)
294-
* 4.0
295-
* self.joint_damping_ratio
296-
* self.joint_damping_ratio)
281+
inv_erp_minus_one * inv_erp_minus_one / ((1.0 + inv_erp_minus_one) * 4.0 * damping_ratio * damping_ratio)
297282
}
298283

299-
/// Amount of penetration the engine wont attempt to correct (default: `0.001` multiplied by
284+
/// Amount of penetration the engine won't attempt to correct (default: `0.001` multiplied by
300285
/// [`Self::length_unit`]).
301286
pub fn allowed_linear_error(&self) -> Real {
302287
self.normalized_allowed_linear_error * self.length_unit
@@ -328,8 +313,6 @@ impl Default for IntegrationParameters {
328313
min_ccd_dt: 1.0 / 60.0 / 100.0,
329314
contact_natural_frequency: 30.0,
330315
contact_damping_ratio: 5.0,
331-
joint_natural_frequency: 1.0e6,
332-
joint_damping_ratio: 1.0,
333316
warmstart_coefficient: 1.0,
334317
num_internal_pgs_iterations: 1,
335318
num_internal_stabilization_iterations: 1,

src/dynamics/joint/fixed_joint.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,31 @@ impl FixedJointBuilder {
138138
self
139139
}
140140

141-
/// Sets the joints anchor, expressed in the local-space of the second rigid-body.
141+
/// Sets the joint's anchor, expressed in the local-space of the second rigid-body.
142142
#[must_use]
143143
pub fn local_anchor2(mut self, anchor2: Point<Real>) -> Self {
144144
self.0.set_local_anchor2(anchor2);
145145
self
146146
}
147147

148+
/// Sets the natural frequency (Hz) for this joint's constraint regularization.
149+
///
150+
/// Higher values make the joint stiffer and resolve constraint violations more quickly.
151+
#[must_use]
152+
pub fn natural_frequency(mut self, frequency: Real) -> Self {
153+
self.0.data.set_natural_frequency(frequency);
154+
self
155+
}
156+
157+
/// Sets the damping ratio for this joint's constraint regularization.
158+
///
159+
/// Larger values make the joint more compliant (allowing more drift before stabilization).
160+
#[must_use]
161+
pub fn damping_ratio(mut self, ratio: Real) -> Self {
162+
self.0.data.set_damping_ratio(ratio);
163+
self
164+
}
165+
148166
/// Build the fixed joint.
149167
#[must_use]
150168
pub fn build(self) -> FixedJoint {

src/dynamics/joint/generic_joint.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,16 @@ pub struct GenericJoint {
282282
/// For coupled degrees of freedoms (DoF), only the first linear (resp. angular) coupled DoF motor and `motor_axes`
283283
/// bitmask is applied to the coupled linear (resp. angular) axes.
284284
pub motors: [JointMotor; SPATIAL_DIM],
285+
/// The natural frequency (Hz) used for the joint constraint spring-like regularization.
286+
///
287+
/// Higher values make the joint stiffer and resolve constraint violations more quickly.
288+
/// (default: `1.0e6`).
289+
pub natural_frequency: Real,
290+
/// The damping ratio used for the joint constraint spring-like regularization.
291+
///
292+
/// Larger values make the joint more compliant (allowing more drift before stabilization).
293+
/// (default: `1.0`).
294+
pub damping_ratio: Real,
285295
/// Are contacts between the attached rigid-bodies enabled?
286296
pub contacts_enabled: bool,
287297
/// Whether the joint is enabled.
@@ -301,6 +311,8 @@ impl Default for GenericJoint {
301311
coupled_axes: JointAxesMask::empty(),
302312
limits: [JointLimits::default(); SPATIAL_DIM],
303313
motors: [JointMotor::default(); SPATIAL_DIM],
314+
natural_frequency: 1.0e6,
315+
damping_ratio: 1.0,
304316
contacts_enabled: true,
305317
enabled: JointEnabled::Enabled,
306318
user_data: 0,
@@ -440,6 +452,75 @@ impl GenericJoint {
440452
self
441453
}
442454

455+
/// The natural frequency (Hz) for this joint's constraint regularization.
456+
#[must_use]
457+
pub fn natural_frequency(&self) -> Real {
458+
self.natural_frequency
459+
}
460+
461+
/// Sets the natural frequency (Hz) for this joint's constraint regularization.
462+
///
463+
/// Higher values make the joint stiffer and resolve constraint violations more quickly.
464+
///
465+
/// # Example
466+
/// ```
467+
/// # use rapier3d::prelude::*;
468+
/// let mut joint = RevoluteJoint::new(Vector::y_axis());
469+
/// joint.set_natural_frequency(5.0e5); // Softer than default (1.0e6)
470+
/// ```
471+
pub fn set_natural_frequency(&mut self, frequency: Real) -> &mut Self {
472+
self.natural_frequency = frequency;
473+
self
474+
}
475+
476+
/// The damping ratio for this joint's constraint regularization.
477+
#[must_use]
478+
pub fn damping_ratio(&self) -> Real {
479+
self.damping_ratio
480+
}
481+
482+
/// Sets the damping ratio for this joint's constraint regularization.
483+
///
484+
/// Larger values make the joint more compliant (allowing more drift before stabilization).
485+
///
486+
/// # Example
487+
/// ```
488+
/// # use rapier3d::prelude::*;
489+
/// let mut joint = RevoluteJoint::new(Vector::y_axis());
490+
/// joint.set_damping_ratio(2.0); // More compliant than default (1.0)
491+
/// ```
492+
pub fn set_damping_ratio(&mut self, ratio: Real) -> &mut Self {
493+
self.damping_ratio = ratio;
494+
self
495+
}
496+
497+
/// The joint's spring angular frequency for constraint regularization.
498+
pub fn joint_angular_frequency(&self) -> Real {
499+
self.natural_frequency * std::f64::consts::TAU as Real
500+
}
501+
502+
/// The joint ERP coefficient (multiplied by inverse timestep).
503+
pub fn joint_erp_inv_dt(&self, dt: Real) -> Real {
504+
let ang_freq = self.joint_angular_frequency();
505+
ang_freq / (dt * ang_freq + 2.0 * self.damping_ratio)
506+
}
507+
508+
/// The effective Error Reduction Parameter for calculating regularization forces.
509+
pub fn joint_erp(&self, dt: Real) -> Real {
510+
dt * self.joint_erp_inv_dt(dt)
511+
}
512+
513+
/// The CFM coefficient for constraint regularization.
514+
pub fn joint_cfm_coeff(&self, dt: Real) -> Real {
515+
let joint_erp = self.joint_erp(dt);
516+
if joint_erp == 0.0 {
517+
return 0.0;
518+
}
519+
let inv_erp_minus_one = 1.0 / joint_erp - 1.0;
520+
inv_erp_minus_one * inv_erp_minus_one
521+
/ ((1.0 + inv_erp_minus_one) * 4.0 * self.damping_ratio * self.damping_ratio)
522+
}
523+
443524
/// The joint limits along the specified axis.
444525
#[must_use]
445526
pub fn limits(&self, axis: JointAxis) -> Option<&JointLimits<Real>> {
@@ -767,6 +848,40 @@ impl GenericJointBuilder {
767848
self
768849
}
769850

851+
/// Sets the natural frequency (Hz) for this joint's constraint regularization.
852+
///
853+
/// Higher values make the joint stiffer and resolve constraint violations more quickly.
854+
///
855+
/// # Example
856+
/// ```
857+
/// # use rapier3d::prelude::*;
858+
/// let joint = RevoluteJointBuilder::new(Vector::y_axis())
859+
/// .natural_frequency(5.0e5) // Softer than default
860+
/// .build();
861+
/// ```
862+
#[must_use]
863+
pub fn natural_frequency(mut self, frequency: Real) -> Self {
864+
self.0.set_natural_frequency(frequency);
865+
self
866+
}
867+
868+
/// Sets the damping ratio for this joint's constraint regularization.
869+
///
870+
/// Larger values make the joint more compliant (allowing more drift before stabilization).
871+
///
872+
/// # Example
873+
/// ```
874+
/// # use rapier3d::prelude::*;
875+
/// let joint = RevoluteJointBuilder::new(Vector::y_axis())
876+
/// .damping_ratio(2.0) // More compliant than default
877+
/// .build();
878+
/// ```
879+
#[must_use]
880+
pub fn damping_ratio(mut self, ratio: Real) -> Self {
881+
self.0.set_damping_ratio(ratio);
882+
self
883+
}
884+
770885
/// An arbitrary user-defined 128-bit integer associated to the joints built by this builder.
771886
pub fn user_data(mut self, data: u128) -> Self {
772887
self.0.user_data = data;

src/dynamics/joint/multibody_joint/multibody_joint.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,8 @@ impl MultibodyJoint {
314314
jacobians,
315315
constraints,
316316
&mut num_constraints,
317+
self.data.natural_frequency,
318+
self.data.damping_ratio,
317319
);
318320
}
319321
curr_free_dof += 1;
@@ -349,6 +351,8 @@ impl MultibodyJoint {
349351
jacobians,
350352
constraints,
351353
&mut num_constraints,
354+
self.data.natural_frequency,
355+
self.data.damping_ratio,
352356
);
353357
Some(limits)
354358
} else {

src/dynamics/joint/multibody_joint/unit_multibody_joint.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,17 @@ pub fn unit_joint_limit_constraint(
1919
jacobians: &mut DVector<Real>,
2020
constraints: &mut [GenericJointConstraint],
2121
insert_at: &mut usize,
22+
natural_frequency: Real,
23+
damping_ratio: Real,
2224
) {
2325
let ndofs = multibody.ndofs();
2426
let min_enabled = curr_pos < limits[0];
2527
let max_enabled = limits[1] < curr_pos;
26-
let erp_inv_dt = params.joint_erp_inv_dt();
27-
let cfm_coeff = params.joint_cfm_coeff();
28+
29+
// Compute per-joint ERP and CFM
30+
let erp_inv_dt = params.joint_erp_inv_dt_with_override(natural_frequency, damping_ratio);
31+
let cfm_coeff = params.joint_cfm_coeff_with_override(natural_frequency, damping_ratio);
32+
2833
let rhs_bias = ((curr_pos - limits[1]).max(0.0) - (limits[0] - curr_pos).max(0.0)) * erp_inv_dt;
2934
let rhs_wo_bias = 0.0;
3035

src/dynamics/joint/pin_slot_joint.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,13 +255,31 @@ impl PinSlotJointBuilder {
255255
self
256256
}
257257

258-
/// Sets the `[min,max]` limit distances attached bodies can translate along the joints principal axis.
258+
/// Sets the `[min,max]` limit distances attached bodies can translate along the joint's principal axis.
259259
#[must_use]
260260
pub fn limits(mut self, limits: [Real; 2]) -> Self {
261261
self.0.set_limits(limits);
262262
self
263263
}
264264

265+
/// Sets the natural frequency (Hz) for this joint's constraint regularization.
266+
///
267+
/// Higher values make the joint stiffer and resolve constraint violations more quickly.
268+
#[must_use]
269+
pub fn natural_frequency(mut self, frequency: Real) -> Self {
270+
self.0.data.set_natural_frequency(frequency);
271+
self
272+
}
273+
274+
/// Sets the damping ratio for this joint's constraint regularization.
275+
///
276+
/// Larger values make the joint more compliant (allowing more drift before stabilization).
277+
#[must_use]
278+
pub fn damping_ratio(mut self, ratio: Real) -> Self {
279+
self.0.data.set_damping_ratio(ratio);
280+
self
281+
}
282+
265283
/// Builds the pin slot joint.
266284
#[must_use]
267285
pub fn build(self) -> PinSlotJoint {

0 commit comments

Comments
 (0)