Spaces:
Sleeping
Sleeping
| import { Quaternion } from '../math/Quaternion.js'; | |
| import { AdditiveAnimationBlendMode } from '../constants.js'; | |
| const AnimationUtils = { | |
| // same as Array.prototype.slice, but also works on typed arrays | |
| arraySlice: function (array, from, to) { | |
| if (AnimationUtils.isTypedArray(array)) { | |
| // in ios9 array.subarray(from, undefined) will return empty array | |
| // but array.subarray(from) or array.subarray(from, len) is correct | |
| return new array.constructor(array.subarray(from, to !== undefined ? to : array.length)); | |
| } | |
| return array.slice(from, to); | |
| }, | |
| // converts an array to a specific type | |
| convertArray: function (array, type, forceClone) { | |
| if ( | |
| !array || // let 'undefined' and 'null' pass | |
| (!forceClone && array.constructor === type) | |
| ) | |
| return array; | |
| if (typeof type.BYTES_PER_ELEMENT === 'number') { | |
| return new type(array); // create typed array | |
| } | |
| return Array.prototype.slice.call(array); // create Array | |
| }, | |
| isTypedArray: function (object) { | |
| return ArrayBuffer.isView(object) && !(object instanceof DataView); | |
| }, | |
| // returns an array by which times and values can be sorted | |
| getKeyframeOrder: function (times) { | |
| function compareTime(i, j) { | |
| return times[i] - times[j]; | |
| } | |
| const n = times.length; | |
| const result = new Array(n); | |
| for (let i = 0; i !== n; ++i) result[i] = i; | |
| result.sort(compareTime); | |
| return result; | |
| }, | |
| // uses the array previously returned by 'getKeyframeOrder' to sort data | |
| sortedArray: function (values, stride, order) { | |
| const nValues = values.length; | |
| const result = new values.constructor(nValues); | |
| for (let i = 0, dstOffset = 0; dstOffset !== nValues; ++i) { | |
| const srcOffset = order[i] * stride; | |
| for (let j = 0; j !== stride; ++j) { | |
| result[dstOffset++] = values[srcOffset + j]; | |
| } | |
| } | |
| return result; | |
| }, | |
| // function for parsing AOS keyframe formats | |
| flattenJSON: function (jsonKeys, times, values, valuePropertyName) { | |
| let i = 1, | |
| key = jsonKeys[0]; | |
| while (key !== undefined && key[valuePropertyName] === undefined) { | |
| key = jsonKeys[i++]; | |
| } | |
| if (key === undefined) return; // no data | |
| let value = key[valuePropertyName]; | |
| if (value === undefined) return; // no data | |
| if (Array.isArray(value)) { | |
| do { | |
| value = key[valuePropertyName]; | |
| if (value !== undefined) { | |
| times.push(key.time); | |
| values.push.apply(values, value); // push all elements | |
| } | |
| key = jsonKeys[i++]; | |
| } while (key !== undefined); | |
| } else if (value.toArray !== undefined) { | |
| // ...assume THREE.Math-ish | |
| do { | |
| value = key[valuePropertyName]; | |
| if (value !== undefined) { | |
| times.push(key.time); | |
| value.toArray(values, values.length); | |
| } | |
| key = jsonKeys[i++]; | |
| } while (key !== undefined); | |
| } else { | |
| // otherwise push as-is | |
| do { | |
| value = key[valuePropertyName]; | |
| if (value !== undefined) { | |
| times.push(key.time); | |
| values.push(value); | |
| } | |
| key = jsonKeys[i++]; | |
| } while (key !== undefined); | |
| } | |
| }, | |
| subclip: function (sourceClip, name, startFrame, endFrame, fps = 30) { | |
| const clip = sourceClip.clone(); | |
| clip.name = name; | |
| const tracks = []; | |
| for (let i = 0; i < clip.tracks.length; ++i) { | |
| const track = clip.tracks[i]; | |
| const valueSize = track.getValueSize(); | |
| const times = []; | |
| const values = []; | |
| for (let j = 0; j < track.times.length; ++j) { | |
| const frame = track.times[j] * fps; | |
| if (frame < startFrame || frame >= endFrame) continue; | |
| times.push(track.times[j]); | |
| for (let k = 0; k < valueSize; ++k) { | |
| values.push(track.values[j * valueSize + k]); | |
| } | |
| } | |
| if (times.length === 0) continue; | |
| track.times = AnimationUtils.convertArray(times, track.times.constructor); | |
| track.values = AnimationUtils.convertArray(values, track.values.constructor); | |
| tracks.push(track); | |
| } | |
| clip.tracks = tracks; | |
| // find minimum .times value across all tracks in the trimmed clip | |
| let minStartTime = Infinity; | |
| for (let i = 0; i < clip.tracks.length; ++i) { | |
| if (minStartTime > clip.tracks[i].times[0]) { | |
| minStartTime = clip.tracks[i].times[0]; | |
| } | |
| } | |
| // shift all tracks such that clip begins at t=0 | |
| for (let i = 0; i < clip.tracks.length; ++i) { | |
| clip.tracks[i].shift(-1 * minStartTime); | |
| } | |
| clip.resetDuration(); | |
| return clip; | |
| }, | |
| makeClipAdditive: function (targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30) { | |
| if (fps <= 0) fps = 30; | |
| const numTracks = referenceClip.tracks.length; | |
| const referenceTime = referenceFrame / fps; | |
| // Make each track's values relative to the values at the reference frame | |
| for (let i = 0; i < numTracks; ++i) { | |
| const referenceTrack = referenceClip.tracks[i]; | |
| const referenceTrackType = referenceTrack.ValueTypeName; | |
| // Skip this track if it's non-numeric | |
| if (referenceTrackType === 'bool' || referenceTrackType === 'string') continue; | |
| // Find the track in the target clip whose name and type matches the reference track | |
| const targetTrack = targetClip.tracks.find(function (track) { | |
| return track.name === referenceTrack.name && track.ValueTypeName === referenceTrackType; | |
| }); | |
| if (targetTrack === undefined) continue; | |
| let referenceOffset = 0; | |
| const referenceValueSize = referenceTrack.getValueSize(); | |
| if (referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline) { | |
| referenceOffset = referenceValueSize / 3; | |
| } | |
| let targetOffset = 0; | |
| const targetValueSize = targetTrack.getValueSize(); | |
| if (targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline) { | |
| targetOffset = targetValueSize / 3; | |
| } | |
| const lastIndex = referenceTrack.times.length - 1; | |
| let referenceValue; | |
| // Find the value to subtract out of the track | |
| if (referenceTime <= referenceTrack.times[0]) { | |
| // Reference frame is earlier than the first keyframe, so just use the first keyframe | |
| const startIndex = referenceOffset; | |
| const endIndex = referenceValueSize - referenceOffset; | |
| referenceValue = AnimationUtils.arraySlice(referenceTrack.values, startIndex, endIndex); | |
| } else if (referenceTime >= referenceTrack.times[lastIndex]) { | |
| // Reference frame is after the last keyframe, so just use the last keyframe | |
| const startIndex = lastIndex * referenceValueSize + referenceOffset; | |
| const endIndex = startIndex + referenceValueSize - referenceOffset; | |
| referenceValue = AnimationUtils.arraySlice(referenceTrack.values, startIndex, endIndex); | |
| } else { | |
| // Interpolate to the reference value | |
| const interpolant = referenceTrack.createInterpolant(); | |
| const startIndex = referenceOffset; | |
| const endIndex = referenceValueSize - referenceOffset; | |
| interpolant.evaluate(referenceTime); | |
| referenceValue = AnimationUtils.arraySlice(interpolant.resultBuffer, startIndex, endIndex); | |
| } | |
| // Conjugate the quaternion | |
| if (referenceTrackType === 'quaternion') { | |
| const referenceQuat = new Quaternion().fromArray(referenceValue).normalize().conjugate(); | |
| referenceQuat.toArray(referenceValue); | |
| } | |
| // Subtract the reference value from all of the track values | |
| const numTimes = targetTrack.times.length; | |
| for (let j = 0; j < numTimes; ++j) { | |
| const valueStart = j * targetValueSize + targetOffset; | |
| if (referenceTrackType === 'quaternion') { | |
| // Multiply the conjugate for quaternion track types | |
| Quaternion.multiplyQuaternionsFlat(targetTrack.values, valueStart, referenceValue, 0, targetTrack.values, valueStart); | |
| } else { | |
| const valueEnd = targetValueSize - targetOffset * 2; | |
| // Subtract each value for all other numeric track types | |
| for (let k = 0; k < valueEnd; ++k) { | |
| targetTrack.values[valueStart + k] -= referenceValue[k]; | |
| } | |
| } | |
| } | |
| } | |
| targetClip.blendMode = AdditiveAnimationBlendMode; | |
| return targetClip; | |
| }, | |
| }; | |
| export { AnimationUtils }; | |