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' 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' 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 };