UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

433 lines (405 loc) 15.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AnimationClip = void 0; var AnimationUtils = _interopRequireWildcard(require("./AnimationUtils.js")); var _KeyframeTrack = require("./KeyframeTrack.js"); var _BooleanKeyframeTrack = require("./tracks/BooleanKeyframeTrack.js"); var _ColorKeyframeTrack = require("./tracks/ColorKeyframeTrack.js"); var _NumberKeyframeTrack = require("./tracks/NumberKeyframeTrack.js"); var _QuaternionKeyframeTrack = require("./tracks/QuaternionKeyframeTrack.js"); var _StringKeyframeTrack = require("./tracks/StringKeyframeTrack.js"); var _VectorKeyframeTrack = require("./tracks/VectorKeyframeTrack.js"); var _MathUtils = require("../math/MathUtils.js"); var _constants = require("../constants.js"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /** * A reusable set of keyframe tracks which represent an animation. */ class AnimationClip { /** * Constructs a new animation clip. * * Note: Instead of instantiating an AnimationClip directly with the constructor, you can * use the static interface of this class for creating clips. In most cases though, animation clips * will automatically be created by loaders when importing animated 3D assets. * * @param {string} [name=''] - The clip's name. * @param {number} [duration=-1] - The clip's duration in seconds. If a negative value is passed, * the duration will be calculated from the passed keyframes. * @param {Array<KeyframeTrack>} tracks - An array of keyframe tracks. * @param {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)} [blendMode=NormalAnimationBlendMode] - Defines how the animation * is blended/combined when two or more animations are simultaneously played. */ constructor(name = '', duration = -1, tracks = [], blendMode = _constants.NormalAnimationBlendMode) { /** * The clip's name. * * @type {string} */ this.name = name; /** * An array of keyframe tracks. * * @type {Array<KeyframeTrack>} */ this.tracks = tracks; /** * The clip's duration in seconds. * * @type {number} */ this.duration = duration; /** * Defines how the animation is blended/combined when two or more animations * are simultaneously played. * * @type {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)} */ this.blendMode = blendMode; /** * The UUID of the animation clip. * * @type {string} * @readonly */ this.uuid = (0, _MathUtils.generateUUID)(); // this means it should figure out its duration by scanning the tracks if (this.duration < 0) { this.resetDuration(); } } /** * Factory method for creating an animation clip from the given JSON. * * @static * @param {Object} json - The serialized animation clip. * @return {AnimationClip} The new animation clip. */ static parse(json) { const tracks = [], jsonTracks = json.tracks, frameTime = 1.0 / (json.fps || 1.0); for (let i = 0, n = jsonTracks.length; i !== n; ++i) { tracks.push(parseKeyframeTrack(jsonTracks[i]).scale(frameTime)); } const clip = new this(json.name, json.duration, tracks, json.blendMode); clip.uuid = json.uuid; return clip; } /** * Serializes the given animation clip into JSON. * * @static * @param {AnimationClip} clip - The animation clip to serialize. * @return {Object} The JSON object. */ static toJSON(clip) { const tracks = [], clipTracks = clip.tracks; const json = { 'name': clip.name, 'duration': clip.duration, 'tracks': tracks, 'uuid': clip.uuid, 'blendMode': clip.blendMode }; for (let i = 0, n = clipTracks.length; i !== n; ++i) { tracks.push(_KeyframeTrack.KeyframeTrack.toJSON(clipTracks[i])); } return json; } /** * Returns a new animation clip from the passed morph targets array of a * geometry, taking a name and the number of frames per second. * * Note: The fps parameter is required, but the animation speed can be * overridden via {@link AnimationAction#setDuration}. * * @static * @param {string} name - The name of the animation clip. * @param {Array<Object>} morphTargetSequence - A sequence of morph targets. * @param {number} fps - The Frames-Per-Second value. * @param {boolean} noLoop - Whether the clip should be no loop or not. * @return {AnimationClip} The new animation clip. */ static CreateFromMorphTargetSequence(name, morphTargetSequence, fps, noLoop) { const numMorphTargets = morphTargetSequence.length; const tracks = []; for (let i = 0; i < numMorphTargets; i++) { let times = []; let values = []; times.push((i + numMorphTargets - 1) % numMorphTargets, i, (i + 1) % numMorphTargets); values.push(0, 1, 0); const order = AnimationUtils.getKeyframeOrder(times); times = AnimationUtils.sortedArray(times, 1, order); values = AnimationUtils.sortedArray(values, 1, order); // if there is a key at the first frame, duplicate it as the // last frame as well for perfect loop. if (!noLoop && times[0] === 0) { times.push(numMorphTargets); values.push(values[0]); } tracks.push(new _NumberKeyframeTrack.NumberKeyframeTrack('.morphTargetInfluences[' + morphTargetSequence[i].name + ']', times, values).scale(1.0 / fps)); } return new this(name, -1, tracks); } /** * Searches for an animation clip by name, taking as its first parameter * either an array of clips, or a mesh or geometry that contains an * array named "animations" property. * * @static * @param {(Array<AnimationClip>|Object3D)} objectOrClipArray - The array or object to search through. * @param {string} name - The name to search for. * @return {?AnimationClip} The found animation clip. Returns `null` if no clip has been found. */ static findByName(objectOrClipArray, name) { let clipArray = objectOrClipArray; if (!Array.isArray(objectOrClipArray)) { const o = objectOrClipArray; clipArray = o.geometry && o.geometry.animations || o.animations; } for (let i = 0; i < clipArray.length; i++) { if (clipArray[i].name === name) { return clipArray[i]; } } return null; } /** * Returns an array of new AnimationClips created from the morph target * sequences of a geometry, trying to sort morph target names into * animation-group-based patterns like "Walk_001, Walk_002, Run_001, Run_002...". * * See {@link MD2Loader#parse} as an example for how the method should be used. * * @static * @param {Array<Object>} morphTargets - A sequence of morph targets. * @param {number} fps - The Frames-Per-Second value. * @param {boolean} noLoop - Whether the clip should be no loop or not. * @return {Array<AnimationClip>} An array of new animation clips. */ static CreateClipsFromMorphTargetSequences(morphTargets, fps, noLoop) { const animationToMorphTargets = {}; // tested with https://regex101.com/ on trick sequences // such flamingo_flyA_003, flamingo_run1_003, crdeath0059 const pattern = /^([\w-]*?)([\d]+)$/; // sort morph target names into animation groups based // patterns like Walk_001, Walk_002, Run_001, Run_002 for (let i = 0, il = morphTargets.length; i < il; i++) { const morphTarget = morphTargets[i]; const parts = morphTarget.name.match(pattern); if (parts && parts.length > 1) { const name = parts[1]; let animationMorphTargets = animationToMorphTargets[name]; if (!animationMorphTargets) { animationToMorphTargets[name] = animationMorphTargets = []; } animationMorphTargets.push(morphTarget); } } const clips = []; for (const name in animationToMorphTargets) { clips.push(this.CreateFromMorphTargetSequence(name, animationToMorphTargets[name], fps, noLoop)); } return clips; } /** * Parses the `animation.hierarchy` format and returns a new animation clip. * * @static * @deprecated since r175. * @param {Object} animation - A serialized animation clip as JSON. * @param {Array<Bones>} bones - An array of bones. * @return {?AnimationClip} The new animation clip. */ static parseAnimation(animation, bones) { console.warn('THREE.AnimationClip: parseAnimation() is deprecated and will be removed with r185'); if (!animation) { console.error('THREE.AnimationClip: No animation in JSONLoader data.'); return null; } const addNonemptyTrack = function (trackType, trackName, animationKeys, propertyName, destTracks) { // only return track if there are actually keys. if (animationKeys.length !== 0) { const times = []; const values = []; AnimationUtils.flattenJSON(animationKeys, times, values, propertyName); // empty keys are filtered out, so check again if (times.length !== 0) { destTracks.push(new trackType(trackName, times, values)); } } }; const tracks = []; const clipName = animation.name || 'default'; const fps = animation.fps || 30; const blendMode = animation.blendMode; // automatic length determination in AnimationClip. let duration = animation.length || -1; const hierarchyTracks = animation.hierarchy || []; for (let h = 0; h < hierarchyTracks.length; h++) { const animationKeys = hierarchyTracks[h].keys; // skip empty tracks if (!animationKeys || animationKeys.length === 0) continue; // process morph targets if (animationKeys[0].morphTargets) { // figure out all morph targets used in this track const morphTargetNames = {}; let k; for (k = 0; k < animationKeys.length; k++) { if (animationKeys[k].morphTargets) { for (let m = 0; m < animationKeys[k].morphTargets.length; m++) { morphTargetNames[animationKeys[k].morphTargets[m]] = -1; } } } // create a track for each morph target with all zero // morphTargetInfluences except for the keys in which // the morphTarget is named. for (const morphTargetName in morphTargetNames) { const times = []; const values = []; for (let m = 0; m !== animationKeys[k].morphTargets.length; ++m) { const animationKey = animationKeys[k]; times.push(animationKey.time); values.push(animationKey.morphTarget === morphTargetName ? 1 : 0); } tracks.push(new _NumberKeyframeTrack.NumberKeyframeTrack('.morphTargetInfluence[' + morphTargetName + ']', times, values)); } duration = morphTargetNames.length * fps; } else { // ...assume skeletal animation const boneName = '.bones[' + bones[h].name + ']'; addNonemptyTrack(_VectorKeyframeTrack.VectorKeyframeTrack, boneName + '.position', animationKeys, 'pos', tracks); addNonemptyTrack(_QuaternionKeyframeTrack.QuaternionKeyframeTrack, boneName + '.quaternion', animationKeys, 'rot', tracks); addNonemptyTrack(_VectorKeyframeTrack.VectorKeyframeTrack, boneName + '.scale', animationKeys, 'scl', tracks); } } if (tracks.length === 0) { return null; } const clip = new this(clipName, duration, tracks, blendMode); return clip; } /** * Sets the duration of this clip to the duration of its longest keyframe track. * * @return {AnimationClip} A reference to this animation clip. */ resetDuration() { const tracks = this.tracks; let duration = 0; for (let i = 0, n = tracks.length; i !== n; ++i) { const track = this.tracks[i]; duration = Math.max(duration, track.times[track.times.length - 1]); } this.duration = duration; return this; } /** * Trims all tracks to the clip's duration. * * @return {AnimationClip} A reference to this animation clip. */ trim() { for (let i = 0; i < this.tracks.length; i++) { this.tracks[i].trim(0, this.duration); } return this; } /** * Performs minimal validation on each track in the clip. Returns `true` if all * tracks are valid. * * @return {boolean} Whether the clip's keyframes are valid or not. */ validate() { let valid = true; for (let i = 0; i < this.tracks.length; i++) { valid = valid && this.tracks[i].validate(); } return valid; } /** * Optimizes each track by removing equivalent sequential keys (which are * common in morph target sequences). * * @return {AnimationClip} A reference to this animation clip. */ optimize() { for (let i = 0; i < this.tracks.length; i++) { this.tracks[i].optimize(); } return this; } /** * Returns a new animation clip with copied values from this instance. * * @return {AnimationClip} A clone of this instance. */ clone() { const tracks = []; for (let i = 0; i < this.tracks.length; i++) { tracks.push(this.tracks[i].clone()); } return new this.constructor(this.name, this.duration, tracks, this.blendMode); } /** * Serializes this animation clip into JSON. * * @return {Object} The JSON object. */ toJSON() { return this.constructor.toJSON(this); } } exports.AnimationClip = AnimationClip; function getTrackTypeForValueTypeName(typeName) { switch (typeName.toLowerCase()) { case 'scalar': case 'double': case 'float': case 'number': case 'integer': return _NumberKeyframeTrack.NumberKeyframeTrack; case 'vector': case 'vector2': case 'vector3': case 'vector4': return _VectorKeyframeTrack.VectorKeyframeTrack; case 'color': return _ColorKeyframeTrack.ColorKeyframeTrack; case 'quaternion': return _QuaternionKeyframeTrack.QuaternionKeyframeTrack; case 'bool': case 'boolean': return _BooleanKeyframeTrack.BooleanKeyframeTrack; case 'string': return _StringKeyframeTrack.StringKeyframeTrack; } throw new Error('THREE.KeyframeTrack: Unsupported typeName: ' + typeName); } function parseKeyframeTrack(json) { if (json.type === undefined) { throw new Error('THREE.KeyframeTrack: track type undefined, can not parse'); } const trackType = getTrackTypeForValueTypeName(json.type); if (json.times === undefined) { const times = [], values = []; AnimationUtils.flattenJSON(json.keys, times, values, 'value'); json.times = times; json.values = values; } // derived classes can define a static parse method if (trackType.parse !== undefined) { return trackType.parse(json); } else { // by default, we assume a constructor compatible with the base return new trackType(json.name, json.times, json.values, json.interpolation); } }