Constraints
Constraints in SuperDex Physics model interactions and attachments between actors. They are the primary mechanism for coupling degrees of freedom (DoFs) across different entities in a simulation, enabling joints, springs, attachments, and kinematic targets.
Modeling a jointed structure (such as a double pendulum) with constraints is the preferred approach when joints are added or removed at runtime. For a fixed joint structure, prefer an articulated actor instead — it is more efficient and robust.
Mathematical Formulation
Constraints are compliant: they contribute an elastic potential and a dissipation potential to the incremental potential of each implicit stage, rather than enforcing an algebraic condition exactly. The notation below follows the dynamics page: is the generalized configuration, the stage-start configuration, and the stage duration.
Constraint Value
Each constraint defines a constraint value that vanishes when the constraint is satisfied, together with its Jacobian
taken with respect to the degrees of freedom of the involved actors. The
Constraint Types below give for each type; GetDeviation (get_deviation in Python) returns its
current value.
Bilateral constraints (joints, attachments, kinematic targets) have a smooth that may take either sign componentwise. Range constraints instead measure a one-sided violation. Written for a single bounded DoF ,
for bounds , so and the corresponding row of vanish inside the bounds. Each range type below bounds its own scalar measure — a joint angle, a rotation-vector component, a slide offset — in place of . A range constraint is inactive, and contributes nothing, when every component is within its bounds both in the current configuration and at the stage start; the stage-start condition matters because the damping term below differences against .
Target-bearing constraints depend on a prescribed target in addition to the configuration, . The target is fixed data within a stage.
Constraint Rate
The rate of the constraint value is discretized as a stage-local difference,
Two properties follow. First, the difference is taken on itself rather than as , which
makes the discrete dissipation term exactly integrable in and therefore admits an incremental
potential. Second, also differences the target: moving a target between stages
produces a damping force. Use UpdateOldTarget (update_old_target in Python) to reset when teleporting a target, so
that no spurious damping transient is generated.
Incremental Potential
A constraint with stiffness and damping contributes
to the stage incremental potential, corresponding to the elastic potential and the dissipation potential of the continuous system.
Saturation
A positive saturation replaces the quadratic elastic term with
using the same ramp as the regularized Coulomb friction in contact. Since , the elastic force magnitude is smoothly capped at , reached once . The damping term is unaffected by saturation.
Saturation is not a clamp applied to the linear spring: for small , so the effective stiffness near is rather than . Saturation is better understood as a force of nearly constant magnitude acting along , regularized over the length scale .
Gradient and Approximate Hessian
The stage residual contributed by a constraint is ,
where the ratio is evaluated in a form that stays regular as . The generalized
constraint force is , which is what GetForce (get_force in Python) reports.
The residual derivative drops the term involving the second derivative of , which is indefinite in general, yielding the Gauss–Newton approximation
Constraint Types
The table below summarizes every constraint type. is the dimension of the constraint value , and is the number of degrees of freedom it couples. Kind distinguishes bilateral constraints, whose is smooth and signed, from unilateral ones, whose is the one-sided violation defined in the Constraint Value section and which contribute nothing while satisfied. Target marks the types that carry a runtime-settable kinematic target.
| Type | Actors | Kind | Target | ||
|---|---|---|---|---|---|
RigidSphericalJoint | 2 | 3 | 12 | Bilateral | |
RigidPrismaticJoint | 2 | 3 | 9 | Mixed | |
JointRotationRange | 2 | 3 | 6 | Unilateral | |
JointRotationTracking | 2 | 3 | 6 | Bilateral | ✓ |
RigidPivotPosition | 1 | 3 | 6 | Bilateral | ✓ |
RigidPivotToRigidTarget | 1 | 3 | 6 | Bilateral | ✓ |
RigidPivotRotation | 1 | 3 | 3 | Bilateral | ✓ |
DeformableNodePosition | 1 | 3 | 3 | Bilateral | ✓ |
DeformableNodeToDeformableNode | 2 | 3 | 6 | Bilateral | |
DeformableNodeToRigid | 2 | 3 | 9 | Bilateral | |
ArticulatedSingleDofTarget | 1 | 1 | 1 | Bilateral | ✓ |
ArticulatedSingleDofRange | 1 | 1 | 1 | Unilateral | |
Articulated3dRotationTarget | 1 | 3 | 3 | Bilateral | ✓ |
Articulated3dRotationRange | 1 | 3 | 3 | Unilateral | |
RodElementRotationToRigid | 2 | 3 | 11 | Bilateral | ✓ |
The formulas below use the following notation. is the world transform of body , with translation and rotation , so is the world position of a point given in 's local frame. Deformable nodes have reference position and displacement . is the rotation vector of a rotation . Hats denote prescribed targets.
The rotational range constraints bound each component of a rotation vector independently. Because rotations do not commute, these components are not independent rotations about fixed axes, and the bounds are only an intuitive description of a range of motion for moderate angles.
Rigid-Rigid Joints
RigidSphericalJoint
Spherical (ball-and-socket) joint between two rigid bodies. The constraint value is the world-space displacement between two pivot points, each rigidly attached to its respective body:
where and are the local pivot positions.
RigidPrismaticJoint
Constrains the relative translation of two rigid bodies transverse to a sliding axis, and optionally limits travel along it. Let be the joint frame expressed in body 's local coordinates: it is specified in world coordinates at creation and converted to -local then, so it is constant thereafter. Its axis is the free axis. The relative translation in that frame is
The two transverse components are constrained bilaterally, and , while is the signed violation of against the optional travel limits: above the upper limit, below the lower one, and zero in between.
This constraint does not restrict relative rotation, and its travel limits are not reported by
GetLimitMinValues / GetLimitMaxValues. Combine it with a
JointRotationTracking constraint to obtain a full prismatic joint.
JointRotationRange
Limits the relative rotation between two rigid bodies, with independent lower and upper bounds per component. The relative rotation is expressed in the joint reference frame , again stored in body 's local coordinates, and measured against a reference rotation :
Each is the one-sided violation of against its bounds. By default
(rangeAroundRest), is the relative rotation observed at creation, so the bounds
are relative to the rest pose rather than absolute in the joint frame.
JointRotationTracking
Drives the relative rotation between two rigid bodies toward a target rotation , which is the orientation of relative to expressed in 's local frame:
The target is initialized to the relative rotation observed at creation, so the constraint starts
satisfied, and is updated at runtime with SetTargetRotation. It is used both to track measured
joint angles, such as finger joints driven by a hand tracking system, and to supply the missing
rotational restriction of a RigidPrismaticJoint.
Rigid Pivots to Kinematic Targets
Each of these constrains a single rigid body against prescribed target data; none of them couples two actors.
RigidPivotPosition
Constrains a pivot point on a rigid body to a target position:
where is the world-space position of the pivot and is the prescribed target position.
RigidPivotToRigidTarget
Constrains a pivot point on a rigid body to the position that the same local point would occupy under a prescribed target transform :
This differs from RigidPivotPosition only in that the target is a full
rigid motion, so the target point also moves with the target's rotation. Despite the name, it
involves a single actor: is prescribed target data, not the state of a second body. Use
RigidSphericalJoint to couple a point on one body to a point on another.
RigidPivotRotation
Constrains a pivot frame on a rigid body to a target orientation:
where is the body rotation, is the pivot frame in the body's local coordinates, and is the prescribed target rotation in world coordinates.
Deformable Couplings
DeformableNodePosition
Fixes a deformable-actor node to a prescribed world-space position:
This is used for boundary conditions such as pinning a shell corner or fixing the base of a deformable object.
DeformableNodeToDeformableNode
A spring connecting nodes on two deformable actors:
DeformableNodeToRigid
Attaches a deformable-actor node to a pivot point on a rigid body:
By default (fixToDeformablePos), the rigid-frame pivot is computed at creation so
that the constraint starts at zero deviation, rather than being supplied by the caller.
Articulated Constraints
These act directly on the reduced pose of an articulated actor, so they involve no world-space frames.
ArticulatedSingleDofTarget
Drives a single joint DoF toward a target value, . Used for articulated actor control.
The single-DoF types apply only to translational DoFs and to single-DoF rotational (revolute) joints. For the rotation of a spherical or free joint, use the 3D variants below.
ArticulatedSingleDofRange
Limits a single joint DoF to , giving the one-sided violation . Used to enforce mechanical joint limits.
Articulated3dRotationTarget
Drives the rotation of a spherical or free joint toward a target orientation:
where is the joint rotation from the reduced pose. The target is expressed in the joint's local frame relative to its rest orientation, so the identity corresponds to the rest pose.
Articulated3dRotationRange
Limits the rotation of a spherical or free joint, applying independent bounds to each component of and returning the one-sided violation componentwise.
Rod Couplings
RodElementRotationToRigid
Drives the rotation of a rod element relative to a rigid body toward a target rotation , which is the orientation of the rod element relative to the body expressed in the body's local frame. The reference frame is stored in those same local coordinates:
where is the rigid body rotation and is the world-space rotation of the rod element's
material frame. As with JointRotationTracking, the target is initialized
to the relative rotation observed at creation and updated with SetTargetRotation; the default
behavior therefore preserves the creation-time relative rotation rather than aligning the rod
element with the body frame. The coupled DoFs are the 3 rigid rotational DoFs and the 8 DoFs
of the rod element's two nodes.
Code Examples
Pinning a Soft Body Node (DeformableNodePosition)
Pin a specific node of a deformable actor to a fixed position in world space. This is useful for boundary conditions such as fixing a shell corner or anchoring the base of a soft body.
- C++
- Python
DeformableNodePositionConstraintParams params;
params.actor = softActor->GetHandle();
params.nodeIndex = 0; // pin the first node
params.position = Real3{0_r, 1_r, 0_r}; // target world position
params.stiffness = 1e6_r; // penalty stiffness
Constraint* constraint = scene->CreateDeformableNodePositionConstraint(params, error);
constraint = scene.create_deformable_node_position_constraint(
actor=soft_actor.get_handle(),
node_index=0,
position=[0, 1, 0],
stiffness=1e6,
)
Connecting Two Rigid Bodies (RigidSphericalJoint)
Create a ball-and-socket joint between two rigid bodies. Each body specifies a local pivot point; the constraint penalizes the distance between those two pivots in world space.
- C++
- Python
RigidSphericalJointConstraintParams params;
params.actorA = bodyA->GetHandle();
params.actorB = bodyB->GetHandle();
params.localPosA = Real3{0.5_r, 0_r, 0_r}; // pivot on body A
params.localPosB = Real3{-0.5_r, 0_r, 0_r}; // pivot on body B
params.stiffness = 1e6_r;
Constraint* constraint = scene->CreateRigidSphericalJointConstraint(params, error);
constraint = scene.create_rigid_spherical_joint_constraint(
actor_a=body_a.get_handle(),
actor_b=body_b.get_handle(),
local_pos_a=[0.5, 0, 0],
local_pos_b=[-0.5, 0, 0],
stiffness=1e6,
)
Attaching a Deformable Actor to a Rigid Body (DeformableNodeToRigid)
Couple a deformable mesh node to a point on a rigid body. This is commonly used to attach a deformable actor to a rigid handle.
- C++
- Python
DeformableNodeToRigidConstraintParams params;
params.deformableActor = softActor->GetHandle();
params.rigidActor = rigidActor->GetHandle();
params.deformableNodeIndex = 10; // which deformable-actor node
params.fixToDeformablePos = true; // use the node's current position
params.stiffness = 1e4_r;
Constraint* constraint = scene->CreateDeformableNodeToRigidConstraint(params, error);
constraint = scene.create_deformable_node_to_rigid_constraint(
deformable_actor=soft_actor.get_handle(),
rigid_actor=rigid_actor.get_handle(),
deformable_node_index=10,
fix_to_deformable_pos=True,
stiffness=1e4,
)
Working with Constraints at Runtime
The Constraint Types above describe how to define a constraint at creation time. Every Constraint also exposes a uniform runtime interface: the same methods work on all types, but many are type-specific and report a graceful Error (in Python, methods on Constraint raise mochi.Error) when they don't apply. The constraints page tours this whole interface as a worked example.
Lifecycle and Identity
C++ Scene constraint factory methods return a Constraint* and a stable ConstraintHandle. Look a constraint back up with GetConstraint, iterate all of them with ForEachConstraint, and remove one with DestroyConstraint (by pointer or handle). Python provides the corresponding get_constraint, for_each_constraint, and destroy_constraint methods. A constraint is auto-destroyed when any actor it references is destroyed.
- C++
- Python
ConstraintHandle handle = constraint->GetHandle();
Constraint* same = scene->GetConstraint(handle); // look up by handle
scene->ForEachConstraint([](Constraint* c) { /* ... */ }); // enumerate
scene->DestroyConstraint(handle); // explicit removal
// scene->DestroyActor(actorHandle) also removes every constraint on that actor
handle = constraint.get_handle()
same = scene.get_constraint(handle) # look up by handle
scene.for_each_constraint(lambda c: ...) # enumerate
scene.destroy_constraint(handle) # explicit removal
# scene.destroy_actor(actor_handle) also removes every constraint on that actor
Introspection
The generic interface reports how a constraint is wired up, regardless of its type. GetNumActors (get_num_actors in Python) is a useful discriminator — a world-anchored RigidPivotPosition involves one actor, while a body-to-body RigidSphericalJoint involves two.
- C++
- Python
ConstraintType type = constraint->GetType();
int numActors = constraint->GetNumActors();
for (int i = 0; i < numActors; ++i) {
Actor* actor = constraint->GetActor(i);
Span<int const> dofs = constraint->GetDofIndicesForActor(i); // affected DoFs
}
constraint_type = constraint.get_type()
for i in range(constraint.get_num_actors()):
actor = constraint.get_actor(actor_index=i)
dofs = constraint.get_dof_indices_for_actor(actor_index=i) # affected DoFs
Parameters
Stiffness, damping, and saturation can be read and changed at any time. See
Mathematical Formulation for how each enters the stage problem;
in short, saturation caps the elastic force magnitude at stiffness * saturation, and a negative
value disables the cap.
- C++
- Python
constraint->SetStiffness(1e5_r, error); // [N/m] (or [N*m/rad] for rotational constraints)
constraint->SetDamping(20_r, error); // [N*s/m] (or [N*m*s/rad])
constraint->SetSaturation(-1_r, error); // force cap = stiffness * saturation; negative disables
constraint.set_stiffness(stiffness=1e5) # [N/m] (or [N*m/rad] for rotational constraints)
constraint.set_damping(damping=20.0) # [N*s/m] (or [N*m*s/rad])
constraint.set_saturation(saturation=-1.0) # force cap = stiffness * saturation; negative disables
Kinematic Targets
Target-bearing constraints track a prescribed target. Updating the target each step animates it — for example, driving a RigidPivotPosition anchor along a path to create a moving base. Use UpdateOldTarget to teleport the target without producing a spurious damping transient.
- C++
- Python
pivot->SetTargetPosition(Real3{x, y, z}, error); // RigidPivotPosition
// SetTargetRotation / SetTargetDof apply to rotation- / DoF-target constraints
pivot.set_target_position(mochi.Real3(x, y, z)) # RigidPivotPosition
# set_target_rotation / set_target_dof apply to rotation- / DoF-target constraints
Diagnostics and Queries
GetDeviation returns the current constraint value (the position/rotation error) and needs no setup. GetForce returns the constraint force but requires a ConstraintForce query to be registered before stepping. The query is identified by the C++ QueryType::ConstraintForce or corresponding Python QueryType value. IsQuerySupported reports whether a query type applies.
- C++
- Python
constraint->RegisterQuery(QueryType::ConstraintForce, error); // once, before Step
scene->Step(dt, error);
DynamicArray<real> deviation = constraint->GetDeviation(); // constraint value g
Span<real const> force = constraint->GetForce(error); // needs the query above
constraint.register_query(mochi.QueryType.CONSTRAINT_FORCE) # once, before stepping
scene.step(dt)
deviation = constraint.get_deviation() # constraint value g
force = constraint.get_force() # needs the query above
Limits
Range constraints (e.g. JointRotationRange, ArticulatedSingleDofRange) expose their bounds via GetLimitMinValues / GetLimitMaxValues; unconstrained components report . Python provides get_limit_min_values / get_limit_max_values.