Home Reference Source

scripts/path-controller/curve/curve1d_anim.js

import nstructjs from "../util/struct.js";
import {CurveConstructors, CurveTypeData} from "./curve1d_base.js";
import Ease from './ease.js';
import * as util from '../util/util.js';

function bez3(a, b, c, t) {
  var r1 = a + (b - a)*t;
  var r2 = b + (c - b)*t;

  return r1 + (r2 - r1)*t;
}

function bez4(a, b, c, d, t) {
  var r1 = bez3(a, b, c, t);
  var r2 = bez3(b, c, d, t);

  return r1 + (r2 - r1)*t;
}

export class ParamKey {
  constructor(key, val) {
    this.key = key;
    this.val = val;
  }
}

ParamKey.STRUCT = `
ParamKey {
  key : string;
  val : float;
}
`
nstructjs.register(ParamKey);
let BOOL_FLAG = 1e17;

let _udigest = new util.HashDigest();

export class SimpleCurveBase extends CurveTypeData {
  constructor() {
    super();

    this.type = this.constructor.name;

    let def = this.constructor.define();
    let params = def.params;

    this.params = {};
    for (let k in params) {
      this.params[k] = params[k][1];
    }
  }

  get hasGUI() {
    return true;
  }

  calcHashKey(digest = _udigest.reset()) {
    let d = digest;
    super.calcHashKey(d);

    for (let k in this.params) {
      digest.add(k);
      digest.add(this.params[k]);
    }

    return d.get();
  }

  equals(b) {
    if (this.type !== b.type) {
      return false;
    }

    for (let k in this.params) {
      if (Math.abs(this.params[k] - b.params[k]) > 0.000001) {
        return false;
      }
    }

    return true;
  }

  redraw() {
    if (this.parent)
      this.parent.redraw();
  }

  makeGUI(container) {
    let def = this.constructor.define();
    let params = def.params;

    for (let k in params) {
      let p = params[k];

      if (p[2] === BOOL_FLAG) {
        let check = container.check(undefined, p[0]);
        check.checked = !!this.params[k];
        check.key = k;

        let this2 = this;
        check.onchange = function () {
          this2.params[this.key] = this.checked ? 1 : 0;
          this2.update();
          this2.redraw();
        }
      } else {
        let slider = container.slider(undefined, {
          name      : p[0],
          defaultval: this.params[k],
          min       : p[2],
          max       : p[3]
        })
        slider.baseUnit = slider.displayUnit = "none";

        slider.key = k;

        let this2 = this;
        slider.onchange = function () {
          this2.params[this.key] = this.value;
          this2.update();
          this2.redraw();
        }
      }
    }
  }

  killGUI(container) {
    container.clear();
  }

  evaluate(s) {
    throw new Error("implement me!");
  }

  reset() {

  }

  update() {
    super.update();
  }

  draw(canvas, g, draw_transform) {
    let steps = 128;
    let s = 0, ds = 1.0/(steps - 1);

    g.beginPath();
    for (let i = 0; i < steps; i++, s += ds) {
      let co = this.evaluate(s);

      if (i) {
        g.lineTo(co[0], co[1]);
      } else {
        g.moveTo(co[0], co[1]);
      }
    }

    g.stroke();
  }

  _saveParams() {
    let ret = [];
    for (let k in this.params) {
      ret.push(new ParamKey(k, this.params[k]));
    }

    return ret;
  }

  toJSON() {
    return Object.assign(super.toJSON(), {
      params: this.params
    });
  }

  loadJSON(obj) {
    for (let k in obj.params) {
      this.params[k] = obj.params[k];
    }

    return this;
  }

  loadSTRUCT(reader) {
    reader(this);
    super.loadSTRUCT(reader);

    let ps = this.params;
    this.params = {};

    let pdef = this.constructor.define().params;
    if (!pdef) {
      console.warn("Missing define function for curve", this.constructor.name)
      return;
    }

    for (let pair of ps) {
      if (pair.key in pdef) {
        this.params[pair.key] = pair.val;
      }
    }

    for (let k in pdef) {
      if (!(k in this.params)) {
        this.params[k] = pdef[k][1];
      }
    }
  }
}

SimpleCurveBase.STRUCT = nstructjs.inherit(SimpleCurveBase, CurveTypeData) + `
  params : array(ParamKey) | obj._saveParams();
}
`;
nstructjs.register(SimpleCurveBase);

export class BounceCurve extends SimpleCurveBase {
  static define() {
    return {
      params  : {
        decay : ["Decay", 1.0, 0.1, 5.0],
        scale : ["Scale", 1.0, 0.01, 10.0],
        freq  : ["Freq", 1.0, 0.01, 50.0],
        phase : ["Phase", 0.0, -Math.PI*2.0, Math.PI*2.0],
        offset: ["Offset", 0.0, -2.0, 2.0]
      },
      name    : "bounce",
      uiname  : "Bounce",
      typeName: "BounceCurve"

    }
  }

  _evaluate(t) {
    let params = this.params;
    let decay = params.decay + 1.0;
    let scale = params.scale;
    let freq = params.freq;
    let phase = params.phase;
    let offset = params.offset;

    t *= freq;
    let t2 = Math.abs(Math.cos(phase + t*Math.PI*2.0))*scale;
    ;//+ (1.0-scale);

    t2 *= Math.exp(decay*t)/Math.exp(decay);

    return t2;
  }

  evaluate(t) {
    let s = this._evaluate(0.0);
    let e = this._evaluate(1.0);

    return (this._evaluate(t) - s)/(e - s) + this.params.offset;
  }
}

CurveTypeData.register(BounceCurve);
BounceCurve.STRUCT = nstructjs.inherit(BounceCurve, SimpleCurveBase) + `
}`;
nstructjs.register(BounceCurve);


export class ElasticCurve extends SimpleCurveBase {
  constructor() {
    super();

    this._func = undefined;
    this._last_hash = undefined;
  }

  static define() {
    return {
      params  : {
        mode     : ["Out Mode", false, BOOL_FLAG, BOOL_FLAG],
        amplitude: ["Amplitude", 1.0, 0.01, 10.0],
        period   : ["Period", 1.0, 0.01, 5.0]
      },
      name    : "elastic",
      uiname  : "Elastic",
      typeName: "ElasticCurve",
    }
  }

  evaluate(t) {
    let hash = ~~(this.params.mode*127 + this.params.amplitude*256 + this.params.period*512);

    if (hash !== this._last_hash || !this._func) {
      this._last_hash = hash;

      if (this.params.mode) {
        this._func = Ease.getElasticOut(this.params.amplitude, this.params.period);
      } else {
        this._func = Ease.getElasticIn(this.params.amplitude, this.params.period);
      }
    }
    return this._func(t);
  }
}

CurveTypeData.register(ElasticCurve);
ElasticCurve.STRUCT = nstructjs.inherit(ElasticCurve, SimpleCurveBase) + `
}`;
nstructjs.register(ElasticCurve);


export class EaseCurve extends SimpleCurveBase {
  constructor() {
    super();
  }

  static define() {
    return {
      params  : {
        mode_in  : ["in", true, BOOL_FLAG, BOOL_FLAG],
        mode_out : ["out", true, BOOL_FLAG, BOOL_FLAG],
        amplitude: ["Amplitude", 1.0, 0.01, 4.0]
      },
      name    : "ease",
      uiname  : "Ease",
      typeName: "EaseCurve"
    }
  }

  evaluate(t) {
    let amp = this.params.amplitude;
    let a1 = this.params.mode_in ? 1.0 - amp : 1.0/3.0;
    let a2 = this.params.mode_out ? amp : 2.0/3.0;

    return bez4(0.0, a1, a2, 1.0, t);
  }
}

CurveTypeData.register(EaseCurve);
EaseCurve.STRUCT = nstructjs.inherit(EaseCurve, SimpleCurveBase) + `
}`;
nstructjs.register(EaseCurve);


export class RandCurve extends SimpleCurveBase {
  constructor() {
    super();
    this.random = new util.MersenneRandom();
    this.seed = 0;
  }

  get seed() {
    return this._seed;
  }

  set seed(v) {
    this.random.seed(v);
    this._seed = v;
  }

  static define() {
    return {
      params  : {
        amplitude: ["Amplitude", 1.0, 0.01, 4.0],
        decay    : ["Decay", 1.0, 0.0, 5.0],
        in_mode  : ["In", true, BOOL_FLAG, BOOL_FLAG]
      },
      name    : "random",
      uiname  : "Random",
      typeName: "RandCurve"
    }
  }

  evaluate(t) {
    let r = this.random.random();
    let decay = this.params.decay + 1.0;
    let amp = this.params.amplitude;
    let in_mode = this.params.in_mode;

    if (in_mode) {
      t = 1.0 - t;
    }
    //r *= t;

    let d;

    //r *= 0.5;

    if (in_mode) {
      d = Math.exp(t*decay)/Math.exp(decay);
    } else {
      d = Math.exp(t*decay)/Math.exp(decay);
    }

    t = t + (r - t)*d;

    if (in_mode) {
      t = 1.0 - t;
    }

    return t;
  }
}

CurveTypeData.register(RandCurve);
RandCurve.STRUCT = nstructjs.inherit(RandCurve, SimpleCurveBase) + `
}`;
nstructjs.register(RandCurve);