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 };