UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

366 lines (336 loc) 12.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AnimationUtils = void 0; exports.convertArray = convertArray; exports.flattenJSON = flattenJSON; exports.getKeyframeOrder = getKeyframeOrder; exports.isTypedArray = isTypedArray; exports.makeClipAdditive = makeClipAdditive; exports.sortedArray = sortedArray; exports.subclip = subclip; var _Quaternion = require("../math/Quaternion.js"); var _constants = require("../constants.js"); /** * Converts an array to a specific type. * * @param {TypedArray|Array} array - The array to convert. * @param {TypedArray.constructor} type - The constructor of a typed array that defines the new type. * @return {TypedArray} The converted array. */ function convertArray(array, type) { if (!array || 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 } /** * Returns `true` if the given object is a typed array. * * @param {any} object - The object to check. * @return {boolean} Whether the given object is a typed array. */ function isTypedArray(object) { return ArrayBuffer.isView(object) && !(object instanceof DataView); } /** * Returns an array by which times and values can be sorted. * * @param {Array<number>} times - The keyframe time values. * @return {Array<number>} The array. */ function getKeyframeOrder(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; } /** * Sorts the given array by the previously computed order via `getKeyframeOrder()`. * * @param {Array<number>} values - The values to sort. * @param {number} stride - The stride. * @param {Array<number>} order - The sort order. * @return {Array<number>} The sorted values. */ function sortedArray(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; } /** * Used for parsing AOS keyframe formats. * * @param {Array<number>} jsonKeys - A list of JSON keyframes. * @param {Array<number>} times - This array will be filled with keyframe times by this function. * @param {Array<number>} values - This array will be filled with keyframe values by this function. * @param {string} valuePropertyName - The name of the property to use. */ function flattenJSON(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(...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); } } /** * Creates a new clip, containing only the segment of the original clip between the given frames. * * @param {AnimationClip} sourceClip - The values to sort. * @param {string} name - The name of the clip. * @param {number} startFrame - The start frame. * @param {number} endFrame - The end frame. * @param {number} [fps=30] - The FPS. * @return {AnimationClip} The new sub clip. */ function subclip(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 = convertArray(times, track.times.constructor); track.values = 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; } /** * Converts the keyframes of the given animation clip to an additive format. * * @param {AnimationClip} targetClip - The clip to make additive. * @param {number} [referenceFrame=0] - The reference frame. * @param {AnimationClip} [referenceClip=targetClip] - The reference clip. * @param {number} [fps=30] - The FPS. * @return {AnimationClip} The updated clip which is now additive. */ function makeClipAdditive(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 = referenceTrack.values.slice(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 = referenceTrack.values.slice(startIndex, endIndex); } else { // Interpolate to the reference value const interpolant = referenceTrack.createInterpolant(); const startIndex = referenceOffset; const endIndex = referenceValueSize - referenceOffset; interpolant.evaluate(referenceTime); referenceValue = interpolant.resultBuffer.slice(startIndex, endIndex); } // Conjugate the quaternion if (referenceTrackType === 'quaternion') { const referenceQuat = new _Quaternion.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.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 = _constants.AdditiveAnimationBlendMode; return targetClip; } /** * A class with various methods to assist with animations. * * @hideconstructor */ class AnimationUtils { /** * Converts an array to a specific type * * @static * @param {TypedArray|Array} array - The array to convert. * @param {TypedArray.constructor} type - The constructor of a type array. * @return {TypedArray} The converted array */ static convertArray(array, type) { return convertArray(array, type); } /** * Returns `true` if the given object is a typed array. * * @static * @param {any} object - The object to check. * @return {boolean} Whether the given object is a typed array. */ static isTypedArray(object) { return isTypedArray(object); } /** * Returns an array by which times and values can be sorted. * * @static * @param {Array<number>} times - The keyframe time values. * @return {Array<number>} The array. */ static getKeyframeOrder(times) { return getKeyframeOrder(times); } /** * Sorts the given array by the previously computed order via `getKeyframeOrder()`. * * @static * @param {Array<number>} values - The values to sort. * @param {number} stride - The stride. * @param {Array<number>} order - The sort order. * @return {Array<number>} The sorted values. */ static sortedArray(values, stride, order) { return sortedArray(values, stride, order); } /** * Used for parsing AOS keyframe formats. * * @static * @param {Array<number>} jsonKeys - A list of JSON keyframes. * @param {Array<number>} times - This array will be filled with keyframe times by this method. * @param {Array<number>} values - This array will be filled with keyframe values by this method. * @param {string} valuePropertyName - The name of the property to use. */ static flattenJSON(jsonKeys, times, values, valuePropertyName) { flattenJSON(jsonKeys, times, values, valuePropertyName); } /** * Creates a new clip, containing only the segment of the original clip between the given frames. * * @static * @param {AnimationClip} sourceClip - The values to sort. * @param {string} name - The name of the clip. * @param {number} startFrame - The start frame. * @param {number} endFrame - The end frame. * @param {number} [fps=30] - The FPS. * @return {AnimationClip} The new sub clip. */ static subclip(sourceClip, name, startFrame, endFrame, fps = 30) { return subclip(sourceClip, name, startFrame, endFrame, fps); } /** * Converts the keyframes of the given animation clip to an additive format. * * @static * @param {AnimationClip} targetClip - The clip to make additive. * @param {number} [referenceFrame=0] - The reference frame. * @param {AnimationClip} [referenceClip=targetClip] - The reference clip. * @param {number} [fps=30] - The FPS. * @return {AnimationClip} The updated clip which is now additive. */ static makeClipAdditive(targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30) { return makeClipAdditive(targetClip, referenceFrame, referenceClip, fps); } } exports.AnimationUtils = AnimationUtils;