/**Modified to adapting to ECS.
* Source from:
* Tween.js - Licensed under the MIT license
* https://github.com/tweenjs/tween.js
*
*/
import * as ECS from '../../../packages/ecs-js/index';
import {XError} from '../../xutils/xcommon';
import {mat4} from '../../xmath/vec';
/**Exception for XTweener
*
* @class XTweenException
*/
function XTweenException(message) {
this.message = message;
this.name = 'XTweenException';
}
const iffTween = {any: ['CmpTween', 'CmpTweens']}
/**X-visual tween driving system.
* @class XTweener
*/
export default class XTweener extends ECS.System {
/**Initialize all tweens.
* @param {ECS} ecs
* @param {object} x singleton
* @param {object} startingTrigged triggered buffer (Animizer found tweens should started)
* @constructor XTweener
*/
constructor (ecs, x, startingTrigged) {
super(ecs);
console.log('XTweener v2 ...');
this.ecs = ecs;
const entities = ecs.queryEntities( iffTween );
this.initTweens( ecs, entities || new Array() );
this.resolvingStarts = startingTrigged || new Object();
}
/**
* @param {ECS} ecs
* @param {Set} entites
* @member XTweener#initTweens
* @function */
initTweens (ecs, entities) {
for (const e of entities) {
if (e.CmpTween) {
XTweener.initween(e.CmpTween);
}
if (e.CmpTweens && e.CmpTweens.tweens) {
if (XTweener.validate(e)) {
for (const ctwn of e.CmpTweens.tweens) {
XTweener.initween(ctwn);
}
}
}
}
}
/** Validating the entity can be animized
* @param {ECS.Entity} entity
* @return {bool} ok or not
* @member XTweener.validate
* @function
*/
static validate (entity) {
if (entity.ModelSeqs && (entity.CmpTweens.tweens === undefined
|| entity.CmpTweens.tweens.length === 0)) {
console.warn(
'CmpTweens shouldn\'t be null if there are scripts to be animized: ',
entity.ModelSeqs, entity.CmpTweens,
'Tip: Make sure XTweener is created/updating after animizer like MorphingAnim.');
return false;
}
return true;
}
/**
* @param {array|component} cmp
* @member XTweener.initween
* @function */
static initween (cmp) {
if (Array.isArray(cmp)) {
for (const compt of cmp)
XTweener.initween(compt);
}
else {
if (!cmp.easingFunction)
cmp.easingFunction = TWEEN.Easing.Linear.None;
if (!cmp.interpolationFunction)
cmp.interpolationFunction = TWEEN.Interpolation.Linear;
}
}
getAll () {
throw new XTweenException('Why asking XTweener.TWEEN to getAll?');
return Object.keys(this._tweens).map(function (tweenId) {
return this._tweens[tweenId];
}.bind(this));
}
removeAll () {
throw new XTweenException('Why asking XTweener.TWEEN to removeAll?');
this._tweens = new Object();
}
add (tween) {
throw new XTweenException('Why asking XTweener.TWEEN to add component?');
this._tweens[tween.getId()] = tween;
this._tweensAddedDuringUpdate[tween.getId()] = tween;
}
remove (tween) {
throw new XTweenException('Why asking XTweener.TWEEN to remove component?');
delete this._tweens[tween.getId()];
delete this._tweensAddedDuringUpdate[tween.getId()];
}
/**
* @param {int} tick
* @param {array<Entity>} entities
* @member XTweener#update
* @function
*/
update (tick, entities) {
var time = TWEEN.now();
for (const e of entities) {
// driving shader tween
if (e.Obj3 && e.Obj3.mesh && e.Obj3.mesh.material
&& e.Obj3.mesh.material.uniforms && e.Obj3.mesh.material.uniforms.now ) {
e.Obj3.mesh.material.uniforms.now.value = time;
}
if (e.CmpTweens && e.CmpTweens.tweens) {
if (e.CmpTweens.startCmds.length > 0) {
for (var cmdx of e.CmpTweens.startCmds) {
if (0 <= cmdx && cmdx < e.CmpTweens.tweens.length) {
var twSeq = e.CmpTweens.tweens[cmdx];
// debug note: important!
// don't let it set startTime after "now" - won't start
XTweener.startween(twSeq[0], 0);
e.CmpTweens.twindx[cmdx] = 0;
XTweener.pushTriggerings(TriggerEvent.STARTWITH, TWEEN.now(),
twSeq[0],
this.resolvingStarts, twSeq[0].startWith);
}
}
e.CmpTweens.startCmds.splice(0, e.CmpTweens.startCmds.length);
}
// for the entity, resolve startings triggered by Animizer startWith or followBy
var beingStartwiths = this.resolvingStarts[e.id];
var dirty = false;
for (var seqx = 0; seqx < e.CmpTweens.twindx.length; seqx ++) {
var twnx = e.CmpTweens.twindx[seqx];
if (twnx >= e.CmpTweens.tweens[seqx].length) {
// FIXME we need optimize this (with CmpTweens.isPlaying?)
}
else {
const tween = e.CmpTweens.tweens[seqx][twnx];
if (tween && tween.isPlaying) {
dirty = true;
tween.isPlaying = XTweener.updateTween(tween, time, this.resolvingStarts);
if (tween.appChildUniform && e.Obj3 && e.Obj3.mesh && e.Obj3.mesh.children) {
updateMeshes(tween, tween.appChildUniform, e.Obj3.mesh.children);
}
}
if (tween && tween.isCompleted) {
// tween finished, start next
e.CmpTweens.twindx[seqx] = twnx + 1;
if (twnx + 1 < e.CmpTweens.tweens[seqx].length
&& e.CmpTweens.tweens[seqx][twnx + 1]) {
const nxtween = e.CmpTweens.tweens[seqx][twnx + 1];
XTweener.startween(nxtween);
}
else if (twnx + 1 >= e.CmpTweens.tweens[seqx].length) {
// sequence finished
e.CmpTweens.endingFiring[seqx] = true;
}
else {
console.warn("it's subtle to debug if this branch reached",
seqx, twnx, e.CmpTweens);
}
}
}
if (beingStartwiths) {
XTweener.startTriggered(e.CmpTweens, beingStartwiths, this.resolvingStarts);
}
}
if (dirty) {
// some one playing
if (e.CmpTweens.idle) // idle -> playing
e.CmpTweens.playRising = true;
else
e.CmpTweens.playRising = false;
e.CmpTweens.idle = false;
}
else {
// nothing happened
if (!e.CmpTweens.idle) // playing -> idle
e.CmpTweens.idleRising = true;
else
e.CmpTweens.idleRising = false;
e.CmpTweens.idle = true;
}
// this.resolvingStarts[e.id] a.k.a beingStartwiths
if (this.resolvingStarts[e.id] && this.resolvingStarts[e.id].length === 0)
delete this.resolvingStarts[e.id];
}
}
function updateMeshes(cmp, props, meshes) {
if (!props) return;
else if (typeof props === 'string') {
props = [props];
}
for (var prop of props) {
var v = cmp.object[prop];
for (var mesh of meshes) {
if (mesh.material && mesh.material.uniforms)
mesh.material.uniforms[prop] = v;
if (mesh.children)
updateMeshes(cmp, props, mesh.children);
}
}
}
}
/**Update a Tween. Modified from TWEEN.update.
*
* Debug Notes:
* twn.onStart is called in XTweener.updateTween, later than startTween, but
* isPlaying is set in startTween. So there are chance such that twn.isPlaying = true
* but e.Obj3.mi is not initialized.
*
* Shall we revise Tween.js?
* @param {CmpTween} cmp tween component
* @param {number} time
* @param {object} resolvingStarts
* @return {bool} always true (compitibility with TWEEN.js?)
* @member XTweener.updateTween
* @function
*/
static updateTween (cmp, time, resolvingStarts) {
var property, elapsed, value;
if (time < cmp.startTime) return true;
if (cmp.onStartCallbackFired === false) {
if (cmp.onStartHandler &&
typeof cmp.onStartHandler.onStart === 'function') {
cmp.onStartHandler.onStart(cmp.object);
}
cmp.onStartCallbackFired = true;
}
elapsed = (time - cmp.startTime) / cmp.duration;
elapsed = (cmp.duration === 0 || elapsed > 1) ? 1 : elapsed;
value = cmp.easingFunction(elapsed);
for (property in cmp.valuesEnd) {
// Don't update properties that do not exist in the source object
if (cmp.valuesStart[property] === undefined) continue;
var start = cmp.valuesStart[property] || 0;
var end = cmp.valuesEnd[property];
// for three.js uniforms - FIXME not perfectly fit to the design?
var hasValue = false;
if (typeof end.value === 'number') {
start = start.value;
end = end.value;
hasValue = true;
}
if (end instanceof Array) {
cmp.object[property] = cmp.interpolationFunction(end, value);
} else {
// Parses relative end values with start as base (e.g.: +10, -3)
if (typeof (end) === 'string') {
if (end.charAt(0) === '+' || end.charAt(0) === '-') {
end = start + parseFloat(end);
} else {
end = parseFloat(end);
}
}
// Protect against non numeric properties.
if (typeof (end) === 'number') {
// forr three.js uniforms - FIXME not perfectly fit to the design?
//cmp.object[property] = start + (end - start) * value;
var tv = start + (end - start) * value;
if (hasValue) {
cmp.object[property].value = tv;
}
else
cmp.object[property] = tv;
}
}
}
if (cmp.onUpdateHandler &&
typeof cmp.onUpdateHandler.onUpdate === 'function') {
cmp.onUpdateHandler.onUpdate(cmp.object, elapsed, cmp, cmp.parent.entity);
}
if (elapsed === 1) {
if (cmp.repeat > 0) {
if (isFinite(cmp.repeat)) {
cmp.repeat--;
}
// Reassign starting values, restart by making startTime = now
for (property in cmp.valuesStartRepeat) {
if (typeof (cmp.valuesEnd[property]) === 'string') {
cmp.valuesStartRepeat[property] = cmp.valuesStartRepeat[property] + parseFloat(cmp.valuesEnd[property]);
}
if (cmp.yoyo) {
var tmp = cmp.valuesStartRepeat[property];
cmp.valuesStartRepeat[property] = cmp.valuesEnd[property];
cmp.valuesEnd[property] = tmp;
}
cmp.valuesStart[property] = cmp.valuesStartRepeat[property];
}
if (cmp.yoyo) {
cmp.reversed = !cmp.reversed;
}
if (cmp.repeatDelayTime !== undefined) {
cmp.startTime = time + cmp.repeatDelayTime;
} else {
cmp.startTime = time + cmp.delayTime;
}
if (typeof cmp.onRepeatHandler === 'object') {
cmp.onRepeatHandler.onRepeat(cmp.object);
}
return true;
}
else {
cmp.isCompleted = true; // added by ody
cmp.isPlaying = false; // added by ody
if (cmp.onCompleteHandler &&
typeof cmp.onCompleteHandler.onComplete === 'function') {
cmp.onCompleteHandler.onComplete(cmp.object, cmp);
}
// deprecated branch
// chained tweens is not used
if (cmp.chainedTweens) {
for (var i = 0, numChainedTweens = cmp.chainedTweens.length; i < numChainedTweens; i++) {
// Make the chained tweens start exactly at the time they should,
// even if the `update()` method was called way past the duration of the tween
cmp.chainedTweens[i].start(cmp.startTime + cmp.duration);
}
}
// added by ody, handle followBy
var folls = cmp.followBy;
if (folls && folls.length > 0) {
XTweener.pushTriggerings(TriggerEvent.FOLLOWBY, time, cmp, resolvingStarts, folls);
}
return false;
}
}
return true;
}
/**
* @param {CmpTween} cmp
* @param {number} time
* @member XTweener.startTween
* @function
*/
static startween (cmp, time) {
cmp.isPlaying = true;
cmp.isPaused = false;
cmp.isCompleted = false;
// ody:
cmp.onStartCallbackFired = false;
cmp.startTime = time !== undefined ? TWEEN.now() + (typeof time === 'string' ? parseFloat(time) : time) : TWEEN.now();
cmp.startTime += cmp.delayTime;
for (var property in cmp.valuesEnd) {
// Check is an array was provided as property value
if (cmp.valuesEnd[property] instanceof Array) {
if (cmp.valuesEnd[property].length === 0) {
continue;
}
// Create a local copy of the Array with the start value at the front
cmp.valuesEnd[property] = [cmp.object[property]].concat(cmp.valuesEnd[property]);
}
// If `to()` specifies a property that doesn't exist in the source object,
// we should not set that property in the object
if (cmp.object[property] === undefined) {
continue;
}
// ody: why we don't reset the value before startTime? (time < startTiem)
else if (cmp.valuesStartRepeat[property] !== undefined) {
if (typeof cmp.object[property].value !== 'undefined') // include case of number 0
cmp.object[property].value = cmp.valuesStartRepeat[property];
else
cmp.object[property] = cmp.valuesStartRepeat[property];
}
// Save the starting value, but only once.
if (typeof(cmp.valuesStart[property]) === 'undefined') {
// Ody
// This line should be problem for uniform{value} - referencing and modified startValue while updating
// cmp.valuesStart[property] = cmp.object[property];
if (typeof cmp.object[property].value !== 'undefined') // include case of number 0
cmp.valuesStart[property] = {value: cmp.object[property].value};// copy value
else
cmp.valuesStart[property] = cmp.object[property];
}
// added by Ody
// for three.js uniforms - FIXME not perfectly fit to the design?
if (cmp.object[property].value !== undefined) {
// uniform values can't be an array?
cmp.valuesStartRepeat[property] = cmp.valuesStart[property].value || 0;
}
else {
if ((cmp.valuesStart[property] instanceof Array) === false) {
cmp.valuesStart[property] *= 1.0; // Ensures we're using numbers, not strings
}
cmp.valuesStartRepeat[property] = cmp.valuesStart[property] || 0;
}
}
}
/** Buffering triggering tweens by 'startWith' and 'followBy'.
* Triggering will started at next update, starting by startTriggered()
* @param {const} start_or_follow STARTWITH | FOLLOWBY
* @param {number} now current time
* @param {CmpTween} cmpStarter component triggered the event
* @param {object} resolvingBuff all triggering tween are put into here
* @param {array<startWith|followBy>} withs scripts array defining tweens to be started
* @member XTweener.pushTriggerings
* @function
*/
static pushTriggerings (start_or_follow, now, cmpStarter, resolvingBuff, withs) {
if (withs && withs.length > 0) {
// we don't have the entity's CmpTweens here, can only start it in update()
for (var wx of withs) {
wx.starter = cmpStarter;
wx.triggerAt = start_or_follow;
wx.startime = now + (wx.start || 0) * 1000;
if (resolvingBuff[wx.entity] === undefined)
resolvingBuff[wx.entity] = [];
resolvingBuff[wx.entity].push(wx);
}
}
}
/**Start triggereds, recursively, return the 'triggerings' - in wich elements started
* successfully have been removed.
* @param {CmpTweens} cmpTweens target tween components to be started
* @param {object} triggerings [in / out] {entity-id: triggering}, the triggering
* component description, where trigering is pushed by #pushTriggerings():
* {seqx, starter, triggerAt: STARTWITH | FOLLOWBY}
* @param {object} resolvingBuff, the tweener.resolvingStarts, buffer for push
* other triggered recursively
* @member XTweener.startTriggered
* @function
*/
static startTriggered (cmpTweens, triggerings, resolvingBuff) {
var slicings = [];
var now = TWEEN.now();
for (var i = 0; i < triggerings.length; i++) {
var beingStart = triggerings[i];
if (beingStart.triggerAt == TriggerEvent.BEGINNING
|| beingStart.triggerAt === TriggerEvent.STARTWITH && beingStart.starter.isPlaying
|| beingStart.triggerAt === TriggerEvent.FOLLOWBY && beingStart.starter.isCompleted) {
var tweens = cmpTweens.tweens[beingStart.seqx];
if (!tweens || !tweens[0]) {
console.error("XTweener.update(): StartWith or followBy's script index is out of range.",
"\nSeq Index: ", beingStart.seqx, "\nTweens: ", cmpTweens.tweens);
}
// else if (!tweens[0].isPlaying && !tweens[0].isCompleted) {
else if (tweens && tweens[0] && beingStart.startime <= now) {
// console.log(tweens[0].delay + beingStart.start * 1000);
XTweener.startween(tweens[0], beingStart.start * 1000);
cmpTweens.twindx[beingStart.seqx] = 0;
slicings.push(i);
if (tweens[0].startWith) {
XTweener.pushTriggerings(TriggerEvent.STARTWITH, now, tweens[0], resolvingBuff, tweens[0].startWith);
}
}
}
}
for (var i = slicings.length - 1; i >= 0; i--) {
triggerings.splice(slicings[i], 1);
}
return triggerings;
}
/**Helper to find out is the sequences is playing.
* @param {CmpTweens} cmpTweens
* @param {int} seqx sequnce index
* @return {bool} is playing
* @member XTweener.isPlaying
* @function
*/
static isPlaying (cmpTweens, seqx) {
var twnx = cmpTweens.twindx[seqx];
return 0 <= twnx && twnx < cmpTweens.tweens[seqx].length;
}
/**Start animation sequence
* @see XTweener.startAll
* @param {CmpTweens} cmpTweens
* @param {int} seqx sequnce index
* @member XTweener.startSeq
* @function
*/
static startSeq (cmpTweens, seqx) {
cmpTweens.startCmds.push(seqx);
}
/**Start ALL animation sequences.
* @see XTweener.startSeq
* @param {CmpTweens} cmpTweens
* @param {int} seqx sequnce index
* @member XTweener.startSeq
* @function
*/
static startAll (cmpTweens) {
for (var seqx = 0; cmpTweens && seqx < cmpTweens.twindx.length; seqx++) {
this.startSeq(cmpTweens, seqx);
}
}
/**Pause all animation sequences
* @param {CmpTweens} cmpTweens
* @param {int} seqx sequnce index
* @member XTweener.startSeq
* @function
*/
static pauseTween (cmpTweens, pause) {
var now = TWEEN.now();
for (var seqx = 0; cmpTweens && seqx < cmpTweens.twindx.length; seqx++) {
var twnx = cmpTweens.twindx[seqx];
if (0 <= twnx && twnx < cmpTweens.tweens[seqx].length) {
var twn = cmpTweens.tweens[seqx][twnx];
// see Tween.pause & resume
if(!!pause && twn &&
!twn.isPaused || !twn.isPlaying) {
// pause
twn.isPaused = true;
twn.pauseStart = now;
}
else if(!pause && twn &&
twn.isPaused || !twn.isPlaying) {
// resume
twn.isPaused = false;
twn.startTime += now - twn.pauseStart;
twn.pauseStart = 0;
}
}
}
}
};
XTweener.query = iffTween;
const TWEEN = {};
// triggering event id
export const TriggerEvent = {
STARTWITH: 1,
FOLLOWBY: 2,
BEGINNING: 3
}
TWEEN._nextId = 0;
TWEEN.nextId = function () {
return TWEEN._nextId++;
};
// Include a performance.now polyfill.
// In node.js, use process.hrtime.
// FIXME what's self used for?
if (typeof (self) === 'undefined' && typeof (process) !== 'undefined' && process.hrtime) {
TWEEN.now = function () {
var time = process.hrtime();
// Convert [seconds, nanoseconds] to milliseconds.
return time[0] * 1000 + time[1] / 1000000;
};
}
// In a browser, use self.performance.now if it is available.
else if (typeof (self) !== 'undefined' &&
self.performance !== undefined &&
self.performance.now !== undefined) {
// This must be bound, because directly assigning this function
// leads to an invocation exception in Chrome.
TWEEN.now = self.performance.now.bind(self.performance);
}
// Use Date.now if it is available.
else if (Date.now !== undefined) {
TWEEN.now = Date.now;
}
// Otherwise, use 'new Date().getTime()'.
else {
TWEEN.now = function () {
return new Date().getTime();
};
}
/** TWEEN equivolent, a CmpTween (TWEEN.Tween) component operation helper.
* API Stype: XTweener.Tween(CmpTween).to(CmpTween, properties, duration);
* @param {XComponent.CmpTween} cmpTween tween component
* @param {object} tweenee object to be tweened (e.g. a THREE.Object3D property)
* @class Tween
* */
TWEEN.Tween = function(cmpTween, tweenee) {
const cmp = cmpTween;
cmp.duration = 1000;
cmp.repeat = 0;
cmp.delayTime = 0;
cmp.startTime = null;
cmp.object = tweenee;
cmp.valuesStart = {};
cmp.valuesEnd = {};
cmp.valuesStartRepeat = {};
// cmp.onStartCallbackFired = null;
// cmp.onUpdateCallback = null;
return new function() {
this.getId = function () { return cmp.id; }
this.isPlaying = function () { return cmp.isPlaying; }
this.isPaused = function () { return cmp.isPaused; }
this.to = function (properties, duration) {
cmp.valuesEnd = Object.create(properties);
if (duration !== undefined) {
cmp.duration = duration;
}
return this;
}
// duration: function duration(d) {
this.duration = function (d) {
cmp.duration = d;
return this;
}
/**Start this animation.
* @param {object} resolvingBuff resolving buffer for triggered tweens
* - the buffer of enityId-script key values that Animizer ask for
* starting by 'startWith'.
* @param {number} time [optional] seconds
*/
this.start = function (resolvingBuff, time) {
XTweener.startween(cmp, time);
XTweener.pushTriggerings(TriggerEvent.STARTWITH, TWEEN.now(), cmp, resolvingBuff, cmp.startWith);
return this;
}
this.stop = function () {
if (!cmp.isPlaying) {
return this;
}
// this.group.remove(this);
cmp.isPlaying = false;
cmp.isPaused = false;
if (cmp.onStopHandler &&
typeof cmp.onStopHandler.onStop === 'function') {
cmp.onStopHandler.onStop(cmp.object);
}
cmp.stopChainedTweens();
return this;
}
this.end = function () {
cmp.update(Infinity);
return this;
}
this.pause = function (time) {
if (cmp.isPaused || !cmp.isPlaying) {
return this;
}
cmp.isPaused = true;
cmp.pauseStart = time === undefined ? TWEEN.now() : time;
// this.group.remove(this);
return this;
}
this.resume = function (time) {
if (!cmp.isPaused || !cmp.isPlaying) {
return this;
}
cmp.isPaused = false;
cmp.startTime += (time === undefined ? TWEEN.now() : time)
- cmp.pauseStart;
cmp.pauseStart = 0;
// this.group.add(this);
return this;
}
this.stopChainedTweens = function () {
for (var i = 0, numChainedTweens = cmp.chainedTweens.length; i < numChainedTweens; i++) {
cmp.chainedTweens[i].stop();
}
}
// group (group) {
// this.group = group;
// return this;
// }
this.delay = function (amount) {
cmp.delayTime = amount;
return cmp;
}
this.repeat = function (times) {
cmp.repeat = times;
return cmp;
}
this.repeatDelay = function (amount) {
cmp.repeatDelayTime = amount;
return cmp;
}
this.yoyo = function (yoyo) {
cmp.yoyo = yoyo;
return this;
}
this.easing = function (easingFunction) {
cmp.easingFunction = easingFunction;
return this;
}
this.interpolation = function (interpolationFunction) {
cmp.interpolationFunction = interpolationFunction;
return this;
}
/**Chain another tween.
* Note: XTweener.XTWEEN is controled by a ECS subsystem, chained tweening
* is not recommended using this way.
* This method is reserved while user creating tween.
*/
this.chain = function () {
cmp.chainedTweens = arguments;
return this;
}
this.onStart = function (handler) {
// cmp.onStartCallback = callback;
// keep API compitable to Tween.js
if (typeof handler === 'function'){
cmp.onStartHandler = {onStart: handler};
}
else {
cmp.onStartHandler = handler;
}
return this;
}
this.onUpdate = function (handler) {
// cmp.onUpdateCallback = callback;
// keep API compitable to Tween.js
if (typeof handler === 'function'){
cmp.onUpdateHandler = {onUpdate: handler};
}
else {
cmp.onUpdateHandler = handler;
}
return this;
}
// onRepeat: function onRepeat(callback) {
this.onRepeat = function (handler) {
// cmp.onRepeatCallback = callback;
// keep API compitable to Tween.js
if (typeof handler === 'function'){
cmp.onRepeatHandler = {onRepeat: handler};
}
else {
cmp.onRepeatHandler = handler;
}
return this;
}
this.onComplete = function (handler) {
// cmp.onCompleteCallback = callback;
// keep API compitable to Tween.js
if (typeof handler === 'function'){
cmp.onCompleteHandler = {onComplete: handler};
}
else {
cmp.onCompleteHandler = handler;
}
return this;
}
this.onStop = function (handler) {
// keep API compitable to Tween.js
if (typeof handler === 'function'){
cmp.onStopHandler = {onStop: handler};
}
else {
cmp.onStopHandler = handler;
}
return this;
}
}
};
/**Tween easing function
* @type XEasing
*/
const XEasing = {
/** @member XEasing#Linear */
Linear: {
None: function (k) {
return k;
}
},
/** @member XEasing#Quadratic */
Quadratic: {
In: function (k) {
return k * k;
},
Out: function (k) {
return k * (2 - k);
},
InOut: function (k) {
if ((k *= 2) < 1) {
return 0.5 * k * k;
}
return - 0.5 * (--k * (k - 2) - 1);
}
},
/** @member XEasing#Cubic */
Cubic: {
In: function (k) {
return k * k * k;
},
Out: function (k) {
return --k * k * k + 1;
},
InOut: function (k) {
if ((k *= 2) < 1) {
return 0.5 * k * k * k;
}
return 0.5 * ((k -= 2) * k * k + 2);
}
},
/** @member XEasing#Quartic */
Quartic: {
In: function (k) {
return k * k * k * k;
},
Out: function (k) {
return 1 - (--k * k * k * k);
},
InOut: function (k) {
if ((k *= 2) < 1) {
return 0.5 * k * k * k * k;
}
return - 0.5 * ((k -= 2) * k * k * k - 2);
}
},
/** @member XEasing#Quintic */
Quintic: {
In: function (k) {
return k * k * k * k * k;
},
Out: function (k) {
return --k * k * k * k * k + 1;
},
InOut: function (k) {
if ((k *= 2) < 1) {
return 0.5 * k * k * k * k * k;
}
return 0.5 * ((k -= 2) * k * k * k * k + 2);
}
},
/** @member XEasing#Sinusoidal */
Sinusoidal: {
In: function (k) {
return 1 - Math.cos(k * Math.PI / 2);
},
Out: function (k) {
return Math.sin(k * Math.PI / 2);
},
InOut: function (k) {
return 0.5 * (1 - Math.cos(Math.PI * k));
}
},
/** @member XEasing#Exponential */
Exponential: {
In: function (k) {
return k === 0 ? 0 : Math.pow(1024, k - 1);
},
Out: function (k) {
return k === 1 ? 1 : 1 - Math.pow(2, - 10 * k);
},
InOut: function (k) {
if (k === 0) {
return 0;
}
if (k === 1) {
return 1;
}
if ((k *= 2) < 1) {
return 0.5 * Math.pow(1024, k - 1);
}
return 0.5 * (- Math.pow(2, - 10 * (k - 1)) + 2);
}
},
/** @member XEasing#Circular */
Circular: {
In: function (k) {
return 1 - Math.sqrt(1 - k * k);
},
Out: function (k) {
return Math.sqrt(1 - (--k * k));
},
InOut: function (k) {
if ((k *= 2) < 1) {
return - 0.5 * (Math.sqrt(1 - k * k) - 1);
}
return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1);
}
},
/** @member XEasing#Elastic */
Elastic: {
In: function (k) {
if (k === 0) {
return 0;
}
if (k === 1) {
return 1;
}
return -Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI);
},
Out: function (k) {
if (k === 0) {
return 0;
}
if (k === 1) {
return 1;
}
return Math.pow(2, -10 * k) * Math.sin((k - 0.1) * 5 * Math.PI) + 1;
},
InOut: function (k) {
if (k === 0) {
return 0;
}
if (k === 1) {
return 1;
}
k *= 2;
if (k < 1) {
return -0.5 * Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI);
}
return 0.5 * Math.pow(2, -10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI) + 1;
}
},
/** @member XEasing#Back */
Back: {
In: function (k) {
var s = 1.70158;
return k * k * ((s + 1) * k - s);
},
Out: function (k) {
var s = 1.70158;
return --k * k * ((s + 1) * k + s) + 1;
},
InOut: function (k) {
var s = 1.70158 * 1.525;
if ((k *= 2) < 1) {
return 0.5 * (k * k * ((s + 1) * k - s));
}
return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2);
}
},
/** @member XEasing#Bounce */
Bounce: {
In: function (k) {
return 1 - TWEEN.Easing.Bounce.Out(1 - k);
},
Out: function (k) {
if (k < (1 / 2.75)) {
return 7.5625 * k * k;
} else if (k < (2 / 2.75)) {
return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75;
} else if (k < (2.5 / 2.75)) {
return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375;
} else {
return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375;
}
},
InOut: function (k) {
if (k < 0.5) {
return TWEEN.Easing.Bounce.In(k * 2) * 0.5;
}
return TWEEN.Easing.Bounce.Out(k * 2 - 1) * 0.5 + 0.5;
}
}
};
TWEEN.Easing = XEasing;
TWEEN.Interpolation = {
Linear: function (v, k) {
var m = v.length - 1;
var f = m * k;
var i = Math.floor(f);
var fn = TWEEN.Interpolation.Utils.Linear;
if (k < 0) {
return fn(v[0], v[1], f);
}
if (k > 1) {
return fn(v[m], v[m - 1], m - f);
}
return fn(v[i], v[i + 1 > m ? m : i + 1], f - i);
},
Bezier: function (v, k) {
var b = 0;
var n = v.length - 1;
var pw = Math.pow;
var bn = TWEEN.Interpolation.Utils.Bernstein;
for (var i = 0; i <= n; i++) {
b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i);
}
return b;
},
CatmullRom: function (v, k) {
var m = v.length - 1;
var f = m * k;
var i = Math.floor(f);
var fn = TWEEN.Interpolation.Utils.CatmullRom;
if (v[0] === v[m]) {
if (k < 0) {
i = Math.floor(f = m * (1 + k));
}
return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i);
} else {
if (k < 0) {
return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]);
}
if (k > 1) {
return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]);
}
return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i);
}
},
Utils: {
Linear: function (p0, p1, t) {
return (p1 - p0) * t + p0;
},
Bernstein: function (n, i) {
var fc = TWEEN.Interpolation.Utils.Factorial;
return fc(n) / fc(i) / fc(n - i);
},
Factorial: (function () {
var a = [1];
return function (n) {
var s = 1;
if (a[n]) {
return a[n];
}
for (var i = n; i > 1; i--) {
s *= i;
}
a[n] = s;
return s;
};
})(),
CatmullRom: function (p0, p1, p2, p3, t) {
var v0 = (p2 - p0) * 0.5;
var v1 = (p3 - p1) * 0.5;
var t2 = t * t;
var t3 = t * t2;
return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (- 3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1;
}
}
};
TWEEN.version = '0.8.0';
export { TWEEN, XEasing };