Spaces:
Sleeping
Sleeping
| import { Quaternion } from '../math/Quaternion.js'; | |
| class PropertyMixer { | |
| constructor(binding, typeName, valueSize) { | |
| this.binding = binding; | |
| 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; | |
| this.cumulativeWeight = 0; | |
| this.cumulativeWeightAdditive = 0; | |
| this.useCount = 0; | |
| this.referenceCount = 0; | |
| } | |
| // accumulate data in the 'incoming' region into 'accu<i>' | |
| 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; | |
| } | |
| // accumulate data in the 'incoming' region into 'add' | |
| 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; | |
| } | |
| // apply the state of 'accu<i>' to the binding when accus differ | |
| 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; | |
| } | |
| } | |
| } | |
| // remember 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; | |
| } | |
| // apply the state previously taken via 'saveOriginalState' to the binding | |
| restoreOriginalState() { | |
| const originalValueOffset = this.valueSize * 3; | |
| this.binding.setValue(this.buffer, originalValueOffset); | |
| } | |
| _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 }; | |