|
@@ -92,22 +92,16 @@
|
|
public static AllowMatricesInterpolation = false;
|
|
public static AllowMatricesInterpolation = false;
|
|
|
|
|
|
private _keys: Array<{frame:number, value: any, inTangent?: any, outTangent?: any}>;
|
|
private _keys: Array<{frame:number, value: any, inTangent?: any, outTangent?: any}>;
|
|
- private _offsetsCache = {};
|
|
|
|
- private _highLimitsCache = {};
|
|
|
|
- private _stopped = false;
|
|
|
|
- public _target;
|
|
|
|
- private _blendingFactor = 0;
|
|
|
|
private _easingFunction: IEasingFunction;
|
|
private _easingFunction: IEasingFunction;
|
|
|
|
|
|
|
|
+ public _runtimeAnimations = new Array<RuntimeAnimation>();
|
|
|
|
+
|
|
// The set of event that will be linked to this animation
|
|
// The set of event that will be linked to this animation
|
|
private _events = new Array<AnimationEvent>();
|
|
private _events = new Array<AnimationEvent>();
|
|
|
|
|
|
public targetPropertyPath: string[];
|
|
public targetPropertyPath: string[];
|
|
- public currentFrame: number;
|
|
|
|
-
|
|
|
|
|
|
|
|
public blendingSpeed = 0.01;
|
|
public blendingSpeed = 0.01;
|
|
- private _originalBlendValue: any;
|
|
|
|
|
|
|
|
private _ranges: { [name: string]: AnimationRange; } = {};
|
|
private _ranges: { [name: string]: AnimationRange; } = {};
|
|
|
|
|
|
@@ -225,7 +219,24 @@
|
|
var animation: BABYLON.Animatable = scene.beginAnimation(host, 0, endFrame, false);
|
|
var animation: BABYLON.Animatable = scene.beginAnimation(host, 0, endFrame, false);
|
|
animation.onAnimationEnd = onAnimationEnd;
|
|
animation.onAnimationEnd = onAnimationEnd;
|
|
return animation;
|
|
return animation;
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Return the array of runtime animations currently using this animation
|
|
|
|
+ */
|
|
|
|
+ public get runtimeAnimations(): RuntimeAnimation[] {
|
|
|
|
+ return this._runtimeAnimations;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public get hasRunningRuntimeAnimations(): boolean {
|
|
|
|
+ for (var runtimeAnimation of this._runtimeAnimations) {
|
|
|
|
+ if (!runtimeAnimation.isStopped) {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
|
|
constructor(public name: string, public targetProperty: string, public framePerSecond: number, public dataType: number, public loopMode?: number, public enableBlending?: boolean) {
|
|
constructor(public name: string, public targetProperty: string, public framePerSecond: number, public dataType: number, public loopMode?: number, public enableBlending?: boolean) {
|
|
this.targetPropertyPath = targetProperty.split(".");
|
|
this.targetPropertyPath = targetProperty.split(".");
|
|
@@ -277,6 +288,10 @@
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ public getEvents(): AnimationEvent[] {
|
|
|
|
+ return this._events;
|
|
|
|
+ }
|
|
|
|
+
|
|
public createRange(name: string, from: number, to: number): void {
|
|
public createRange(name: string, from: number, to: number): void {
|
|
// check name not already in use; could happen for bones after serialized
|
|
// check name not already in use; could happen for bones after serialized
|
|
if (!this._ranges[name]) {
|
|
if (!this._ranges[name]) {
|
|
@@ -305,19 +320,8 @@
|
|
return this._ranges[name];
|
|
return this._ranges[name];
|
|
}
|
|
}
|
|
|
|
|
|
- public reset(): void {
|
|
|
|
- this._offsetsCache = {};
|
|
|
|
- this._highLimitsCache = {};
|
|
|
|
- this.currentFrame = 0;
|
|
|
|
- this._blendingFactor = 0;
|
|
|
|
- this._originalBlendValue = null;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public isStopped(): boolean {
|
|
|
|
- return this._stopped;
|
|
|
|
- }
|
|
|
|
|
|
|
|
- public getKeys(): Array<{ frame: number, value: any }> {
|
|
|
|
|
|
+ public getKeys(): Array<{frame:number, value: any, inTangent?: any, outTangent?: any}> {
|
|
return this._keys;
|
|
return this._keys;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -406,341 +410,6 @@
|
|
|
|
|
|
public setKeys(values: Array<{ frame: number, value: any }>): void {
|
|
public setKeys(values: Array<{ frame: number, value: any }>): void {
|
|
this._keys = values.slice(0);
|
|
this._keys = values.slice(0);
|
|
- this._offsetsCache = {};
|
|
|
|
- this._highLimitsCache = {};
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private _getKeyValue(value: any): any {
|
|
|
|
- if (typeof value === "function") {
|
|
|
|
- return value();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return value;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private _interpolate(currentFrame: number, repeatCount: number, loopMode: number, offsetValue?, highLimitValue?) {
|
|
|
|
- if (loopMode === Animation.ANIMATIONLOOPMODE_CONSTANT && repeatCount > 0) {
|
|
|
|
- return highLimitValue.clone ? highLimitValue.clone() : highLimitValue;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- this.currentFrame = currentFrame;
|
|
|
|
-
|
|
|
|
- // Try to get a hash to find the right key
|
|
|
|
- var startKeyIndex = Math.max(0, Math.min(this._keys.length - 1, Math.floor(this._keys.length * (currentFrame - this._keys[0].frame) / (this._keys[this._keys.length - 1].frame - this._keys[0].frame)) - 1));
|
|
|
|
-
|
|
|
|
- if (this._keys[startKeyIndex].frame >= currentFrame) {
|
|
|
|
- while (startKeyIndex - 1 >= 0 && this._keys[startKeyIndex].frame >= currentFrame) {
|
|
|
|
- startKeyIndex--;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- for (var key = startKeyIndex; key < this._keys.length; key++) {
|
|
|
|
- var endKey = this._keys[key + 1];
|
|
|
|
-
|
|
|
|
- if (endKey.frame >= currentFrame) {
|
|
|
|
-
|
|
|
|
- var startKey = this._keys[key];
|
|
|
|
- var startValue = this._getKeyValue(startKey.value);
|
|
|
|
- var endValue = this._getKeyValue(endKey.value);
|
|
|
|
-
|
|
|
|
- var useTangent = startKey.outTangent !== undefined && endKey.inTangent !== undefined;
|
|
|
|
- var frameDelta = endKey.frame - startKey.frame;
|
|
|
|
-
|
|
|
|
- // gradient : percent of currentFrame between the frame inf and the frame sup
|
|
|
|
- var gradient = (currentFrame - startKey.frame) / frameDelta;
|
|
|
|
-
|
|
|
|
- // check for easingFunction and correction of gradient
|
|
|
|
- if (this._easingFunction != null) {
|
|
|
|
- gradient = this._easingFunction.ease(gradient);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- switch (this.dataType) {
|
|
|
|
- // Float
|
|
|
|
- case Animation.ANIMATIONTYPE_FLOAT:
|
|
|
|
- var floatValue = useTangent ? this.floatInterpolateFunctionWithTangents(startValue, startKey.outTangent * frameDelta, endValue, endKey.inTangent * frameDelta, gradient) : this.floatInterpolateFunction(startValue, endValue, gradient);
|
|
|
|
- switch (loopMode) {
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_CYCLE:
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_CONSTANT:
|
|
|
|
- return floatValue;
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_RELATIVE:
|
|
|
|
- return offsetValue * repeatCount + floatValue;
|
|
|
|
- }
|
|
|
|
- break;
|
|
|
|
- // Quaternion
|
|
|
|
- case Animation.ANIMATIONTYPE_QUATERNION:
|
|
|
|
- var quatValue = useTangent ? this.quaternionInterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient) : this.quaternionInterpolateFunction(startValue, endValue, gradient);
|
|
|
|
- switch (loopMode) {
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_CYCLE:
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_CONSTANT:
|
|
|
|
- return quatValue;
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_RELATIVE:
|
|
|
|
- return quatValue.add(offsetValue.scale(repeatCount));
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return quatValue;
|
|
|
|
- // Vector3
|
|
|
|
- case Animation.ANIMATIONTYPE_VECTOR3:
|
|
|
|
- var vec3Value = useTangent ? this.vector3InterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient) : this.vector3InterpolateFunction(startValue, endValue, gradient);
|
|
|
|
- switch (loopMode) {
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_CYCLE:
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_CONSTANT:
|
|
|
|
- return vec3Value;
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_RELATIVE:
|
|
|
|
- return vec3Value.add(offsetValue.scale(repeatCount));
|
|
|
|
- }
|
|
|
|
- // Vector2
|
|
|
|
- case Animation.ANIMATIONTYPE_VECTOR2:
|
|
|
|
- var vec2Value = useTangent ? this.vector2InterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient) : this.vector2InterpolateFunction(startValue, endValue, gradient);
|
|
|
|
- switch (loopMode) {
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_CYCLE:
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_CONSTANT:
|
|
|
|
- return vec2Value;
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_RELATIVE:
|
|
|
|
- return vec2Value.add(offsetValue.scale(repeatCount));
|
|
|
|
- }
|
|
|
|
- // Size
|
|
|
|
- case Animation.ANIMATIONTYPE_SIZE:
|
|
|
|
- switch (loopMode) {
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_CYCLE:
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_CONSTANT:
|
|
|
|
- return this.sizeInterpolateFunction(startValue, endValue, gradient);
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_RELATIVE:
|
|
|
|
- return this.sizeInterpolateFunction(startValue, endValue, gradient).add(offsetValue.scale(repeatCount));
|
|
|
|
- }
|
|
|
|
- // Color3
|
|
|
|
- case Animation.ANIMATIONTYPE_COLOR3:
|
|
|
|
- switch (loopMode) {
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_CYCLE:
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_CONSTANT:
|
|
|
|
- return this.color3InterpolateFunction(startValue, endValue, gradient);
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_RELATIVE:
|
|
|
|
- return this.color3InterpolateFunction(startValue, endValue, gradient).add(offsetValue.scale(repeatCount));
|
|
|
|
- }
|
|
|
|
- // Matrix
|
|
|
|
- case Animation.ANIMATIONTYPE_MATRIX:
|
|
|
|
- switch (loopMode) {
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_CYCLE:
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_CONSTANT:
|
|
|
|
- if (Animation.AllowMatricesInterpolation) {
|
|
|
|
- return this.matrixInterpolateFunction(startValue, endValue, gradient);
|
|
|
|
- }
|
|
|
|
- case Animation.ANIMATIONLOOPMODE_RELATIVE:
|
|
|
|
- return startValue;
|
|
|
|
- }
|
|
|
|
- default:
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- return this._getKeyValue(this._keys[this._keys.length - 1].value);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public setValue(currentValue: any, blend: boolean = false): void {
|
|
|
|
- // Set value
|
|
|
|
- var path: any;
|
|
|
|
- var destination: any;
|
|
|
|
-
|
|
|
|
- if (this.targetPropertyPath.length > 1) {
|
|
|
|
- var property = this._target[this.targetPropertyPath[0]];
|
|
|
|
-
|
|
|
|
- for (var index = 1; index < this.targetPropertyPath.length - 1; index++) {
|
|
|
|
- property = property[this.targetPropertyPath[index]];
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- path = this.targetPropertyPath[this.targetPropertyPath.length - 1];
|
|
|
|
- destination = property;
|
|
|
|
- } else {
|
|
|
|
- path = this.targetPropertyPath[0];
|
|
|
|
- destination = this._target;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Blending
|
|
|
|
- if (this.enableBlending && this._blendingFactor <= 1.0) {
|
|
|
|
- if (!this._originalBlendValue) {
|
|
|
|
- if (destination[path].clone) {
|
|
|
|
- this._originalBlendValue = destination[path].clone();
|
|
|
|
- } else {
|
|
|
|
- this._originalBlendValue = destination[path];
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (this._originalBlendValue.prototype) { // Complex value
|
|
|
|
-
|
|
|
|
- if (this._originalBlendValue.prototype.Lerp) { // Lerp supported
|
|
|
|
- destination[path] = this._originalBlendValue.construtor.prototype.Lerp(currentValue, this._originalBlendValue, this._blendingFactor);
|
|
|
|
- } else { // Blending not supported
|
|
|
|
- destination[path] = currentValue;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- } else if (this._originalBlendValue.m) { // Matrix
|
|
|
|
- destination[path] = Matrix.Lerp(this._originalBlendValue, currentValue, this._blendingFactor);
|
|
|
|
- } else { // Direct value
|
|
|
|
- destination[path] = this._originalBlendValue * (1.0 - this._blendingFactor) + this._blendingFactor * currentValue;
|
|
|
|
- }
|
|
|
|
- this._blendingFactor += this.blendingSpeed;
|
|
|
|
- } else {
|
|
|
|
- destination[path] = currentValue;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (this._target.markAsDirty) {
|
|
|
|
- this._target.markAsDirty(this.targetProperty);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public goToFrame(frame: number): void {
|
|
|
|
- if (frame < this._keys[0].frame) {
|
|
|
|
- frame = this._keys[0].frame;
|
|
|
|
- } else if (frame > this._keys[this._keys.length - 1].frame) {
|
|
|
|
- frame = this._keys[this._keys.length - 1].frame;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- var currentValue = this._interpolate(frame, 0, this.loopMode);
|
|
|
|
-
|
|
|
|
- this.setValue(currentValue);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public animate(delay: number, from: number, to: number, loop: boolean, speedRatio: number, blend: boolean = false): boolean {
|
|
|
|
- if (!this.targetPropertyPath || this.targetPropertyPath.length < 1) {
|
|
|
|
- this._stopped = true;
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- var returnValue = true;
|
|
|
|
-
|
|
|
|
- // Adding a start key at frame 0 if missing
|
|
|
|
- if (this._keys[0].frame !== 0) {
|
|
|
|
- var newKey = { frame: 0, value: this._keys[0].value };
|
|
|
|
- this._keys.splice(0, 0, newKey);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Check limits
|
|
|
|
- if (from < this._keys[0].frame || from > this._keys[this._keys.length - 1].frame) {
|
|
|
|
- from = this._keys[0].frame;
|
|
|
|
- }
|
|
|
|
- if (to < this._keys[0].frame || to > this._keys[this._keys.length - 1].frame) {
|
|
|
|
- to = this._keys[this._keys.length - 1].frame;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- //to and from cannot be the same key
|
|
|
|
- if(from === to) {
|
|
|
|
- from++;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Compute ratio
|
|
|
|
- var range = to - from;
|
|
|
|
- var offsetValue;
|
|
|
|
- // ratio represents the frame delta between from and to
|
|
|
|
- var ratio = delay * (this.framePerSecond * speedRatio) / 1000.0;
|
|
|
|
- var highLimitValue = 0;
|
|
|
|
-
|
|
|
|
- if (((to > from && ratio > range) || (from > to && ratio < range)) && !loop) { // If we are out of range and not looping get back to caller
|
|
|
|
- returnValue = false;
|
|
|
|
- highLimitValue = this._getKeyValue(this._keys[this._keys.length - 1].value);
|
|
|
|
- } else {
|
|
|
|
- // Get max value if required
|
|
|
|
-
|
|
|
|
- if (this.loopMode !== Animation.ANIMATIONLOOPMODE_CYCLE) {
|
|
|
|
-
|
|
|
|
- var keyOffset = to.toString() + from.toString();
|
|
|
|
- if (!this._offsetsCache[keyOffset]) {
|
|
|
|
- var fromValue = this._interpolate(from, 0, Animation.ANIMATIONLOOPMODE_CYCLE);
|
|
|
|
- var toValue = this._interpolate(to, 0, Animation.ANIMATIONLOOPMODE_CYCLE);
|
|
|
|
- switch (this.dataType) {
|
|
|
|
- // Float
|
|
|
|
- case Animation.ANIMATIONTYPE_FLOAT:
|
|
|
|
- this._offsetsCache[keyOffset] = toValue - fromValue;
|
|
|
|
- break;
|
|
|
|
- // Quaternion
|
|
|
|
- case Animation.ANIMATIONTYPE_QUATERNION:
|
|
|
|
- this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
|
|
|
|
- break;
|
|
|
|
- // Vector3
|
|
|
|
- case Animation.ANIMATIONTYPE_VECTOR3:
|
|
|
|
- this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
|
|
|
|
- // Vector2
|
|
|
|
- case Animation.ANIMATIONTYPE_VECTOR2:
|
|
|
|
- this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
|
|
|
|
- // Size
|
|
|
|
- case Animation.ANIMATIONTYPE_SIZE:
|
|
|
|
- this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
|
|
|
|
- // Color3
|
|
|
|
- case Animation.ANIMATIONTYPE_COLOR3:
|
|
|
|
- this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
|
|
|
|
- default:
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- this._highLimitsCache[keyOffset] = toValue;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- highLimitValue = this._highLimitsCache[keyOffset];
|
|
|
|
- offsetValue = this._offsetsCache[keyOffset];
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (offsetValue === undefined) {
|
|
|
|
- switch (this.dataType) {
|
|
|
|
- // Float
|
|
|
|
- case Animation.ANIMATIONTYPE_FLOAT:
|
|
|
|
- offsetValue = 0;
|
|
|
|
- break;
|
|
|
|
- // Quaternion
|
|
|
|
- case Animation.ANIMATIONTYPE_QUATERNION:
|
|
|
|
- offsetValue = new Quaternion(0, 0, 0, 0);
|
|
|
|
- break;
|
|
|
|
- // Vector3
|
|
|
|
- case Animation.ANIMATIONTYPE_VECTOR3:
|
|
|
|
- offsetValue = Vector3.Zero();
|
|
|
|
- break;
|
|
|
|
- // Vector2
|
|
|
|
- case Animation.ANIMATIONTYPE_VECTOR2:
|
|
|
|
- offsetValue = Vector2.Zero();
|
|
|
|
- break;
|
|
|
|
- // Size
|
|
|
|
- case Animation.ANIMATIONTYPE_SIZE:
|
|
|
|
- offsetValue = Size.Zero();
|
|
|
|
- break;
|
|
|
|
- // Color3
|
|
|
|
- case Animation.ANIMATIONTYPE_COLOR3:
|
|
|
|
- offsetValue = Color3.Black();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Compute value
|
|
|
|
- var repeatCount = (ratio / range) >> 0;
|
|
|
|
- var currentFrame = returnValue ? from + ratio % range : to;
|
|
|
|
- var currentValue = this._interpolate(currentFrame, repeatCount, this.loopMode, offsetValue, highLimitValue);
|
|
|
|
-
|
|
|
|
- // Set value
|
|
|
|
- this.setValue(currentValue);
|
|
|
|
- // Check events
|
|
|
|
- for (var index = 0; index < this._events.length; index++) {
|
|
|
|
- // Make sure current frame has passed event frame and that event frame is within the current range
|
|
|
|
- // Also, handle both forward and reverse animations
|
|
|
|
- if (
|
|
|
|
- (range > 0 && currentFrame >= this._events[index].frame && this._events[index].frame >= from) ||
|
|
|
|
- (range < 0 && currentFrame <= this._events[index].frame && this._events[index].frame <= from)
|
|
|
|
- ){
|
|
|
|
- var event = this._events[index];
|
|
|
|
- if (!event.isDone) {
|
|
|
|
- // If event should be done only once, remove it.
|
|
|
|
- if (event.onlyOnce) {
|
|
|
|
- this._events.splice(index, 1);
|
|
|
|
- index--;
|
|
|
|
- }
|
|
|
|
- event.isDone = true;
|
|
|
|
- event.action();
|
|
|
|
- } // Don't do anything if the event has already be done.
|
|
|
|
- } else if (this._events[index].isDone && !this._events[index].onlyOnce) {
|
|
|
|
- // reset event, the animation is looping
|
|
|
|
- this._events[index].isDone = false;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- if (!returnValue) {
|
|
|
|
- this._stopped = true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return returnValue;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
public serialize(): any {
|
|
public serialize(): any {
|