How DART Handles Friction and Force-Dependent Slip in Contact Constraints
DART implements force-dependent slip by injecting a constraint-force-mixing (CFM) damping term into the ContactConstraint velocity-change step, establishing a linear relationship between steady-state sliding velocity and applied tangential friction force.
DART (Dynamic Animation and Robotics Toolkit) models contact between rigid bodies through the ContactConstraint class, which extends standard Coulomb friction with force-dependent slip (slip compliance). This allows surfaces to drift at velocities proportional to applied tangential forces, critical for simulating tires, robotic grasps, or compliant sliding contacts.
Slip Compliance Parameters in ShapeNode Dynamics
DART stores slip-compliance properties within the DynamicsAspect of each ShapeNode. These parameters define how much a surface will slide under a given friction load.
Primary and Secondary Slip Compliance
Each ShapeNode exposes two independent compliance values through the C++ API:
// Access the DynamicsAspect and set slip compliance
shapeNode->getDynamicsAspect()->setPrimarySlipCompliance(0.02); // 2% slip
shapeNode->getDynamicsAspect()->setSecondarySlipCompliance(0.03); // 3% slip
PrimarySlipCompliance: Governs sliding along the first friction direction.SecondarySlipCompliance: Governs sliding along the orthogonal friction direction.
Default Values and Validation
The default value for both parameters is DART_DEFAULT_SLIP_COMPLIANCE (defined as 0.0). Negative values, including the historic sentinel -1.0, are interpreted as "use the default" during validation. In dart/constraint/contact_surface.cpp (lines 97–130), the DefaultContactSurfaceHandler::computePrimarySlipCompliance method reads these aspect properties, validates them, and falls back to the default when necessary.
Parameter Aggregation Pipeline
Before simulation, DART aggregates per-shape slip compliances into per-contact constraint parameters through a three-stage pipeline.
Extraction and Combination
When a contact is detected, DefaultContactSurfaceHandler::createParams (in dart/constraint/contact_surface.cpp, lines 131–138) combines the individual compliances of the two colliding bodies by simple addition:
combinedSlipCompliance = slipComplianceA + slipComplianceB
This additive combination preserves the physical interpretation that both surfaces contribute to the overall contact compliance.
Scaling by Contact Count
To ensure the effect remains independent of collision-detection tessellation, DefaultContactSurfaceHandler::createConstraint (lines 90–97) scales the combined compliance by the number of contact points generated for that collision:
// Pseudo-code from the scaling step
mPrimarySlipCompliance *= numContactsOnCollisionObject;
This scaling guarantees that splitting a single contact into multiple points does not artificially increase the total slip.
CFM-Based Implementation in ContactConstraint
The actual physics integration occurs within ContactConstraint, where slip compliance modifies the constraint velocity through a CFM damping term.
Velocity Change Damping
During the constraint resolution step, ContactConstraint::getVelocityChange (in dart/constraint/contact_constraint.cpp, lines 96–106) adds a damping offset proportional to the slip compliance when the withCfm flag is enabled:
// For primary tangential direction (index 1)
velocityChange[1] += mPrimarySlipCompliance / mTimeStep;
// For secondary tangential direction (index 2)
velocityChange[2] += mSecondarySlipCompliance / mTimeStep;
This damping term relaxes the strict zero-relative-velocity constraint of Coulomb friction, allowing persistent sliding.
Physical Interpretation
The implementation produces the force-dependent slip relationship:
v_slip = slip_compliance × F_friction
As verified in tests/integration/constraint/test_force_dependent_slip.cpp, a body subjected to constant external force F_ext reaches a steady-state velocity of F_ext × slip_compliance, demonstrating that slip compliance acts as a mobility coefficient rather than modifying the friction impulse directly (the impulse calculation in applyImpulse remains unchanged; only the velocity-level constraint is relaxed).
Practical Usage Examples
C++ Configuration
The following example configures a box with 2% primary slip compliance:
#include <dart/dart.hpp>
int main()
{
auto skel = dart::dynamics::Skeleton::create("box");
auto* body = skel->createJointAndBodyNodePair<dart::dynamics::FreeJoint>().second;
auto shape = std::make_shared<dart::dynamics::BoxShape>(
Eigen::Vector3d(0.3, 0.3, 0.3));
auto* shapeNode = body->createShapeNodeWith<
dart::dynamics::VisualAspect,
dart::dynamics::CollisionAspect,
dart::dynamics::DynamicsAspect>(shape);
// Enable 2% slip in the primary friction direction (+X)
shapeNode->getDynamicsAspect()->setPrimarySlipCompliance(0.02);
shapeNode->getDynamicsAspect()->setFirstFrictionDirection(
Eigen::Vector3d::UnitX());
// ... add to world, apply force, step simulation ...
// Steady-state velocity will be F_ext * 0.02
}
Python Configuration
Using dartpy, the same configuration is accomplished as follows:
import dartpy as dart
import numpy as np
world = dart.simulation.World()
world.setGravity([0, 0, -9.81])
# Create floor
floor = dart.dynamics.Skeleton()
floor_body = floor.create_joint_and_body_node_pair(dart.dynamics.WeldJoint)[1]
floor_shape = dart.dynamics.BoxShape([10, 10, 0.01])
floor_body.create_shape_node(floor_shape)
world.add_skeleton(floor)
# Create sliding box
box = dart.dynamics.Skeleton()
body = box.create_joint_and_body_node_pair(dart.dynamics.FreeJoint)[1]
shape = dart.dynamics.BoxShape([0.3, 0.3, 0.3])
node = body.create_shape_node(shape)
# Configure slip compliance
node.set_primary_slip_compliance(0.02)
node.set_first_friction_direction(np.array([1, 0, 0]))
world.add_skeleton(box)
# Apply constant 10N force in +X direction
force = np.array([10.0, 0.0, 0.0])
for _ in range(2000):
body.add_ext_force(force)
world.step()
# Verify: velocity should approach 0.2 m/s (10N * 0.02 s/kg)
print(f"Final velocity: {body.linear_velocity()[0]:.4f} m/s")
Summary
- Storage: Slip compliance is stored per-shape in
DynamicsAspectviasetPrimarySlipCompliance()andsetSecondarySlipCompliance(). - Aggregation:
DefaultContactSurfaceHandlercombines compliances by addition and scales by contact count to maintain physical consistency. - Integration:
ContactConstraint::getVelocityChange()applies the compliance as a CFM damping termslipCompliance / timeStepto the tangential velocity constraints. - Behavior: The system exhibits steady-state sliding velocity proportional to applied force (
v = F × compliance), validated bytest_force_dependent_slip.cpp.
Frequently Asked Questions
What is the default slip compliance in DART?
The default slip compliance is 0.0, defined as DART_DEFAULT_SLIP_COMPLIANCE in the source headers. When set to zero, the contact enforces standard Coulomb friction without force-dependent slip. Negative values passed to the API are automatically interpreted as requests to use this default.
How does DART combine slip compliance values for two contacting bodies?
DART combines the individual PrimarySlipCompliance values of both collision objects through simple addition in DefaultContactSurfaceHandler::createParams. The combined value is then scaled by the number of contact points in createConstraint to ensure the total slip behavior remains invariant to contact sampling density.
Does slip compliance affect the friction impulse directly?
No. According to the implementation in dart/constraint/contact_constraint.cpp, slip compliance does not modify the impulse calculation in applyImpulse(). Instead, it affects the velocity-level constraint by adding a damping term during getVelocityChange(), effectively softening the friction constraint without altering the maximum friction force limit.
How can I verify force-dependent slip behavior in my simulation?
You can validate the implementation by creating a rigid body with non-zero slip compliance, applying a constant external force, and measuring the terminal sliding velocity. As demonstrated in tests/integration/constraint/test_force_dependent_slip.cpp, the steady-state velocity should equal the applied force multiplied by the slip compliance coefficient.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →