提交 c32f31a5 编写于 作者: T tschw

Animation: New API, caching and masses.

上级 6db066d9
......@@ -78,7 +78,7 @@
mixer = new THREE.AnimationMixer( mesh );
var clip = THREE.AnimationClip.CreateFromMorphTargetSequence( 'gallop', geometry.morphTargets, 30 );
mixer.addAction( new THREE.AnimationAction( clip ).warpToDuration( 1 ) );
mixer.clipAction( clip ).setDuration( 1 ).play();
} );
......
......@@ -4,7 +4,6 @@
THREE.BlendCharacter = function () {
this.animations = {};
this.weightSchedule = [];
this.warpSchedule = [];
......@@ -20,13 +19,13 @@ THREE.BlendCharacter = function () {
THREE.SkinnedMesh.call( scope, geometry, originalMaterial );
scope.mixer = new THREE.AnimationMixer( scope );
var mixer = new THREE.AnimationMixer( scope );
scope.mixer = mixer;
// Create the animations
// Create the animations
for ( var i = 0; i < geometry.animations.length; ++ i ) {
var animName = geometry.animations[ i ].name;
scope.animations[ animName ] = geometry.animations[ i ];
mixer.clipAction( geometry.animations[ i ] );
}
......@@ -45,49 +44,45 @@ THREE.BlendCharacter = function () {
this.play = function( animName, weight ) {
this.mixer.removeAllActions();
this.mixer.play( new THREE.AnimationAction( this.animations[ animName ] ) );
//console.log("play('%s', %f)", animName, weight);
return this.mixer.clipAction( animName ).
setEffectiveWeight( weight ).play();
};
this.crossfade = function( fromAnimName, toAnimName, duration ) {
this.mixer.removeAllActions();
var fromAction = new THREE.AnimationAction( this.animations[ fromAnimName ] );
var toAction = new THREE.AnimationAction( this.animations[ toAnimName ] );
this.mixer.stopAllAction();
this.mixer.play( fromAction );
this.mixer.play( toAction );
var fromAction = this.play( fromAnimName, 1 );
var toAction = this.play( toAnimName, 1 );
this.mixer.crossFade( fromAction, toAction, duration, false );
fromAction.crossFadeTo( toAction, duration, false );
};
this.warp = function( fromAnimName, toAnimName, duration ) {
this.mixer.removeAllActions();
var fromAction = new THREE.AnimationAction( this.animations[ fromAnimName ] );
var toAction = new THREE.AnimationAction( this.animations[ toAnimName ] );
this.mixer.stopAllAction();
this.mixer.play( fromAction );
this.mixer.play( toAction );
var fromAction = this.play( fromAnimName, 1 );
var toAction = this.play( toAnimName, 1 );
this.mixer.crossFade( fromAction, toAction, duration, true );
fromAction.crossFadeTo( toAction, duration, true );
};
this.applyWeight = function( animName, weight ) {
var action = this.mixer.findActionByName( animName );
if( action ) {
action.weight = weight;
}
this.mixer.clipAction( animName ).setEffectiveWeight( weight );
};
this.getWeight = function( animName ) {
return this.mixer.clipAction( animName ).getEffectiveWeight();
}
this.pauseAll = function() {
this.mixer.timeScale = 0;
......@@ -103,7 +98,7 @@ THREE.BlendCharacter = function () {
this.stopAll = function() {
this.mixer.removeAllActions();
this.mixer.stopAllAction();
};
......
......@@ -40,16 +40,9 @@ function BlendCharacterGui( blendMesh ) {
this.update = function( time ) {
var getWeight = function( actionName ) {
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' );
controls[ 'idle' ] = blendMesh.getWeight( 'idle' );
controls[ 'walk' ] = blendMesh.getWeight( 'walk' );
controls[ 'run' ] = blendMesh.getWeight( 'run' );
};
......
......@@ -33,7 +33,6 @@ THREE.MD2Character = function () {
var weaponsTextures = [];
for ( var i = 0; i < config.weapons.length; i ++ ) weaponsTextures[ i ] = config.weapons[ i ][ 1 ];
// SKINS
this.skinsBody = loadTextures( config.baseUrl + "skins/", config.skins );
......@@ -81,6 +80,21 @@ THREE.MD2Character = function () {
scope.weapons[ index ] = mesh;
scope.meshWeapon = mesh;
// the animation system requires unique names, so append the
// uuid of the source geometry:
var geometry = mesh.geometry,
animations = geometry.animations;
for ( var i = 0, n = animations.length; i !== n; ++ i ) {
var animation = animations[ i ];
animation.name += geometry.uuid;
}
checkLoadingComplete();
}
......@@ -154,17 +168,15 @@ THREE.MD2Character = function () {
if ( this.meshBody ) {
if( this.meshBody.activeAction ) {
scope.mixer.removeAction( this.meshBody.activeAction );
this.meshBody.activeAction.stop();
this.meshBody.activeAction = null;
}
var clip = THREE.AnimationClip.findByName( this.meshBody.geometry.animations, clipName );
if( clip ) {
var action = new THREE.AnimationAction( clip, this.mixer.time ).setLocalRoot( this.meshBody );
scope.mixer.addAction( action );
this.meshBody.activeAction = action;
this.meshBody.activeAction =
this.mixer.clipAction( clip, this.meshBody ).play();
}
......@@ -183,17 +195,19 @@ THREE.MD2Character = function () {
if ( scope.meshWeapon ) {
if( this.meshWeapon.activeAction ) {
scope.mixer.removeAction( this.meshWeapon.activeAction );
this.meshWeapon.activeAction.stop();
this.meshWeapon.activeAction = null;
}
var clip = THREE.AnimationClip.findByName( this.meshWeapon.geometry.animations, clipName );
if( clip ) {
var geometry = this.meshWeapon.geometry,
animations = geometry.animations;
var action = new THREE.AnimationAction( clip ).syncWith( this.meshBody.activeAction ).setLocalRoot( this.meshWeapon );
scope.mixer.addAction( action );
var clip = THREE.AnimationClip.findByName( animations, clipName + geometry.uuid );
if( clip ) {
this.meshWeapon.activeAction = action;
this.meshWeapon.activeAction =
this.mixer.clipAction( clip, this.meshWeapon ).
syncWith( this.meshBody.activeAction ).play();
}
......
......@@ -31,7 +31,7 @@ THREE.MorphAnimMesh.prototype.playAnimation = function ( label, fps ) {
if( this.activeAction ) {
this.mixer.removeAction( this.activeAction );
this.activeAction.stop();
this.activeAction = null;
}
......@@ -40,10 +40,9 @@ THREE.MorphAnimMesh.prototype.playAnimation = function ( label, fps ) {
if ( clip ) {
var action = new THREE.AnimationAction( clip );
var action = this.mixer.clipAction( clip );
action.timeScale = ( clip.tracks.length * fps ) / clip.duration;
this.mixer.addAction( action );
this.activeAction = action;
this.activeAction = action.play();
} else {
......
......@@ -55,7 +55,7 @@ THREE.UCSCharacter = function() {
mesh.castShadow = true;
mesh.receiveShadow = true;
scope.mixer.addAction( new THREE.AnimationAction( geometry.animations[0] ).setLocalRoot( mesh ) );
scope.mixer.clipAction( geometry.animations[0], mesh ).play();
scope.setSkin( 0 );
......
......@@ -133,7 +133,7 @@
var blendObject = scene.getObjectByName( 'tree-morph' );
var clip = blendObject.geometry.animations[0];
mixer = new THREE.AnimationMixer( blendObject );
mixer.addAction( new THREE.AnimationAction( clip ) );
mixer.clipAction( clip ).play();
} );
......
......@@ -131,7 +131,7 @@
mixer = new THREE.AnimationMixer( scene );
mixer.addAction( new THREE.AnimationAction( sceneAnimationClip ) );
mixer.clipAction( sceneAnimationClip ).play();
} );
......
......@@ -156,17 +156,7 @@
var data = event.detail;
for ( var i = 0; i < data.anims.length; ++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];
}
}
blendMesh.applyWeight( data.anims[ i ], data.weights[ i ] );
}
......
......@@ -22,6 +22,12 @@
padding: 5px;
}
#meminfo {
margin-top: 8px;
font-size: 10px;
display: none;
}
a {
color: #0af;
}
......@@ -40,6 +46,7 @@
<div id="info">
<a href="http://threejs.org" target="_blank">three.js</a> webgl - clip system
- knight by <a href="http://vimeo.com/36113323">apendua</a>
<div id="meminfo"></div>
</div>
<script src="../build/three.min.js"></script>
......@@ -59,9 +66,9 @@
var camera, scene;
var renderer;
var mesh, helper;
var mesh, mesh2, helper;
var mixer, facesAction, bonesAction;
var mixer, facesClip, bonesClip;
var mouseX = 0, mouseY = 0;
......@@ -70,6 +77,9 @@
var clock = new THREE.Clock();
var domMemInfo = document.getElementById( 'meminfo' ),
showMemInfo = false;
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
init();
......@@ -212,6 +222,7 @@
}
mesh = new THREE.SkinnedMesh( geometry, new THREE.MeshFaceMaterial( materials ) );
mesh.name = "Knight Mesh";
mesh.position.set( x, y - bb.min.y * s, z );
mesh.scale.set( s, s, s );
scene.add( mesh );
......@@ -219,6 +230,19 @@
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh2 = new THREE.SkinnedMesh( geometry, new THREE.MeshFaceMaterial( materials ) );
mesh2.name = "Lil' Bro Mesh";
mesh2.position.set( x - 240, y - bb.min.y * s, z + 500 );
mesh2.scale.set( s / 2, s / 2, s / 2 );
mesh2.rotation.y = THREE.Math.degToRad( 60 );
mesh2.visible = false;
mesh2.castShadow = true;
mesh2.receiveShadow = true;
scene.add( mesh2 );
helper = new THREE.SkeletonHelper( mesh );
helper.material.linewidth = 3;
helper.visible = false;
......@@ -226,87 +250,375 @@
mixer = new THREE.AnimationMixer( mesh );
var clipBones = geometry.animations[0];
bonesAction = new THREE.AnimationAction( clipBones );
var clipMorpher = THREE.AnimationClip.CreateFromMorphTargetSequence( 'facialExpressions', mesh.geometry.morphTargets, 3 );
facesAction = new THREE.AnimationAction( clipMorpher );
bonesClip = geometry.animations[0];
facesClip = THREE.AnimationClip.CreateFromMorphTargetSequence( 'facialExpressions', mesh.geometry.morphTargets, 3 );
}
function initGUI() {
var API = {
'show model' : true,
'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(); }
'show model' : true,
'show skeleton' : false,
'show 2nd model' : false,
'show mem. info' : false
};
var gui = new dat.GUI();
gui.add( API, 'show model' ).onChange( function() { mesh.visible = API[ 'show model' ]; } );
gui.add( API, 'show model' ).onChange( function() {
mesh.visible = API[ 'show model' ];
} );
gui.add( API, 'show skeleton' ).onChange( function() {
helper.visible = API[ 'show skeleton' ];
} );
gui.add( API, 'show skeleton' ).onChange( function() { helper.visible = API[ 'show skeleton' ]; } );
gui.add( API, 'show 2nd model' ).onChange( function() {
mesh2.visible = API[ 'show 2nd model' ];
} );
// 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.
gui.add( API, 'show mem. info' ).onChange( function() {
function actionControls( key, action ) {
showMemInfo = API[ 'show mem. info' ];
domMemInfo.style.display = showMemInfo ? 'block' : 'none';
var guiNameAddRemove = key + ' action';
var guiNameEnabled = key + ' enable';
} );
// utility function used for drop-down options lists in the GUI
var objectNames = function( objects ) {
// set initial state
var result = [];
if ( API[ guiNameAddRemove ] ) {
for ( var i = 0, n = objects.length; i !== n; ++ i ) {
action.enabled = API[ guiNameEnabled ];
mixer.addAction( action );
var obj = objects[ i ];
result.push( obj && obj.name || '&lt;null&gt;' );
}
// attach controls
return result;
gui.add( API, guiNameAddRemove ).onChange( function() {
};
if ( API[ guiNameAddRemove ] ) {
mixer.addAction( action );
// creates gui folder with tests / examples for the action API
var clipControl = function clipControl( gui, mixer, clip, rootObjects ) {
} else {
var folder = gui.addFolder( "Clip '" + clip.name + "'" ),
mixer.removeAction( action );
rootNames = objectNames( rootObjects ),
rootName = rootNames[ 0 ],
root = rootObjects[ 0 ],
}
action = null,
} );
API = {
'play()': function play() {
action = mixer.clipAction( clip, root );
action.play();
},
'stop()': function() {
action = mixer.clipAction( clip, root );
action.stop();
},
'reset()': function() {
action = mixer.clipAction( clip, root );
action.reset();
},
get 'time ='() {
return action !== null ? action.time : 0;
},
set 'time ='( value ) {
action = mixer.clipAction( clip, root );
action.time = value;
},
get 'paused ='() {
return action !== null && action.paused;
},
set 'paused ='( value ) {
action = mixer.clipAction( clip, root );
action.paused = value;
},
get 'enabled ='() {
return action !== null && action.enabled;
},
set 'enabled ='( value ) {
action = mixer.clipAction( clip, root );
action.enabled = value;
},
get 'clamp ='() {
return action !== null ? action.clampWhenFinished : true;
},
set 'clamp ='( value ) {
action = mixer.clipAction( clip, root );
action.clampWhenFinished = value;
},
get 'isRunning() ='() {
return action !== null && action.isRunning();
},
set 'isRunning() ='( value ) {
alert( "Read only - this is the result of a method." );
},
'play delayed': function() {
action = mixer.clipAction( clip, root );
action.startAt( mixer.time + 0.5 ).play();
},
get 'weight ='() {
return action !== null ? action.weight : 1;
},
set 'weight ='( value ) {
action = mixer.clipAction( clip, root );
action.weight = value;
},
get 'eff. weight'() {
return action !== null ? action.getEffectiveWeight() : 1;
},
set 'eff. weight'( value ) {
action = mixer.clipAction( clip, root );
action.setEffectiveWeight( value );
},
gui.add( API, guiNameEnabled ).onChange( function() {
'fade in': function() {
action.enabled = API[ guiNameEnabled ];
action = mixer.clipAction( clip, root );
action.reset().fadeIn( 0.25 ).play();
},
'fade out': function() {
action = mixer.clipAction( clip, root );
action.fadeOut( 0.25 ).play();
},
get 'timeScale ='() {
return ( action !== null ) ? action.timeScale : 1;
},
set 'timeScale ='( value ) {
action = mixer.clipAction( clip, root );
action.timeScale = value;
},
get 'eff.T.Scale'() {
return ( action !== null ) ? action.getEffectiveTimeScale() : 1;
},
set 'eff.T.Scale'( value ) {
action = mixer.clipAction( clip, root );
action.setEffectiveTimeScale( value );
},
'time warp': function() {
action = mixer.clipAction( clip, root );
var timeScaleNow = action.getEffectiveTimeScale();
var destTimeScale = timeScaleNow > 0 ? -1 : 1;
action.warp( timeScaleNow, destTimeScale, 4 ).play();
},
get 'loop mode'() {
return action !== null ? action.loop : THREE.LoopRepeat;
},
set 'loop mode'( value ) {
action = mixer.clipAction( clip, root );
action.loop = + value;
},
get 'repetitions'() {
return action !== null ? action.repetitions : Infinity;
},
set 'repetitions'( value ) {
action = mixer.clipAction( clip, root );
action.repetitions = + value;
},
get 'local root'() { return rootName; },
set 'local root'( value ) {
rootName = value;
root = rootObjects[ rootNames.indexOf( rootName ) ];
if ( action !== null ) {
// TODO
}
}
};
folder.add( API, 'play()' );
folder.add( API, 'stop()' );
folder.add( API, 'reset()' );
folder.add( API, 'time =', 0, clip.duration ).listen();
folder.add( API, 'paused =' ).listen();
folder.add( API, 'enabled =' ).listen();
folder.add( API, 'clamp =' );
folder.add( API, 'isRunning() =').listen();
folder.add( API, 'play delayed' );
folder.add( API, 'weight =', 0, 1 ).listen();
folder.add( API, 'eff. weight', 0, 1 ).listen();
folder.add( API, 'fade in' );
folder.add( API, 'fade out' );
folder.add( API, 'timeScale =', -2, 2).listen();
folder.add( API, 'eff.T.Scale', -2, 2).listen();
folder.add( API, 'time warp' );
folder.add( API, 'loop mode', {
"LoopOnce": THREE.LoopOnce,
"LoopRepeat": THREE.LoopRepeat,
"LoopPingPong": THREE.LoopPingPong
} );
folder.add( API, 'repetitions', 0, Infinity );
folder.add( API, 'local root', rootNames );
}; // function clipControl
// one folder per clip
clipControl( gui, mixer, bonesClip, [ null, mesh, mesh2 ] );
clipControl( gui, mixer, facesClip, [ null, mesh, mesh2 ] );
var memoryControl = function( gui, mixer, clips, rootObjects ) {
var clipNames = objectNames( clips ),
rootNames = objectNames( rootObjects );
var folder = gui.addFolder( "Memory Management" ),
clipName = clipNames[ 0 ],
clip = clips[ 0 ],
rootName = rootNames[ 0 ],
root = rootObjects[ 0 ],
API = {
get 'clip'() { return clipName; },
set 'clip'( value ) {
clipName = value;
clip = clips[ clipNames.indexOf( clipName ) ];
},
get 'root'() { return rootName; },
set 'root'( value ) {
rootName = value;
root = rootObjects[ rootNames.indexOf( rootName ) ];
},
'uncache clip': function() {
mixer.uncacheClip( clip );
},
'uncache root': function() {
mixer.uncacheRoot( root );
},
'uncache action': function() {
mixer.uncacheAction( clip, root );
}
};
folder.add( API, 'clip', clipNames );
folder.add( API, 'root', rootNames );
folder.add( API, 'uncache root' );
folder.add( API, 'uncache clip' );
folder.add( API, 'uncache action' );
}
actionControls( 'bones', bonesAction );
actionControls( 'faces', facesAction );
memoryControl( gui, mixer,
[ bonesClip, facesClip ], [ mesh, mesh2 ] );
gui.add( API, 'release props' );
gui.add( API, 'purge cache' );
}
......@@ -326,6 +638,18 @@
render();
stats.update();
if ( showMemInfo ) {
var s = mixer.stats,
ciS = s.controlInterpolants;
domMemInfo.innerHTML =
s.actions.inUse + " / " + s.actions.total + " actions " +
s.bindings.inUse + " / " + s.bindings.total + " bindings " +
ciS.inUse + " / " + ciS.total + " control interpolants";
}
}
function render() {
......
......@@ -214,7 +214,7 @@
scene.add( mesh );
var mixer = new THREE.AnimationMixer( mesh );
mixer.addAction( new THREE.AnimationAction( geometry.animations[ 0 ] ).warpToDuration( 1 ) );
mixer.clipAction( geometry.animations[ 0 ] ).setDuration( 1 ).play();
mixers.push( mixer );
} );
......
......@@ -59,7 +59,7 @@
var dae;
var clock = new THREE.Clock();
var mixers = [];
var mixer;
// Collada model
......@@ -102,6 +102,8 @@
// Add Blender exported Collada model
mixer = new THREE.AnimationMixer( scene );
var loader = new THREE.JSONLoader();
loader.load( 'models/animated/monster/monster.js', function ( geometry, materials ) {
......@@ -137,13 +139,10 @@
scene.add( mesh );
var mixer = new THREE.AnimationMixer( mesh );
mixer.addAction( new THREE.AnimationAction( geometry.animations[0] ).warpToDuration( 1 ) );
// random animation offset
mixer.update( 1000 * Math.random() );
mixers.push( mixer );
mixer.clipAction( geometry.animations[0], mesh )
.setDuration( 1 ) // one second
.startAt( - Math.random() ) // random phase (already running)
.play(); // let's go
}
......@@ -203,11 +202,7 @@
THREE.AnimationHandler.update( delta );
for ( var i = 0; i < mixers.length; i ++ ) {
mixers[ i ].update( delta );
}
mixer.update( delta );
render();
......
......@@ -214,7 +214,7 @@
if( object.geometry && object.geometry.animations && object.geometry.animations.length > 0 ) {
var mixer = new THREE.AnimationMixer( object );
mixer.addAction( new THREE.AnimationAction( object.geometry.animations[0] ) );
mixer.clipAction( object.geometry.animations[0] ).play();
mixers.push( mixer );
}
......
......@@ -93,7 +93,7 @@
scene.add( mesh );
var mixer = new THREE.AnimationMixer( mesh );
mixer.addAction( new THREE.AnimationAction( geometry.animations[ 0 ] ).warpToDuration( 1 ) );
mixer.clipAction( geometry.animations[ 0 ] ).setDuration( 1 ).play();
mixers.push( mixer );
......@@ -120,7 +120,7 @@
scene.add( mesh );
var mixer = new THREE.AnimationMixer( mesh );
mixer.addAction( new THREE.AnimationAction( geometry.animations[ 0 ] ).warpToDuration( 1 ) );
mixer.clipAction( geometry.animations[ 0 ] ).setDuration( 1 ).play();
mixers.push( mixer );
......
......@@ -72,7 +72,7 @@
mixer = new THREE.AnimationMixer( mesh );
var clip = THREE.AnimationClip.CreateFromMorphTargetSequence( 'gallop', geometry.morphTargets, 30 );
mixer.addAction( new THREE.AnimationAction( clip ).warpToDuration( 1 ) );
mixer.clipAction( clip ).setDuration( 1 ).play();
} );
......
......@@ -275,7 +275,7 @@
mixer = new THREE.AnimationMixer( mesh );
mixer.addAction( new THREE.AnimationAction( geometry.animations[0] ).warpToDuration( 10 ) );
mixer.clipAction( geometry.animations[0] ).setDuration( 10 ).play();
var s = 200;
mesh.scale.set( s, s, s );
......
......@@ -64,7 +64,7 @@
var sceneHUD, cameraOrtho, hudMesh;
var morphs = [];
var mixer, morphs = [];
var light;
......@@ -306,6 +306,8 @@
// MORPHS
mixer = new THREE.AnimationMixer( scene );
function addMorph( geometry, speed, duration, x, y, z, fudgeColor ) {
var material = new THREE.MeshLambertMaterial( { color: 0xffaa55, morphTargets: true, vertexColors: THREE.FaceColors } );
......@@ -319,10 +321,13 @@
var mesh = new THREE.Mesh( geometry, material );
mesh.speed = speed;
var mixer = new THREE.AnimationMixer( mesh );
mixer.addAction( new THREE.AnimationAction( geometry.animations[0] ).warpToDuration( duration ) );
mixer.update( 600 * Math.random() );
mesh.mixer = mixer;
var clip = geometry.animations[ 0 ];
mixer.clipAction( clip, mesh ).
setDuration( duration ).
// to shift the playback out of phase:
startAt( - duration * Math.random() ).
play();
mesh.position.set( x, y, z );
mesh.rotation.y = Math.PI/2;
......@@ -383,12 +388,12 @@
var delta = clock.getDelta();
mixer.update( delta );
for ( var i = 0; i < morphs.length; i ++ ) {
morph = morphs[ i ];
morph.mixer.update( delta );
morph.position.x += morph.speed * delta;
if ( morph.position.x > 2000 ) {
......
......@@ -54,12 +54,14 @@
var SCREEN_HEIGHT = window.innerHeight;
var FLOOR = -250;
var ANIMATION_GROUPS = 25;
var camera, controls, scene, renderer;
var container, stats;
var NEAR = 5, FAR = 3000;
var morph, morphs = [], mixer;
var morph, morphs = [], mixer, animGroups = [];
var light;
......@@ -240,9 +242,16 @@
mixer = new THREE.AnimationMixer( scene );
for ( var i = 0; i !== ANIMATION_GROUPS; ++ i ) {
var group = new THREE.AnimationObjectGroup();
animGroups.push( new THREE.AnimationObjectGroup() );
}
// MORPHS
function addMorph( geometry, speed, duration, x, y, z, fudgeColor ) {
function addMorph( geometry, speed, duration, x, y, z, fudgeColor, massOptimization ) {
var material = new THREE.MeshLambertMaterial( { color: 0xffaa55, morphTargets: true, vertexColors: THREE.FaceColors } );
......@@ -255,7 +264,35 @@
var mesh = new THREE.Mesh( geometry, material );
mesh.speed = speed;
mixer.addAction( new THREE.AnimationAction( geometry.animations[0], Math.random() ).warpToDuration( duration ).setLocalRoot( mesh ) );
var clip = geometry.animations[ 0 ];
if ( massOptimization ) {
var index = Math.floor( Math.random() * ANIMATION_GROUPS ),
animGroup = animGroups[ index ];
animGroup.add( mesh );
if ( ! mixer.existingAction( clip, animGroup ) ) {
var randomness = 0.6 * Math.random() - 0.3;
var phase = ( index + randomness ) / ANIMATION_GROUPS;
mixer.clipAction( clip, animGroup ).
setDuration( duration ).
startAt( - duration * phase ).
play();
}
} else {
mixer.clipAction( clip, mesh ).
setDuration( duration ).
startAt( - duration * Math.random() ).
play();
}
mesh.position.set( x, y, z );
mesh.rotation.y = Math.PI/2;
......@@ -275,7 +312,7 @@
for ( var i = - 600; i < 601; i += 2 ) {
addMorph( geometry, 550, 1, 100 - Math.random() * 3000, FLOOR, i, true );
addMorph( geometry, 550, 1, 100 - Math.random() * 3000, FLOOR, i, true, true );
}
......@@ -342,7 +379,7 @@
var delta = clock.getDelta();
if( mixer ) mixer.update( delta );
if ( mixer ) mixer.update( delta );
for ( var i = 0; i < morphs.length; i ++ ) {
......
......@@ -76,7 +76,7 @@
scene.add( skinnedMesh );
mixer = new THREE.AnimationMixer( skinnedMesh );
mixer.addAction( new THREE.AnimationAction( skinnedMesh.geometry.animations[0] ) );
mixer.clipAction( skinnedMesh.geometry.animations[ 0 ] ).play();
});
......
......@@ -502,7 +502,7 @@
mesh.speed = speed;
var mixer = new THREE.AnimationMixer( mesh );
mixer.addAction( new THREE.AnimationAction( geometry.animations[0] ).warpToDuration( duration ) );
mixer.clipAction( geometry.animations[ 0 ] ).setDuration( duration ).play();
mixer.update( 600 * Math.random() );
mesh.mixer = mixer;
......
/**
*
* 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;
this.timeScale = timeScale || 1;
this.weight = weight || 1;
this.loop = loop || THREE.LoopRepeat;
this.loopCount = 0;
this.enabled = true; // allow for easy disabling of the action.
this.actionTime = - this.startTime;
this.clipTime = 0;
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 = '';
};
/*
THREE.LoopOnce = 2200;
THREE.LoopRepeat = 2201;
THREE.LoopPingPing = 2202;
*/
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 ) {
var previousClipTime = this.clipTime;
var previousLoopCount = this.loopCount;
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 ) {
if( this.clipTime === duration ) {
this.mixer.dispatchEvent( { type: 'finished', action: this, direction: 1 } );
}
else if( this.clipTime === 0 ) {
this.mixer.dispatchEvent( { type: 'finished', action: this, direction: -1 } );
}
}
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 ) {
if( Math.abs( this.loopCount % 2 ) === 1 ) {
newClipTime = duration - newClipTime;
}
}
this.clipTime = newClipTime;
if( this.loopCount !== previousLoopCount ) {
this.mixer.dispatchEvent( { type: 'loop', action: this, loopDelta: ( this.loopCount - this.loopCount ) } );
}
return this.clipTime;
},
syncWith: function( action ) {
this.actionTime = action.actionTime;
this.timeScale = action.timeScale;
return this;
},
warpToDuration: function( duration ) {
this.timeScale = this.clip.duration / duration;
return this;
},
init: function( time ) {
this.clipTime = time - this.startTime;
return this;
},
// pass in time, not clip time, allows for fadein/fadeout across multiple loops of the clip
getTimeScaleAt: function( time ) {
var timeScale = this.timeScale;
if( timeScale.evaluate !== undefined ) {
return timeScale.evaluate( time )[ 0 ];
}
return timeScale;
},
// pass in time, not clip time, allows for fadein/fadeout across multiple loops of the clip
getWeightAt: function( time ) {
var weight = this.weight;
if( weight.evaluate !== undefined ) {
return weight.evaluate( time )[ 0 ];
}
return weight;
}
};
......@@ -8,7 +8,7 @@
THREE.AnimationClip = function ( name, duration, tracks ) {
this.name = name;
this.name = name || THREE.Math.generateUUID();
this.tracks = tracks;
this.duration = ( duration !== undefined ) ? duration : -1;
......
此差异已折叠。
/**
*
* A group of objects that receives a shared animation state.
*
* Usage:
*
* - Add objects you would otherwise pass as 'root' to the
* constructor or the .clipAction method of AnimationMixer.
*
* - Instead pass this object as 'root'.
*
* - You can also add and remove objects later when the mixer
* is running.
*
* Note:
*
* Objects of this class appear as one object to the mixer,
* so cache control of the individual objects must be done
* on the group.
*
* Limitation:
*
* - The animated properties must be compatible among the
* all objects in the group.
*
* - A single property can either be controlled through a
* target group or directly, but not both.
*
* @author tschw
*/
THREE.AnimationObjectGroup = function( var_args ) {
this.uuid = THREE.Math.generateUUID();
// cached objects followed by the active ones
this._objects = Array.prototype.slice.call( arguments );
this.nCachedObjects_ = 0; // threshold
// note: read by PropertyBinding.Composite
var indices = {};
this._indicesByUUID = indices; // for bookkeeping
for ( var i = 0, n = arguments.length; i !== n; ++ i ) {
indices[ arguments[ i ].uuid ] = i;
}
this._paths = []; // inside: string
this._parsedPaths = []; // inside: { we don't care, here }
this._bindings = []; // inside: Array< PropertyBinding >
this._bindingsIndicesByPath = {}; // inside: indices in these arrays
var scope = this;
this.stats = {
objects: {
get total() { return scope._objects.length; },
get inUse() { return this.total - scope.nCachedObjects_; }
},
get bindingsPerObject() { return scope._bindings.length; }
};
};
THREE.AnimationObjectGroup.prototype = {
constructor: THREE.AnimationObjectGroup,
add: function( var_args ) {
var objects = this._objects,
nObjects = objects.length,
nCachedObjects = this.nCachedObjects_,
indicesByUUID = this._indicesByUUID,
paths = this._paths,
parsedPaths = this._parsedPaths,
bindings = this._bindings,
nBindings = bindings.length;
for ( var i = 0, n = arguments.length; i !== n; ++ i ) {
var object = arguments[ i ],
uuid = object.uuid,
index = indicesByUUID[ uuid ];
if ( index === undefined ) {
// unknown object -> add it to the ACTIVE region
index = nObjects ++;
indicesByUUID[ uuid ] = index;
objects.push( object );
// accounting is done, now do the same for all bindings
for ( var j = 0, m = nBindings; j !== m; ++ j ) {
bindings[ j ].push(
new THREE.PropertyBinding(
object, paths[ j ], parsedPaths[ j ] ) );
}
} else if ( index < nCachedObjects ) {
var knownObject = objects[ index ];
// move existing object to the ACTIVE region
var firstActiveIndex = -- nCachedObjects,
lastCachedObject = objects[ firstActiveIndex ];
indicesByUUID[ lastCachedObject.uuid ] = index;
objects[ index ] = lastCachedObject;
indicesByUUID[ uuid ] = firstActiveIndex;
objects[ firstActiveIndex ] = object;
// accounting is done, now do the same for all bindings
for ( var j = 0, m = nBindings; j !== m; ++ j ) {
var bindingsForPath = bindings[ j ],
lastCached = bindingsForPath[ firstActiveIndex ],
binding = bindingsForPath[ index ];
bindingsForPath[ index ] = lastCached;
if ( binding === undefined ) {
// since we do not bother to create new bindings
// for objects that are cached, the binding may
// or may not exist
binding = new THREE.PropertyBinding(
object, paths[ j ], parsedPaths[ j ] );
}
bindingsForPath[ firstActiveIndex ] = binding;
}
} else if ( objects[ index ] !== knownObject) {
console.error( "Different objects with the same UUID " +
"detected. Clean the caches or recreate your " +
"infrastructure when reloading scenes..." );
} // else the object is already where we want it to be
} // for arguments
this.nCachedObjects_ = nCachedObjects;
},
remove: function( var_args ) {
var objects = this._objects,
nObjects = objects.length,
nCachedObjects = this.nCachedObjects_,
indicesByUUID = this._indicesByUUID,
bindings = this._bindings,
nBindings = bindings.length;
for ( var i = 0, n = arguments.length; i !== n; ++ i ) {
var object = arguments[ i ],
uuid = object.uuid,
index = indicesByUUID[ uuid ];
if ( index !== undefined && index >= nCachedObjects ) {
// move existing object into the CACHED region
var lastCachedIndex = nCachedObjects ++,
firstActiveObject = objects[ lastCachedIndex ];
indicesByUUID[ firstActiveObject.uuid ] = index;
objects[ index ] = firstActiveObject;
indicesByUUID[ uuid ] = lastCachedIndex;
objects[ lastCachedIndex ] = object;
// accounting is done, now do the same for all bindings
for ( var j = 0, m = nBindings; j !== m; ++ j ) {
var bindingsForPath = bindings[ j ],
firstActive = bindingsForPath[ lastCachedIndex ],
binding = bindingsForPath[ index ];
bindingsForPath[ index ] = firstActive;
bindingsForPath[ lastCachedIndex ] = binding;
}
}
} // for arguments
this.nCachedObjects_ = nCachedObjects;
},
// remove & forget
uncache: function( var_args ) {
var objects = this._objects,
nObjects = objects.length,
nCachedObjects = this.nCachedObjects_,
indicesByUUID = this._indicesByUUID,
bindings = this._bindings,
nBindings = bindings.length;
for ( var i = 0, n = arguments.length; i !== n; ++ i ) {
var object = arguments[ i ],
uuid = object.uuid,
index = indicesByUUID[ uuid ];
if ( index !== undefined ) {
delete indicesByUUID[ uuid ];
if ( index < nCachedObjects ) {
// object is cached, shrink the CACHED region
var firstActiveIndex = -- nCachedObjects,
lastCachedObject = objects[ firstActiveIndex ],
lastIndex = -- nObjects,
lastObject = objects[ lastIndex ];
// last cached object takes this object's place
indicesByUUID[ lastCachedObject.uuid ] = index;
objects[ index ] = lastCachedObject;
// last object goes to the activated slot and pop
indicesByUUID[ lastObject.uuid ] = firstActiveIndex;
objects[ firstActiveIndex ] = lastObject;
objects.pop();
// accounting is done, now do the same for all bindings
for ( var j = 0, m = nBindings; j !== m; ++ j ) {
var bindingsForPath = bindings[ j ],
lastCached = bindingsForPath[ firstActiveIndex ],
last = bindingsForPath[ lastIndex ];
bindingsForPath[ index ] = lastCached;
bindingsForPath[ firstActiveIndex ] = last;
bindingsForPath.pop();
}
} else {
// object is active, just swap with the last and pop
var lastIndex = -- nObjects,
lastObject = objects[ lastIndex ];
indicesByUUID[ lastObject.uuid ] = index;
objects[ index ] = lastObject;
objects.pop();
// accounting is done, now do the same for all bindings
for ( var j = 0, m = nBindings; j !== m; ++ j ) {
var bindingsForPath = bindings[ j ];
bindingsForPath[ index ] = bindingsForPath[ lastIndex ];
bindingsForPath.pop();
}
} // cached or active
} // if object is known
} // for arguments
this.nCachedObjects_ = nCachedObjects;
},
// Internal interface used by befriended PropertyBinding.Composite:
subscribe_: function( path, parsedPath ) {
// returns an array of bindings for the given path that is changed
// according to the contained objects in the group
var indicesByPath = this._bindingsIndicesByPath,
index = indicesByPath[ path ],
bindings = this._bindings;
if ( index !== undefined ) return bindings[ index ];
var paths = this._paths,
parsedPaths = this._parsedPaths,
objects = this._objects,
nObjects = objects.length,
nCachedObjects = this.nCachedObjects_,
bindingsForPath = new Array( nObjects );
index = bindings.length;
indicesByPath[ path ] = index;
paths.push( path );
parsedPaths.push( parsedPath );
bindings.push( bindingsForPath );
for ( var i = nCachedObjects,
n = objects.length; i !== n; ++ i ) {
var object = objects[ i ];
bindingsForPath[ i ] =
new THREE.PropertyBinding( object, path, parsedPath );
}
return bindingsForPath;
},
unsubscribe_: function( path ) {
// tells the group to forget about a property path and no longer
// update the array previously obtained with 'subscribe_'
var indicesByPath = this._bindingsIndicesByPath,
index = indicesByPath[ path ];
if ( index !== undefined ) {
var paths = this._paths,
parsedPaths = this._parsedPaths,
bindings = this._bindings,
lastBindingsIndex = bindings.length - 1,
lastBindings = bindings[ lastBindingsIndex ],
lastBindingsPath = path[ lastBindingsIndex ];
indicesByPath[ lastBindingsPath ] = index;
bindings[ index ] = lastBindings;
bindings.pop();
parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ];
parsedPaths.pop();
paths[ index ] = paths[ lastBindingsIndex ];
paths.pop();
}
}
};
......@@ -99,8 +99,7 @@ THREE.AnimationUtils = {
var value = key[ valuePropertyName ];
if ( value === undefined ) return; // no data
if ( value[ 'splice' ] !== undefined ) {
// ...assume array
if ( Array.isArray( value ) ) {
do {
......
......@@ -34,8 +34,8 @@ THREE.KeyframeTrack.prototype = {
constructor: THREE.KeyframeTrack,
TimeBufferType: Float64Array,
ValueBufferType: Float64Array,
TimeBufferType: Float32Array,
ValueBufferType: Float32Array,
DefaultInterpolation: THREE.InterpolateLinear,
......@@ -250,7 +250,7 @@ THREE.KeyframeTrack.prototype = {
var currTime = times[ i ];
if ( Number.isNaN( currTime ) ) {
if ( typeof currTime === 'number' && isNaN( currTime ) ) {
console.error( "time is not a valid number", this, i, currTime );
valid = false;
......@@ -278,7 +278,7 @@ THREE.KeyframeTrack.prototype = {
var value = values[ i ];
if ( Number.isNaN( value ) ) {
if ( isNaN( value ) ) {
console.error( "value is not a valid number", this, i, value );
valid = false;
......@@ -392,7 +392,7 @@ Object.assign( THREE.KeyframeTrack, {
}
var trackType = THREE.KeyframeTrack.GetTrackTypeForValueTypeName( json.type );
var trackType = THREE.KeyframeTrack._getTrackTypeForValueTypeName( json.type );
if ( json[ 'times' ] === undefined ) {
......@@ -460,7 +460,7 @@ Object.assign( THREE.KeyframeTrack, {
},
GetTrackTypeForValueTypeName: function( typeName ) {
_getTrackTypeForValueTypeName: function( typeName ) {
switch( typeName.toLowerCase() ) {
......
......@@ -8,21 +8,16 @@
* @author tschw
*/
THREE.PropertyBinding = function ( rootNode, path ) {
THREE.PropertyBinding = function ( rootNode, path, parsedPath ) {
this.rootNode = rootNode;
this.path = path;
this.parsedPath = parsedPath ||
THREE.PropertyBinding.parseTrackName( path );
var parseResults = THREE.PropertyBinding.parseTrackName( path );
this.directoryName = parseResults.directoryName;
this.nodeName = parseResults.nodeName;
this.objectName = parseResults.objectName;
this.objectIndex = parseResults.objectIndex;
this.propertyName = parseResults.propertyName;
this.propertyIndex = parseResults.propertyIndex;
this.node = THREE.PropertyBinding.findNode(
rootNode, this.parsedPath.nodeName ) || rootNode;
this.setRootNode( rootNode );
this.rootNode = rootNode;
};
......@@ -50,31 +45,20 @@ THREE.PropertyBinding.prototype = {
},
// change the root used for binding
setRootNode: function( rootNode ) {
var oldNode = this.node,
newNode = THREE.PropertyBinding.findNode( rootNode, this.nodeName ) || rootNode;
if ( oldNode && oldNode !== newNode ) {
this.unbind(); // for the change to take effect on the next call
}
this.rootNode = rootNode;
this.node = newNode;
},
// create getter / setter pair for a property in the scene graph
bind: function() {
var targetObject = this.node;
var targetObject = this.node,
parsedPath = this.parsedPath,
objectName = parsedPath.objectName,
propertyName = parsedPath.propertyName,
propertyIndex = parsedPath.propertyIndex;
if ( ! targetObject ) {
targetObject = THREE.PropertyBinding.findNode( this.rootNode, this.nodeName ) || this.rootNode;
targetObject = THREE.PropertyBinding.findNode(
this.rootNode, parsedPath.nodeName ) || this.rootNode;
this.node = targetObject;
......@@ -85,262 +69,403 @@ THREE.PropertyBinding.prototype = {
this.setValue = this._setValue_unavailable;
// ensure there is a value node
if( ! targetObject ) {
if ( ! targetObject ) {
console.error( " trying to update node for track: " + this.path + " but it wasn't found." );
return;
}
if( this.objectName ) {
// special case were we need to reach deeper into the hierarchy to get the face materials....
if( this.objectName === "materials" ) {
if( ! targetObject.material ) {
console.error( ' can not bind to material as node does not have a material', this );
return;
}
if( ! targetObject.material.materials ) {
console.error( ' can not bind to material.materials as node.material does not have a materials array', this );
return;
}
targetObject = targetObject.material.materials;
}
else if( this.objectName === "bones" ) {
if( ! targetObject.skeleton ) {
console.error( ' can not bind to bones as node does not have a skeleton', this );
return;
}
// potential future optimization: skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
if( objectName ) {
targetObject = targetObject.skeleton.bones;
var objectIndex = parsedPath.objectIndex;
// special cases were we need to reach deeper into the hierarchy to get the face materials....
switch ( objectName ) {
case 'materials':
if( ! targetObject.material ) {
console.error( ' can not bind to material as node does not have a material', this );
return;
// support resolving morphTarget names into indices.
for( var i = 0; i < targetObject.length; i ++ ) {
if( targetObject[i].name === this.objectIndex ) {
this.objectIndex = i;
break;
}
}
}
else {
if( targetObject[ this.objectName ] === undefined ) {
console.error( ' can not bind to objectName of node, undefined', this );
return;
}
targetObject = targetObject[ this.objectName ];
if( ! targetObject.material.materials ) {
console.error( ' can not bind to material.materials as node.material does not have a materials array', this );
return;
}
targetObject = targetObject.material.materials;
break;
case 'bones':
if( ! targetObject.skeleton ) {
console.error( ' can not bind to bones as node does not have a skeleton', this );
return;
}
// potential future optimization: skip this if propertyIndex is already an integer
// and convert the integer string to a true integer.
targetObject = targetObject.skeleton.bones;
// support resolving morphTarget names into indices.
for ( var i = 0; i < targetObject.length; i ++ ) {
if ( targetObject[i].name === objectIndex ) {
objectIndex = i;
break;
}
}
break;
default:
if ( targetObject[ objectName ] === undefined ) {
console.error( ' can not bind to objectName of node, undefined', this );
return;
}
targetObject = targetObject[ objectName ];
}
if( this.objectIndex !== undefined ) {
if( targetObject[ this.objectIndex ] === undefined ) {
if ( objectIndex !== undefined ) {
if( targetObject[ objectIndex ] === undefined ) {
console.error( " trying to bind to objectIndex of objectName, but is undefined:", this, targetObject );
return;
}
targetObject = targetObject[ this.objectIndex ];
targetObject = targetObject[ objectIndex ];
}
}
// special case mappings
var nodeProperty = targetObject[ this.propertyName ];
if( ! nodeProperty ) {
console.error( " trying to update property for track: " + this.nodeName + '.' + this.propertyName + " but it wasn't found.", targetObject );
// resolve property
var nodeProperty = targetObject[ propertyName ];
if ( ! nodeProperty ) {
var nodeName = parsedPath.nodeName;
console.error( " trying to update property for track: " + nodeName +
'.' + propertyName + " but it wasn't found.", targetObject );
return;
}
// determine versioning scheme
var versioning = 0;
var NeedsUpdate = 1;
var MatrixWorldNeedsUpdate = 2;
var versioning = this.Versioning.None;
if( targetObject.needsUpdate !== undefined ) { // material
if ( targetObject.needsUpdate !== undefined ) { // material
versioning = NeedsUpdate;
versioning = this.Versioning.NeedsUpdate;
this.targetObject = targetObject;
} else if( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform
} else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform
versioning = MatrixWorldNeedsUpdate;
versioning = this.Versioning.MatrixWorldNeedsUpdate;
this.targetObject = targetObject;
}
// access a sub element of the property array (only primitives are supported right now)
if( this.propertyIndex !== undefined ) {
// determine how the property gets bound
var bindingType = this.BindingType.Direct;
if ( propertyIndex !== undefined ) {
// access a sub element of the property array (only primitives are supported right now)
if( this.propertyName === "morphTargetInfluences" ) {
if ( propertyName === "morphTargetInfluences" ) {
// potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
// support resolving morphTarget names into indices.
if( ! targetObject.geometry ) {
if ( ! targetObject.geometry ) {
console.error( ' can not bind to morphTargetInfluences becasuse node does not have a geometry', this );
return;
}
if( ! targetObject.geometry.morphTargets ) {
if ( ! targetObject.geometry.morphTargets ) {
console.error( ' can not bind to morphTargetInfluences becasuse node does not have a geometry.morphTargets', this );
return;
}
for( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {
if( targetObject.geometry.morphTargets[i].name === this.propertyIndex ) {
this.propertyIndex = i;
for ( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {
if ( targetObject.geometry.morphTargets[i].name === propertyIndex ) {
propertyIndex = i;
break;
}
}
}
var propertyIndex = this.propertyIndex;
bindingType = this.BindingType.ArrayElement;
this.getValue = function getValue_propertyIndexed( buffer, offset ) {
this.resolvedProperty = nodeProperty;
this.propertyIndex = propertyIndex;
buffer[ offset ] = nodeProperty[ this.propertyIndex ];
} else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) {
// must use copy for Object3D.Euler/Quaternion
};
bindingType = this.BindingType.HasFromToArray;
switch ( versioning ) {
this.resolvedProperty = nodeProperty;
case NeedsUpdate:
} else {
this.setValue = function setValue_propertyIndexed( buffer, offset ) {
this.propertyName = propertyName;
nodeProperty[ propertyIndex ] = buffer[ offset ];
targetObject.needsUpdate = true;
}
};
// select getter / setter
this.getValue = this.GetterByBindingType[ bindingType ];
this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];
break;
},
case MatrixWorldNeedsUpdate:
unbind: function() {
this.setValue = function setValue_propertyIndexed( buffer, offset ) {
this.node = null;
nodeProperty[ propertyIndex ] = buffer[ offset ];
targetObject.matrixWorldNeedsUpdate = true;
// back to the prototype version of getValue / setValue
// note: avoiding to mutate the shape of 'this' via 'delete'
this.getValue = this._getValue_unbound;
this.setValue = this._setValue_unbound;
};
}
break;
};
default:
Object.assign( THREE.PropertyBinding.prototype, { // prototype, continued
this.setValue = function setValue_propertyIndexed( buffer, offset ) {
// these are used to "bind" a nonexistent property
_getValue_unavailable: function() {},
_setValue_unavailable: function() {},
nodeProperty[ propertyIndex ] = buffer[ offset ];
// initial state of these methods that calls 'bind'
_getValue_unbound: THREE.PropertyBinding.prototype.getValue,
_setValue_unbound: THREE.PropertyBinding.prototype.setValue,
};
BindingType: {
Direct: 0,
ArrayElement: 1,
HasFromToArray: 2
},
}
Versioning: {
None: 0,
NeedsUpdate: 1,
MatrixWorldNeedsUpdate: 2
},
}
// must use copy for Object3D.Euler/Quaternion
else if( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) {
GetterByBindingType: [
this.getValue = function getValue_propertyObject( buffer, offset ) {
function getValue_direct( buffer, offset ) {
nodeProperty.toArray( buffer, offset );
buffer[ offset ] = this.node[ this.propertyName ];
};
},
function getValue_arrayElement( buffer, offset ) {
switch ( versioning ) {
buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ];
case NeedsUpdate:
},
this.setValue = function setValue_propertyObject( buffer, offset ) {
function getValue_toArray( buffer, offset ) {
nodeProperty.fromArray( buffer, offset );
targetObject.needsUpdate = true;
this.resolvedProperty.toArray( buffer, offset );
}
}
case MatrixWorldNeedsUpdate:
],
this.setValue = function setValue_propertyObject( buffer, offset ) {
SetterByBindingTypeAndVersioning: [
nodeProperty.fromArray( buffer, offset );
targetObject.matrixWorldNeedsUpdate = true;
[
// Direct
}
function setValue_direct( buffer, offset ) {
default:
this.node[ this.propertyName ] = buffer[ offset ];
this.setValue = function setValue_propertyObject( buffer, offset ) {
},
nodeProperty.fromArray( buffer, offset );
function setValue_direct_setNeedsUpdate( buffer, offset ) {
}
this.node[ this.propertyName ] = buffer[ offset ];
this.targetObject.needsUpdate = true;
},
function setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) {
this.node[ this.propertyName ] = buffer[ offset ];
this.targetObject.matrixWorldNeedsUpdate = true;
}
}
// otherwise just set the property directly on the node (do not use nodeProperty as it may not be a reference object)
else {
], [
var propertyName = this.propertyName;
// ArrayElement
this.getValue = function getValue_property( buffer, offset ) {
function setValue_arrayElement( buffer, offset ) {
buffer[ offset ] = nodeProperty[ propertyName ];
this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
};
},
switch ( versioning ) {
function setValue_arrayElement_setNeedsUpdate( buffer, offset ) {
case NeedsUpdate:
this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
this.targetObject.needsUpdate = true;
this.setValue = function setValue_property( buffer, offset ) {
},
nodeProperty[ propertyName ] = buffer[ offset ];
targetObject.needsUpdate = true;
function setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) {
}
this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
this.targetObject.matrixWorldNeedsUpdate = true;
break;
}
case MatrixWorldNeedsUpdate:
], [
this.setValue = function setValue_property( buffer, offset ) {
// HasToFromArray
nodeProperty[ propertyName ] = buffer[ offset ];
targetObject.matrixWorldNeedsUpdate = true;
function setValue_fromArray( buffer, offset ) {
}
this.resolvedProperty.fromArray( buffer, offset );
break;
},
default:
function setValue_fromArray_setNeedsUpdate( buffer, offset ) {
this.setValue = function setValue_property( buffer, offset ) {
this.resolvedProperty.fromArray( buffer, offset );
this.targetObject.needsUpdate = true;
nodeProperty[ propertyName ] = buffer[ offset ];
},
}
function setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) {
this.resolvedProperty.fromArray( buffer, offset );
this.targetObject.matrixWorldNeedsUpdate = true;
}
]
]
} );
THREE.PropertyBinding.Composite =
function( targetGroup, path, optionalParsedPath ) {
var parsedPath = optionalParsedPath ||
THREE.PropertyBinding.parseTrackName( path );
this._targetGroup = targetGroup;
this._bindings = targetGroup.subscribe_( path, parsedPath );
};
THREE.PropertyBinding.Composite.prototype = {
constructor: THREE.PropertyBinding.Composite,
getValue: function( array, offset ) {
this.bind(); // bind all binding
var firstValidIndex = this._targetGroup.nCachedObjects_,
binding = this._bindings[ firstValidIndex ];
// and only call .getValue on the first
if ( binding !== undefined ) binding.getValue( array, offset );
},
setValue: function( array, offset ) {
var bindings = this._bindings;
for ( var i = this._targetGroup.nCachedObjects_,
n = bindings.length; i !== n; ++ i ) {
bindings[ i ].setValue( array, offset );
}
},
bind: function() {
var bindings = this._bindings;
for ( var i = this._targetGroup.nCachedObjects_,
n = bindings.length; i !== n; ++ i ) {
bindings[ i ].bind();
}
},
unbind: function() {
this.node = null;
var bindings = this._bindings;
// back to the prototype version of getValue / setValue
// note: avoiding to mutate the shape of 'this' via 'delete'
this.getValue = this._getValue_unbound;
this.setValue = this._setValue_unbound;
for ( var i = this._targetGroup.nCachedObjects_,
n = bindings.length; i !== n; ++ i ) {
bindings[ i ].unbind();
}
}
};
Object.assign( THREE.PropertyBinding.prototype, {
THREE.PropertyBinding.create = function( root, path, parsedPath ) {
// these are used to "bind" a nonexistent property
_getValue_unavailable: function() {},
_setValue_unavailable: function() {},
if ( ! ( root instanceof THREE.AnimationObjectGroup ) ) {
// initial state of these methods that calls 'bind'
_getValue_unbound: THREE.PropertyBinding.prototype.getValue,
_setValue_unbound: THREE.PropertyBinding.prototype.setValue
return new THREE.PropertyBinding( root, path, parsedPath );
} );
} else {
return new THREE.PropertyBinding.Composite( root, path, parsedPath );
}
};
THREE.PropertyBinding.parseTrackName = function( trackName ) {
......@@ -367,7 +492,7 @@ THREE.PropertyBinding.parseTrackName = function( trackName ) {
}
var results = {
directoryName: matches[1],
// directoryName: matches[1], // (tschw) currently unused
nodeName: matches[3], // allowed to be null, specified root node.
objectName: matches[5],
objectIndex: matches[7],
......@@ -456,4 +581,5 @@ THREE.PropertyBinding.findNode = function( root, nodeName ) {
}
return null;
}
......@@ -8,9 +8,9 @@
* @author tschw
*/
THREE.PropertyMixer = function ( rootNode, path, typeName, valueSize ) {
THREE.PropertyMixer = function ( binding, typeName, valueSize ) {
this.binding = new THREE.PropertyBinding( rootNode, path );
this.binding = binding;
this.valueSize = valueSize;
var bufferType = Float64Array,
......@@ -45,6 +45,7 @@ THREE.PropertyMixer = function ( rootNode, path, typeName, valueSize ) {
this.cumulativeWeight = 0;
this.useCount = 0;
this.referenceCount = 0;
};
......
......@@ -14,7 +14,8 @@ THREE.BooleanKeyframeTrack = function ( name, times, values ) {
};
Object.assign( THREE.BooleanKeyframeTrack.prototype, THREE.KeyframeTrack.prototype, {
THREE.BooleanKeyframeTrack.prototype =
Object.assign( Object.create( THREE.KeyframeTrack.prototype ), {
constructor: THREE.BooleanKeyframeTrack,
......
......@@ -14,7 +14,8 @@ THREE.ColorKeyframeTrack = function ( name, times, values, interpolation ) {
};
Object.assign( THREE.ColorKeyframeTrack.prototype, THREE.KeyframeTrack.prototype, {
THREE.ColorKeyframeTrack.prototype =
Object.assign( Object.create( THREE.KeyframeTrack.prototype ), {
constructor: THREE.ColorKeyframeTrack,
......
......@@ -13,7 +13,8 @@ THREE.NumberKeyframeTrack = function ( name, times, values, interpolation ) {
};
Object.assign( THREE.NumberKeyframeTrack.prototype, THREE.KeyframeTrack.prototype, {
THREE.NumberKeyframeTrack.prototype =
Object.assign( Object.create( THREE.KeyframeTrack.prototype ), {
constructor: THREE.NumberKeyframeTrack,
......
......@@ -13,7 +13,8 @@ THREE.QuaternionKeyframeTrack = function ( name, times, values, interpolation )
};
Object.assign( THREE.QuaternionKeyframeTrack.prototype, THREE.KeyframeTrack.prototype, {
THREE.QuaternionKeyframeTrack.prototype =
Object.assign( Object.create( THREE.KeyframeTrack.prototype ), {
constructor: THREE.QuaternionKeyframeTrack,
......
......@@ -14,7 +14,8 @@ THREE.StringKeyframeTrack = function ( name, times, values, interpolation ) {
};
Object.assign( THREE.StringKeyframeTrack.prototype, THREE.KeyframeTrack.prototype, {
THREE.StringKeyframeTrack.prototype =
Object.assign( Object.create( THREE.KeyframeTrack.prototype ), {
constructor: THREE.StringKeyframeTrack,
......
......@@ -14,7 +14,8 @@ THREE.VectorKeyframeTrack = function ( name, times, values, interpolation ) {
};
Object.assign( THREE.VectorKeyframeTrack.prototype, THREE.KeyframeTrack.prototype, {
THREE.VectorKeyframeTrack.prototype =
Object.assign( Object.create( THREE.KeyframeTrack.prototype ), {
constructor: THREE.VectorKeyframeTrack,
......
/**
* @author tschw
*/
module( "AnimationObjectGroup" );
var ObjectA = new THREE.Object3D(),
ObjectB = new THREE.Object3D(),
ObjectC = new THREE.Object3D(),
PathA = 'object.position',
PathB = 'object.rotation',
PathC = 'object.scale',
ParsedPathA = THREE.PropertyBinding.parseTrackName( PathA ),
ParsedPathB = THREE.PropertyBinding.parseTrackName( PathB ),
ParsedPathC = THREE.PropertyBinding.parseTrackName( PathC );
test( "smoke test", function() {
var expect = function expect( testIndex, group, bindings, path, cached, roots ) {
var rootNodes = [], pathsOk = true, nodesOk = true;
for ( var i = group.nCachedObjects_, n = bindings.length; i !== n; ++ i ) {
if ( bindings[ i ].path !== path ) pathsOk = false;
rootNodes.push( bindings[ i ].rootNode );
}
for ( var i = 0, n = roots.length; i !== n; ++ i ) {
if ( rootNodes.indexOf( roots[ i ] ) === -1 ) nodesOk = false;
}
ok( pathsOk, testIndex + " paths" );
ok( nodesOk, testIndex + " nodes");
ok( group.nCachedObjects_ === cached, testIndex + " cache size" );
ok( bindings.length - group.nCachedObjects_ === roots.length, testIndex + " object count" );
};
// initial state
var groupA = new THREE.AnimationObjectGroup();
ok( groupA instanceof THREE.AnimationObjectGroup, "constructor (w/o args)" );
var bindingsAA = groupA.subscribe_( PathA, ParsedPathA );
expect( 0, groupA, bindingsAA, PathA, 0, [] );
var groupB = new THREE.AnimationObjectGroup( ObjectA, ObjectB );
ok( groupB instanceof THREE.AnimationObjectGroup, "constructor (with args)" );
var bindingsBB = groupB.subscribe_( PathB, ParsedPathB );
expect( 1, groupB, bindingsBB, PathB, 0, [ ObjectA, ObjectB ] );
// add
groupA.add( ObjectA, ObjectB );
expect( 2, groupA, bindingsAA, PathA, 0, [ ObjectA, ObjectB ] );
groupB.add( ObjectC );
expect( 3, groupB, bindingsBB, PathB, 0, [ ObjectA, ObjectB, ObjectC ] );
// remove
groupA.remove( ObjectA, ObjectC );
expect( 4, groupA, bindingsAA, PathA, 1, [ ObjectB ] );
groupB.remove( ObjectA, ObjectB, ObjectC );
expect( 5, groupB, bindingsBB, PathB, 3, [] );
// subscribe after re-add
groupA.add( ObjectC );
expect( 6, groupA, bindingsAA, PathA, 1, [ ObjectB, ObjectC ] );
var bindingsAC = groupA.subscribe_( PathC, ParsedPathC );
expect( 7, groupA, bindingsAC, PathC, 1, [ ObjectB, ObjectC ] );
// re-add after subscribe
var bindingsBC = groupB.subscribe_( PathC, ParsedPathC );
groupB.add( ObjectA, ObjectB );
expect( 8, groupB, bindingsBB, PathB, 1, [ ObjectA, ObjectB ] );
// unsubscribe
var copyOfBindingsBC = bindingsBC.slice();
groupB.unsubscribe_( PathC );
groupB.add( ObjectC );
deepEqual( bindingsBC, copyOfBindingsBC, "no more update after unsubscribe" );
// uncache active
groupB.uncache( ObjectA );
expect( 9, groupB, bindingsBB, PathB, 0, [ ObjectB, ObjectC ] );
// uncache cached
groupA.uncache( ObjectA );
expect( 10, groupA, bindingsAC, PathC, 0, [ ObjectB, ObjectC ] );
} );
......@@ -47,13 +47,16 @@
<script src="math/Frustum.js"></script>
<script src="math/Interpolant.js"></script>
<script src="geometry/EdgesGeometry.js"></script>
<script src="extras/ImageUtils.test.js"></script>
<script src="animation/AnimationObjectGroup.js"></script>
<script src="lights/AmbientLight.tests.js"></script>
<script src="lights/DirectionalLight.tests.js"></script>
<script src="lights/HemisphereLight.tests.js"></script>
<script src="lights/PointLight.tests.js"></script>
<script src="lights/SpotLight.tests.js"></script>
<script src="geometry/EdgesGeometry.js"></script>
<script src="extras/ImageUtils.test.js"></script>
<script src="extras/geometries/BoxGeometry.tests.js"></script>
<script src="extras/geometries/CircleBufferGeometry.tests.js"></script>
......
......@@ -39,9 +39,9 @@
"src/core/DirectGeometry.js",
"src/core/BufferGeometry.js",
"src/core/InstancedBufferGeometry.js",
"src/animation/AnimationAction.js",
"src/animation/AnimationClip.js",
"src/animation/AnimationMixer.js",
"src/animation/AnimationObjectGroup.js",
"src/animation/AnimationUtils.js",
"src/animation/KeyframeTrack.js",
"src/animation/PropertyBinding.js",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册