Skip to content

Hair should sway, not turn into a tentacle

By Oleg Sidorkin, CTO and Co-Founder of Cinevva

Side-by-side comparison of the girl's layered hair with earlier motion on the left and improved motion on the right.

A character turns her head. Her hair should follow a moment later, swing a little, then settle. Our first version did something else: the ends curled underneath, as though the hairstyle were trying to become a tentacle.

The bones kept their lengths. The simulation stayed stable. The numbers we were watching were passing. But the movement still looked wrong.

This is the next piece of our auto-rigger: giving hair its own joints and secondary motion. We started with a simple test mesh, then moved to two real avatars. That changed both the implementation and what we measure.

First, rebuild a real ponytail

Seed-san has a separate ponytail mesh. For this test, we erased that mesh’s original skin weights, attached it to the head, and asked our geometric fitter to build a new six-joint hair chain. The rest of the character kept its original skinning. The seven short bang chains also kept their artist-authored weights.

The fitter follows the connected hair surface outward from its root, places joints along that path, and blends nearby joint influences. A small band at the attachment stays pinned. This is a selected-clump workflow: we tell it which mesh is hair. It does not discover every strand in an arbitrary character.

Try the ponytail below. Use Hair motion to switch between the earlier and improved solver in a single preview. Choose Move, then settle to watch the hair come to rest. Pause to inspect a pose, or drag the preview to orbit. The Animation selector offers all 260 Universal animations. Choose a clip to see the whole body move; Framing → Hair close-up brings the camera back to the strands. Head-turn test returns to the controlled comparison.

Open this demo in a full window ↗

We removed Seed-san’s separate backpack and robot arm from these views so the ponytail is unobstructed. The body, hair geometry, skin weights and animation are unchanged by that visibility change; this is not a backpack-collision fix.

Try the Universal animation library

The animation selector uses the same world-space retargeter as Cinevva’s rigger and runtime. We map each avatar’s VRM humanoid metadata to the retargeter’s expected joint names, select the body skin rather than an earlier hair-only skin, and capture the body’s unanimated pose before the hair demonstration starts. This avoids treating a turned head or a crouch as the character’s resting pose when another clip is selected.

A selected clip is fetched from the shared Universal library, retargeted once, and played on the selected character. Horizontal travel is removed to keep the character in view. Every selected Universal animation loops in these demos, including actions originally marked as one-shot. Restart replays the selected action. Hair motion runs after the body animation.

We retargeted all 260 clips on each avatar: 520 outputs, with no missing required body joints, invalid numeric tracks or body-animation tracks targeting the hair joints. We also sampled Idle, Walk, Jump, Back Flip, Dance, Crouch Forward and Sitting Idle, checked body motion and synchronization, and inspected rendered poses. These checks establish usable retargeting on these two rigs, not visual perfection in every frame. Retargeting check results.

Clothing needs its authored controls too. This VRM sample uses aim and twist constraints for its sleeves and limb deformation; animating only humanoid joints leaves the sleeves in their original pose. The demo now loads these controls with the official VRM node-constraint module and updates them after the body animation, before hair simulation. This preserves the supplied clothing rig; it does not add a cloth simulation.

The full library is available for inspection, but the clipping and stretch measurements below still use the controlled head-turn sequence. They do not establish collision-free hair in every dance, fall or flip.

The mistake was in the resting shape

A chain of joints needs a direction to return toward. Our earlier solver calculated that direction relative to the already-bent joint above it. A small deflection at the top therefore changed the resting direction of the next segment, which changed the next one again.

That accumulation produced the curl. Keeping each bone the correct length could not prevent it.

The new solver remembers the hairstyle’s original arc in the moving head’s coordinate frame. Each segment still has inertia and damping, but its restoring direction comes from that original shape. The head can turn while the hair lags behind; the lag no longer becomes a new resting curve for everything below it.

We also tuned the existing artist rigs a little firmer and increased damping. The before-and-after comparison includes that tuning, not just the coordinate-frame change.

A moving target that preserves the original arc

For segment i, let oi be its current joint position and ai its original child offset. The position it tries to return to is

ri=oi+RHRi0(sai).

RH is the animated rotation of the chain's attachment frame, usually the head. Ri0 is the accumulated original rotation from that frame to this segment, and s is model scale. The joint position follows the chain, but the target direction comes from the saved arc. Using the already-bent parent's rotation for that direction was what accumulated the curl.

Each step combines attraction toward this target with gravity and exponential damping:

ui=ech[vi+hfi],fi=k(rixi)+g,yi=xi+hui.

Here xi is the simulated endpoint, vi its velocity, k the restoring coefficient, c the damping rate, and g the gravity acceleration used by the solver. The demo samples body motion and hair together at h=1/120 second. This is a custom spring update with implicit unit mass, not a full elastic-rod or XPBD solver.

We then put the endpoint back at its fixed distance Li=sai from the joint:

x^i=oi+Liyioiyioi.

This projection preserves bone length. It does not, by itself, preserve every triangle edge or keep the strand outside clothing. The implementation uses the resting direction if the candidate direction degenerates.

Then try a whole hairstyle

The second avatar, a pixiv sample, has twelve hair chains and 12,868 hair vertices. Here we preserve the artist’s joints and weights and replace the secondary-motion behavior. This example demonstrates animation of an existing hairstyle, not automatic reconstruction of its fused hair mesh.

Open this demo in a full window ↗

Look at the ends from the side and back. The improved version holds more of the intended silhouette while retaining movement. The earlier version gathers the ends into a more pronounced curl.

Real models also exposed a quieter bug: some short bangs end in a node that is not itself a skinned bone. We were skipping those chains. The importer now accepts those endpoints and reads explicitly named head-hair chains from VRM 1 spring metadata. It also reads sphere and capsule collision proxies and per-joint collision radii.

VRM’s spring-bone specification provides the interchange structure. Our motion is a custom solver with tuned defaults, rather than a complete implementation of every VRM spring setting. The demos use pixiv’s MToon material loader for the authored shading, textures and face/clothing outlines. We disable the expanded hair outline shells because they intersect these thin layered surfaces. Both motion settings use identical lighting and materials. pixiv’s three-vrm spring-bone implementation is an established reference for that ecosystem.

Why the front locks went through the shirt

Checking joint positions alone missed a visible failure: the strand between two joints could pass through a shoulder even when both ends were outside its collision shape. We also found an indexing mistake in our VRM radius handling: reading the next joint’s radius gave the final segment a zero radius.

The two front locks now use collision checks along each entire segment. A shared fitting pass measures visible body and clothing geometry around the existing collision shapes, with expansion limits based on body size. Strand reach, position and skin-weight ownership identify which chains need segment checks. Both avatars now use this algorithm without model-name or strand-name rules. Expanding collision checks across the entire hairstyle increased stretching, so head attachment constraints and short bangs keep their original treatment.

We checked the result against the actual skinned shirt, independently of those proxies. Over one head-turn cycle, the shirt hid up to 555 of the 1,503 front-lock vertices with the earlier motion. The automatic fit recorded zero hits beyond the 1 mm measurement threshold, at both 30 and 120 frames per second. The earlier maximum measured depth was 75.2 mm. This is a front-view occlusion measurement, not a claim of perfect surface collision or a guarantee for every animation. Download the front-lock measurements.

Keeping the locks outside the shirt has a tradeoff: the layered hairstyle’s 99th-percentile stretch increased from 17.1% before collision fitting to 18.0%. It remains below the earlier solver’s 26.0%; the table below reflects the current result.

Contact response needed a separate correction: pushing a strand out of clothing was being converted into velocity, causing a visible rebound on the next step. We now resolve overlap without adding that impulse, remove incoming motion at contact, and preserve sliding motion. A resting-strand regression checks both endpoint and full-segment contact: the corrected strand stays at the contact position instead of launching away.

Contact along the strand, without a rebound impulse

A hair link and a capsule axis are both line segments. Write their points as p(u) and q(v), with u,v[0,1]. Their clearance is

d=minu,v[0,1]p(u)q(v),C=d(rb+rh).

rb is the body proxy radius and rh the hair collision radius, both in world units. Negative C means overlap. Checking only the child endpoint fixes u=1 and can miss the middle of a long lock; segment contact searches the whole interval. A sphere is the special case where the capsule axis has zero length.

To avoid turning position correction into a bounce, we reconstruct velocity before pushing the endpoint out of contact:

vi=x^ixih.

After a nonzero contact correction, let n be its normalized direction. We remove only the component moving into that correction:

vi+=vimin(0,vin)n.

Tangential motion survives, so the strand can slide. The position correction itself is not added to velocity. The solver repeats contact projection and removes velocity along the link's radial direction afterward; interacting constraints can still leave residual overlap.

Back Flip exposed a different coverage gap on the rebuilt ponytail: its separate hair skin did not contain the torso bones. Collider discovery now searches the body hierarchy, and the shared fitter adds a torso capsule where trunk coverage is missing. A six-second Back Flip check at both 30 and 120 frames per second found no sampled ponytail-link penetration into that capsule. This is a proxy-collision check, not a guarantee against every clothing triangle; the original head-turn deformation checks still pass.

Fast motion also needs checks between poses. The demo now advances the body, clothing constraints and hair together at 120 steps per second, and checks strands against moving sphere and capsule proxies between those steps. Contact keeps sideways sliding instead of stopping the entire strand, which otherwise made hair stick. Walk and Back Flip on both avatars produced identical sampled hair positions and velocities at 30, 60 and 120 frames per second in our four-second checks. This handles sampled primitive motion; it is not continuous collision detection against every clothing triangle, and further constraints can still leave unresolved contacts. Download the frame-rate and contact checks.

That consistency has a cost. In the earlier two-view benchmark at 60 frames per second, the recorded 95th-percentile update time was about 1 ms for Seed-san and 14–23 ms for the layered model, excluding rendering. Those costs cover two characters, while the live demo now renders one. The layered case still needs optimization; these desktop measurements do not establish phone performance.

The fitter still uses anatomical assumptions and bounded expansion. It preserves authored settings when the required body roles or geometry are missing, and reports unsupported transforms. Eight additional checks cover renamed bones, scale changes, clothing thickness, hidden accessories and restoring the original settings. These two avatars were used for tuning; testing unseen hairstyles and garments remains necessary before calling the result generally reliable.

Measure the mesh, not just the skeleton

A bone can keep its length while the triangles around it stretch. We therefore measured every hair triangle edge during a twelve-second run: four seconds of head movement, followed by eight seconds of settling. The simulation runs at 120 steps per second; mesh measurements are sampled 30 times per second.

For each edge, we record its worst stretch during the run. The table reports the 99th percentile of those per-edge maxima: 99% of edges stayed at or below that amount of stretching.

For mesh edge e=(a,b) with original length e0, we measure the skinned vertex positions za(t) and zb(t):

ρe(t)=za(t)zb(t)e0,me=maxtTρe(t).

T contains the sampled frames. The reported percentage is 100(Q0.99({me})1), where Q0.99 is the 99th percentile across edges. Taking each edge's maximum before the percentile retains brief stretching events that an average over time could hide. The worst-edge figure uses maxeme instead, so a small badly deformed region remains visible in the report.

Real-model testEarlier motionImproved motion
Seed-san: rebuilt ponytail plus original bangs8.0%5.0%
Seed-san: original hair rig7.6%5.1%
pixiv: original layered-hair rig26.0%18.0%

The worst individual edge still matters. On the layered model it improved from 102.7% to 70.8% stretch—a remaining defect that the percentile must not hide. Seed-san’s worst edge improved from 35.2% to 27.8% in both cases. These are measurements on development examples we used for tuning, not a success rate on unseen hairstyles.

The chain roots stayed fixed. By the final two seconds, measured tip movement between sampled frames was below 0.001 mm in each improved case. Thirteen focused tests cover rigging, fixed roots, shape preservation, collisions, short endpoints, import and animation ownership. We also checked applying and undoing the rig in the viewer and exporting cyclic and one-shot animations.

Download the measurements. The live examples use the same solver code as the prototype’s viewer and export path. They are interactive renders, not prerecorded videos.

What this makes possible—and what it does not yet solve

For a separate ponytail or lock, we now have a path from selected geometry to joints, weights and secondary motion. For an existing compatible hair rig, we can preserve the artist’s work and animate it. When an animation already contains tracks for those hair joints, the procedural solver yields to the authored motion rather than applying a second movement on top.

The numerical hair benchmark uses head turns and gentle body translation. The Universal selector lets readers inspect other actions, but those measurements do not establish hair quality during running, jumping or tumbling. Collision proxies are not full body-surface collision, and we do not yet solve hair against itself. Braids, curls, fused hair regions and physical phone performance need their own tests.

The feature remains experimental. But checking real characters made the improvement concrete: the hair can move without continually rewriting the shape it is supposed to return to.


Model credits. Seed-san © VirtualCast, Inc., from the official VRM sample collection. VRM1_Constraint_Twist_Sample © 2022 pixiv Inc., from three-vrm’s example models. Both files include VRM Public License 1.0 metadata permitting redistribution and modification. The rebuilt ponytail and custom motion are our changes. Source URLs and file hashes identify the exact samples used.

Animation credits. The Universal clips are by Quaternius, from Universal Animation Library and Universal Animation Library 2, provided under CC0.