dramatis/three
The three.js embed. It turns a built character into three.js objects you add
to your scene, and keeps them live as the character changes. It never imports
Babylon: src/no-babylon.test.ts fails the build if it does, in the bundle or
in the emitted types.
three is a peer dependency: bring your own (0.160 or later). The package
is not on npm yet; in a checkout, import from src/three/index.ts.
This page is for building the character in your page. To load a GLB you exported, read the three.js guide instead.
A character in a scene
import { buildCharacter } from 'dramatis'
import { characterObject } from 'dramatis/three'
const built = await buildCharacter(character, pack)
const c = characterObject(built)
scene.add(c.root)
c.setFaceUnit('JawDrop', 0.4)
characterObject(built, { naming }) returns:
| field | what it is |
|---|---|
root |
a Group holding the root bone and the meshes. Add it to the scene. |
skeleton |
the one Skeleton every mesh is bound to: the profile's 65 joints in order. |
meshes |
one SkinnedMesh per built mesh (body, eyes, garment_…), in the built order. |
setFaceUnit(unit, weight) |
sets a face unit (0..1) on every mesh that carries it. |
dispose() |
frees geometries, materials and textures. |
It is the scene graph three's GLTFLoader gives for the exported GLB, made
directly: the same node and bone names (naming: 'mixamo' gives Mixamo's,
with : dropped as three does), the face units as morph targets named as in
the export profile, and MeshStandardMaterial
(MeshPhysicalMaterial where a garment has sheen). Textures decode in the
background, so the call itself is synchronous.
Live updates
When a slider moves, rebuild and update the object in place:
import { updateCharacterObject } from 'dramatis/three'
updateCharacterObject(c, await buildCharacter(changed, pack))
The root, the bones and the skeleton stay the same objects, so the scene and
an AnimationMixer keep working. Meshes with the same topology are rewritten
in place, others are remade, and face-unit weights carry over. Face units
need no rebuild at all: setFaceUnit is a morph weight.
Playing animations
Characters are skinned in UAL's T-pose with UAL's joint frames, so Quaternius'
Universal Animation Library clips (CC0) play by name. characterClip keeps a
clip's rotations and only the hips' translation, so the clip doesn't stretch
the body to the mannequin's bone lengths:
import { characterClip } from 'dramatis/three'
const mixer = new AnimationMixer(c.root)
mixer
.clipAction(characterClip(walk, { translationScale: hipHeightRatio }))
.play()
The sample
samples/three/ is a plain three.js page (no framework) with live sliders,
orbit controls and a UAL walk: bun samples/three/serve.ts. It still shows
the adapter's hand-made stand-in character on UAL's skeleton
(src/three/fixture-character.ts), not a character from buildCharacter:
it was written before the builder landed and has not been moved over yet.
For a real export in three.js, samples/three-glb/ (the
three.js guide) loads one.
Next step: a character controller. Dramatis builds none. For
react-three-fiber, ecctrl takes any
Object3D as its character: pass c.root as its child and drive the mixer
from its animation state. That sample is not built yet.
The body alone
For an unrigged body, morphedBodyGeometry(pack, body) gives the pack's base
mesh morphed by its macro targets, through tosijs-3d's engine-free subpaths:
import { loadHumanPack } from 'tosijs-3d/human-pack'
import { HM08_PACK_URL } from 'dramatis'
import { DEFAULT_HUMAN_BODY, morphedBodyGeometry } from 'dramatis/three'
const pack = await loadHumanPack(HM08_PACK_URL)
const geometry = await morphedBodyGeometry(pack, {
...DEFAULT_HUMAN_BODY,
muscle: 0.8,
})
scene.add(new Mesh(geometry, new MeshStandardMaterial()))
What you download
Installing dramatis (or tosijs-3d) also installs Babylon.js (about
16 MB of download) because tosijs-3d declares it as a peer, even though none
of it reaches your bundle. To skip it: npm install --legacy-peer-deps or
bun install --omit=peer. The measurements are in reports/spike-4.md.