three
Version:
JavaScript 3D library
386 lines (253 loc) • 7.86 kB
JavaScript
import { Quaternion } from '../math/Quaternion.js';
/**
* Buffered scene graph property that allows weighted accumulation; used internally.
*/
class PropertyMixer {
/**
* Constructs a new property mixer.
*
* @param {PropertyBinding} binding - The property binding.
* @param {string} typeName - The keyframe track type name.
* @param {number} valueSize - The keyframe track value size.
*/
constructor( binding, typeName, valueSize ) {
/**
* The property binding.
*
* @type {PropertyBinding}
*/
this.binding = binding;
/**
* The keyframe track value size.
*
* @type {number}
*/
this.valueSize = valueSize;
let mixFunction,
mixFunctionAdditive,
setIdentity;
// buffer layout: [ incoming | accu0 | accu1 | orig | addAccu | (optional work) ]
//
// interpolators can use .buffer as their .result
// the data then goes to 'incoming'
//
// 'accu0' and 'accu1' are used frame-interleaved for
// the cumulative result and are compared to detect
// changes
//
// 'orig' stores the original state of the property
//
// 'add' is used for additive cumulative results
//
// 'work' is optional and is only present for quaternion types. It is used
// to store intermediate quaternion multiplication results
switch ( typeName ) {
case 'quaternion':
mixFunction = this._slerp;
mixFunctionAdditive = this._slerpAdditive;
setIdentity = this._setAdditiveIdentityQuaternion;
this.buffer = new Float64Array( valueSize * 6 );
this._workIndex = 5;
break;
case 'string':
case 'bool':
mixFunction = this._select;
// Use the regular mix function and for additive on these types,
// additive is not relevant for non-numeric types
mixFunctionAdditive = this._select;
setIdentity = this._setAdditiveIdentityOther;
this.buffer = new Array( valueSize * 5 );
break;
default:
mixFunction = this._lerp;
mixFunctionAdditive = this._lerpAdditive;
setIdentity = this._setAdditiveIdentityNumeric;
this.buffer = new Float64Array( valueSize * 5 );
}
this._mixBufferRegion = mixFunction;
this._mixBufferRegionAdditive = mixFunctionAdditive;
this._setIdentity = setIdentity;
this._origIndex = 3;
this._addIndex = 4;
/**
* TODO
*
* @type {number}
* @default 0
*/
this.cumulativeWeight = 0;
/**
* TODO
*
* @type {number}
* @default 0
*/
this.cumulativeWeightAdditive = 0;
/**
* TODO
*
* @type {number}
* @default 0
*/
this.useCount = 0;
/**
* TODO
*
* @type {number}
* @default 0
*/
this.referenceCount = 0;
}
/**
* Accumulates data in the `incoming` region into `accu<i>`.
*
* @param {number} accuIndex - The accumulation index.
* @param {number} weight - The weight.
*/
accumulate( accuIndex, weight ) {
// note: happily accumulating nothing when weight = 0, the caller knows
// the weight and shouldn't have made the call in the first place
const buffer = this.buffer,
stride = this.valueSize,
offset = accuIndex * stride + stride;
let currentWeight = this.cumulativeWeight;
if ( currentWeight === 0 ) {
// accuN := incoming * weight
for ( let i = 0; i !== stride; ++ i ) {
buffer[ offset + i ] = buffer[ i ];
}
currentWeight = weight;
} else {
// accuN := accuN + incoming * weight
currentWeight += weight;
const mix = weight / currentWeight;
this._mixBufferRegion( buffer, offset, 0, mix, stride );
}
this.cumulativeWeight = currentWeight;
}
/**
* Accumulates data in the `incoming` region into `add`.
*
* @param {number} weight - The weight.
*/
accumulateAdditive( weight ) {
const buffer = this.buffer,
stride = this.valueSize,
offset = stride * this._addIndex;
if ( this.cumulativeWeightAdditive === 0 ) {
// add = identity
this._setIdentity();
}
// add := add + incoming * weight
this._mixBufferRegionAdditive( buffer, offset, 0, weight, stride );
this.cumulativeWeightAdditive += weight;
}
/**
* Applies the state of `accu<i>` to the binding when accus differ.
*
* @param {number} accuIndex - The accumulation index.
*/
apply( accuIndex ) {
const stride = this.valueSize,
buffer = this.buffer,
offset = accuIndex * stride + stride,
weight = this.cumulativeWeight,
weightAdditive = this.cumulativeWeightAdditive,
binding = this.binding;
this.cumulativeWeight = 0;
this.cumulativeWeightAdditive = 0;
if ( weight < 1 ) {
// accuN := accuN + original * ( 1 - cumulativeWeight )
const originalValueOffset = stride * this._origIndex;
this._mixBufferRegion(
buffer, offset, originalValueOffset, 1 - weight, stride );
}
if ( weightAdditive > 0 ) {
// accuN := accuN + additive accuN
this._mixBufferRegionAdditive( buffer, offset, this._addIndex * stride, 1, stride );
}
for ( let i = stride, e = stride + stride; i !== e; ++ i ) {
if ( buffer[ i ] !== buffer[ i + stride ] ) {
// value has changed -> update scene graph
binding.setValue( buffer, offset );
break;
}
}
}
/**
* Remembers the state of the bound property and copy it to both accus.
*/
saveOriginalState() {
const binding = this.binding;
const buffer = this.buffer,
stride = this.valueSize,
originalValueOffset = stride * this._origIndex;
binding.getValue( buffer, originalValueOffset );
// accu[0..1] := orig -- initially detect changes against the original
for ( let i = stride, e = originalValueOffset; i !== e; ++ i ) {
buffer[ i ] = buffer[ originalValueOffset + ( i % stride ) ];
}
// Add to identity for additive
this._setIdentity();
this.cumulativeWeight = 0;
this.cumulativeWeightAdditive = 0;
}
/**
* Applies the state previously taken via {@link PropertyMixer#saveOriginalState} to the binding.
*/
restoreOriginalState() {
const originalValueOffset = this.valueSize * 3;
this.binding.setValue( this.buffer, originalValueOffset );
}
// internals
_setAdditiveIdentityNumeric() {
const startIndex = this._addIndex * this.valueSize;
const endIndex = startIndex + this.valueSize;
for ( let i = startIndex; i < endIndex; i ++ ) {
this.buffer[ i ] = 0;
}
}
_setAdditiveIdentityQuaternion() {
this._setAdditiveIdentityNumeric();
this.buffer[ this._addIndex * this.valueSize + 3 ] = 1;
}
_setAdditiveIdentityOther() {
const startIndex = this._origIndex * this.valueSize;
const targetIndex = this._addIndex * this.valueSize;
for ( let i = 0; i < this.valueSize; i ++ ) {
this.buffer[ targetIndex + i ] = this.buffer[ startIndex + i ];
}
}
// mix functions
_select( buffer, dstOffset, srcOffset, t, stride ) {
if ( t >= 0.5 ) {
for ( let i = 0; i !== stride; ++ i ) {
buffer[ dstOffset + i ] = buffer[ srcOffset + i ];
}
}
}
_slerp( buffer, dstOffset, srcOffset, t ) {
Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t );
}
_slerpAdditive( buffer, dstOffset, srcOffset, t, stride ) {
const workOffset = this._workIndex * stride;
// Store result in intermediate buffer offset
Quaternion.multiplyQuaternionsFlat( buffer, workOffset, buffer, dstOffset, buffer, srcOffset );
// Slerp to the intermediate result
Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, workOffset, t );
}
_lerp( buffer, dstOffset, srcOffset, t, stride ) {
const s = 1 - t;
for ( let i = 0; i !== stride; ++ i ) {
const j = dstOffset + i;
buffer[ j ] = buffer[ j ] * s + buffer[ srcOffset + i ] * t;
}
}
_lerpAdditive( buffer, dstOffset, srcOffset, t, stride ) {
for ( let i = 0; i !== stride; ++ i ) {
const j = dstOffset + i;
buffer[ j ] = buffer[ j ] + buffer[ srcOffset + i ] * t;
}
}
}
export { PropertyMixer };