Three.JS Multiple SkinnedMesh w/ Shared Skeleton

Demonstrates a multi-part character with a shared animated skeleton

by Adi Jaya

HTML

<script src="https://rawgit.com/mrdoob/three.js/dev/build/three.js"></script>
<canvas id="viewport"></canvas>
<div id="controls">
  <button id="toggle-body">Toggle Body</button>
  <button id="toggle-clothes">Toggle Clothes</button>
  <button id="toggle-skeleton">Toggle Skeleton</button>
  <button id="arms-down">Arms Down</button>
  <button id="arms-up">Arms Up</button>
  <button id="toggle-animation">Toggle Animation</button>
  <p>Multiple THREE.SkinnedMesh (Indexed BufferGeometry) instances sharing a single THREE.Skeleton w/ THREE.AnimationClip</p>
</div>

CSS

html, body { margin: 0; height: 100%; color: #999; font-family: monospace }
#viewport { display: block; position: absolute; width: 100%; height: 100%; }
#controls { position: absolute; top: 0; left: 0; padding: .25em; z-index: 9999; }

JavaScript

// This demonstrates creating and binding a shared THREE.Skeleton to multiple THREE.SkinnedMesh instances.
//
// A THREE.Group instance is used as an armature root object.
// Body and Clothes THREE.SkinnedMesh instances, and a THREE.Bone
// heirarchy are parented to the root object.
//
// NOTE: This is using unpatched dev branch 81dev

const SCENE_URL = 'https://gist.githubusercontent.com/satori99/6f642d37999d73fafbdecd5deee0a93b/raw/human.json'

var viewport, renderer, clock, scene, camera
var root, skeleton, body, clothes
var mixer, action

init()

function init() {
	viewport = document.querySelector('#viewport')
	renderer = new THREE.WebGLRenderer( { canvas: viewport, antialias: true } )
	renderer.shadowMap.enabled = true
	renderer.shadowMap.type = THREE.PCFSoftShadowMap
  renderer.setSize( viewport.clientWidth, viewport.clientHeight )
  renderer.clear()
  clock = new THREE.Clock()
  document.body.appendChild( renderer.domElement )
  new THREE.ObjectLoader().load( SCENE_URL, result => {
    scene = result
    // find camera
    camera = scene.getObjectByName( 'Camera' )
    camera.aspect = viewport.clientWidth / viewport.clientHeight
    camera.updateProjectionMatrix()
    // find armature root object.
    // This is a group instance with a userData.bones property
    // containing bones in the same format as would normally be 
    // found on a SkinnedMesh Geometry instance
    root = scene.getObjectByName( 'Human' )
    // manually create bones and parent them to the root object
    // NOTE: This is normally done in the SkinnedMesh constructor
    const bones = createBones( root, root.userData.bones )
    // Important! must update world matrices before creating skeleton instance
    root.updateMatrixWorld()
    // create skeleton
    skeleton = new THREE.Skeleton( bones, undefined, true )
    // create SkinnedMesh from static mesh geometry
    body = createSkinnedMesh( root.getObjectByName( 'Body' ), skeleton )
    clothes = createSkinnedMesh(...