Building an open world in the browser, part 25: One skeleton, every outfit
By Oleg Sidorkin, CTO and Co-Founder of Cinevva
New here? Use the series guide. It explains what a spike is and links all parts.
Part 24 made the world persistent and gave its vegetation wind. This part is about the people in it again, but from the wardrobe side: can a creator pick a base body, swap clothing, choose a hairstyle, and play any animation, all on a free asset pack, with no rigging and no animation authoring? Building a custom rig is not the right thing to de-risk. The question worth answering is whether a CC0 pack can carry the whole load.
A pack that was built to fit together
Open Spike 50 in a new tab ↗ · View source
Quaternius ships four assets that line up almost suspiciously well: Universal Base Characters with male and female bodies plus eight hairstyles and two eyebrow sets, Modular Character Outfits split into body, arms, legs, and feet parts with accessories like pauldrons and a hood, and the Universal Animation Library packs one and two, 262 clips in total covering locomotion, combat, climbing, farming, fishing, idles, and social moves. Inspecting their glTFs revealed why they fit: every single mesh in all four packs is rigged to the same 65-joint UE5-style skeleton, the same joint names, the same bind poses, root through pelvis and spine to head, mirrored arms with fifteen finger joints per side, mirrored legs. No retargeting needed. This is the exact opposite of the three-skeleton retargeting problem from Spike 35, where a 213-bone face rig had to talk to a 65-bone Mixamo rig. Here all four packs were authored on the animation library's skeleton on purpose, and the bond is exact.
That turned the spike into a binding exercise rather than an animation-pipeline one. The base layer loads a body glTF, clones the scene with SkeletonUtils.clone so each character gets a fresh skeleton instance rather than feeding bone mutations back into the loader's cached scene, and caches that 65-bone skeleton as canonical along with the armature object that roots animation.
Rebinding a coat onto a body
The trick that makes the whole thing work is that you can't simply reparent an outfit's mesh into the scene, because it still references the bones from its own glTF, which sit in a different subtree. Each modular part, a peasant body or a ranger's legs, arrives as its own SkinnedMesh with its own clone of the same armature. The binding walks the source skeleton's bones in their original order, looks each one up by name in the canonical skeleton, and builds a new Skeleton from those rebound bones reusing the source's bind-inverse matrices, then calls SkinnedMesh.bind with the source's identity bind matrix. After binding, the mesh detaches from its source scene and parents directly under the character root. Because the bind matrix captures the world transform at bind time and the root sits at identity, the mesh's own world transform never affects the output and skinning happens entirely through the now-shared bones.
Hair and eyebrows are slots too, and they corrected a wrong assumption. The folder structure said "rigged to head bone," which suggested hair would be a static mesh parented to the head with a baked offset. It isn't. Every hair file is a SkinnedMesh weighted across all 65 joints, with the long hair's weights bleeding into the upper spine and neck so it drapes when the character looks down. So hair takes the same by-name rebinding path as any outfit part. The one special case is the outfit body slot: when it's attached, the base body mesh is hidden so the outfit doesn't clip through it, while arms, legs, feet, and accessories just stack because the pack authored them to coexist with the base body showing through wherever the clothing leaves skin exposed.
262 animations for free
Animation is almost anticlimactic once the skeleton is shared. The two library glbs load, their clips flatten into one named list, and the track names in those clips reference the exact joint names by bone.position, bone.quaternion, and bone.scale. An AnimationMixer rooted at the character's armature finds those bones by name and drives them directly, crossfading on switch. The viewer's right panel is a filterable list of all 262 clips with a source pill, so searching "fish" surfaces every fishing animation across both libraries. Standard three-point lighting with soft shadows, a polar grid underfoot, and ACES tone mapping keep the stylized base textures readable without crushing the dark bodysuit's shadows.
What this de-risks for the product is large. The avatar customization feature can ship on a CC0 pack with zero rigging work and zero animation authoring: drop in the four packs, wire a wardrobe UI, and you have roughly six outfits times eight hairstyles times two sexes times 262 animations of variation out of the box. The 65-joint skeleton is small enough that GPU skinning cost is negligible, and the batched-skinning work from Spike 45 already handles 200-plus avatars on a rig this size. CC0 carries no use restrictions, so commercial use on a paid creator platform is fine. The remaining open question is custom-uploaded outfits, which is a Phase 3 problem solved by retargeting at upload time onto this canonical skeleton, the inverse of Spike 35's runtime retargeting. The asset footprint is about 147 MB on disk as raw 2K PNGs and uncompressed animation glbs, which production would shrink to roughly 30 MB with KTX2 texture compression and a gltf-transform pass.
Technology referenced in this chapter
One canonical skeleton across a pack. Four Quaternius packs (base bodies, modular outfits, two animation libraries) are all rigged to an identical 65-joint UE5-style skeleton with the same joint names and bind poses, so combining them needs no retargeting. Cloning the base with SkeletonUtils.clone gives each character a fresh skeleton instance instead of mutating the loader's cached scene.
By-name skeleton rebinding. Each modular part ships its own SkinnedMesh referencing its own armature. Rebinding walks the source bones in order, maps each by name into the canonical skeleton, builds a new Skeleton reusing the source bind-inverses, and calls SkinnedMesh.bind with the identity bind matrix, then parents the mesh under the shared character root so skinning runs entirely through the shared bones. The outfit body slot hides the base body to avoid clipping; other slots stack.
Hair as a skinned slot. Hair is not a static head-bone attachment but a SkinnedMesh weighted across all 65 joints, with long hair weighted into the upper spine and neck so it drapes on look-down. It takes the same rebinding path as outfit parts.
Name-keyed animation retargeting for free. Animation Library clips reference joints by exact name, so an AnimationMixer rooted at the shared armature drives all 262 clips directly with crossfade, no per-clip retargeting. A CC0 license covers commercial use, and the small joint count keeps GPU skinning cheap enough to reuse the Spike 45 batched-skinning path. See GPU-driven LOD.
Part 25 of 29. Previous: Part 24 - Saving a world, and wind you can see Next: Part 26 - Water that holds up at every scale Series guide: /blog/2026-02-25-open-world-browser-series-guide