npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@three-ws/retarget

v0.1.0

Published

Retarget animations onto any humanoid GLB. Canonicalizes bone names from Mixamo, Avaturn, VRM/VRoid, Daz/Genesis, MakeHuman, Unreal, and Blender rigs onto one canonical skeleton, retargets clips with rest-pose and hip-axis correction, and drives them with

Readme

@three-ws/retarget

Retarget animations onto any humanoid GLB. One package that maps any rig's bone names onto a canonical skeleton, retargets clips onto it with rest-pose and hip-axis correction, and drives them with a crossfading runtime — so no humanoid ever freezes in a T-pose.

This is the animation engine behind every avatar on three.ws — the same code that lets @three-ws/walk, @three-ws/tour, and the platform's viewers animate a GLB from any source without a curated rig allowlist. Extracted here so you can use it with plain Three.js.

Rig conventions it understands out of the box: Mixamo (mixamorig:LeftArm), Avaturn / Wolf3D, VRM 0.x / VRoid (J_Bip_L_UpperArm), VRM 1.0, Daz/Genesis, MakeHuman, Unreal mannequin, HumanIK/Maya, Blender .L/.R, bare shoulderL-style names, and casing/separator variants of all of them. A rig that genuinely isn't a skinned humanoid is detected and refused cleanly — never half-animated.


Install

npm install @three-ws/retarget three

three is a peer dependency (>= 0.150).

Quick start — play a clip on a rig it was never made for

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { canonicalizeGLBBones, retargetClipToObject, parseClipJSON } from '@three-ws/retarget';

// 1. Canonicalize the avatar's bone names (byte-safe GLB rewrite, in memory).
const raw = await fetch('/avatars/anything-humanoid.glb').then((r) => r.arrayBuffer());
const { buffer } = canonicalizeGLBBones(raw);
const gltf = await new GLTFLoader().parseAsync(buffer, '');

// 2. Retarget a canonical-space clip onto it — rest-pose (A/T-pose) skew and
//    hip up-axis differences are corrected automatically.
const clipJSON = await fetch('https://three.ws/animations/clips/walk.json').then((r) => r.json());
const clip = parseClipJSON(clipJSON, 'walk');
const retargeted = retargetClipToObject(clip, gltf.scene);

// 3. Play it with your own mixer…
const mixer = new THREE.AnimationMixer(gltf.scene);
mixer.clipAction(retargeted).play();

retargetClipToObject returns null when bone coverage is below MIN_COVERAGE (50%) — that's the "this isn't a humanoid" gate. Handle it; don't force-play.

Or let the runtime drive everything

AnimationManager owns the mixer: it loads a clip library from a manifest, retargets per-rig, crossfades between states, layers one-shot overlays (wave, jump) over a base loop (idle, walk), and rejects clips that would leave the character fallen or broken.

import { AnimationManager } from '@three-ws/retarget';

const manager = new AnimationManager();
manager.attach(gltf.scene);

if (manager.supportsCanonicalClips()) {
  manager.setAnimationDefs([
    { name: 'idle', url: 'https://three.ws/animations/clips/idle.json', loop: true },
    { name: 'walk', url: 'https://three.ws/animations/clips/walk.json', loop: true },
    { name: 'wave', url: 'https://three.ws/animations/clips/wave.json', loop: false },
  ]);
  await manager.loadAll();
  manager.crossfadeTo('idle', 0.25);
}

// per frame:
manager.update(deltaSeconds);

The clip URLs above are real — three.ws serves its shared clip library with open CORS, so the snippet runs as-is. Ship your own clips in production if you need guaranteed availability.

API surface

Canonicalize (glb-canonicalize): | Export | What it does | | --- | --- | | CANONICAL_BONES | The frozen 53-bone canonical humanoid set | | canonicalizeBoneName(name) | One bone name → canonical name (or passthrough) | | canonicalizeJointNodes(json) | Rename joints across a parsed glTF JSON tree | | canonicalizeArmatureOrientation(json) | Normalize armature up-axis quirks | | canonicalizeGLBBones(arrayBuffer) | The whole pipeline on a GLB binary — byte-exact when nothing needs changing |

Retarget (animation-retarget): | Export | What it does | | --- | --- | | retargetClipToObject(clip, root, opts?) | Clip → any Object3D hierarchy (most common) | | retargetClipToRig(clip, rig, opts?) / retargetClip(clip, map, opts?) | Lower-level variants when you already hold a rig/node map | | canonicalNodeMapFromObject/FromRig, canonicalRestMapFrom…, canonicalWorldRestMapFrom… | Build the bone/rest lookups yourself | | hipsParentWorldQuat, hipRestHeight, hipRestLocalHeight, clipHipBaselineY | Hip-space helpers (root motion, height normalization) | | scaleClipSpeed(clip, factor), parseClipJSON(json, name) | Clip utilities | | MIN_COVERAGE | The humanoid-coverage gate (0.5) |

Drive (animation-manager): | Export | What it does | | --- | --- | | AnimationManager | attach/detach, setAnimationDefs, loadAll, crossfadeTo, overlays, supportsCanonicalClips(), fallen-pose guards, update(dt) | | measureHipsTiltDeg(clip, model, map) | Diagnostic: how far a retargeted clip tilts the hips |

Reference data: CANONICAL_REST, CANONICAL_REST_WORLD — the canonical rig's rest quaternions the corrections are computed against.

How it works

Retargeting quality comes from doing the correction in world space: for each bone the engine computes q' = L · q · R, where L/R are derived from the difference between the clip's canonical rest pose and the target rig's actual rest pose (so an A-pose character plays T-pose clips without shoulder skew), plus a hip-space fix for rigs whose hips aren't Y-up. Coverage below 50% of the canonical bones aborts the retarget instead of producing a half-puppet.

Hit a skeleton convention it doesn't know? Add the bone-name mapping to the canonicalizer and it works everywhere at once — that's the design: map names once, never maintain a rig allowlist.

Related packages

License

Apache-2.0 © three.ws