Skip to content

Commit

Permalink
Animation: Interpolants & extensibility overhaul.
Browse files Browse the repository at this point in the history
  • Loading branch information
tschw committed Oct 22, 2015
1 parent 31111f3 commit 2e5d9ef
Show file tree
Hide file tree
Showing 23 changed files with 1,913 additions and 884 deletions.
12 changes: 5 additions & 7 deletions examples/js/BlendCharacterGui.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,12 @@ function BlendCharacterGui( blendMesh ) {
this.update = function( time ) {

var getWeight = function( actionName ) {
for( var i = 0; i < blendMesh.mixer.actions.length; i ++ ) {
var action = blendMesh.mixer.actions[i];
if( action.clip.name === actionName ) {
return action.getWeightAt( time );
}
}
return 0;

var action = blendMesh.mixer.findActionByName( actionName );
return ( action !== null) ? action.getWeightAt( time ) : 0;

}

controls[ 'idle' ] = getWeight( 'idle' );
controls[ 'walk' ] = getWeight( 'walk' );
controls[ 'run' ] = getWeight( 'run' );
Expand Down
15 changes: 9 additions & 6 deletions examples/webgl_animation_skinning_blending.html
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,16 @@
var data = event.detail;
for ( var i = 0; i < data.anims.length; ++i ) {

for( var j = 0; j < blendMesh.mixer.actions.length; j ++ ) {
var action = blendMesh.mixer.actions[j];
if( action.clip.name === data.anims[i] ) {
if( action.getWeightAt( blendMesh.mixer.time ) !== data.weights[i] ) {
action.weight = data.weights[i];
}
var action = blendMesh.mixer.findActionByName( data.anims[i] );

if ( action !== null ) {

if( action.getWeightAt( blendMesh.mixer.time ) !== data.weights[i] ) {

action.weight = data.weights[i];

}

}

}
Expand Down
80 changes: 71 additions & 9 deletions examples/webgl_animation_skinning_morph.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@

var mesh, helper;

var mixer;
var mixer, facesAction, bonesAction;

var mouseX = 0, mouseY = 0;

Expand Down Expand Up @@ -152,11 +152,11 @@

createScene( geometry, materials, 0, FLOOR, -300, 60 )

} );
// GUI

// GUI
initGUI();

initGUI();
} );

//

Expand Down Expand Up @@ -224,20 +224,26 @@
helper.visible = false;
scene.add( helper );

mixer = new THREE.AnimationMixer( mesh );

var clipMorpher = THREE.AnimationClip.CreateFromMorphTargetSequence( 'facialExpressions', mesh.geometry.morphTargets, 3 );
var clipBones = geometry.animations[0];
bonesAction = new THREE.AnimationAction( clipBones );

mixer = new THREE.AnimationMixer( mesh );
mixer.addAction( new THREE.AnimationAction( clipMorpher ) );
mixer.addAction( new THREE.AnimationAction( clipBones ) );
var clipMorpher = THREE.AnimationClip.CreateFromMorphTargetSequence( 'facialExpressions', mesh.geometry.morphTargets, 3 );
facesAction = new THREE.AnimationAction( clipMorpher );
}

function initGUI() {

var API = {
'show model' : true,
'show skeleton' : false
'show skeleton' : false,
'bones action' : true, // use false to see initial allocation
'bones enable' : true,
'faces action' : true,
'faces enable' : true,
'release props' : function() { mixer.releaseCachedBindings( true ); },
'purge cache' : function() { mixer.releaseCachedBindings(); }
};

var gui = new dat.GUI();
Expand All @@ -246,6 +252,62 @@

gui.add( API, 'show skeleton' ).onChange( function() { helper.visible = API[ 'show skeleton' ]; } );


// Note: .add/removeAction and .enabled = true / false have
// different performance characteristics:
//
// The former changes dynamic data structures in the mixer,
// therefore the switch is more expensive but removes the
// per-action base cost caused by the unique property
// bindings it uses.
//
// The latter is a zero-cost switch, but the per-frame base
// cost for having the action added to the mixer remains.

function actionControls( key, action ) {

var guiNameAddRemove = key + ' action';
var guiNameEnabled = key + ' enable';

// set initial state

if ( API[ guiNameAddRemove ] ) {

action.enabled = API[ guiNameEnabled ];
mixer.addAction( action );

}

// attach controls

gui.add( API, guiNameAddRemove ).onChange( function() {

if ( API[ guiNameAddRemove ] ) {

mixer.addAction( action );

} else {

mixer.removeAction( action );

}

} );

gui.add( API, guiNameEnabled ).onChange( function() {

action.enabled = API[ guiNameEnabled ];

} );

}

actionControls( 'bones', bonesAction );
actionControls( 'faces', facesAction );

gui.add( API, 'release props' );
gui.add( API, 'purge cache' );

}

function onDocumentMouseMove( event ) {
Expand Down
6 changes: 6 additions & 0 deletions src/Three.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,12 @@ THREE.LoopOnce = 2200;
THREE.LoopRepeat = 2201;
THREE.LoopPingPong = 2202;

// Interpolation

THREE.InterpolateDiscrete = 2300;
THREE.InterpolateLinear = 2301;
THREE.InterpolateSmooth = 2302;

// DEPRECATED

THREE.Projector = function () {
Expand Down
82 changes: 53 additions & 29 deletions src/animation/AnimationAction.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
/**
*
* A clip that has been explicitly scheduled.
*
* Runnable instance of an AnimationClip.
*
* Multiple Actions are required to add the same clip with the (same or
* different) mixer(s) simultaneously.
*
*
* @author Ben Houston / http://clara.io/
* @author David Sarno / http://lighthaus.us/
*/

THREE.AnimationAction = function ( clip, startTime, timeScale, weight, loop ) {

if( clip === undefined ) throw new Error( 'clip is null' );
this.name = '';
this.clip = clip;
this.localRoot = null;
this.startTime = startTime || 0;
Expand All @@ -21,7 +26,24 @@ THREE.AnimationAction = function ( clip, startTime, timeScale, weight, loop ) {
this.actionTime = - this.startTime;
this.clipTime = 0;

this.propertyBindings = [];
this.mixer = null;

var tracks = clip.tracks,
nTracks = tracks.length,
interpolants = new Array( nTracks );

for ( var i = 0; i !== nTracks; ++ i ) {

interpolants[ i ] = tracks[ i ].createInterpolant( null );

}

this._interpolants = interpolants;
this._propertyBindings = new Array( nTracks );

this._prevRootUuid = '';
this._prevMixerUuid = '';

};

/*
Expand All @@ -34,12 +56,18 @@ THREE.AnimationAction.prototype = {

constructor: THREE.AnimationAction,

getName: function() {

return this.name || this.clip.name;

},

setLocalRoot: function( localRoot ) {

this.localRoot = localRoot;

return this;

},

updateTime: function( clipDeltaTime ) {
Expand All @@ -49,14 +77,14 @@ THREE.AnimationAction.prototype = {
var previousActionTime = this.actionTime;

var duration = this.clip.duration;

this.actionTime = this.actionTime + clipDeltaTime;

if( this.loop === THREE.LoopOnce ) {

this.loopCount = 0;
this.clipTime = Math.min( Math.max( this.actionTime, 0 ), duration );

// if time is changed since last time, see if we have hit a start/end limit
if( this.clipTime !== previousClipTime ) {

Expand All @@ -73,16 +101,16 @@ THREE.AnimationAction.prototype = {

}


return this.clipTime;

}

this.loopCount = Math.floor( this.actionTime / duration );

var newClipTime = this.actionTime - this.loopCount * duration;
newClipTime = newClipTime % duration;

// if we are ping pong looping, ensure that we go backwards when appropriate
if( this.loop == THREE.LoopPingPong ) {

Expand All @@ -101,7 +129,7 @@ THREE.AnimationAction.prototype = {
this.mixer.dispatchEvent( { type: 'loop', action: this, loopDelta: ( this.loopCount - this.loopCount ) } );

}

return this.clipTime;

},
Expand Down Expand Up @@ -129,37 +157,33 @@ THREE.AnimationAction.prototype = {

},

update: function( clipDeltaTime ) {

this.updateTime( clipDeltaTime );
// pass in time, not clip time, allows for fadein/fadeout across multiple loops of the clip
getTimeScaleAt: function( time ) {

var clipResults = this.clip.getAt( this.clipTime );
var timeScale = this.timeScale;

return clipResults;

},
if( timeScale.getAt !== undefined ) {

getTimeScaleAt: function( time ) {

if( this.timeScale.getAt ) {
// pass in time, not clip time, allows for fadein/fadeout across multiple loops of the clip
return this.timeScale.getAt( time );
return timeScale.getAt( time )[ 0 ];

}

return this.timeScale;
return timeScale;

},

// pass in time, not clip time, allows for fadein/fadeout across multiple loops of the clip
getWeightAt: function( time ) {

if( this.weight.getAt ) {
// pass in time, not clip time, allows for fadein/fadeout across multiple loops of the clip
return this.weight.getAt( time );
var weight = this.weight;

if( weight.getAt !== undefined ) {

return weight.getAt( time )[ 0 ];

}

return this.weight;
return weight;

}

Expand Down
Loading

0 comments on commit 2e5d9ef

Please sign in to comment.