提交 8590ad56 编写于 作者: B Ben Houston

allow for PropertyBindings to bind/unbind. PropertyBindings are reference...

allow for PropertyBindings to bind/unbind.  PropertyBindings are reference counted by Mixer for efficiency.
上级 87058875
......@@ -34,21 +34,35 @@ THREE.AnimationMixer.prototype = {
var track = tracks[ i ];
var propertyBinding = this.propertyBindings[ track.name ];
if( ! this.propertyBindings[ track.name ] ) {
var propertyBinding = new THREE.PropertyBinding( this.root, track.name );
propertyBinding = new THREE.PropertyBinding( this.root, track.name );
this.propertyBindings[ track.name ] = propertyBinding;
this.propertyBindingsArray.push( propertyBinding );
}
// track usages of shared property bindings, because if we leave too many around, the mixer can get slow
propertyBinding.referenceCount += 1;
}
},
removeAllActions: function() {
// unbind all property bindings
for( var i = 0; i < this.propertyBindingsArray.length; i ++ ) {
this.propertyBindingsArray[i].unbind();
}
this.actions = [];
this.propertyBindings = {};
this.propertyBindingsArray = [];
},
......@@ -62,6 +76,27 @@ THREE.AnimationMixer.prototype = {
this.actions.splice( index, 1 );
}
// remove unused property bindings because if we leave them around the mixer can get slow
var tracks = action.clip.tracks;
for( var i = 0; i < tracks.length; i ++ ) {
var track = tracks[ i ];
var propertyBinding = this.propertyBindings[ track.name ];
propertyBinding.referenceCount -= 1;
if( propertyBinding.referenceCount <= 0 ) {
propertyBinding.unbind();
delete this.propertyBindings[ track.name ];
this.propertyBindingArray.splice( this.propertyBindingArray.indexOf( propertyBinding ), 1 );
}
}
},
fadeOut: function( action, duration ) {
......@@ -85,6 +120,13 @@ THREE.AnimationMixer.prototype = {
},
crossFade: function( fadeOutAction, faceInAction, duration ) {
this.fadeOut( fadeOutAction, duration );
this.fadeIn( fadeInAction, duration );
},
update: function( deltaTime ) {
this.time += deltaTime * this.timeScale;
......
......@@ -10,6 +10,8 @@ THREE.PropertyBinding = function ( rootNode, trackName ) {
this.rootNode = rootNode;
this.trackName = trackName;
this.referenceCount = 0;
this.originalValue = null; // the value of the property before it was controlled by this binding
var parseResults = THREE.PropertyBinding.parseTrackName( trackName );
......@@ -40,185 +42,229 @@ THREE.PropertyBinding.prototype = {
accumulate: function( value, weight ) {
var lerp = THREE.AnimationUtils.getLerpFunc( value, true );
if( this.cumulativeWeight === 0 ) {
this.accumulate = function( value, weight ) {
if( this.cumulativeWeight === 0 ) {
if( this.cumulativeValue === null ) {
this.cumulativeValue = THREE.AnimationUtils.clone( value );
}
this.cumulativeWeight = weight;
//console.log( this );
if( this.cumulativeValue === null ) {
this.cumulativeValue = THREE.AnimationUtils.clone( value );
}
else {
this.cumulativeWeight = weight;
//console.log( this );
var lerpAlpha = weight / ( this.cumulativeWeight + weight );
this.cumulativeValue = lerp( this.cumulativeValue, value, lerpAlpha );
this.cumulativeWeight += weight;
//console.log( this );
}
else {
var lerpAlpha = weight / ( this.cumulativeWeight + weight );
this.cumulativeValue = this.lerp( this.cumulativeValue, value, lerpAlpha );
this.cumulativeWeight += weight;
//console.log( this );
}
}
this.accumulate( value, weight );
},
unbind: function() {
if( ! this.setValue ) throw new Error( "can not unbind if not bound in the first place." );
this.setValue( this.originalValue );
},
apply: function() {
// creates the member functions:
// - setValue( value )
// - getValue()
// - triggerDirty()
// for speed capture the setter pattern as a closure (sort of a memoization pattern: https://en.wikipedia.org/wiki/Memoization)
if( ! this.internalApply ) {
bind: function() {
//console.log( "PropertyBinding", this );
if( this.setValue ) throw new Error( "can not bind if already bound." );
//console.log( "PropertyBinding", this );
var equalsFunc = THREE.AnimationUtils.getEqualsFunc( this.cumulativeValue );
var equalsFunc = THREE.AnimationUtils.getEqualsFunc( this.cumulativeValue );
var targetObject = this.node;
var targetObject = this.node;
// ensure there is a value node
if( ! targetObject ) {
console.error( " trying to update node for track: " + this.trackName + " but it wasn't found." );
return;
}
// ensure there is a value node
if( ! targetObject ) {
console.error( " trying to update node for track: " + this.trackName + " 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;
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;
}
else if( this.objectName === "bones" ) {
if( ! targetObject.skeleton ) {
console.error( ' can not bind to bones as node does not have a skeleton', this );
}
targetObject = targetObject.skeleton.bones;
// TODO/OPTIMIZE, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
// support resolving morphTarget names into indices.
//console.log( " resolving bone name: ", this.objectIndex );
for( var i = 0; i < this.node.skeleton.bones.length; i ++ ) {
if( this.node.skeleton.bones[i].name === this.objectIndex ) {
//console.log( " resolved to index: ", i );
this.objectIndex = i;
break;
}
}
if( ! targetObject.material.materials ) {
console.error( ' can not bind to material.materials as node.material does not have a materials array', this );
return;
}
else {
if( targetObject[ this.objectName ] === undefined ) {
console.error( ' can not bind to objectName of node, undefined', this );
return;
}
targetObject = targetObject[ this.objectName ];
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 );
}
targetObject = targetObject.skeleton.bones;
// TODO/OPTIMIZE, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
if( this.objectIndex !== undefined ) {
if( targetObject[ this.objectIndex ] === undefined ) {
console.error( " trying to bind to objectIndex of objectName, but is undefined:", this, targetObject );
return;
// support resolving morphTarget names into indices.
//console.log( " resolving bone name: ", this.objectIndex );
for( var i = 0; i < this.node.skeleton.bones.length; i ++ ) {
if( this.node.skeleton.bones[i].name === this.objectIndex ) {
//console.log( " resolved to index: ", i );
this.objectIndex = i;
break;
}
targetObject = targetObject[ this.objectIndex ];
}
}
else {
if( targetObject[ this.objectName ] === undefined ) {
console.error( ' can not bind to objectName of node, undefined', this );
return;
}
targetObject = targetObject[ this.objectName ];
}
if( this.objectIndex !== undefined ) {
if( targetObject[ this.objectIndex ] === undefined ) {
console.error( " trying to bind to objectIndex of objectName, but is undefined:", this, targetObject );
return;
}
// 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 );
return;
targetObject = targetObject[ this.objectIndex ];
}
// access a sub element of the property array (only primitives are supported right now)
if( this.propertyIndex !== undefined ) {
}
if( this.propertyName === "morphTargetInfluences" ) {
// TODO/OPTIMIZE, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
// support resolving morphTarget names into indices.
//console.log( " resolving morphTargetInfluence name: ", this.propertyIndex );
if( ! this.node.geometry ) {
console.error( ' can not bind to morphTargetInfluences becasuse node does not have a geometry', this );
}
if( ! this.node.geometry.morphTargets ) {
console.error( ' can not bind to morphTargetInfluences becasuse node does not have a geometry.morphTargets', this );
}
for( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {
if( this.node.geometry.morphTargets[i].name === this.propertyIndex ) {
//console.log( " resolved to index: ", i );
this.propertyIndex = i;
break;
}
}
}
// 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 );
return;
}
//console.log( ' update property array ' + this.propertyName + '[' + this.propertyIndex + '] via assignment.' );
this.internalApply = function() {
if( ! equalsFunc( nodeProperty[ this.propertyIndex ], this.cumulativeValue ) ) {
nodeProperty[ this.propertyIndex ] = this.cumulativeValue;
return true;
}
return false;
};
}
// must use copy for Object3D.Euler/Quaternion
else if( nodeProperty.copy ) {
//console.log( ' update property ' + this.name + '.' + this.propertyName + ' via a set() function.' );
this.internalApply = function() {
if( ! equalsFunc( nodeProperty, this.cumulativeValue ) ) {
nodeProperty.copy( this.cumulativeValue );
return true;
// access a sub element of the property array (only primitives are supported right now)
if( this.propertyIndex !== undefined ) {
if( this.propertyName === "morphTargetInfluences" ) {
// TODO/OPTIMIZE, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
// support resolving morphTarget names into indices.
//console.log( " resolving morphTargetInfluence name: ", this.propertyIndex );
if( ! this.node.geometry ) {
console.error( ' can not bind to morphTargetInfluences becasuse node does not have a geometry', this );
}
if( ! this.node.geometry.morphTargets ) {
console.error( ' can not bind to morphTargetInfluences becasuse node does not have a geometry.morphTargets', this );
}
for( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {
if( this.node.geometry.morphTargets[i].name === this.propertyIndex ) {
//console.log( " resolved to index: ", i );
this.propertyIndex = i;
break;
}
return false;
}
}
// otherwise just set the property directly on the node (do not use nodeProperty as it may not be a reference object)
else {
//console.log( ' update property ' + this.name + '.' + this.propertyName + ' via assignment.' );
this.internalApply = function() {
if( ! equalsFunc( targetObject[ this.propertyName ], this.cumulativeValue ) ) {
targetObject[ this.propertyName ] = this.cumulativeValue;
return true;
}
return false;
//console.log( ' update property array ' + this.propertyName + '[' + this.propertyIndex + '] via assignment.' );
this.setValue = function( value ) {
if( ! equalsFunc( nodeProperty[ this.propertyIndex ], value ) ) {
nodeProperty[ this.propertyIndex ] = value;
return true;
}
}
return false;
};
this.getValue = function() {
return nodeProperty[ this.propertyIndex ];
};
// trigger node dirty
if( targetObject.needsUpdate !== undefined ) { // material
//console.log( ' triggering material as dirty' );
this.triggerDirty = function() {
this.node.needsUpdate = true;
}
// must use copy for Object3D.Euler/Quaternion
else if( nodeProperty.copy ) {
//console.log( ' update property ' + this.name + '.' + this.propertyName + ' via a set() function.' );
this.setValue = function( value ) {
if( ! equalsFunc( nodeProperty, value ) ) {
nodeProperty.copy( value );
return true;
}
}
else if( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform
//console.log( ' triggering node as dirty' );
this.triggerDirty = function() {
targetObject.matrixWorldNeedsUpdate = true;
return false;
}
this.getValue = function() {
return nodeProperty;
};
}
// otherwise just set the property directly on the node (do not use nodeProperty as it may not be a reference object)
else {
//console.log( ' update property ' + this.name + '.' + this.propertyName + ' via assignment.' );
this.setValue = function( value ) {
if( ! equalsFunc( targetObject[ this.propertyName ], value ) ) {
targetObject[ this.propertyName ] = value;
return true;
}
return false;
}
this.getValue = function() {
return targetObject[ this.propertyName ];
};
}
// trigger node dirty
if( targetObject.needsUpdate !== undefined ) { // material
//console.log( ' triggering material as dirty' );
this.triggerDirty = function() {
this.node.needsUpdate = true;
}
}
else if( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform
//console.log( ' triggering node as dirty' );
this.triggerDirty = function() {
targetObject.matrixWorldNeedsUpdate = true;
}
}
this.originalValue = this.getValue();
this.lerp = THREE.AnimationUtils.getLerpFunc( value, true );
},
apply: function() {
// for speed capture the setter pattern as a closure (sort of a memoization pattern: https://en.wikipedia.org/wiki/Memoization)
if( ! this.setValue ) {
this.bind();
}
// early exit if there is nothing to apply.
if( this.cumulativeWeight > 0 ) {
var valueChanged = this.internalApply();
// blend with original value
if( this.cumulativeWeight < 1 ) {
var remainingWeight = 1 - this.cumulativeWeight;
var lerpAlpha = remainingWeight / ( this.cumulativeWeight + remainingWeight );
this.cumulativeValue = this.lerp( this.cumulativeValue, this.originalValue, lerpAlpha );
}
var valueChanged = this.setValue( this.cumulativeValue );
if( valueChanged && this.triggerDirty ) {
this.triggerDirty();
......@@ -229,12 +275,6 @@ THREE.PropertyBinding.prototype = {
this.cumulativeWeight = 0;
}
},
get: function() {
throw new Error( "TODO" );
}
};
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册