/**
* Provide timeline playback of movieclip
* @memberof PIXI.animate
* @class Tween
* @constructor
* @param {PIXI.animate.MovieClip} target The target to play
* @param {Object} startProps The starting properties
* @param {Object} endProps The ending properties
* @param {int} startFrame frame number on which to begin tweening
* @param {int} duration Number of frames to tween
* @param {Function} [ease] Ease function to use
*/
class Tween {
constructor(target, startProps, endProps, startFrame, duration, ease) {
/**
* target display object
* @name PIXI.animate.Tween#target
* @type {Object}
*/
this.target = target;
/**
* properties at the start of the tween
* @type {Object}
* @name PIXI.animate.Tween#startProps
*/
this.startProps = startProps;
/**
* properties at the end of the tween, as well as any properties that are set
* instead of tweened
* @type {Object}
* @name PIXI.animate.Tween#endProps
*/
this.endProps = {};
/**
* duration of tween in frames. For a keyframe with no tweening, the duration will be 0.
* @type {int}
* @name PIXI.animate.Tween#duration
*/
this.duration = duration;
/**
* The frame that the tween starts on
* @type {int}
* @name PIXI.animate.Tween#startFrame
*/
this.startFrame = startFrame;
/**
* the frame that the tween ends on
* @type {int}
* @name PIXI.animate.Tween#endFrame
*/
this.endFrame = startFrame + duration;
/**
* easing function to use, if any
* @type {Function}
* @name PIXI.animate.Tween#ease
*/
this.ease = ease;
/**
* If we don't tween.
* @type {Boolean}
* @name PIXI.animate.Tween#isTweenlessFrame
*/
this.isTweenlessFrame = !endProps;
let prop;
if (endProps) {
//make a copy to safely include any unchanged values from the start of the tween
for (prop in endProps) {
this.endProps[prop] = endProps[prop];
}
}
//copy in any starting properties don't change
for (prop in startProps) {
if (!this.endProps.hasOwnProperty(prop)) {
this.endProps[prop] = startProps[prop];
}
}
}
/**
* Set the current frame.
* @method PIXI.animate.Tween#setPosition
* @param {int} currentFrame
*/
setPosition(currentFrame) {
//if this is a single frame with no tweening, or at the end of the tween, then
//just speed up the process by setting values
if (currentFrame >= this.endFrame) {
this.setToEnd();
return;
}
if (this.isTweenlessFrame) {
this.setToEnd();
return;
}
let time = (currentFrame - this.startFrame) / this.duration;
if (this.ease) {
time = this.ease(time);
}
let target = this.target;
let startProps = this.startProps;
let endProps = this.endProps;
for (let prop in endProps) {
let lerp = props[prop];
if (lerp) {
setPropFromShorthand(target, prop, lerp(startProps[prop], endProps[prop], time));
} else {
setPropFromShorthand(target, prop, startProps[prop]);
}
}
}
/**
* Set to the end position
* @method PIXI.animate.Tween#setToEnd
*/
setToEnd() {
let endProps = this.endProps;
let target = this.target;
for (let prop in endProps) {
setPropFromShorthand(target, prop, endProps[prop]);
}
}
}
//standard tweening
function lerpValue(start, end, t) {
return start + (end - start) * t;
}
const props = {
//position
x: lerpValue,
y: lerpValue,
//scale
sx: lerpValue,
sy: lerpValue,
//skew
kx: lerpValue,
ky: lerpValue,
//rotation
r: lerpRotation,
//alpha
a: lerpValue,
//tinting
// t: lerpColor,
t: null,
//values to be set
v: null, //visible
c: null, //colorTransform
m: null, //mask
g: null //not sure if we'll actually handle graphics this way?
};
//split r, g, b into separate values for tweening
/*function lerpColor(start, end, t)
{
//split start color into components
let sR = start >> 16 & 0xFF;
let sG = start >> 8 & 0xFF;
let sB = start & 0xFF;
//split end color into components
let eR = end >> 16 & 0xFF;
let eG = end >> 8 & 0xFF;
let eB = end & 0xFF;
//lerp red
let r = sR + (eR - sR) * percent;
//clamp red to valid values
if (r < 0)
r = 0;
else if (r > 255)
r = 255;
//lerp green
let g = sG + (eG - sG) * percent;
//clamp green to valid values
if (g < 0)
g = 0;
else if (g > 255)
g = 255;
//lerp blue
let b = sB + (eB - sB) * percent;
//clamp blue to valid values
if (b < 0)
b = 0;
else if (b > 255)
b = 255;
let combined = (r << 16) | (g << 8) | b;
return combined;
}*/
const PI = Math.PI;
const TWO_PI = PI * 2;
//handle 355 -> 5 degrees only going through a 10 degree change instead of
//the long way around
//Math from http://stackoverflow.com/a/2708740
function lerpRotation(start, end, t) {
let difference = Math.abs(end - start);
if (difference > PI) {
// We need to add on to one of the values.
if (end > start) {
// We'll add it on to start...
start += TWO_PI;
} else {
// Add it on to end.
end += PI + TWO_PI;
}
}
// Interpolate it.
let value = (start + ((end - start) * t));
// wrap to 0-2PI
/*if (value >= 0 && value <= TWO_PI)
return value;
return value % TWO_PI;*/
//just return, as it's faster
return value;
}
function setPropFromShorthand(target, prop, value) {
switch (prop) {
case "x":
target.transform.position.x = value;
break;
case "y":
target.transform.position.y = value;
break;
case "sx":
target.transform.scale.x = value;
break;
case "sy":
target.transform.scale.y = value;
break;
case "kx":
target.transform.skew.x = value;
break;
case "ky":
target.transform.skew.y = value;
break;
case "r":
target.transform.rotation = value;
break;
case "a":
target.alpha = value;
break;
case "t":
target.i(value); // i = setTint
break;
case "c":
target.c.apply(target, value); // c = setColorTransform
break;
case "v":
target.visible = value;
break;
case "m":
target.ma(value); // ma = setMask
break;
}
}
// Assign to namespace
export default Tween;