10

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?

Hobbes
  • 781
  • 10
  • 29
  • The 79dev fiddle you linked to does not work for me. JS "can not read property" errors which seem to have something to do with JSONLoader.parse(). I also noticed you JSON is version 3, isn't there a newer version of the three.js json format? – 2pha Jun 30 '16 at 05:35
  • They must have broke something in a recent push to 79dev, I set it to 78. They have a newer json spec yes, however their exporter does not use it (yet). Not too mention the formatting of the json file is not entirely relevant in this case, the json loader is built for spec 3. Thank you for letting me know about the break in dev. – Hobbes Jun 30 '16 at 16:50
  • The new fiddles of the sailing ship seem to work fine for me – 2pha Jul 01 '16 at 04:50
  • This is looking like a bug in three.js. One of the authors has acknowledged this (#9056). Are you saying that in the r78 version you are not seeing a delay after the last frame of the "Sail" animation? – Hobbes Jul 01 '16 at 16:16
  • Oh, in the r78 version, yes I do see a delay. I also see it play the morph animation when it is already in that state. meaning... If I click sail, it morphs to the sail state, but then if I click sail again, it plays the morph animation again even though it is in the sail state. Sorry, it seems I did not check it thoroughly last time. – 2pha Jul 02 '16 at 03:14
  • Yea in my actual app I have it to where it won't "replay" the animation, I did not want to over-complicate this example. – Hobbes Jul 03 '16 at 18:35

1 Answers1

5

This is a bug in three.js.

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

Closing.

Hobbes
  • 781
  • 10
  • 29