Home Reference Source

scripts/path-controller/curve/curve1d_basic.js

import nstructjs from "../util/struct.js";
import {CurveFlags, TangentModes, CurveTypeData} from './curve1d_base.js';
import {Vector2, Vector3, Vector4, Quat, Matrix4} from '../util/vectormath.js';
import {genHermiteTable, evalHermiteTable} from './curve1d_base.js';
import * as util from '../util/util.js';

let _udigest = new util.HashDigest();

function feq(a, b) {
  return Math.abs(a - b) < 0.00001;
}

class EquationCurve extends CurveTypeData {
  constructor(type) {
    super();

    this.equation = "x";
    this._last_equation = "";
    this.hermite = undefined;
  }

  get hasGUI() {
    return this.uidata !== undefined;
  }

  static define() {
    return {
      uiname  : "Equation",
      name    : "equation",
      typeName: "EquationCurve"
    }
  }

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

    super.calcHashKey(d);

    d.add(this.equation);

    return d.get();
  }

  equals(b) {
    return super.equals(b) && this.equation === b.equation;
  }

  toJSON() {
    let ret = super.toJSON();

    return Object.assign(ret, {
      equation: this.equation
    });
  }

  loadJSON(obj) {
    super.loadJSON(obj);

    if (obj.equation !== undefined) {
      this.equation = obj.equation;
    }

    return this;
  }

  makeGUI(container, canvas, drawTransform) {
    this.uidata = {
      canvas    : canvas,
      g         : canvas.g,
      draw_trans: drawTransform,
    };

    let row = container.row();

    let text = this.uidata.textbox = row.textbox(undefined, ""+this.equation);
    text.onchange = (val) => {
      console.log(val);
      this.equation = val;
      this.update();
      this.redraw();
    }

    container.label("Equation");

  }

  killGUI(dom, gui, canvas, g, draw_transform) {
    if (this.uidata !== undefined) {
      this.uidata.textbox.remove();
    }

    this.uidata = undefined;
  }

  updateTextBox() {
    if (this.uidata && this.uidata.textbox) {
      this.uidata.textbox.text = this.equation;
    }
  }

  evaluate(s) {
    if (!this.hermite || this._last_equation !== this.equation) {
      this._last_equation = this.equation;

      this.updateTextBox();

      this._evaluate(0.0);

      if (this._haserror) {
        console.warn("ERROR!");
        return 0.0;
      }

      let steps = 32;
      this.hermite = genHermiteTable((s) => this._evaluate(s), steps);
    }

    return evalHermiteTable(this.hermite, s);
  }

  _evaluate(s) {
    let sin = Math.sin, cos = Math.cos, pi = Math.PI, PI = Math.PI,
        e                                                = Math.E, E                                    = Math.E, tan                      = Math.tan, abs      = Math.abs,
        floor                                            = Math.floor, ceil                         = Math.ceil, acos = Math.acos,
        asin                                             = Math.asin, atan                           = Math.atan, cosh = Math.cos,
        sinh                                             = Math.sinh, log                            = Math.log, pow = Math.pow,
        exp                                              = Math.exp, sqrt                             = Math.sqrt, cbrt = Math.cbrt,
        min                                              = Math.min, max = Math.max;

    try {
      let x = s;
      let ret = eval(this.equation);

      this._haserror = false;

      return ret;
    } catch (error) {
      this._haserror = true;
      console.warn("ERROR!");
      return 0.0;
    }
  }

  derivative(s) {
    let df = 0.0001;

    if (s > 1.0 - df*3) {
      return (this.evaluate(s) - this.evaluate(s - df))/df;
    } else if (s < df*3) {
      return (this.evaluate(s + df) - this.evaluate(s))/df;
    } else {
      return (this.evaluate(s + df) - this.evaluate(s - df))/(2*df);
    }
  }

  derivative2(s) {
    let df = 0.0001;

    if (s > 1.0 - df*3) {
      return (this.derivative(s) - this.derivative(s - df))/df;
    } else if (s < df*3) {
      return (this.derivative(s + df) - this.derivative(s))/df;
    } else {
      return (this.derivative(s + df) - this.derivative(s - df))/(2*df);
    }
  }

  inverse(y) {
    let steps = 9;
    let ds = 1.0/steps, s = 0.0;
    let best = undefined;
    let ret = undefined;

    for (let i = 0; i < steps; i++, s += ds) {
      let s1 = s, s2 = s + ds;

      let mid;

      for (let j = 0; j < 11; j++) {
        let y1 = this.evaluate(s1);
        let y2 = this.evaluate(s2);
        mid = (s1 + s2)*0.5;

        if (Math.abs(y1 - y) < Math.abs(y2 - y)) {
          s2 = mid;
        } else {
          s1 = mid;
        }
      }

      let ymid = this.evaluate(mid);

      if (best === undefined || Math.abs(y - ymid) < best) {
        best = Math.abs(y - ymid);
        ret = mid;
      }
    }

    return ret === undefined ? 0.0 : ret;
  }

  onActive(parent, draw_transform) {
  }

  onInactive(parent, draw_transform) {
  }

  reset() {
    this.equation = "x";
  }

  destroy() {
  }

  draw(canvas, g, draw_transform) {
    g.save();
    if (this._haserror) {

      g.fillStyle = g.strokeStyle = "rgba(255, 50, 0, 0.25)";
      g.beginPath();
      g.rect(0, 0, 1, 1);
      g.fill();

      g.beginPath();
      g.moveTo(0, 0);
      g.lineTo(1, 1);
      g.moveTo(0, 1);
      g.lineTo(1, 0);

      g.lineWidth *= 3;
      g.stroke();

      g.restore();
      return;
    }

    g.restore();
  }
}

EquationCurve.STRUCT = nstructjs.inherit(EquationCurve, CurveTypeData) + `
  equation : string;
}
`;
nstructjs.register(EquationCurve);
CurveTypeData.register(EquationCurve);


class GuassianCurve extends CurveTypeData {
  constructor(type) {
    super();

    this.height = 1.0;
    this.offset = 1.0;
    this.deviation = 0.3; //standard deviation
  }

  get hasGUI() {
    return this.uidata !== undefined;
  }

  static define() {
    return {
      uiname  : "Guassian",
      name    : "guassian",
      typeName: "GuassianCurve"
    }
  }

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

    let d = digest;

    d.add(this.height);
    d.add(this.offset);
    d.add(this.deviation);

    return d.get();
  }

  equals(b) {
    let r = super.equals(b);

    r = r && feq(this.height, b.height);
    r = r && feq(this.offset, b.offset);
    r = r && feq(this.deviation, b.deviation);

    return r;
  }

  toJSON() {
    let ret = super.toJSON();

    return Object.assign(ret, {
      height   : this.height,
      offset   : this.offset,
      deviation: this.deviation
    });
  }

  loadJSON(obj) {
    super.loadJSON(obj);

    this.height = obj.height !== undefined ? obj.height : 1.0;
    this.offset = obj.offset;
    this.deviation = obj.deviation;

    return this;
  }

  makeGUI(container, canvas, drawTransform) {
    this.uidata = {
      canvas    : canvas,
      g         : canvas.g,
      draw_trans: drawTransform,
    };

    this.uidata.hslider = container.slider(undefined, "Height", this.height, -10, 10, 0.0001);
    this.uidata.hslider.onchange = () => {
      this.height = this.uidata.hslider.value;
      this.redraw();
      this.update();
    }
    this.uidata.oslider = container.slider(undefined, "Offset", this.offset, -10, 10, 0.0001);
    this.uidata.oslider.onchange = () => {
      this.offset = this.uidata.oslider.value;
      this.redraw();
      this.update();
    }
    this.uidata.dslider = container.slider(undefined, "STD Deviation", this.deviation, -10, 10, 0.0001);
    this.uidata.dslider.onchange = () => {
      this.deviation = this.uidata.dslider.value;
      this.redraw();
      this.update();
    }

    /*
    this.uidata.oslider = gui.slider(undefined, "Offset", this.offset,
      -2.5, 2.5, 0.0001, false, false, (val) => {this.offset = val, this.update(), this.redraw();});
    this.uidata.dslider = gui.slider(undefined, "STD Deviation", this.deviation,
      0.0001, 1.25, 0.0001, false, false, (val) => {this.deviation = val, this.update(), this.redraw();});
    //*/
  }

  killGUI(dom, gui, canvas, g, draw_transform) {
    if (this.uidata !== undefined) {
      this.uidata.hslider.remove();
      this.uidata.oslider.remove();
      this.uidata.dslider.remove();
    }

    this.uidata = undefined;
  }

  evaluate(s) {
    let r = this.height*Math.exp(-((s - this.offset)*(s - this.offset))/(2*this.deviation*this.deviation));
    return r;
  }

  derivative(s) {
    let df = 0.0001;

    if (s > 1.0 - df*3) {
      return (this.evaluate(s) - this.evaluate(s - df))/df;
    } else if (s < df*3) {
      return (this.evaluate(s + df) - this.evaluate(s))/df;
    } else {
      return (this.evaluate(s + df) - this.evaluate(s - df))/(2*df);
    }
  }

  derivative2(s) {
    let df = 0.0001;

    if (s > 1.0 - df*3) {
      return (this.derivative(s) - this.derivative(s - df))/df;
    } else if (s < df*3) {
      return (this.derivative(s + df) - this.derivative(s))/df;
    } else {
      return (this.derivative(s + df) - this.derivative(s - df))/(2*df);
    }
  }

  inverse(y) {
    let steps = 9;
    let ds = 1.0/steps, s = 0.0;
    let best = undefined;
    let ret = undefined;

    for (let i = 0; i < steps; i++, s += ds) {
      let s1 = s, s2 = s + ds;

      let mid;

      for (let j = 0; j < 11; j++) {
        let y1 = this.evaluate(s1);
        let y2 = this.evaluate(s2);
        mid = (s1 + s2)*0.5;

        if (Math.abs(y1 - y) < Math.abs(y2 - y)) {
          s2 = mid;
        } else {
          s1 = mid;
        }
      }

      let ymid = this.evaluate(mid);

      if (best === undefined || Math.abs(y - ymid) < best) {
        best = Math.abs(y - ymid);
        ret = mid;
      }
    }

    return ret === undefined ? 0.0 : ret;
  }
}

GuassianCurve.STRUCT = nstructjs.inherit(GuassianCurve, CurveTypeData) + `
  height    : float;
  offset    : float;
  deviation : float;
}
`;
nstructjs.register(GuassianCurve);
CurveTypeData.register(GuassianCurve);