123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992 |
- import { Animation } from "./animation.js";
- import { Observable } from "../Misc/observable.js";
- import { EngineStore } from "../Engines/engineStore.js";
- import { Tags } from "../Misc/tags.js";
- /**
- * This class defines the direct association between an animation and a target
- */
- export class TargetedAnimation {
- /**
- * Returns the string "TargetedAnimation"
- * @returns "TargetedAnimation"
- */
- getClassName() {
- return "TargetedAnimation";
- }
- /**
- * Serialize the object
- * @returns the JSON object representing the current entity
- */
- serialize() {
- const serializationObject = {};
- serializationObject.animation = this.animation.serialize();
- serializationObject.targetId = this.target.id;
- return serializationObject;
- }
- }
- /**
- * Use this class to create coordinated animations on multiple targets
- */
- export class AnimationGroup {
- /**
- * Gets or sets the mask associated with this animation group. This mask is used to filter which objects should be animated.
- */
- get mask() {
- return this._mask;
- }
- set mask(value) {
- if (this._mask === value) {
- return;
- }
- this._mask = value;
- this.syncWithMask(true);
- }
- /**
- * Makes sure that the animations are either played or stopped according to the animation group mask.
- * Note however that the call won't have any effect if the animation group has not been started yet.
- * @param forceUpdate If true, forces to loop over the animatables even if no mask is defined (used internally, you shouldn't need to use it). Default: false.
- */
- syncWithMask(forceUpdate = false) {
- if (!this.mask && !forceUpdate) {
- this._numActiveAnimatables = this._targetedAnimations.length;
- return;
- }
- this._numActiveAnimatables = 0;
- for (let i = 0; i < this._animatables.length; ++i) {
- const animatable = this._animatables[i];
- if (!this.mask || this.mask.disabled || this.mask.retainsTarget(animatable.target.name)) {
- this._numActiveAnimatables++;
- if (animatable.paused) {
- animatable.restart();
- }
- }
- else {
- if (!animatable.paused) {
- animatable.pause();
- }
- }
- }
- }
- /**
- * Removes all animations for the targets not retained by the animation group mask.
- * Use this function if you know you won't need those animations anymore and if you want to free memory.
- */
- removeUnmaskedAnimations() {
- if (!this.mask || this.mask.disabled) {
- return;
- }
- // Removes all animatables (in case the animation group has already been started)
- for (let i = 0; i < this._animatables.length; ++i) {
- const animatable = this._animatables[i];
- if (!this.mask.retainsTarget(animatable.target.name)) {
- animatable.stop();
- this._animatables.splice(i, 1);
- --i;
- }
- }
- // Removes the targeted animations
- for (let index = 0; index < this._targetedAnimations.length; index++) {
- const targetedAnimation = this._targetedAnimations[index];
- if (!this.mask.retainsTarget(targetedAnimation.target.name)) {
- this._targetedAnimations.splice(index, 1);
- --index;
- }
- }
- }
- /**
- * Gets or sets the first frame
- */
- get from() {
- return this._from;
- }
- set from(value) {
- if (this._from === value) {
- return;
- }
- this._from = value;
- for (let index = 0; index < this._animatables.length; index++) {
- const animatable = this._animatables[index];
- animatable.fromFrame = this._from;
- }
- }
- /**
- * Gets or sets the last frame
- */
- get to() {
- return this._to;
- }
- set to(value) {
- if (this._to === value) {
- return;
- }
- this._to = value;
- for (let index = 0; index < this._animatables.length; index++) {
- const animatable = this._animatables[index];
- animatable.toFrame = this._to;
- }
- }
- /**
- * Define if the animations are started
- */
- get isStarted() {
- return this._isStarted;
- }
- /**
- * Gets a value indicating that the current group is playing
- */
- get isPlaying() {
- return this._isStarted && !this._isPaused;
- }
- /**
- * Gets or sets the speed ratio to use for all animations
- */
- get speedRatio() {
- return this._speedRatio;
- }
- /**
- * Gets or sets the speed ratio to use for all animations
- */
- set speedRatio(value) {
- if (this._speedRatio === value) {
- return;
- }
- this._speedRatio = value;
- for (let index = 0; index < this._animatables.length; index++) {
- const animatable = this._animatables[index];
- animatable.speedRatio = this._speedRatio;
- }
- }
- /**
- * Gets or sets if all animations should loop or not
- */
- get loopAnimation() {
- return this._loopAnimation;
- }
- set loopAnimation(value) {
- if (this._loopAnimation === value) {
- return;
- }
- this._loopAnimation = value;
- for (let index = 0; index < this._animatables.length; index++) {
- const animatable = this._animatables[index];
- animatable.loopAnimation = this._loopAnimation;
- }
- }
- /**
- * Gets or sets if all animations should be evaluated additively
- */
- get isAdditive() {
- return this._isAdditive;
- }
- set isAdditive(value) {
- if (this._isAdditive === value) {
- return;
- }
- this._isAdditive = value;
- for (let index = 0; index < this._animatables.length; index++) {
- const animatable = this._animatables[index];
- animatable.isAdditive = this._isAdditive;
- }
- }
- /**
- * Gets or sets the weight to apply to all animations of the group
- */
- get weight() {
- return this._weight;
- }
- set weight(value) {
- if (this._weight === value) {
- return;
- }
- this._weight = value;
- this.setWeightForAllAnimatables(this._weight);
- }
- /**
- * Gets the targeted animations for this animation group
- */
- get targetedAnimations() {
- return this._targetedAnimations;
- }
- /**
- * returning the list of animatables controlled by this animation group.
- */
- get animatables() {
- return this._animatables;
- }
- /**
- * Gets the list of target animations
- */
- get children() {
- return this._targetedAnimations;
- }
- /**
- * Gets or sets the order of play of the animation group (default: 0)
- */
- get playOrder() {
- return this._playOrder;
- }
- set playOrder(value) {
- if (this._playOrder === value) {
- return;
- }
- this._playOrder = value;
- if (this._animatables.length > 0) {
- for (let i = 0; i < this._animatables.length; i++) {
- this._animatables[i].playOrder = this._playOrder;
- }
- this._scene.sortActiveAnimatables();
- }
- }
- /**
- * Allows the animations of the animation group to blend with current running animations
- * Note that a null value means that each animation will use their own existing blending configuration (Animation.enableBlending)
- */
- get enableBlending() {
- return this._enableBlending;
- }
- set enableBlending(value) {
- if (this._enableBlending === value) {
- return;
- }
- this._enableBlending = value;
- if (value !== null) {
- for (let i = 0; i < this._targetedAnimations.length; ++i) {
- this._targetedAnimations[i].animation.enableBlending = value;
- }
- }
- }
- /**
- * Gets or sets the animation blending speed
- * Note that a null value means that each animation will use their own existing blending configuration (Animation.blendingSpeed)
- */
- get blendingSpeed() {
- return this._blendingSpeed;
- }
- set blendingSpeed(value) {
- if (this._blendingSpeed === value) {
- return;
- }
- this._blendingSpeed = value;
- if (value !== null) {
- for (let i = 0; i < this._targetedAnimations.length; ++i) {
- this._targetedAnimations[i].animation.blendingSpeed = value;
- }
- }
- }
- /**
- * Gets the length (in seconds) of the animation group
- * This function assumes that all animations are played at the same framePerSecond speed!
- * Note: you can only call this method after you've added at least one targeted animation!
- * @param from Starting frame range (default is AnimationGroup.from)
- * @param to Ending frame range (default is AnimationGroup.to)
- * @returns The length in seconds
- */
- getLength(from, to) {
- from = from ?? this._from;
- to = to ?? this._to;
- const fps = this.targetedAnimations[0].animation.framePerSecond * this._speedRatio;
- return (to - from) / fps;
- }
- /**
- * Merge the array of animation groups into a new animation group
- * @param animationGroups List of animation groups to merge
- * @param disposeSource If true, animation groups will be disposed after being merged (default: true)
- * @param normalize If true, animation groups will be normalized before being merged, so that all animations have the same "from" and "to" frame (default: false)
- * @param weight Weight for the new animation group. If not provided, it will inherit the weight from the first animation group of the array
- * @returns The new animation group or null if no animation groups were passed
- */
- static MergeAnimationGroups(animationGroups, disposeSource = true, normalize = false, weight) {
- if (animationGroups.length === 0) {
- return null;
- }
- weight = weight ?? animationGroups[0].weight;
- let beginFrame = Number.MAX_VALUE;
- let endFrame = -Number.MAX_VALUE;
- if (normalize) {
- for (const animationGroup of animationGroups) {
- if (animationGroup.from < beginFrame) {
- beginFrame = animationGroup.from;
- }
- if (animationGroup.to > endFrame) {
- endFrame = animationGroup.to;
- }
- }
- }
- const mergedAnimationGroup = new AnimationGroup(animationGroups[0].name + "_merged", animationGroups[0]._scene, weight);
- for (const animationGroup of animationGroups) {
- if (normalize) {
- animationGroup.normalize(beginFrame, endFrame);
- }
- for (const targetedAnimation of animationGroup.targetedAnimations) {
- mergedAnimationGroup.addTargetedAnimation(targetedAnimation.animation, targetedAnimation.target);
- }
- if (disposeSource) {
- animationGroup.dispose();
- }
- }
- return mergedAnimationGroup;
- }
- /**
- * Instantiates a new Animation Group.
- * This helps managing several animations at once.
- * @see https://doc.babylonjs.com/features/featuresDeepDive/animation/groupAnimations
- * @param name Defines the name of the group
- * @param scene Defines the scene the group belongs to
- * @param weight Defines the weight to use for animations in the group (-1.0 by default, meaning "no weight")
- * @param playOrder Defines the order of play of the animation group (default is 0)
- */
- constructor(
- /** The name of the animation group */
- name, scene = null, weight = -1, playOrder = 0) {
- this.name = name;
- this._targetedAnimations = new Array();
- this._animatables = new Array();
- this._from = Number.MAX_VALUE;
- this._to = -Number.MAX_VALUE;
- this._speedRatio = 1;
- this._loopAnimation = false;
- this._isAdditive = false;
- this._weight = -1;
- this._playOrder = 0;
- this._enableBlending = null;
- this._blendingSpeed = null;
- this._numActiveAnimatables = 0;
- /** @internal */
- this._parentContainer = null;
- /**
- * This observable will notify when one animation have ended
- */
- this.onAnimationEndObservable = new Observable();
- /**
- * Observer raised when one animation loops
- */
- this.onAnimationLoopObservable = new Observable();
- /**
- * Observer raised when all animations have looped
- */
- this.onAnimationGroupLoopObservable = new Observable();
- /**
- * This observable will notify when all animations have ended.
- */
- this.onAnimationGroupEndObservable = new Observable();
- /**
- * This observable will notify when all animations have paused.
- */
- this.onAnimationGroupPauseObservable = new Observable();
- /**
- * This observable will notify when all animations are playing.
- */
- this.onAnimationGroupPlayObservable = new Observable();
- /**
- * Gets or sets an object used to store user defined information for the node
- */
- this.metadata = null;
- this._mask = null;
- this._animationLoopFlags = [];
- this._scene = scene || EngineStore.LastCreatedScene;
- this._weight = weight;
- this._playOrder = playOrder;
- this.uniqueId = this._scene.getUniqueId();
- this._scene.addAnimationGroup(this);
- }
- /**
- * Add an animation (with its target) in the group
- * @param animation defines the animation we want to add
- * @param target defines the target of the animation
- * @returns the TargetedAnimation object
- */
- addTargetedAnimation(animation, target) {
- const targetedAnimation = new TargetedAnimation();
- targetedAnimation.animation = animation;
- targetedAnimation.target = target;
- const keys = animation.getKeys();
- if (this._from > keys[0].frame) {
- this._from = keys[0].frame;
- }
- if (this._to < keys[keys.length - 1].frame) {
- this._to = keys[keys.length - 1].frame;
- }
- if (this._enableBlending !== null) {
- animation.enableBlending = this._enableBlending;
- }
- if (this._blendingSpeed !== null) {
- animation.blendingSpeed = this._blendingSpeed;
- }
- this._targetedAnimations.push(targetedAnimation);
- return targetedAnimation;
- }
- /**
- * Remove an animation from the group
- * @param animation defines the animation we want to remove
- */
- removeTargetedAnimation(animation) {
- for (let index = this._targetedAnimations.length - 1; index > -1; index--) {
- const targetedAnimation = this._targetedAnimations[index];
- if (targetedAnimation.animation === animation) {
- this._targetedAnimations.splice(index, 1);
- }
- }
- }
- /**
- * This function will normalize every animation in the group to make sure they all go from beginFrame to endFrame
- * It can add constant keys at begin or end
- * @param beginFrame defines the new begin frame for all animations or the smallest begin frame of all animations if null (defaults to null)
- * @param endFrame defines the new end frame for all animations or the largest end frame of all animations if null (defaults to null)
- * @returns the animation group
- */
- normalize(beginFrame = null, endFrame = null) {
- if (beginFrame == null) {
- beginFrame = this._from;
- }
- if (endFrame == null) {
- endFrame = this._to;
- }
- for (let index = 0; index < this._targetedAnimations.length; index++) {
- const targetedAnimation = this._targetedAnimations[index];
- const keys = targetedAnimation.animation.getKeys();
- const startKey = keys[0];
- const endKey = keys[keys.length - 1];
- if (startKey.frame > beginFrame) {
- const newKey = {
- frame: beginFrame,
- value: startKey.value,
- inTangent: startKey.inTangent,
- outTangent: startKey.outTangent,
- interpolation: startKey.interpolation,
- };
- keys.splice(0, 0, newKey);
- }
- if (endKey.frame < endFrame) {
- const newKey = {
- frame: endFrame,
- value: endKey.value,
- inTangent: endKey.inTangent,
- outTangent: endKey.outTangent,
- interpolation: endKey.interpolation,
- };
- keys.push(newKey);
- }
- }
- this._from = beginFrame;
- this._to = endFrame;
- return this;
- }
- _processLoop(animatable, targetedAnimation, index) {
- animatable.onAnimationLoop = () => {
- this.onAnimationLoopObservable.notifyObservers(targetedAnimation);
- if (this._animationLoopFlags[index]) {
- return;
- }
- this._animationLoopFlags[index] = true;
- this._animationLoopCount++;
- if (this._animationLoopCount === this._numActiveAnimatables) {
- this.onAnimationGroupLoopObservable.notifyObservers(this);
- this._animationLoopCount = 0;
- this._animationLoopFlags.length = 0;
- }
- };
- }
- /**
- * Start all animations on given targets
- * @param loop defines if animations must loop
- * @param speedRatio defines the ratio to apply to animation speed (1 by default)
- * @param from defines the from key (optional)
- * @param to defines the to key (optional)
- * @param isAdditive defines the additive state for the resulting animatables (optional)
- * @returns the current animation group
- */
- start(loop = false, speedRatio = 1, from, to, isAdditive) {
- if (this._isStarted || this._targetedAnimations.length === 0) {
- return this;
- }
- this._loopAnimation = loop;
- this._animationLoopCount = 0;
- this._animationLoopFlags.length = 0;
- for (let index = 0; index < this._targetedAnimations.length; index++) {
- const targetedAnimation = this._targetedAnimations[index];
- const animatable = this._scene.beginDirectAnimation(targetedAnimation.target, [targetedAnimation.animation], from !== undefined ? from : this._from, to !== undefined ? to : this._to, loop, speedRatio, undefined, undefined, isAdditive !== undefined ? isAdditive : this._isAdditive);
- animatable.weight = this._weight;
- animatable.playOrder = this._playOrder;
- animatable.onAnimationEnd = () => {
- this.onAnimationEndObservable.notifyObservers(targetedAnimation);
- this._checkAnimationGroupEnded(animatable);
- };
- this._processLoop(animatable, targetedAnimation, index);
- this._animatables.push(animatable);
- }
- this.syncWithMask();
- this._scene.sortActiveAnimatables();
- this._speedRatio = speedRatio;
- this._isStarted = true;
- this._isPaused = false;
- this.onAnimationGroupPlayObservable.notifyObservers(this);
- return this;
- }
- /**
- * Pause all animations
- * @returns the animation group
- */
- pause() {
- if (!this._isStarted) {
- return this;
- }
- this._isPaused = true;
- for (let index = 0; index < this._animatables.length; index++) {
- const animatable = this._animatables[index];
- animatable.pause();
- }
- this.onAnimationGroupPauseObservable.notifyObservers(this);
- return this;
- }
- /**
- * Play all animations to initial state
- * This function will start() the animations if they were not started or will restart() them if they were paused
- * @param loop defines if animations must loop
- * @returns the animation group
- */
- play(loop) {
- // only if all animatables are ready and exist
- if (this.isStarted && this._animatables.length === this._targetedAnimations.length) {
- if (loop !== undefined) {
- this.loopAnimation = loop;
- }
- this.restart();
- }
- else {
- this.stop();
- this.start(loop, this._speedRatio);
- }
- this._isPaused = false;
- return this;
- }
- /**
- * Reset all animations to initial state
- * @returns the animation group
- */
- reset() {
- if (!this._isStarted) {
- this.play();
- this.goToFrame(0);
- this.stop();
- return this;
- }
- for (let index = 0; index < this._animatables.length; index++) {
- const animatable = this._animatables[index];
- animatable.reset();
- }
- return this;
- }
- /**
- * Restart animations from key 0
- * @returns the animation group
- */
- restart() {
- if (!this._isStarted) {
- return this;
- }
- for (let index = 0; index < this._animatables.length; index++) {
- const animatable = this._animatables[index];
- animatable.restart();
- }
- this.syncWithMask();
- this.onAnimationGroupPlayObservable.notifyObservers(this);
- return this;
- }
- /**
- * Stop all animations
- * @returns the animation group
- */
- stop() {
- if (!this._isStarted) {
- return this;
- }
- const list = this._animatables.slice();
- for (let index = 0; index < list.length; index++) {
- list[index].stop(undefined, undefined, true);
- }
- // We will take care of removing all stopped animatables
- let curIndex = 0;
- for (let index = 0; index < this._scene._activeAnimatables.length; index++) {
- const animatable = this._scene._activeAnimatables[index];
- if (animatable._runtimeAnimations.length > 0) {
- this._scene._activeAnimatables[curIndex++] = animatable;
- }
- }
- this._scene._activeAnimatables.length = curIndex;
- this._isStarted = false;
- return this;
- }
- /**
- * Set animation weight for all animatables
- *
- * @since 6.12.4
- * You can pass the weight to the AnimationGroup constructor, or use the weight property to set it after the group has been created,
- * making it easier to define the overall animation weight than calling setWeightForAllAnimatables() after the animation group has been started
- * @param weight defines the weight to use
- * @returns the animationGroup
- * @see https://doc.babylonjs.com/features/featuresDeepDive/animation/advanced_animations#animation-weights
- */
- setWeightForAllAnimatables(weight) {
- for (let index = 0; index < this._animatables.length; index++) {
- const animatable = this._animatables[index];
- animatable.weight = weight;
- }
- return this;
- }
- /**
- * Synchronize and normalize all animatables with a source animatable
- * @param root defines the root animatable to synchronize with (null to stop synchronizing)
- * @returns the animationGroup
- * @see https://doc.babylonjs.com/features/featuresDeepDive/animation/advanced_animations#animation-weights
- */
- syncAllAnimationsWith(root) {
- for (let index = 0; index < this._animatables.length; index++) {
- const animatable = this._animatables[index];
- animatable.syncWith(root);
- }
- return this;
- }
- /**
- * Goes to a specific frame in this animation group. Note that the animation group must be in playing or paused status
- * @param frame the frame number to go to
- * @returns the animationGroup
- */
- goToFrame(frame) {
- if (!this._isStarted) {
- return this;
- }
- for (let index = 0; index < this._animatables.length; index++) {
- const animatable = this._animatables[index];
- animatable.goToFrame(frame);
- }
- return this;
- }
- /**
- * Dispose all associated resources
- */
- dispose() {
- this._targetedAnimations.length = 0;
- this._animatables.length = 0;
- // Remove from scene
- const index = this._scene.animationGroups.indexOf(this);
- if (index > -1) {
- this._scene.animationGroups.splice(index, 1);
- }
- if (this._parentContainer) {
- const index = this._parentContainer.animationGroups.indexOf(this);
- if (index > -1) {
- this._parentContainer.animationGroups.splice(index, 1);
- }
- this._parentContainer = null;
- }
- this.onAnimationEndObservable.clear();
- this.onAnimationGroupEndObservable.clear();
- this.onAnimationGroupPauseObservable.clear();
- this.onAnimationGroupPlayObservable.clear();
- this.onAnimationLoopObservable.clear();
- this.onAnimationGroupLoopObservable.clear();
- }
- _checkAnimationGroupEnded(animatable) {
- // animatable should be taken out of the array
- const idx = this._animatables.indexOf(animatable);
- if (idx > -1) {
- this._animatables.splice(idx, 1);
- }
- // all animatables were removed? animation group ended!
- if (this._animatables.length === 0) {
- this._isStarted = false;
- this.onAnimationGroupEndObservable.notifyObservers(this);
- }
- }
- /**
- * Clone the current animation group and returns a copy
- * @param newName defines the name of the new group
- * @param targetConverter defines an optional function used to convert current animation targets to new ones
- * @param cloneAnimations defines if the animations should be cloned or referenced
- * @returns the new animation group
- */
- clone(newName, targetConverter, cloneAnimations = false) {
- const newGroup = new AnimationGroup(newName || this.name, this._scene, this._weight, this._playOrder);
- newGroup._from = this.from;
- newGroup._to = this.to;
- newGroup._speedRatio = this.speedRatio;
- newGroup._loopAnimation = this.loopAnimation;
- newGroup._isAdditive = this.isAdditive;
- newGroup._enableBlending = this.enableBlending;
- newGroup._blendingSpeed = this.blendingSpeed;
- newGroup.metadata = this.metadata;
- newGroup.mask = this.mask;
- for (const targetAnimation of this._targetedAnimations) {
- newGroup.addTargetedAnimation(cloneAnimations ? targetAnimation.animation.clone() : targetAnimation.animation, targetConverter ? targetConverter(targetAnimation.target) : targetAnimation.target);
- }
- return newGroup;
- }
- /**
- * Serializes the animationGroup to an object
- * @returns Serialized object
- */
- serialize() {
- const serializationObject = {};
- serializationObject.name = this.name;
- serializationObject.from = this.from;
- serializationObject.to = this.to;
- serializationObject.speedRatio = this.speedRatio;
- serializationObject.loopAnimation = this.loopAnimation;
- serializationObject.isAdditive = this.isAdditive;
- serializationObject.weight = this.weight;
- serializationObject.playOrder = this.playOrder;
- serializationObject.enableBlending = this.enableBlending;
- serializationObject.blendingSpeed = this.blendingSpeed;
- serializationObject.targetedAnimations = [];
- for (let targetedAnimationIndex = 0; targetedAnimationIndex < this.targetedAnimations.length; targetedAnimationIndex++) {
- const targetedAnimation = this.targetedAnimations[targetedAnimationIndex];
- serializationObject.targetedAnimations[targetedAnimationIndex] = targetedAnimation.serialize();
- }
- if (Tags && Tags.HasTags(this)) {
- serializationObject.tags = Tags.GetTags(this);
- }
- // Metadata
- if (this.metadata) {
- serializationObject.metadata = this.metadata;
- }
- return serializationObject;
- }
- // Statics
- /**
- * Returns a new AnimationGroup object parsed from the source provided.
- * @param parsedAnimationGroup defines the source
- * @param scene defines the scene that will receive the animationGroup
- * @returns a new AnimationGroup
- */
- static Parse(parsedAnimationGroup, scene) {
- const animationGroup = new AnimationGroup(parsedAnimationGroup.name, scene, parsedAnimationGroup.weight, parsedAnimationGroup.playOrder);
- for (let i = 0; i < parsedAnimationGroup.targetedAnimations.length; i++) {
- const targetedAnimation = parsedAnimationGroup.targetedAnimations[i];
- const animation = Animation.Parse(targetedAnimation.animation);
- const id = targetedAnimation.targetId;
- if (targetedAnimation.animation.property === "influence") {
- // morph target animation
- const morphTarget = scene.getMorphTargetById(id);
- if (morphTarget) {
- animationGroup.addTargetedAnimation(animation, morphTarget);
- }
- }
- else {
- const targetNode = scene.getNodeById(id);
- if (targetNode != null) {
- animationGroup.addTargetedAnimation(animation, targetNode);
- }
- }
- }
- if (Tags) {
- Tags.AddTagsTo(animationGroup, parsedAnimationGroup.tags);
- }
- if (parsedAnimationGroup.from !== null && parsedAnimationGroup.to !== null) {
- animationGroup.normalize(parsedAnimationGroup.from, parsedAnimationGroup.to);
- }
- if (parsedAnimationGroup.speedRatio !== undefined) {
- animationGroup._speedRatio = parsedAnimationGroup.speedRatio;
- }
- if (parsedAnimationGroup.loopAnimation !== undefined) {
- animationGroup._loopAnimation = parsedAnimationGroup.loopAnimation;
- }
- if (parsedAnimationGroup.isAdditive !== undefined) {
- animationGroup._isAdditive = parsedAnimationGroup.isAdditive;
- }
- if (parsedAnimationGroup.weight !== undefined) {
- animationGroup._weight = parsedAnimationGroup.weight;
- }
- if (parsedAnimationGroup.playOrder !== undefined) {
- animationGroup._playOrder = parsedAnimationGroup.playOrder;
- }
- if (parsedAnimationGroup.enableBlending !== undefined) {
- animationGroup._enableBlending = parsedAnimationGroup.enableBlending;
- }
- if (parsedAnimationGroup.blendingSpeed !== undefined) {
- animationGroup._blendingSpeed = parsedAnimationGroup.blendingSpeed;
- }
- if (parsedAnimationGroup.metadata !== undefined) {
- animationGroup.metadata = parsedAnimationGroup.metadata;
- }
- return animationGroup;
- }
- /** @internal */
- static MakeAnimationAdditive(sourceAnimationGroup, referenceFrameOrOptions, range, cloneOriginal = false, clonedName) {
- let options;
- if (typeof referenceFrameOrOptions === "object") {
- options = referenceFrameOrOptions;
- }
- else {
- options = {
- referenceFrame: referenceFrameOrOptions,
- range: range,
- cloneOriginalAnimationGroup: cloneOriginal,
- clonedAnimationName: clonedName,
- };
- }
- let animationGroup = sourceAnimationGroup;
- if (options.cloneOriginalAnimationGroup) {
- animationGroup = sourceAnimationGroup.clone(options.clonedAnimationGroupName || animationGroup.name);
- }
- const targetedAnimations = animationGroup.targetedAnimations;
- for (let index = 0; index < targetedAnimations.length; index++) {
- const targetedAnimation = targetedAnimations[index];
- targetedAnimation.animation = Animation.MakeAnimationAdditive(targetedAnimation.animation, options);
- }
- animationGroup.isAdditive = true;
- if (options.clipKeys) {
- // We need to recalculate the from/to frames for the animation group because some keys may have been removed
- let from = Number.MAX_VALUE;
- let to = -Number.MAX_VALUE;
- const targetedAnimations = animationGroup.targetedAnimations;
- for (let index = 0; index < targetedAnimations.length; index++) {
- const targetedAnimation = targetedAnimations[index];
- const animation = targetedAnimation.animation;
- const keys = animation.getKeys();
- if (from > keys[0].frame) {
- from = keys[0].frame;
- }
- if (to < keys[keys.length - 1].frame) {
- to = keys[keys.length - 1].frame;
- }
- }
- animationGroup._from = from;
- animationGroup._to = to;
- }
- return animationGroup;
- }
- /**
- * Creates a new animation, keeping only the keys that are inside a given key range
- * @param sourceAnimationGroup defines the animation group on which to operate
- * @param fromKey defines the lower bound of the range
- * @param toKey defines the upper bound of the range
- * @param name defines the name of the new animation group. If not provided, use the same name as animationGroup
- * @param dontCloneAnimations defines whether or not the animations should be cloned before clipping the keys. Default is false, so animations will be cloned
- * @returns a new animation group stripped from all the keys outside the given range
- */
- static ClipKeys(sourceAnimationGroup, fromKey, toKey, name, dontCloneAnimations) {
- const animationGroup = sourceAnimationGroup.clone(name || sourceAnimationGroup.name);
- return AnimationGroup.ClipKeysInPlace(animationGroup, fromKey, toKey, dontCloneAnimations);
- }
- /**
- * Updates an existing animation, keeping only the keys that are inside a given key range
- * @param animationGroup defines the animation group on which to operate
- * @param fromKey defines the lower bound of the range
- * @param toKey defines the upper bound of the range
- * @param dontCloneAnimations defines whether or not the animations should be cloned before clipping the keys. Default is false, so animations will be cloned
- * @returns the animationGroup stripped from all the keys outside the given range
- */
- static ClipKeysInPlace(animationGroup, fromKey, toKey, dontCloneAnimations) {
- return AnimationGroup.ClipInPlace(animationGroup, fromKey, toKey, dontCloneAnimations, false);
- }
- /**
- * Creates a new animation, keeping only the frames that are inside a given frame range
- * @param sourceAnimationGroup defines the animation group on which to operate
- * @param fromFrame defines the lower bound of the range
- * @param toFrame defines the upper bound of the range
- * @param name defines the name of the new animation group. If not provided, use the same name as animationGroup
- * @param dontCloneAnimations defines whether or not the animations should be cloned before clipping the frames. Default is false, so animations will be cloned
- * @returns a new animation group stripped from all the frames outside the given range
- */
- static ClipFrames(sourceAnimationGroup, fromFrame, toFrame, name, dontCloneAnimations) {
- const animationGroup = sourceAnimationGroup.clone(name || sourceAnimationGroup.name);
- return AnimationGroup.ClipFramesInPlace(animationGroup, fromFrame, toFrame, dontCloneAnimations);
- }
- /**
- * Updates an existing animation, keeping only the frames that are inside a given frame range
- * @param animationGroup defines the animation group on which to operate
- * @param fromFrame defines the lower bound of the range
- * @param toFrame defines the upper bound of the range
- * @param dontCloneAnimations defines whether or not the animations should be cloned before clipping the frames. Default is false, so animations will be cloned
- * @returns the animationGroup stripped from all the frames outside the given range
- */
- static ClipFramesInPlace(animationGroup, fromFrame, toFrame, dontCloneAnimations) {
- return AnimationGroup.ClipInPlace(animationGroup, fromFrame, toFrame, dontCloneAnimations, true);
- }
- /**
- * Updates an existing animation, keeping only the keys that are inside a given key or frame range
- * @param animationGroup defines the animation group on which to operate
- * @param start defines the lower bound of the range
- * @param end defines the upper bound of the range
- * @param dontCloneAnimations defines whether or not the animations should be cloned before clipping the keys. Default is false, so animations will be cloned
- * @param useFrame defines if the range is defined by frame numbers or key indices (default is false which means use key indices)
- * @returns the animationGroup stripped from all the keys outside the given range
- */
- static ClipInPlace(animationGroup, start, end, dontCloneAnimations, useFrame = false) {
- let from = Number.MAX_VALUE;
- let to = -Number.MAX_VALUE;
- const targetedAnimations = animationGroup.targetedAnimations;
- for (let index = 0; index < targetedAnimations.length; index++) {
- const targetedAnimation = targetedAnimations[index];
- const animation = dontCloneAnimations ? targetedAnimation.animation : targetedAnimation.animation.clone();
- if (useFrame) {
- // Make sure we have keys corresponding to the bounds of the frame range
- animation.createKeyForFrame(start);
- animation.createKeyForFrame(end);
- }
- const keys = animation.getKeys();
- const newKeys = [];
- let startFrame = Number.MAX_VALUE;
- for (let k = 0; k < keys.length; k++) {
- const key = keys[k];
- if ((!useFrame && k >= start && k <= end) || (useFrame && key.frame >= start && key.frame <= end)) {
- const newKey = {
- frame: key.frame,
- value: key.value.clone ? key.value.clone() : key.value,
- inTangent: key.inTangent,
- outTangent: key.outTangent,
- interpolation: key.interpolation,
- lockedTangent: key.lockedTangent,
- };
- if (startFrame === Number.MAX_VALUE) {
- startFrame = newKey.frame;
- }
- newKey.frame -= startFrame;
- newKeys.push(newKey);
- }
- }
- if (newKeys.length === 0) {
- targetedAnimations.splice(index, 1);
- index--;
- continue;
- }
- if (from > newKeys[0].frame) {
- from = newKeys[0].frame;
- }
- if (to < newKeys[newKeys.length - 1].frame) {
- to = newKeys[newKeys.length - 1].frame;
- }
- animation.setKeys(newKeys, true);
- targetedAnimation.animation = animation; // in case the animation has been cloned
- }
- animationGroup._from = from;
- animationGroup._to = to;
- return animationGroup;
- }
- /**
- * Returns the string "AnimationGroup"
- * @returns "AnimationGroup"
- */
- getClassName() {
- return "AnimationGroup";
- }
- /**
- * Creates a detailed string about the object
- * @param fullDetails defines if the output string will support multiple levels of logging within scene loading
- * @returns a string representing the object
- */
- toString(fullDetails) {
- let ret = "Name: " + this.name;
- ret += ", type: " + this.getClassName();
- if (fullDetails) {
- ret += ", from: " + this._from;
- ret += ", to: " + this._to;
- ret += ", isStarted: " + this._isStarted;
- ret += ", speedRatio: " + this._speedRatio;
- ret += ", targetedAnimations length: " + this._targetedAnimations.length;
- ret += ", animatables length: " + this._animatables;
- }
- return ret;
- }
- }
- //# sourceMappingURL=animationGroup.js.map
|