Soft Skinned Actors
A soft skinned actor is an articulated actor that combines the articulated skeleton with one or more soft (deformable FEM) volumes rigidly attached to the skeleton. The skeleton provides the overall pose while the soft volumes add deformation — surface-level dynamics, compression, and contact response.
This one actor type spans three flavors of articulation:
- Rigid links with soft leaf volumes — a mostly-rigid articulation with soft volumes attached to selected links (e.g. an articulated robot with soft sensors on its end-effectors).
- An elastic skin over the articulation — a deformable skin wrapping the skeleton that deforms under contact (e.g. a character with an articulated skeleton and a soft body).
- A blended skin — a single surface that is soft and deforming in some regions and rigidly skeleton-following in others (e.g. a hand with soft fingertips).
Architecture
A soft skinned actor is not an actor entity of its own, it couples two actor types:
- The articulated body actor is the skeleton: rigid links connected by joints (see Articulated Actors).
- Each soft FEM actor is a deformable volume: a tetrahedral mesh with a material model and elastic parameters (see Soft Actors).
Multiple soft actors are supported per soft skinned actor (DynamicArray<SoftActorParams>), so different regions can use distinct meshes or materials. Every step, the soft volumes are skinned to the current skeletal pose, then their deformation is solved on top of that posed configuration (see Energy Formulations).
Attaching Soft Volumes to the Skeleton
Each soft volume is attached to the skeleton through a two-step mechanism; both steps are always required. First, a subset of its nodes — the constrained nodes — are pinned to the articulation. Second, the soft volume is driven via linear blend skinning by a subset of skeleton links. There are two ways to specify skinning, and every soft uses one of them:
- Attach to a link — name the target link in
softAttachLinks(1-to-1 withsoftParams). The soft is skinned by a single link, hence skinning weights and indices are not explicitly provided. Contact between the soft and its attach link is disabled automatically. - Skinning data — leave
softAttachLinksempty and embed skinning weights in the soft shape, mapping its nodes across multiple links.
Contact Surface
- By default, contact is computed on the individual soft surfaces. Set
enableCollidingLinksto also collide the rigid links. - With a blended skin (
skeletonParams.skin), a single overarching mesh becomes the colliding surface, presenting one seamless surface — deforming where it is backed by a soft region and rigidly following the skeleton elsewhere. See Blended Skin.
The Three Flavors
The flavors listed above are combinations of these two choices:
| Flavor | Soft coverage | Attachment | Colliding surface |
|---|---|---|---|
| 1. Rigid + soft leaves | soft volumes on some links | softAttachLinks | soft surfaces + rigid links |
| 2. Elastic skin | skin spanning the body | skinning data | the soft skin |
| 3. Blended skin | mixed soft / rigid-follow | attach or skinning data | blended skin mesh |
Energy Formulations
Simulating a soft skinned actor combines skinning — the geometric transform that carries a soft volume along with the skeleton — with the elastic deformation solved by the soft FEM. Three things determine the outcome: how the two compose, how the soft is pinned to the skeleton, and where each physical energy is evaluated.
Deformation and Skinning
Using the soft actor notation, let be a node's rest (unposed) position and its elastic displacement, so is the deformed position in the rest frame. The skeleton contributes a skinning map for its current configuration — linear blend skinning over the links that drive the node:
where is link 's world transform and its skinning weight. The two compose into the posed configuration:
- Pure skinning — the volume simply follows the skeleton.
- Skinned deformation — deform in the rest frame, then skin.
- Blended skin — a per-node weight mixes the deformed and purely-skinned positions ( fully soft, rigidly following the skeleton). See Blended Skin.
Constrained Nodes
The rigid coupling to the skeleton is enforced by fixing on a subset of nodes — the constrained nodes — as zero-displacement Dirichlet boundary conditions. Those nodes then sit exactly at , following the skeleton rigidly. This gives perfect rigid attachment robustly and efficiently, with no additional coupling constraints between the two actor types, and is always required (see Attaching Soft Volumes to the Skeleton).
Posed vs. Unposed Energies
Each physical energy can be evaluated at the unposed positions or the posed positions . Evaluating everything posed is the most physically accurate. But since skinning is a geometric — not physical — deformation, some terms are cheaper (and sometimes exact) unposed. The choice is made per term: set a term's flag on the parent SoftSkinnedActorParams to evaluate it posed, or on the child SoftActorParams to evaluate it unposed.
| Energy | Posed (SoftSkinnedActorParams) | Unposed (SoftActorParams) |
|---|---|---|
Elasticity (hasStress) | physically accurate | cheaper — and exact when the soft is rigidly attached to a single link via softAttachLinks, since a rigid skinning transform preserves elastic energy |
Inertia (hasInertia) | physically accurate | cheaper, but a cruder approximation |
Gravity (hasGravity) | physically accurate | not available — keep gravity posed on the parent, or disable it |
Contact is always posed: it acts on the world-space surface and is not a choice.
- A term must not be enabled on both the parent and the child — it would be counted twice.
SoftActorParams.hasGravitymust befalse: gravity is only ever posed (onSoftSkinnedActorParams) or off.
Creating Soft Skinned Actors
A soft skinned actor is created in a single call to CreateSoftSkinnedActor / create_soft_skinned_actor from a SoftSkinnedActorParams, which bundles:
- a
skeletonParams(ArticulatedActorParams) — the articulated skeleton, built from paralleljointsandlinksarrays (see Articulated Actors); and - one or more
softParams(SoftActorParams) — the soft volumes, each a tetrahedral mesh with constrained nodes.
Bind each soft region to a skeleton link with softAttachLinks (1-to-1 with softParams). Name any link you attach to so it can be referenced; contact between a soft actor and its attach link is disabled automatically.
- C++
- Python
ArticulatedActorParams skeletonParams;
skeletonParams.name = "SoftSkinnedDoublePendulum";
// ... joints and links built as in Articulated Actors; links named
// "UpperArm"/"LowerArm" on layer "Pendulum" ...
// A soft skin actor: a tet mesh with constrained nodes (softShape). hasGravity must be
// false; this uses unposed elasticity (hasStress on the soft), accurate when rigidly attached.
SoftActorParams softParams;
softParams.name = "SoftArm";
softParams.layer = "Soft";
softParams.shape = softShape;
softParams.material.type = SoftMaterialType::NeoHookean;
softParams.material.neoHookean.youngsModulus = 1e4_r;
softParams.material.density = 500.0_r;
softParams.hasGravity = false;
softParams.hasInertia = false;
softParams.hasStress = true;
// Wrap skeleton + soft into the top-level articulated actor; posed gravity + inertia there.
SoftSkinnedActorParams softSkinnedParams;
softSkinnedParams.skeletonParams = skeletonParams;
softSkinnedParams.softParams = {softParams};
softSkinnedParams.softAttachLinks = {"LowerArm"}; // attach the soft to the lower arm
softSkinnedParams.enableCollidingLinks = true;
softSkinnedParams.hasGravity = true;
softSkinnedParams.hasInertia = true;
softSkinnedParams.hasStress = false;
Actor* actor = scene->CreateSoftSkinnedActor(softSkinnedParams, error);
skeleton_params = mochi.ArticulatedActorParams(name="SoftSkinnedDoublePendulum")
# ... joints and links built as in Articulated Actors; links named
# "UpperArm"/"LowerArm" on layer "Pendulum" ...
# A soft skin actor: a tet mesh with constrained nodes (soft_shape). has_gravity must be
# False; this uses unposed elasticity (has_stress on the soft), accurate when rigidly attached.
soft_params = mochi.SoftActorParams(
name="SoftArm", layer="Soft", shape=soft_shape,
has_gravity=False, has_inertia=False, has_stress=True,
)
soft_params.material.type = mochi.SoftMaterialType.NEO_HOOKEAN
soft_params.material.neo_hookean.youngs_modulus = 1e4
soft_params.material.density = 500.0
# Wrap skeleton + soft into the top-level articulated actor; posed gravity + inertia there.
actor = scene.create_soft_skinned_actor(
skeleton_params=skeleton_params,
soft_params=[soft_params],
soft_attach_links=["LowerArm"], # attach the soft to the lower arm
enable_colliding_links=True,
has_gravity=True,
has_inertia=True,
has_stress=False,
)
This example mixes the two energy formulations: gravity and inertia are posed (on the top-level articulated actor) while elasticity is unposed (hasStress on the soft). This is valid as long as no flag is true on both sides.
The same scene can be authored declaratively as a prefab (.mochi_scene JSON) and loaded with prefab::AddToScene / mochi.prefab.add_to_scene. The Soft Skinned Double Pendulum example ships both forms.
SoftSkinnedActorParams Reference
The top-level parameters use SoftSkinnedActorParams (C++, Python).
| Field | C++ Type | Python Name | Default | Description |
|---|---|---|---|---|
skeletonParams | ArticulatedActorParams | skeleton_params | -- | The articulated skeleton. See Skeleton Parameters. |
softParams | DynamicArray<SoftActorParams> | soft_params | [] | One entry per deformable skin region. See Soft Skin Parameters. |
softAttachLinks | DynamicArray<String> | soft_attach_links | [] | Names of skeleton links where soft actors attach. If provided, must be 1-to-1 with softParams. If empty, soft shapes must include skinning data. Contact between a soft actor and its attach link is disabled automatically. |
enableCollidingLinks | bool | enable_colliding_links | false | Enable internal skeleton links as colliding surfaces. If false, only the soft actors (or the blended skin) act as colliding surfaces. |
hasGravity | bool | has_gravity | false | Gravity for the whole actor. Each SoftActorParams entry must have hasGravity = false. |
hasInertia | bool | has_inertia | false | Inertia on posed (post-skinning) positions. If true, each SoftActorParams must have hasInertia = false. See Energy Formulations. |
hasStress | bool | has_stress | false | Elasticity on posed (post-skinning) positions. If true, each SoftActorParams must have hasStress = false. See Energy Formulations. |
Skeleton Parameters
skeletonParams is a full ArticulatedActorParams (C++, Python); see the parameter reference. Joints, links, cycles, initial velocities, and root placement all behave exactly as for a standalone articulated actor. Only two aspects are specific to soft skinned actors:
skin(ArticulatedSkinParams) is the one field with soft-skinned-specific meaning: it enables the blended skin coupling mode. See Blended Skin.worldFromRootplaces the whole actor. Because each soft actor'sworldFromLocalis forced to identity, the skeleton root transform is the single source of global placement — author the soft rest mesh directly in the skeleton's rest frame.
To keep the rigid links from colliding with each other or the environment (leaving the soft skin as the contact surface), disable their contact layer. In this scene the links share the Pendulum layer:
- C++
- Python
scene->EnableLayerContactSymmetric("Pendulum", "Pendulum", false, error);
scene->EnableLayerContactSymmetric("Pendulum", "Environment", false, error);
scene.enable_layer_contact_symmetric("Pendulum", "Pendulum", enable=False)
scene.enable_layer_contact_symmetric("Pendulum", "Environment", enable=False)
Soft Skin Parameters
Each entry in softParams uses SoftActorParams (C++, Python); see Soft Actors for the full field list. The members that carry soft-skinned-specific requirements:
| Member | Requirement inside a soft skinned actor |
|---|---|
hasGravity | Must be false. Gravity is driven by SoftSkinnedActorParams.hasGravity. |
hasInertia / hasStress | Select the unposed side of the energy formulation. Must not both be true here and on the parent. |
shape | Tetrahedral mesh with constrained nodes baked in — the nodes pinned to the skeleton (always required; see Architecture). |
worldFromLocal | Forced to identity on creation; author the mesh in the skeleton rest frame. |
name | For a blended skin, must match the soft actor's blending data in the skin mesh. |
material | Each region may use its own model (e.g. Neo-Hookean) and parameters (density, Young's modulus). |
Blended Skin
The third flavor uses a blended skin: a single overarching triangle mesh that presents one seamless surface for the whole actor — deforming where it is backed by a soft region and rigidly following the skeleton everywhere else. This blended mesh, not the individual soft surfaces, is the colliding surface, so contact faithfully represents the actor's outer shape.
Attach it by setting skin (an ArticulatedSkinParams) on skeletonParams; the skin mesh must include blending data linking each soft region by name. The underlying soft volumes still couple to the skeleton the usual way — via softAttachLinks or skinning data — and always carry constrained nodes.
- C++
- Python
skeletonParams.skin = ArticulatedSkinParams{
.shape = skinShape, // triangle mesh with per-soft-actor blending data
.layer = "Skin",
};
skeleton_params.skin = mochi.ArticulatedSkinParams(
shape=skin_shape, # triangle mesh with per-soft-actor blending data
layer="Skin",
)
See ArticulatedSkinParams for the field reference.
Working with Soft Skinned Actors at Runtime
The creation of a soft skinned actor returns the articulated-skeleton actor, so the whole articulated runtime API — GetNumDofs, GetArticulatedShapeInfo, GetArticulatedPose, SetArticulatedJointVelocities, and so on — applies directly. Beyond that, a soft skinned actor exposes its nested sub-actors and splits query support between them.
Nested Link and Soft Actors
Enumerate the nested rigid links and soft skins through the parent Actor and look them up with GetActor / get_actor.
- C++
- Python
auto links = actor->GetNestedLinkActors(error);
for (auto handle : links) {
Actor* link = scene->GetActor(handle); // link->GetType() == ActorType::Rigid
}
auto softs = actor->GetNestedSoftActors(error);
for (auto handle : softs) {
Actor* soft = scene->GetActor(handle); // soft->GetType() == ActorType::Soft
}
for handle in actor.get_nested_link_actors():
link = scene.get_actor(handle)
assert link.get_type() == mochi.ActorType.RIGID
for handle in actor.get_nested_soft_actors():
soft = scene.get_actor(handle)
assert soft.get_type() == mochi.ActorType.SOFT
Queries and Contact
Deformation and contact queries (e.g. NodePositions, ContactPoints, TotalContactForce) are supported on the nested soft actor, not on the articulated actor — use is_query_supported to check. Register the query on the nested soft, call scene.step so the data is computed, then read it back. In the example scene, ball is a rigid actor placed within reach of the swinging soft:
soft_actor = scene.get_actor(actor.get_nested_soft_actors()[0])
force_query = soft_actor.register_query(mochi.QueryType.TOTAL_CONTACT_FORCE)
scene.step(1.0 / 60.0)
force = soft_actor.get_contact_force_from_actor_world(ball) # net force from the ball
soft_actor.cancel_query(force_query)
The C++ equivalents are RegisterQuery, GetContactForceFromActorWorld, and CancelQuery. When a blended skin is used, contact is reported on the skin surface instead of the individual soft actors.
Examples
Soft Skinned Double Pendulum: attaches a tetrahedral soft rod via constrained nodes and softAttachLinks, with unposed elasticity.
- Python —
uv run --no-project superdex_physics/examples/example_articulations_soft_skinned_double_pendulum.py - Prefab —
superdex_physics/assets/samples/articulations_soft_skinned_double_pendulum.mochi_scene
Related Concepts
- Articulated Actors — The skeleton component of a soft skinned actor.
- Soft Actors — The deformable skin component.
- Contact Filtering — Layer-based contact configuration for isolating skeleton and skin.
- Pose Controller — Implicit PD control for driving the articulated skeleton toward joint-space or Cartesian link targets.
- Solvers — Newton solver configuration for tuning convergence.