Articulated Actors
Articulated actors represent multi-body systems composed of rigid links connected by joints. They are the primary building block for simulating robots, characters, mechanisms, and any structure where rigid parts move relative to each other through well-defined kinematic constraints.
Unlike independent rigid bodies, an articulated actor has a set of generalized joint coordinates that determine all link poses through forward kinematics. These coordinates include joint angles and displacements, but rotational joints make their combined configuration a nonlinear manifold.
Formulation
Configuration and Kinematics
The overall structure of the articulation is a rooted tree, where rigid bodies called links form the vertices of the tree graph, while the edges are joints.
There is also a root joint, connecting the root link to the articulation's root frame, defined by the actor's worldFromRoot transform.
Different types of joints have different configurations, e.g., a revolute joint has a single scalar angle parameterizing its possible configurations, while a spherical joint's configuration is defined by a general rotation in .
The complete articulated configuration is then in the product manifold of all the joint configuration spaces:
where is the configuration of joint and is the number of tree joints. Generalized velocities and solver increments belong to the tangent space ; the distinction is nontrivial because of the rotational configurations of spherical and free joints. Pose updates, differences, and state combinations use manifold-aware operations that are natural extrapolations of those described for rigid-rotation reconstruction.
To simulate articulations with non-tree topologies, SuperDex Physics introduces compliant penalty constraints for cycle joints, with a formulation analogous to other constraints. As such, they become part of the energy formulation determining the equations of motion, not the kinematic description of the configuration.
Forward kinematics composes the fixed worldFromRoot placement with the joint transforms along the tree, mapping to the world-space rigid transform of each link . The corresponding articulation Jacobian maps generalized velocity to link velocity :
Here and are the link's world-space center-of-mass and angular velocities, respectively, using the notation defined for rigid actors. The transpose pulls link-space forces and residuals back to the cotangent space . This lets each link use the same rigid-body mechanics as an independent rigid actor, while the articulation is solved in its generalized variables.
Dynamics
Articulated actors specialize the shared Lagrangian dynamics by composing each rigid link's mechanics with forward kinematics. If and are the rigid-body kinetic and potential energies of link , the per-link contributions have the form
The discrete incremental potential for the kinetic energy uses the rigid-actor center-of-mass and rotation discretizations computed from each link's and , so enters only when differentiating with respect to for the residual.
Aside from these link contributions, several other sources can contribute energy and dissipation to an articulation's dynamics. Joint inertia adds kinetic energy, joint limits and cycle joints add constraint potentials, and joint friction and damping add dissipation. Controllers, direct generalized loads, and transmissions contribute through the conservative potential , dissipation potential , or other generalized forces according to their models. Soft-skinned actors strongly couple an articulation to a soft body through skinning. Contact and external constraints may couple the articulation to other actors, so their energies and residuals depend on the coupled system state rather than on alone. SuperDex Physics assembles all contributions and advances the coupled system with the implicit stages described in Dynamics.
Joint Types
SuperDex Physics supports six joint types:
| Joint Type | Enum Value | DoFs | Description |
|---|---|---|---|
| Free | Free | 6 | Unrestricted motion (3 translational + 3 rotational). Typically used for the root link of a floating-base system. |
| Spherical | Spherical | 3 | Three rotational degrees of freedom (ball-and-socket joint). Rotation is parameterized as a rotation vector. |
| Revolute | Revolute | 1 | Single rotational degree of freedom around a specified axis (hinge joint). |
| Prismatic | Prismatic | 1 | Single translational degree of freedom along a specified axis (slider joint). |
| Hard | Hard | 0 | Rigidly fuses the child link to its parent. No relative motion. Useful for fixing the root to the world or combining geometry. |
| Cycle | Cycle | -- | Creates a closed kinematic loop by connecting two links that are not in a direct parent-child relationship. Enforced as a soft spherical constraint. Declared via cycles, not joints. |
Creating Articulated Actors
An articulated actor is created in a single call from parallel joints and links arrays:
joints[i]is the inbound joint connectinglinks[i]to its parent.- Links are listed parent-first: the root is at index 0 with
parentLink = -1, and every other link'sparentLinkis smaller than its own index. joint.parentLinkFromJointplaces the joint frame in its parent link's frame;link.parentJointFromLinkplaces the link body relative to its inbound joint frame.- Cycle-closing joints are declared separately in
cycles(see Closed Kinematic Chains).
- C++
- Python
#include <mochi_physics/mochi_physics.h>
using namespace mochi;
// A 2-link arm: Free root + Revolute child (hinge about Z).
ArticulatedActorParams params;
params.name = "arm";
params.worldFromRoot = TransformRT{Real3{0, 0.2, 0}};
params.joints = {
{.type = ArticulatedJointType::Free},
{.type = ArticulatedJointType::Revolute,
.parentLinkFromJoint = TransformRT{Real3{0.1, 0, 0}},
.axis = Real3{0, 0, 1}},
};
params.links = {
{.parentLink = -1, .shape = rootShape, .colliderType = ColliderType::Box, .density = 1000_r},
{.parentLink = 0, .shape = childShape, .colliderType = ColliderType::Box, .density = 1000_r},
};
Actor* actor = scene->CreateArticulatedActor(params, error);
import mochi
# A 2-link arm: Free root + Revolute child (hinge about Z).
params = mochi.ArticulatedActorParams(name="arm")
params.world_from_root = mochi.TransformRT(translation=[0, 0.2, 0])
params.joints = [
mochi.ArticulatedJointParams(type=mochi.ArticulatedJointType.FREE),
mochi.ArticulatedJointParams(
type=mochi.ArticulatedJointType.REVOLUTE,
parent_link_from_joint=mochi.TransformRT(translation=[0.1, 0, 0]),
axis=[0, 0, 1],
),
]
params.links = [
mochi.ArticulatedLinkParams(
parent_link=-1, shape=root_shape, collider_type=mochi.ColliderType.BOX, density=1000.0
),
mochi.ArticulatedLinkParams(
parent_link=0, shape=child_shape, collider_type=mochi.ColliderType.BOX, density=1000.0
),
]
actor = scene.create_articulated_actor(params)
Each link becomes a queryable rigid sub-actor named "actorName/linkName"; retrieve them with GetNestedLinkActors / get_nested_link_actors.
Scenes — including articulated actors and URDF-imported skeletons — can also be authored declaratively as prefabs (.mochi_scene JSON) and loaded with prefab::AddToScene / mochi.prefab.add_to_scene.
ArticulatedJointParams Reference
ArticulatedJointParams (C++, Python) describes a single joint. joints[i] is the inbound joint of links[i].
| Field | C++ Type | Python Name | Description |
|---|---|---|---|
name | DynamicString | name | Joint name (unique per actor). Auto-generated as "joint_0", ... if empty. |
type | ArticulatedJointType | type | Joint type (Free, Spherical, Revolute, Prismatic, Hard). Required. |
parentLinkFromJoint | TransformRT | parent_link_from_joint | Joint frame relative to the parent link's frame. |
axis | Real3 | axis | Axis of motion in the joint's local frame. Revolute/Prismatic only; auto-normalized. |
friction | ArticulatedJointFrictionParams | friction | Per-joint friction/damping. Ignored for Free/Hard. |
inertia | optional<real> | inertia | Joint inertia coefficient [kg or kg·m²]. Ignored for Free/Hard. Default: none (0). |
minLimit / maxLimit | optional<Real3> | min_limit / max_limit | Per-DoF limits [m or rad]. For 1-DoF joints, encode as scalar · axis. |
limitStiffness | real | limit_stiffness | Stiffness [N/m or N·m/rad] for limit constraints. Default: 100. |
limitDamping | real | limit_damping | Damping [N·s/m or N·m·s/rad] for limit constraints. Default: 0. |
ArticulatedLinkParams Reference
ArticulatedLinkParams (C++, Python) describes a single rigid link. The type mirrors RigidActorParams for the per-link rigid-body properties, plus the tree-structure fields.
| Field | C++ Type | Python Name | Description |
|---|---|---|---|
name | DynamicString | name | Link name (unique per actor). Auto-generated as "link_0", ... if empty. |
parentLink | int | parent_link | Parent link index; -1 for the root. Must satisfy parentLink < i. |
parentJointFromLink | TransformRT | parent_joint_from_link | Link frame relative to its inbound joint frame. Rotation must be identity. |
shape | ShapeHandle | shape | Link collision/visual geometry. |
layer | DynamicString | layer | Contact layer name for contact filtering. |
colliderType | ColliderType | collider_type | Collision geometry type. Default: Auto. |
contact | ContactParams | contact | Contact mechanics parameters. |
hasGravity | bool | has_gravity | Whether the link is affected by gravity. Default: true. |
density | optional<real> | density | Uniform density [kg/m³]. Specify either density or mass. |
mass | optional<real> | mass | Total mass [kg]. Mutually exclusive with density. |
centerOfMass | optional<Real3> | center_of_mass | CoM in the link frame. Computed from geometry if unset. |
momentOfInertia | optional<Real6> | moment_of_inertia | Inertia tensor [ixx, ixy, ixz, iyy, iyz, izz]. Computed from geometry if unset. |
ArticulatedActorParams Reference
The top-level parameters use ArticulatedActorParams (C++, Python).
| Field | C++ Type | Python Name | Description |
|---|---|---|---|
name | DynamicString | name | Actor name. Link actors are named "name/linkName". |
worldFromRoot | TransformRT | world_from_root | Initial world-space transform of the actor's root frame. |
joints | DynamicArray<ArticulatedJointParams> | joints | Per-joint parameters. Size must equal links. |
links | DynamicArray<ArticulatedLinkParams> | links | Per-link parameters. Parent-first order; at least one link. |
cycles | DynamicArray<ArticulatedCycleJointParams> | cycles | Optional cycle-closing joints for closed loops. |
skin | optional<ArticulatedSkinParams> | skin | Optional skinned mesh for surface collision/rendering. |
jointVelocities | optional<DynamicArray<real>> | joint_velocities | Initial per-DoF joint velocities [m/s or rad/s]. Zero if unset. |
Joint Configuration
Joint Friction and Damping
Joint friction is configured per joint via ArticulatedJointParams::friction (ArticulatedJointFrictionParams), which supports viscous damping, Coulomb (dry) friction, and an experimental Stribeck effect.
| Field | Default | Description |
|---|---|---|
viscous | 0.0 | Viscous friction coefficient [Ns/m or Nm*s/rad]. Produces a force proportional to joint velocity. |
coulomb | 0.0 | Coulomb friction coefficient [N or N*m]. Constant opposing force once the joint is moving. |
falloffVel | 1e-3 | Velocity threshold [m/s or rad/s] for dry friction smoothing. Smaller values are more physical but may reduce stability. |
stictionExtra | 0.0 | (Experimental) Extra stiction force [N or N*m], representing the difference between peak static and dynamic friction. |
stribeckVel | 0.0 | (Experimental) Stribeck velocity [m/s or rad/s] governing the static-to-dynamic friction transition sharpness. |
For a one-DoF joint, let be its tangent velocity, its viscous coefficient, its coulomb force, its stictionExtra, its falloffVel, and its stribeckVel. When , define and
The joint's dissipation potential is