123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670 |
- import { Animatable } from "./animatable";
- import { Animation } from "./animation";
- import { IAnimationKey } from "./animationKey";
- import { Scene, IDisposable } from "../scene";
- import { Observable } from "../Misc/observable";
- import { Nullable } from "../types";
- import { EngineStore } from "../Engines/engineStore";
- import "./animatable";
- /**
- * This class defines the direct association between an animation and a target
- */
- export class TargetedAnimation {
- /**
- * Animation to perform
- */
- public animation: Animation;
- /**
- * Target to animate
- */
- public target: any;
- /**
- * Serialize the object
- * @returns the JSON object representing the current entity
- */
- public serialize(): any {
- var serializationObject: any = {};
- 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 implements IDisposable {
- private _scene: Scene;
- private _targetedAnimations = new Array<TargetedAnimation>();
- private _animatables = new Array<Animatable>();
- private _from = Number.MAX_VALUE;
- private _to = -Number.MAX_VALUE;
- private _isStarted: boolean;
- private _isPaused: boolean;
- private _speedRatio = 1;
- private _loopAnimation = false;
- private _isAdditive = false;
- /**
- * Gets or sets the unique id of the node
- */
- public uniqueId: number;
- /**
- * This observable will notify when one animation have ended
- */
- public onAnimationEndObservable = new Observable<TargetedAnimation>();
- /**
- * Observer raised when one animation loops
- */
- public onAnimationLoopObservable = new Observable<TargetedAnimation>();
- /**
- * Observer raised when all animations have looped
- */
- public onAnimationGroupLoopObservable = new Observable<AnimationGroup>();
- /**
- * This observable will notify when all animations have ended.
- */
- public onAnimationGroupEndObservable = new Observable<AnimationGroup>();
- /**
- * This observable will notify when all animations have paused.
- */
- public onAnimationGroupPauseObservable = new Observable<AnimationGroup>();
- /**
- * This observable will notify when all animations are playing.
- */
- public onAnimationGroupPlayObservable = new Observable<AnimationGroup>();
- /**
- * Gets the first frame
- */
- public get from(): number {
- return this._from;
- }
- /**
- * Gets the last frame
- */
- public get to(): number {
- return this._to;
- }
- /**
- * Define if the animations are started
- */
- public get isStarted(): boolean {
- return this._isStarted;
- }
- /**
- * Gets a value indicating that the current group is playing
- */
- public get isPlaying(): boolean {
- return this._isStarted && !this._isPaused;
- }
- /**
- * Gets or sets the speed ratio to use for all animations
- */
- public get speedRatio(): number {
- return this._speedRatio;
- }
- /**
- * Gets or sets the speed ratio to use for all animations
- */
- public set speedRatio(value: number) {
- if (this._speedRatio === value) {
- return;
- }
- this._speedRatio = value;
- for (var index = 0; index < this._animatables.length; index++) {
- let animatable = this._animatables[index];
- animatable.speedRatio = this._speedRatio;
- }
- }
- /**
- * Gets or sets if all animations should loop or not
- */
- public get loopAnimation(): boolean {
- return this._loopAnimation;
- }
- public set loopAnimation(value: boolean) {
- if (this._loopAnimation === value) {
- return;
- }
- this._loopAnimation = value;
- for (var index = 0; index < this._animatables.length; index++) {
- let animatable = this._animatables[index];
- animatable.loopAnimation = this._loopAnimation;
- }
- }
- /**
- * Gets or sets if all animations should be evaluated additively
- */
- public get isAdditive(): boolean {
- return this._isAdditive;
- }
- public set isAdditive(value: boolean) {
- if (this._isAdditive === value) {
- return;
- }
- this._isAdditive = value;
- for (var index = 0; index < this._animatables.length; index++) {
- let animatable = this._animatables[index];
- animatable.isAdditive = this._isAdditive;
- }
- }
- /**
- * Gets the targeted animations for this animation group
- */
- public get targetedAnimations(): Array<TargetedAnimation> {
- return this._targetedAnimations;
- }
- /**
- * returning the list of animatables controlled by this animation group.
- */
- public get animatables(): Array<Animatable> {
- return this._animatables;
- }
- /**
- * Instantiates a new Animation Group.
- * This helps managing several animations at once.
- * @see http://doc.babylonjs.com/how_to/group
- * @param name Defines the name of the group
- * @param scene Defines the scene the group belongs to
- */
- public constructor(
- /** The name of the animation group */
- public name: string,
- scene: Nullable<Scene> = null) {
- this._scene = scene || EngineStore.LastCreatedScene!;
- 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
- */
- public addTargetedAnimation(animation: Animation, target: any): TargetedAnimation {
- let targetedAnimation = new TargetedAnimation();
- targetedAnimation.animation = animation;
- targetedAnimation.target = target;
- let 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;
- }
- this._targetedAnimations.push(targetedAnimation);
- return targetedAnimation;
- }
- /**
- * 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
- */
- public normalize(beginFrame: Nullable<number> = null, endFrame: Nullable<number> = null): AnimationGroup {
- if (beginFrame == null) { beginFrame = this._from; }
- if (endFrame == null) { endFrame = this._to; }
- for (var index = 0; index < this._targetedAnimations.length; index++) {
- let targetedAnimation = this._targetedAnimations[index];
- let keys = targetedAnimation.animation.getKeys();
- let startKey = keys[0];
- let endKey = keys[keys.length - 1];
- if (startKey.frame > beginFrame) {
- let newKey: IAnimationKey = {
- frame: beginFrame,
- value: startKey.value,
- inTangent: startKey.inTangent,
- outTangent: startKey.outTangent,
- interpolation: startKey.interpolation
- };
- keys.splice(0, 0, newKey);
- }
- if (endKey.frame < endFrame) {
- let newKey: IAnimationKey = {
- frame: endFrame,
- value: endKey.value,
- inTangent: endKey.outTangent,
- outTangent: endKey.outTangent,
- interpolation: endKey.interpolation
- };
- keys.push(newKey);
- }
- }
- this._from = beginFrame;
- this._to = endFrame;
- return this;
- }
- private _animationLoopCount: number;
- private _animationLoopFlags: boolean[];
- private _processLoop(animatable: Animatable, targetedAnimation: TargetedAnimation, index: number) {
- animatable.onAnimationLoop = () => {
- this.onAnimationLoopObservable.notifyObservers(targetedAnimation);
- if (this._animationLoopFlags[index]) {
- return;
- }
- this._animationLoopFlags[index] = true;
- this._animationLoopCount++;
- if (this._animationLoopCount === this._targetedAnimations.length) {
- this.onAnimationGroupLoopObservable.notifyObservers(this);
- this._animationLoopCount = 0;
- this._animationLoopFlags = [];
- }
- };
- }
- /**
- * 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
- */
- public start(loop = false, speedRatio = 1, from?: number, to?: number, isAdditive?: boolean): AnimationGroup {
- if (this._isStarted || this._targetedAnimations.length === 0) {
- return this;
- }
- this._loopAnimation = loop;
- this._animationLoopCount = 0;
- this._animationLoopFlags = [];
- for (var index = 0; index < this._targetedAnimations.length; index++) {
- const targetedAnimation = this._targetedAnimations[index];
- let 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.onAnimationEnd = () => {
- this.onAnimationEndObservable.notifyObservers(targetedAnimation);
- this._checkAnimationGroupEnded(animatable);
- };
- this._processLoop(animatable, targetedAnimation, index);
- this._animatables.push(animatable);
- }
- this._speedRatio = speedRatio;
- if (from !== undefined && to !== undefined) {
- if (from < to && this._speedRatio < 0) {
- let temp = to;
- to = from;
- from = temp;
- } else if (from > to && this._speedRatio > 0) {
- this._speedRatio = -speedRatio;
- }
- }
- this._isStarted = true;
- this._isPaused = false;
- this.onAnimationGroupPlayObservable.notifyObservers(this);
- return this;
- }
- /**
- * Pause all animations
- * @returns the animation group
- */
- public pause(): AnimationGroup {
- if (!this._isStarted) {
- return this;
- }
- this._isPaused = true;
- for (var index = 0; index < this._animatables.length; index++) {
- let 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
- */
- public play(loop?: boolean): AnimationGroup {
- // 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
- */
- public reset(): AnimationGroup {
- if (!this._isStarted) {
- return this;
- }
- for (var index = 0; index < this._animatables.length; index++) {
- let animatable = this._animatables[index];
- animatable.reset();
- }
- return this;
- }
- /**
- * Restart animations from key 0
- * @returns the animation group
- */
- public restart(): AnimationGroup {
- if (!this._isStarted) {
- return this;
- }
- for (var index = 0; index < this._animatables.length; index++) {
- let animatable = this._animatables[index];
- animatable.restart();
- }
- this.onAnimationGroupPlayObservable.notifyObservers(this);
- return this;
- }
- /**
- * Stop all animations
- * @returns the animation group
- */
- public stop(): AnimationGroup {
- if (!this._isStarted) {
- return this;
- }
- var list = this._animatables.slice();
- for (var index = 0; index < list.length; index++) {
- list[index].stop();
- }
- this._isStarted = false;
- return this;
- }
- /**
- * Set animation weight for all animatables
- * @param weight defines the weight to use
- * @return the animationGroup
- * @see http://doc.babylonjs.com/babylon101/animations#animation-weights
- */
- public setWeightForAllAnimatables(weight: number): AnimationGroup {
- for (var index = 0; index < this._animatables.length; index++) {
- let 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
- * @return the animationGroup
- * @see http://doc.babylonjs.com/babylon101/animations#animation-weights
- */
- public syncAllAnimationsWith(root: Animatable): AnimationGroup {
- for (var index = 0; index < this._animatables.length; index++) {
- let animatable = this._animatables[index];
- animatable.syncWith(root);
- }
- return this;
- }
- /**
- * Goes to a specific frame in this animation group
- * @param frame the frame number to go to
- * @return the animationGroup
- */
- public goToFrame(frame: number): AnimationGroup {
- if (!this._isStarted) {
- return this;
- }
- for (var index = 0; index < this._animatables.length; index++) {
- let animatable = this._animatables[index];
- animatable.goToFrame(frame);
- }
- return this;
- }
- /**
- * Dispose all associated resources
- */
- public dispose(): void {
- this._targetedAnimations = [];
- this._animatables = [];
- var index = this._scene.animationGroups.indexOf(this);
- if (index > -1) {
- this._scene.animationGroups.splice(index, 1);
- }
- this.onAnimationEndObservable.clear();
- this.onAnimationGroupEndObservable.clear();
- this.onAnimationGroupPauseObservable.clear();
- this.onAnimationGroupPlayObservable.clear();
- this.onAnimationLoopObservable.clear();
- this.onAnimationGroupLoopObservable.clear();
- }
- private _checkAnimationGroupEnded(animatable: Animatable) {
- // animatable should be taken out of the array
- let 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
- * @returns the new aniamtion group
- */
- public clone(newName: string, targetConverter?: (oldTarget: any) => any): AnimationGroup {
- let newGroup = new AnimationGroup(newName || this.name, this._scene);
- for (var targetAnimation of this._targetedAnimations) {
- newGroup.addTargetedAnimation(targetAnimation.animation.clone(), targetConverter ? targetConverter(targetAnimation.target) : targetAnimation.target);
- }
- return newGroup;
- }
- /**
- * Serializes the animationGroup to an object
- * @returns Serialized object
- */
- public serialize(): any {
- var serializationObject: any = {};
- serializationObject.name = this.name;
- serializationObject.from = this.from;
- serializationObject.to = this.to;
- serializationObject.targetedAnimations = [];
- for (var targetedAnimationIndex = 0; targetedAnimationIndex < this.targetedAnimations.length; targetedAnimationIndex++) {
- var targetedAnimation = this.targetedAnimations[targetedAnimationIndex];
- serializationObject.targetedAnimations[targetedAnimationIndex] = targetedAnimation.serialize();
- }
- 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
- */
- public static Parse(parsedAnimationGroup: any, scene: Scene): AnimationGroup {
- var animationGroup = new AnimationGroup(parsedAnimationGroup.name, scene);
- for (var i = 0; i < parsedAnimationGroup.targetedAnimations.length; i++) {
- var targetedAnimation = parsedAnimationGroup.targetedAnimations[i];
- var animation = Animation.Parse(targetedAnimation.animation);
- var id = targetedAnimation.targetId;
- if (targetedAnimation.animation.property === "influence") { // morph target animation
- let morphTarget = scene.getMorphTargetById(id);
- if (morphTarget) {
- animationGroup.addTargetedAnimation(animation, morphTarget);
- }
- }
- else {
- var targetNode = scene.getNodeByID(id);
- if (targetNode != null) {
- animationGroup.addTargetedAnimation(animation, targetNode);
- }
- }
- }
- if (parsedAnimationGroup.from !== null && parsedAnimationGroup.to !== null) {
- animationGroup.normalize(parsedAnimationGroup.from, parsedAnimationGroup.to);
- }
- return animationGroup;
- }
- /**
- * Convert the keyframes for all animations belonging to the group to be relative to a given reference frame.
- * @param sourceAnimationGroup defines the AnimationGroup containing animations to convert
- * @param referenceFrame defines the frame that keyframes in the range will be relative to
- * @param range defines the name of the AnimationRange belonging to the animations in the group to convert
- * @param cloneOriginal defines whether or not to clone the group and convert the clone or convert the original group (default is false)
- * @param clonedName defines the name of the resulting cloned AnimationGroup if cloneOriginal is true
- * @returns a new AnimationGroup if cloneOriginal is true or the original AnimationGroup if cloneOriginal is false
- */
- public static MakeAnimationAdditive(sourceAnimationGroup: AnimationGroup, referenceFrame = 0, range?: string, cloneOriginal = false, clonedName?: string): AnimationGroup {
- let animationGroup = sourceAnimationGroup;
- if (cloneOriginal) {
- animationGroup = sourceAnimationGroup.clone(clonedName || animationGroup.name);
- }
- let targetedAnimations = animationGroup.targetedAnimations;
- for (var index = 0; index < targetedAnimations.length; index++) {
- let targetedAnimation = targetedAnimations[index];
- Animation.MakeAnimationAdditive(targetedAnimation.animation, referenceFrame, range);
- }
- animationGroup.isAdditive = true;
- return animationGroup;
- }
- /**
- * Returns the string "AnimationGroup"
- * @returns "AnimationGroup"
- */
- public getClassName(): string {
- return "AnimationGroup";
- }
- /**
- * Creates a detailled 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
- */
- public toString(fullDetails?: boolean): string {
- var 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;
- }
- }
|