Three.jsでJSON形式でモデルを読み込んでBoneアニメーションさせるときの注意点

ちょっと（だいぶ？）ハマったのでメモ。

いやー、Three.jsはupdateが激しいのと、そもそも日本語記事が少ないのでだいぶ苦戦した。（というか、まだ全然しっかり把握できてないけど）

とりあえず **モデルを読み込んでアニメーションさせる** 、というWebGLでなにかやるときに避けて通れない処理でハマったのでメモ。

<del>まず結論から言うと、**Boneアニメーションをさせる際は、Boneの数が問題になる**ぽい。</del>
<ins>boneの数はどうやら関係ないぽい。改めて書きだしてみたら正常にアニメーションしました。</ins>

ちなみにモデルデータはBlenderでThree.jsのJSON形式にExportしたものを使用。

**Export設定**

![Export設定](http://f.cl.ly/items/2K0Y3r08052l0Z3s011h/settings-capture.png)



モデルを作成してBoneをいくつか適当に追加してから（17くらい？）Exportして読み込ませたところ、モデル自体は正常に読み込まれているものの、アニメーションしない。

試しにBoneをふたつ程度に減らしてExport→読み込み、ってしたら動いた。
なので、Boneを複数使って複雑な動きをさせる場合はモデルを分けてThree.js側で合成するなどの処理が必要かもしれない。

ただ、さすがにそれはめんどくさいのでもう少し調べて見ることにする。

##ちなみにアニメーションしたソースをいちおう載せておく

```js:animation.js
var container;
var camera, scene, projector, renderer;
var mesh;
var animation;
var composer;
 
init();
 
function init() {
 
    container = document.createElement('div');
    document.body.appendChild(container);
 
    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 10000);
    camera.position.z = 800;
    camera.target = new THREE.Vector3(0, 0, 0);
 
    scene = new THREE.Scene();
 
    var light = new THREE.DirectionalLight( 0xefefff, 2);
    light.position.set(1, 1, 1).normalize();
    scene.add(light);
 
    var light = new THREE.DirectionalLight(0xffefef, 2);
    light.position.set(-1, -1, -1).normalize();
    scene.add(light);
 
    var loader = new THREE.JSONLoader(true);
    var url = 'models/some-model.js';
    loader.load(url, function(geometry, materials) {
      var material = new THREE.MeshFaceMaterial(materials);
      mesh = new THREE.SkinnedMesh(geometry, material);
      mesh.scale.x = mesh.scale.y = mesh.scale.z = 150.0;
 
      //enable skinning
      mesh.material.materials.forEach(function (mat) {
          mat.skinning = true;
      });
 
      scene.add(mesh);
 
      if (mesh.geometry.animation.name) {
        THREE.AnimationHandler.add(mesh.geometry.animation);
        animation = new THREE.Animation(mesh, mesh.geometry.animation.name, THREE.AnimationHandler.CATMULLROM);
        animation.play();
      }
 
      animate();
    });
 
    renderer = new THREE.WebGLRenderer({antialias: true, preserveDrawingBuffer: true});
    renderer.sortObjects = false;
    renderer.setSize(window.innerWidth, window.innerHeight);
 
    container.appendChild(renderer.domElement);
 
    window.addEventListener('resize', onWindowResize, false);
 
    //ポストプロセスの設定
    composer = new THREE.EffectComposer(renderer);
    composer.addPass(new THREE.RenderPass(scene, camera));
 
    //オリジナルのポストプロセスを追加
    composer.addPass(new THREE.ShaderPass({
      vertexShader: document.getElementById('vshader').textContent,
      fragmentShader: document.getElementById('fshader').textContent,
      uniforms: {
        ef: { type: 't', value: 0.2 },
        tDiffuse: { type: 't', value: null }
      }
    }));
 
    var toScreen = new THREE.ShaderPass(THREE.CopyShader);
    toScreen.renderToScreen = true;
    composer.addPass(toScreen);
}
 
function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}
 
function animate() {
    requestAnimationFrame(animate);
    animation && animation.update(0.03);
    render();
}
 
function render() {
 
    //theta += 0.1;
    //cnt++;
 
    //camera.position.x = radius * Math.sin(THREE.Math.degToRad(theta));
    //camera.position.z = radius * Math.cos(THREE.Math.degToRad(theta));
    //camera.lookAt(camera.target);
 
    //if (mesh && mesh.morphTargetInfluences) {
 
    //    // Alternate morph targets
 
    //    var time = Date.now() % duration;
 
    //    var keyframe = Math.floor(time / interpolation);
 
    //    if (keyframe != currentKeyframe) {
 
    //        mesh.morphTargetInfluences[lastKeyframe] = 0;
    //        mesh.morphTargetInfluences[currentKeyframe] = 1;
    //        mesh.morphTargetInfluences[keyframe] = 0;
 
    //        lastKeyframe = currentKeyframe;
    //        currentKeyframe = keyframe;
    //    }
 
    //    mesh.scale.set(100, 100, 100);
    //    mesh.morphTargetInfluences[keyframe] = (time % interpolation) / interpolation;
    //    mesh.morphTargetInfluences[lastKeyframe] = 1 - mesh.morphTargetInfluences[keyframe];
    //}
 
    //renderer.render(scene, camera);
    composer.render();
}
```

##参考リンク

* [Three.js](http://threejs.org/)
* [Blender公式サイト](http://www.blender.org/)
* [blender.jp（日本語の非公式サイト）](http://blender.jp/)