javascriptthree.jsskeletal-animation

Updating Three.js Skeletal Animations to New Mixer Based System


The mixer system was introduced in r73, and I've been trying since then to update my game to this new system.

I am ALMOST there except one thing. Cross-fading on some animations with certain geometries have a slight delay that did not exist in r72. I hacked r72's BlendCharacter and Animation functions to allow callbacks and it works great. In 73 this was not necessary has it has this functionality built in via an event trigger.

In the following fiddle everything works as intended (r72).

http://jsfiddle.net/titansoftime/a93w5hw0/

<script src="http://www.titansoftime.com/webgl/Three72.full.js"></script>
<script src="http://www.titansoftime.com/webgl/BlendCharacter2.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/loaders/DDSLoader.js"></script>

var scene, camera, renderer, ambient, directional;
var mesh, geoCache={};
var clock, jsLoader, ddsLoader;

init();
animate();

function init() {

        scene = new THREE.Scene();

        camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
        camera.position.z = 20;
        camera.position.y = 10;

        ambient = new THREE.AmbientLight(0xffffff);    
        scene.add(ambient);

        directional = new THREE.DirectionalLight(0xffffff,1);
        directional.position.set(1,1,0);
        scene.add(directional);

        clock = new THREE.Clock();

        jsLoader = new THREE.JSONLoader(true);    
        ddsLoader = new THREE.DDSLoader();

        renderer = new THREE.WebGLRenderer({antialias:true});
        renderer.setSize( window.innerWidth, window.innerHeight );
        renderer.setClearColor( 0xffffff, 1 );

        document.getElementById('idle').onclick = function(e){
            play('Idle',true);
        };

        document.getElementById('run').onclick = function(e){
            play('Run',true);
        };

        document.getElementById('melee').onclick = function(e){
            play('MelleAttack');
        };

        document.getElementById('magic').onclick = function(e){
            play('MagicAttack');
        };    

        document.body.appendChild( renderer.domElement );

        loadFloor();

        loadModel();

}

function createModel(json){

        var geo, geo2;

        if( geoCache[json.name] ){

                geo = geoCache[json.name];   

        }else{

                geo2 = jsLoader.parse(json).geometry;

                var m = new THREE.SkinnedMesh( geo2 );
                m.normalizeSkinWeights();
                geo2 = m.geometry;

                geo = new THREE.BufferGeometry().fromGeometry(geo2);
                geo.bones = geo2.bones;
                geo.animations = geo2.animations;

                geoCache[json.name] = geo;

        }

        var tex = ddsLoader.load('http://www.titansoftime.com/utils.php?task=getTexture&id=16');

        var mat = new THREE.MeshPhongMaterial({map:tex,skinning:true,side:THREE.DoubleSide});

        mesh = new THREE.BlendCharacter();
        mesh.load(geo,mat);

        //mesh.scale.set(10,10,10);

        //mesh.mixer = new THREE.AnimationMixer( mesh );

        //parseAnimations();    

        scene.add(mesh);

        play('Idle',true);

        camera.lookAt(new THREE.Vector3(mesh.position.x,7,mesh.position.z));

}

function loadModel(){

        $.ajax({

                url: 'http://www.titansoftime.com/utils.php',
                data: 'task=getModel&id=16',
                crossDomain: true,
                type: 'POST',
                success: function(response){
                        createModel(JSON.parse(response));
                }

        });    

}

function loadFloor(){

        var geo = new THREE.PlaneBufferGeometry(50,50);

        geo.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI / 2));

        var mat = new THREE.MeshBasicMaterial({color:0x0000ff});

        var mesh = new THREE.Mesh(geo,mat);

        scene.add(mesh);

}

function play(name,loop){

    loop = loop || false;

    var anim = mesh.animations[name];

    anim.loop = loop;

    if( mesh.currentAnimation ){

        var cur = mesh.animations[mesh.currentAnimation];

        var theTime = 0.175;                

        if( !cur.loop ){

            var diff = cur.data.length - cur.currentTime;

            theTime = Math.max(0,Math.min(theTime,diff));

        }

        console.log('blending: '+name);
        mesh.crossfade(name,theTime,function(){

            play('Idle',true)

        });

    }else{              
        console.log('playing: '+name);
        mesh.play(name,loop);
    }

}

function animate() {

        requestAnimationFrame( animate );

        var delta = clock.getDelta();

        if( mesh ){

                mesh.update( delta );

        } 

        THREE.AnimationHandler.update(delta);

        renderer.render( scene, camera );

}

This one (r78) works almost fine except for one animation (Magic Attack) has a small but noticeable delay before returning to the Idle animation. On other models it's the Melee animation, on some there is no problem at all. Super confused as they all work properly in 72.

http://jsfiddle.net/titansoftime/2sh95etj/

<script src="https://rawgit.com/mrdoob/three.js/master/build/three.min.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/master/examples/js/loaders/DDSLoader.js"></script>

var scene, camera, renderer, ambient, directional;
var mesh, geoCache={};
var clock, jsLoader, ddsLoader;

init();
animate();

function init() {

        scene = new THREE.Scene();

        camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
        camera.position.z = 20;
        camera.position.y = 10;

        ambient = new THREE.AmbientLight(0xffffff);    
        scene.add(ambient);

        directional = new THREE.DirectionalLight(0xffffff,1);
        directional.position.set(1,1,0);
        scene.add(directional);

        clock = new THREE.Clock();

        jsLoader = new THREE.JSONLoader(true);    
        ddsLoader = new THREE.DDSLoader();

        renderer = new THREE.WebGLRenderer({antialias:true});
        renderer.setSize( window.innerWidth, window.innerHeight );
        renderer.setClearColor( 0xffffff, 1 );

        document.getElementById('idle').onclick = function(e){
            play('Idle',true);
        };

        document.getElementById('run').onclick = function(e){
            play('Run',true);
        };

        document.getElementById('melee').onclick = function(e){
            play('MelleAttack');
        };

        document.getElementById('magic').onclick = function(e){
            play('MagicAttack');
        };    

        document.body.appendChild( renderer.domElement );

        loadFloor();

        loadModel();

}

function createModel(json){

        var geo, geo2;

        if( geoCache[json.name] ){

                geo = geoCache[json.name];   

        }else{

                geo2 = jsLoader.parse(json).geometry;

                var m = new THREE.SkinnedMesh( geo2 );
                m.normalizeSkinWeights();
                geo2 = m.geometry;

                geo = new THREE.BufferGeometry().fromGeometry(geo2);
                geo.bones = geo2.bones;
                geo.animations = geo2.animations;

                geoCache[json.name] = geo;

        }

        var tex = ddsLoader.load('http://www.titansoftime.com/utils.php?task=getTexture&id=16');

        var mat = new THREE.MeshPhongMaterial({map:tex,skinning:true,side:THREE.DoubleSide});

        mesh = new THREE.SkinnedMesh(geo,mat);

        //mesh.scale.set(10,10,10);

        mesh.mixer = new THREE.AnimationMixer( mesh );

        parseAnimations();

        play('Idle',true);

        scene.add(mesh);

        camera.lookAt(new THREE.Vector3(mesh.position.x,7,mesh.position.z));

}

function loadModel(){

        $.ajax({

                url: 'http://www.titansoftime.com/utils.php',
                data: 'task=getModel&id=16',
                crossDomain: true,
                type: 'POST',
                success: function(response){
                        createModel(JSON.parse(response));
                }

        });    

}

function loadFloor(){

        var geo = new THREE.PlaneBufferGeometry(50,50);

        geo.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI / 2));

        var mat = new THREE.MeshBasicMaterial({color:0x0000ff});

        var mesh = new THREE.Mesh(geo,mat);

        scene.add(mesh);

}

function play(name,loop){

    var to = mesh.animations[ name ];       

    if( mesh.currentAnimation ){

        var from = mesh.animations[ mesh.currentAnimation ];

        to.reset();

        if( loop ){

            to.setLoop(THREE.LoopRepeat);
            to.clampWhenFinished = false;

        }else{

            to.setLoop(THREE.LoopOnce, 0);
            to.clampWhenFinished = true;                    

            mesh.mixer.addEventListener('finished',function(e){

                play('Idle',true);

            });                     

        }

        from.play();
        to.play();

        from.enabled = true;
        to.enabled = true;

        from.crossFadeTo( to, 0.3 );                    

    }else{

        to.play();

    }

    mesh.currentAnimation = name;

}

function parseAnimations(){

    var o, anim, anims = {};

    console.log(mesh);

    for( var i=0,len=mesh.geometry.animations.length;i<len;i++){

        o = mesh.geometry.animations[i];
        if( o ){

            anim = mesh.mixer.clipAction(o,mesh);
            anim.setEffectiveWeight(1);

            anims[o.name] = anim;

        }

    }

    mesh.animations = anims;

}

function animate() {

        requestAnimationFrame( animate );

        var delta = clock.getDelta();

        if( mesh ){

            if( mesh.mixer ){

                mesh.mixer.update( delta );

            }

         } 

        renderer.render( scene, camera );

}

Why is this happening?

UPDATE: I noticed this is issue is not limited to blending between animations. One of my animations just looping now has a delay!

72: http://jsfiddle.net/titansoftime/8v0pasp5/

78: http://jsfiddle.net/titansoftime/n6apnj3z/

What is going on!? Was there some sort of auto correcting behavior or something along those lines in 72 that has been removed?


Solution

  • This is a bug in three.js.

    https://github.com/mrdoob/three.js/issues/9056

    Closing.