cables_dev/cables/src/core/anim.js
- import { Logger } from "cables-shared-client";
- import { Key } from "./anim_key.js";
- import { CONSTANTS } from "./constants.js";
- import { EventTarget } from "./eventtarget.js";
-
- /**
- * Keyframed interpolated animation.
- *
- * Available Easings:
- * <code>
- * CONSTANTS.ANIM.EASING_LINEAR
- * CONSTANTS.ANIM.EASING_ABSOLUTE
- * CONSTANTS.ANIM.EASING_SMOOTHSTEP
- * CONSTANTS.ANIM.EASING_SMOOTHERSTEP
- * CONSTANTS.ANIM.EASING_CUBICSPLINE
-
- * CONSTANTS.ANIM.EASING_CUBIC_IN
- * CONSTANTS.ANIM.EASING_CUBIC_OUT
- * CONSTANTS.ANIM.EASING_CUBIC_INOUT
-
- * CONSTANTS.ANIM.EASING_EXPO_IN
- * CONSTANTS.ANIM.EASING_EXPO_OUT
- * CONSTANTS.ANIM.EASING_EXPO_INOUT
-
- * CONSTANTS.ANIM.EASING_SIN_IN
- * CONSTANTS.ANIM.EASING_SIN_OUT
- * CONSTANTS.ANIM.EASING_SIN_INOUT
-
- * CONSTANTS.ANIM.EASING_BACK_IN
- * CONSTANTS.ANIM.EASING_BACK_OUT
- * CONSTANTS.ANIM.EASING_BACK_INOUT
-
- * CONSTANTS.ANIM.EASING_ELASTIC_IN
- * CONSTANTS.ANIM.EASING_ELASTIC_OUT
-
- * CONSTANTS.ANIM.EASING_BOUNCE_IN
- * CONSTANTS.ANIM.EASING_BOUNCE_OUT
-
- * CONSTANTS.ANIM.EASING_QUART_IN
- * CONSTANTS.ANIM.EASING_QUART_OUT
- * CONSTANTS.ANIM.EASING_QUART_INOUT
-
- * CONSTANTS.ANIM.EASING_QUINT_IN
- * CONSTANTS.ANIM.EASING_QUINT_OUT
- * CONSTANTS.ANIM.EASING_QUINT_INOUT
- * </code>
- * @class
- * @param cfg
- * @example
- * var anim=new CABLES.Anim();
- * anim.setValue(0,0); // set value 0 at 0 seconds
- * anim.setValue(10,1); // set value 1 at 10 seconds
- * anim.getValue(5); // get value at 5 seconds - this returns 0.5
- */
-
- const Anim = function (cfg)
- {
- EventTarget.apply(this);
-
- cfg = cfg || {};
- this.keys = [];
- this.onChange = null;
- this.stayInTimeline = false;
- this.loop = false;
- this._log = new Logger("Anim");
- this._lastKeyIndex = 0;
- this._cachedIndex = 0;
- this.name = cfg.name || null;
-
- /**
- * @member defaultEasing
- * @memberof Anim
- * @instance
- * @type {Number}
- */
- this.defaultEasing = cfg.defaultEasing || CONSTANTS.ANIM.EASING_LINEAR;
- this.onLooped = null;
-
- this._timesLooped = 0;
- this._needsSort = false;
- };
-
- Anim.prototype.forceChangeCallback = function ()
- {
- if (this.onChange !== null) this.onChange();
- this.emitEvent("onChange", this);
- };
-
- Anim.prototype.getLoop = function ()
- {
- return this.loop;
- };
-
- Anim.prototype.setLoop = function (target)
- {
- this.loop = target;
- this.emitEvent("onChange", this);
- };
-
- /**
- * returns true if animation has ended at @time
- * checks if last key time is < time
- * @param {Number} time
- * @returns {Boolean}
- * @memberof Anim
- * @instance
- * @function
- */
- Anim.prototype.hasEnded = function (time)
- {
- if (this.keys.length === 0) return true;
- if (this.keys[this._lastKeyIndex].time <= time) return true;
- return false;
- };
-
- Anim.prototype.isRising = function (time)
- {
- if (this.hasEnded(time)) return false;
- const ki = this.getKeyIndex(time);
- if (this.keys[ki].value < this.keys[ki + 1].value) return true;
- return false;
- };
-
- /**
- * remove all keys from animation before time
- * @param {Number} time
- * @memberof Anim
- * @instance
- * @function
- */
- Anim.prototype.clearBefore = function (time)
- {
- const v = this.getValue(time);
- const ki = this.getKeyIndex(time);
-
- this.setValue(time, v);
-
- if (ki > 1) this.keys.splice(0, ki);
- this._updateLastIndex();
- };
- /**
- * remove all keys from animation
- * @param {Number} [time=0] set a new key at time with the old value at time
- * @memberof Anim
- * @instance
- * @function
- */
- Anim.prototype.clear = function (time)
- {
- let v = 0;
- if (time) v = this.getValue(time);
- this.keys.length = 0;
- this._updateLastIndex();
- if (time) this.setValue(time, v);
- if (this.onChange !== null) this.onChange();
- this.emitEvent("onChange", this);
- };
-
- Anim.prototype.sortKeys = function ()
- {
- this.keys.sort((a, b) => { return parseFloat(a.time) - parseFloat(b.time); });
- this._updateLastIndex();
- this._needsSort = false;
- if (this.keys.length % 1000 == 0)console.log(this.name, this.keys.length);
- };
-
- Anim.prototype.getLength = function ()
- {
- if (this.keys.length === 0) return 0;
- return this.keys[this.keys.length - 1].time;
- };
-
- Anim.prototype.getKeyIndex = function (time)
- {
- let index = 0;
- let start = 0;
- if (this._cachedIndex && this.keys.length > this._cachedIndex && time >= this.keys[this._cachedIndex].time) start = this._cachedIndex;
- for (let i = start; i < this.keys.length; i++)
- {
- if (time >= this.keys[i].time) index = i;
- if (this.keys[i].time > time)
- {
- if (time != 0) this._cachedIndex = index;
- return index;
- }
- }
-
- return index;
- };
-
- /**
- * set value at time
- * @function setValue
- * @memberof Anim
- * @instance
- * @param {Number} time
- * @param {Number} value
- * @param {Function} cb callback
- */
- Anim.prototype.setValue = function (time, value, cb)
- {
- let found = null;
-
- if (this.keys.length == 0 || time <= this.keys[this.keys.length - 1].time)
- for (let i = 0; i < this.keys.length; i++)
- if (this.keys[i].time == time)
- {
- found = this.keys[i];
- this.keys[i].setValue(value);
- this.keys[i].cb = cb;
- break;
- }
-
- if (!found)
- {
- found = new Key(
- {
- "time": time,
- "value": value,
- "e": this.defaultEasing,
- "cb": cb,
- });
- this.keys.push(found);
-
- // if (this.keys.length % 1000 == 0)console.log(this.name, this.keys.length);
- this._updateLastIndex();
- }
-
- if (this.onChange) this.onChange();
- this.emitEvent("onChange", this);
- this._needsSort = true;
- return found;
- };
-
- Anim.prototype.setKeyEasing = function (index, e)
- {
- if (this.keys[index])
- {
- this.keys[index].setEasing(e);
- this.emitEvent("onChange", this);
- }
- };
-
- Anim.prototype.getSerialized = function ()
- {
- const obj = {};
- obj.keys = [];
- obj.loop = this.loop;
-
- for (let i = 0; i < this.keys.length; i++)
- obj.keys.push(this.keys[i].getSerialized());
-
- return obj;
- };
-
- Anim.prototype.getKey = function (time)
- {
- const index = this.getKeyIndex(time);
- return this.keys[index];
- };
-
- Anim.prototype.getNextKey = function (time)
- {
- let index = this.getKeyIndex(time) + 1;
- if (index >= this.keys.length) index = this.keys.length - 1;
-
- return this.keys[index];
- };
-
- Anim.prototype.isFinished = function (time)
- {
- if (this.keys.length <= 0) return true;
- return time > this.keys[this.keys.length - 1].time;
- };
-
- Anim.prototype.isStarted = function (time)
- {
- if (this.keys.length <= 0) return false;
- return time >= this.keys[0].time;
- };
-
- /**
- * get value at time
- * @function getValue
- * @memberof Anim
- * @instance
- * @param {Number} [time] time
- * @returns {Number} interpolated value at time
- */
- Anim.prototype.getValue = function (time)
- {
- if (this.keys.length === 0)
- {
- return 0;
- }
- if (this._needsSort) this.sortKeys();
-
- if (!this.loop && time > this.keys[this._lastKeyIndex].time)
- {
- if (this.keys[this._lastKeyIndex].cb && !this.keys[this._lastKeyIndex].cbTriggered) this.keys[this._lastKeyIndex].trigger();
-
- return this.keys[this._lastKeyIndex].value;
- }
-
- if (time < this.keys[0].time)
- {
- // if (this.name)console.log("A");
-
- return this.keys[0].value;
- }
-
- if (this.loop && time > this.keys[this._lastKeyIndex].time)
- {
- const currentLoop = time / this.keys[this._lastKeyIndex].time;
- if (currentLoop > this._timesLooped)
- {
- this._timesLooped++;
- if (this.onLooped) this.onLooped();
- }
- time = (time - this.keys[0].time) % (this.keys[this._lastKeyIndex].time - this.keys[0].time);
- time += this.keys[0].time;
- }
-
- const index = this.getKeyIndex(time);
- if (index >= this._lastKeyIndex)
- {
- if (this.keys[this._lastKeyIndex].cb && !this.keys[this._lastKeyIndex].cbTriggered) this.keys[this._lastKeyIndex].trigger();
-
- return this.keys[this._lastKeyIndex].value;
- }
-
-
- const index2 = index + 1;
- const key1 = this.keys[index];
- const key2 = this.keys[index2];
-
- if (key1.cb && !key1.cbTriggered) key1.trigger();
-
- if (!key2) return -1;
-
- const perc = (time - key1.time) / (key2.time - key1.time);
-
- if (!key1.ease) this.log._warn("has no ease", key1, key2);
-
- return key1.ease(perc, key2);
- };
-
- Anim.prototype._updateLastIndex = function ()
- {
- this._lastKeyIndex = this.keys.length - 1;
- };
-
- Anim.prototype.addKey = function (k)
- {
- if (k.time === undefined)
- {
- this.log.warn("key time undefined, ignoring!");
- }
- else
- {
- this.keys.push(k);
- if (this.onChange !== null) this.onChange();
- this.emitEvent("onChange", this);
- }
- this._updateLastIndex();
- };
-
- Anim.prototype.easingFromString = function (str)
- {
- if (str == "linear") return CONSTANTS.ANIM.EASING_LINEAR;
- if (str == "absolute") return CONSTANTS.ANIM.EASING_ABSOLUTE;
- if (str == "smoothstep") return CONSTANTS.ANIM.EASING_SMOOTHSTEP;
- if (str == "smootherstep") return CONSTANTS.ANIM.EASING_SMOOTHERSTEP;
-
- if (str == "Cubic In") return CONSTANTS.ANIM.EASING_CUBIC_IN;
- if (str == "Cubic Out") return CONSTANTS.ANIM.EASING_CUBIC_OUT;
- if (str == "Cubic In Out") return CONSTANTS.ANIM.EASING_CUBIC_INOUT;
-
- if (str == "Expo In") return CONSTANTS.ANIM.EASING_EXPO_IN;
- if (str == "Expo Out") return CONSTANTS.ANIM.EASING_EXPO_OUT;
- if (str == "Expo In Out") return CONSTANTS.ANIM.EASING_EXPO_INOUT;
-
- if (str == "Sin In") return CONSTANTS.ANIM.EASING_SIN_IN;
- if (str == "Sin Out") return CONSTANTS.ANIM.EASING_SIN_OUT;
- if (str == "Sin In Out") return CONSTANTS.ANIM.EASING_SIN_INOUT;
-
- if (str == "Back In") return CONSTANTS.ANIM.EASING_BACK_IN;
- if (str == "Back Out") return CONSTANTS.ANIM.EASING_BACK_OUT;
- if (str == "Back In Out") return CONSTANTS.ANIM.EASING_BACK_INOUT;
-
- if (str == "Elastic In") return CONSTANTS.ANIM.EASING_ELASTIC_IN;
- if (str == "Elastic Out") return CONSTANTS.ANIM.EASING_ELASTIC_OUT;
-
- if (str == "Bounce In") return CONSTANTS.ANIM.EASING_BOUNCE_IN;
- if (str == "Bounce Out") return CONSTANTS.ANIM.EASING_BOUNCE_OUT;
-
- if (str == "Quart Out") return CONSTANTS.ANIM.EASING_QUART_OUT;
- if (str == "Quart In") return CONSTANTS.ANIM.EASING_QUART_IN;
- if (str == "Quart In Out") return CONSTANTS.ANIM.EASING_QUART_INOUT;
-
- if (str == "Quint Out") return CONSTANTS.ANIM.EASING_QUINT_OUT;
- if (str == "Quint In") return CONSTANTS.ANIM.EASING_QUINT_IN;
- if (str == "Quint In Out") return CONSTANTS.ANIM.EASING_QUINT_INOUT;
- };
-
- Anim.prototype.createPort = function (op, title, cb)
- {
- const port = op.inDropDown(title, CONSTANTS.ANIM.EASINGS, "Cubic Out");
-
- // const port = op.addInPort(
- // new Port(op, title, CONSTANTS.OP.OP_PORT_TYPE_VALUE, {
- // "display": "dropdown",
- // "values": CONSTANTS.ANIM.EASINGS,
- // }),
- // );
-
- port.set("linear");
- port.defaultValue = "linear";
-
- port.onChange = function ()
- {
- this.defaultEasing = this.easingFromString(port.get());
- this.emitEvent("onChangeDefaultEasing", this);
-
- if (cb) cb();
- }.bind(this);
-
- return port;
- };
-
- // ------------------------------
-
- Anim.slerpQuaternion = function (time, q, animx, animy, animz, animw)
- {
- if (!Anim.slerpQuaternion.q1)
- {
- Anim.slerpQuaternion.q1 = quat.create();
- Anim.slerpQuaternion.q2 = quat.create();
- }
-
- const i1 = animx.getKeyIndex(time);
- let i2 = i1 + 1;
- if (i2 >= animx.keys.length) i2 = animx.keys.length - 1;
-
- if (i1 == i2)
- {
- quat.set(q, animx.keys[i1].value, animy.keys[i1].value, animz.keys[i1].value, animw.keys[i1].value);
- }
- else
- {
- const key1Time = animx.keys[i1].time;
- const key2Time = animx.keys[i2].time;
- const perc = (time - key1Time) / (key2Time - key1Time);
-
- quat.set(Anim.slerpQuaternion.q1, animx.keys[i1].value, animy.keys[i1].value, animz.keys[i1].value, animw.keys[i1].value);
-
- quat.set(Anim.slerpQuaternion.q2, animx.keys[i2].value, animy.keys[i2].value, animz.keys[i2].value, animw.keys[i2].value);
-
- quat.slerp(q, Anim.slerpQuaternion.q1, Anim.slerpQuaternion.q2, perc);
- }
- return q;
- };
-
- const ANIM = { "Key": Key };
-
- export { ANIM };
- export { Anim };