Skip to main content

Soft-Skinned Actors

A soft-skinned actor is an articulated actor that combines the articulated skeleton with one or more soft (deformable) 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:

  1. 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).
  2. 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).
  3. 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 nested soft actor is a deformable volume: a tetrahedral mesh with a material model and elastic parameters (see Soft Actors).

Multiple nested 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 nested soft actor uses one of them:

  • Attach to a link — name the target link in softAttachLinks (1-to-1 with softParams). The nested soft actor is skinned by a single link, hence skinning weights and indices are not explicitly provided. Contact between the nested soft actor and its attach link is disabled automatically.
  • Skinning data — leave softAttachLinks empty 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 enableCollidingLinks to 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:

FlavorSoft coverageAttachmentColliding surface
1. Rigid + soft leavessoft volumes on some linkssoftAttachLinkssoft surfaces + rigid links
2. Elastic skinskin spanning the bodyskinning datathe soft skin
3. Blended skinmixed soft / rigid-followattach or skinning datablended 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 nested soft actor. Three things determine the outcome: how the two compose, how the nested soft actor is pinned to the skeleton, and where each physical energy is evaluated.

Deformation and Skinning​

Using the soft actor notation, let X\mathbf{X} be a node's rest (unposed) position and u\mathbf{u} its elastic displacement, so x=X+u\mathbf{x} = \mathbf{X} + \mathbf{u} is the deformed position in the rest frame. The skeleton contributes a skinning map Tq\mathbf{T}_q for its current configuration q∈Qq\in\mathcal Q — linear blend skinning over the links that drive the node:

Tq(y)=∑ℓwℓ Tℓ(q) y ,\mathbf{T}_q(\mathbf{y}) = \sum_{\ell} w_{\ell}\,\mathbf{T}_{\ell}(q)\,\mathbf{y}~,

where Tℓ(q)\mathbf{T}_{\ell}(q) is link ℓ\ell's world transform and wℓw_{\ell} its skinning weight. The two compose into the posed configuration:

  • Pure skinning Tq(X)\mathbf{T}_q(\mathbf{X}) — the volume simply follows the skeleton.
  • Skinned deformation Tq(X+u)\mathbf{T}_q(\mathbf{X} + \mathbf{u}) — deform in the rest frame, then skin.
  • Blended skin b Tq(X+u)+(1−b) Tq(X)b\,\mathbf{T}_q(\mathbf{X} + \mathbf{u}) + (1 - b)\,\mathbf{T}_q(\mathbf{X}) — a per-node weight b∈[0,1]b \in [0, 1] mixes the deformed and purely-skinned positions (b=1b = 1 fully soft, b=0b = 0 rigidly following the skeleton). See Blended Skin.

Constrained Nodes​

The rigid coupling to the skeleton is enforced by fixing u=0\mathbf{u} = 0 on a subset of nodes — the constrained nodes — as zero-displacement Dirichlet boundary conditions. Those nodes then sit exactly at Tq(X)\mathbf{T}_q(\mathbf{X}), 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 X+u\mathbf{X} + \mathbf{u} or the posed positions Tq(X+u)\mathbf{T}_q(\mathbf{X} + \mathbf{u}). 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.

EnergyPosed (SoftSkinnedActorParams)Unposed (SoftActorParams)
Elasticity (hasStress)physically accuratecheaper — and exact when the nested soft actor is rigidly attached to a single link via softAttachLinks, since a rigid skinning transform preserves elastic energy
Inertia (hasInertia)physically accuratecheaper, but a cruder approximation
Gravity (hasGravity)physically accuratenot 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.

Energy Flag Rules

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:

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 nested soft actor and its attach link is disabled automatically.

ArticulatedActorParams skeletonParams;
skeletonParams.name = "SoftSkinnedDoublePendulum";
// ... joints and links built as in Articulated Actors; links named
// "UpperArm"/"LowerArm" on layer "Pendulum" ...

// A nested soft 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;

// Build the soft-skinned actor with posed gravity and inertia.
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);
Mixed energy formulation

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 nested soft actor). This is valid as long as no flag is true on both sides.

Declarative alternative

The same scene can be authored declaratively as a prefab (.mochi_scene JSON) and loaded with prefab::AddToScene / sdp.prefab.add_to_scene. The Soft-Skinned Double Pendulum example ships both forms.

SoftSkinnedActorParams Reference​

The top-level parameters use SoftSkinnedActorParams (C++, Python).

FieldC++ TypePython NameDefaultDescription
skeletonParamsArticulatedActorParamsskeleton_params--The articulated skeleton. See Skeleton Parameters.
softParamsDynamicArray<SoftActorParams>soft_params[]One entry per nested soft actor. See Nested Soft Actor Parameters.
softAttachLinksDynamicArray<String>soft_attach_links[]Names of skeleton links where nested soft actors attach. If provided, must be 1-to-1 with softParams. If empty, soft shapes must include skinning data. Contact between a nested soft actor and its attach link is disabled automatically.
enableCollidingLinksboolenable_colliding_linksfalseEnable internal skeleton links as colliding surfaces. If false, only the nested soft actors (or the blended skin) act as colliding surfaces.
hasGravityboolhas_gravityfalseGravity for the whole actor. Each SoftActorParams entry must have hasGravity = false.
hasInertiaboolhas_inertiafalseInertia on posed (post-skinning) positions. If true, each SoftActorParams must have hasInertia = false. See Energy Formulations.
hasStressboolhas_stressfalseElasticity 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.
  • worldFromRoot places the whole actor. Because each nested soft actor's worldFromLocal is 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:

scene->EnableLayerContactSymmetric("Pendulum", "Pendulum", false, error);
scene->EnableLayerContactSymmetric("Pendulum", "Environment", false, error);

Nested Soft Actor 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:

MemberRequirement inside a soft-skinned actor
hasGravityMust be false. Gravity is driven by SoftSkinnedActorParams.hasGravity.
hasInertia / hasStressSelect the unposed side of the energy formulation. Must not both be true here and on the parent.
shapeTetrahedral mesh with constrained nodes baked in — the nodes pinned to the skeleton (always required; see Architecture).
worldFromLocalForced to identity on creation; author the mesh in the skeleton rest frame.
nameFor a blended skin, must match the nested soft actor's blending data in the skin mesh.
materialEach 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. Each soft region has one source vertex index and one weight per articulation-skin vertex. A weight of 0 keeps the articulated position, a weight of 1 uses the nested-soft position, and intermediate values interpolate as skin + weight * (soft - skin). The source index is required to identify a vertex in the corresponding nested soft mesh only when the weight is positive; it is ignored when the weight is zero. The underlying soft volumes still couple to the skeleton the usual way — via softAttachLinks or skinning data — and always carry constrained nodes.

The outer skin and nested soft shapes share one rest frame. For every mapping with a positive nested-soft weight, the mapped vertices must have exactly equal rest positions and identical ordered skinning indices and weights. Zero-weight mappings are ignored. Use the articulation skin as the authoring source of truth and copy its complete rest position and skinning tuple into each mapped nested-soft vertex. Independent export or numeric conversion can change a value enough to violate exact equality, so canonicalize the assets again after either operation.

skeletonParams.skin = ArticulatedSkinParams{
.shape = skinShape, // triangle mesh with per-nested-soft-actor blending data
.layer = "Skin",
};

See ArticulatedSkinParams for the field reference.

Working with Soft-Skinned Actors at Runtime​

Creating a soft-skinned actor returns the top-level articulated actor, so the full articulated runtime API — GetNumDofs, GetArticulatedShapeInfo, GetArticulatedPose, SetArticulatedJointVelocities, and so on — applies directly. The returned top-level actor also provides handles to its nested link actors and nested soft actors. Query support depends on the target actor and configuration.

Enumerate the nested link actors and nested soft actors through the parent Actor and look them up with GetActor / get_actor.

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
}

Queries and Contact​

Register queries for nested soft actor data, such as NodePositions, ElementsDeformationGradient, and ElasticEnergy, on nested soft actors. When no blended skin is configured, register contact queries, such as ContactPoints and TotalContactForce, on nested soft actors as well. With a blended skin, register contact queries on the articulated actor; contact is reported on the skin surface rather than on the individual nested soft actors. When link contact is enabled, nested link actors may also support contact queries. Use is_query_supported to determine which actor supports each query.

The following example does not configure a blended skin, so it registers a TotalContactForce query on the nested soft actor. Call scene.step before reading the query result. Here, ball is a rigid actor placed within reach of the swinging nested soft actor:

soft_actor = scene.get_actor(actor.get_nested_soft_actors()[0])
force_query = soft_actor.register_query(sdp.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.

Examples​

Soft-Skinned Double Pendulum: attaches a tetrahedral soft rod via constrained nodes and softAttachLinks, with unposed elasticity.

  • Python — uv run superdex_physics/examples/example_articulations_soft_skinned_double_pendulum.py
  • Prefab — superdex_physics/assets/samples/articulations_soft_skinned_double_pendulum.mochi_scene
  • 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.