ベジェ曲線、もうx回。 t==x の近似値を求めて返す

仕事中でも、3次方程式の解法を妄想するも、撃沈。
誤差1/10000でも、平均数回程度で収束するので、黙認。
これから、遅れを取り戻す!を組み込む。

// ベジェ曲線 (4点限定)
function QBezierCurve_create (x2, y2 , x3, y3) {
  var step;
  var err = 0.0001;

  x2 *= 3; y2 *= 3;
  x3 *= 3; y3 *= 3;
  
  return function (t) {
    if (t < 0 || 1 < t)
      throw new Error(t);

    var p = step || t;
    var a, b, c, d, x, s;

    do {
      a = 1 - p;
      b = a * a;
      c = p * p;
      d = c * p;
      
      x = x2 * b * p + x3 * a * c + d;
      s = t - x;
      p += s *.5;
    } while (err < Math.abs(s));

    step = p;
    return  y2 * b * p + y3 * a * c + d;
  };
}

これでOK?

<!DOCTYPE html>
<title>遅延時間を考えて移動する</title>
<meta charset="UTF-8">
<style>
canvas { background-color:black;}
</style>

<p>
<canvas id="hoge" width="300" height="300"></canvas>
</p>


<script>


//_______________
// CANVAS を利用して、点と線を簡単に描くもの
(function () {
  
  function Graffiti (canvas) {
    this.ctx = canvas.getContext ('2d');
  }
  
  
  function line (/*[x0, y0], [x1, y1], ...*/) {
    var index = 0;
    var x = arguments[index][0];
    var y = arguments[index][1];
    var arg;
    var ctx = this.ctx;
    
    ctx.beginPath ();
    ctx.moveTo (x, y);
    
    while (arg = arguments[++index]) {
      if ('b' === arg) {
        ctx.closePath ();
        break;
      }
      if ('bx' === arg) {
        ctx.closePath ();
        ctx.fillRect ();
        break;
      }
      ctx.lineTo (arg[0], arg[1]);
    }
    ctx.stroke ();
    
    return this;
  }
  
  function pset (x, y, c) {
    var ctx = this.ctx;
    if (c instanceof Array) {
      ctx.fillStyle = 'rgb(' + c[0] + ',' + c[1] +',' + c[2] + ')';
    }
    
    ctx.fillRect (x, y, 1, 1);
    return this;  
  }  


  function create (canvas) {
    if (! canvas.getContext)
      return null;
    return new Graffiti (canvas);
  }
  
  
  //__________
  
  Graffiti.prototype.line = line;
  Graffiti.prototype.pset = pset;
  
  Graffiti.create = create;
  
  this.Graffiti = Graffiti;
})();



//_______________

// ベジェ曲線 (4点限定)
function cubic_bezier (x2, y2 , x3, y3) {
  var step;
  var err = 0.0001;

  x2 *= 3; y2 *= 3;
  x3 *= 3; y3 *= 3;
  
  return function (t) {
    if (t < 0 || 1 < t)
      throw new Error(t);
    
    var p = step || t;
    var a, b, c, d, x, s;

    do {
      a = 1 - p;
      b = a * a;
      c = p * p;
      d = c * p;
      
      x = x2 * b * p + x3 * a * c + d;
      s = t - x;
      p += s *.5;
    } while (err < Math.abs(s));

    step = p;
    return  y2 * b * p + y3 * a * c + d;
  };
}




  if ('undefined' === typeof this.requestAnimationFrame) {
    this.requestAnimationFrame =
      this.requestAnimationFrame ||
      this.webkitRequestAnimationFrame||
      this.mozRequestAnimationFrame   ||
      this.oRequestAnimationFrame     ||
      this.msRequestAnimationFrame    ||
      function (callback, that) {
        var tmpFunc = function () {
          var timestamp = +(new Date); //(new Date).getTime ();
          callback (timestamp); //callback.call (that, timestamp);
        };
        setTimeout (tmpFunc, Math.floor (1000/60));
      };
  }

(function () {
  //_________________
  // アニメーションの遅延を感知しながら補う
  function CompensateInterval (cbFunc, duration, timingFunc, delay, maxCompensate, interval, through) {
    this.cbFunc         = cbFunc; // コールバック
    this.duration       = duration; // 作動時間
    this.timingFunc     = timingFunc; // ベジェ曲線の関数
    this.delay          = delay;  // ms 秒後に開始
    this.maxCompensate  = maxCompensate; //最大補間回数
    this.interval       = interval; // 間隔
    this.through        = through; // 遅延が発生したならコールバックを呼ばない (default: true)
    
    this.step           = 1 / (duration / interval);
    this.count          = null;
    this.startTime      = null;
    this.timeCount      = null; //ベジェ曲線に渡す引数(time)
  }
  
  
  function loop (timeStamp) {
    var max = this.maxCompensate;
    var delay = (timeStamp - this.startTime) - this.interval * this.count;
    var r;


    while (0 <= delay) {
      if (max === this.maxCompensate || ! this.through) { //遅延中&スルーするなら何もしない
        r = this.timingFunc (this.timeCount);
        this.cbFunc (r, this.timeCount);
      }

      this.timeCount += this.step;

      if (1 <= this.timeCount)
        return 1, 1;

      this.count += 1;

      if (0 === max++) //打ち切り
        break;

      delay -= this.interval;
    }

    return requestAnimationFrame (loop.bind (this));
  }
  
  
  function CompensateInterval_init () {
    this.count = 0;
    this.timeCount = 0;
    this.startTime = (new Date).getTime ();
    loop.call (this);
  }
  
  
  function CompensateInterval_start () {
    if (this.delay)
      setTimeout (CompensateInterval_init.bind (this), this.delay);
    else
      CompensateInterval_init.call (this);
  }
  
  
  function CompensateInterval_create (cbFunc, duration, timingFunc, delay, maxCompensate, interval, through) {
    if (3 > arguments.length)
      throw new Error;
    
    switch (typeof timingFunc) {
    case 'function' :
      break;
    
    case 'string' :
      switch (timingFunc) {
      
      case 'ease' :
        timingFunc = cubic_bezier (0.25, 0.1, 0.25, 1.0);
        break;
      
      case 'linear' :
        timingFunc = cubic_bezier (0.0, 0.0, 1.0, 1.0);
        break;
        
      case 'ease-in' :
        timingFunc = cubic_bezier (0.42, 0, 1.0, 1.0);
        break;
        
      case 'ease-out' :
        timingFunc = cubic_bezier (0, 0, 0.58, 1.0);
        break;
      
      default :
        throw new Error ('Not know name');
      }
      break;
    
    default :
      throw new Error ('Syntax error');
    }
    
    return new CompensateInterval (cbFunc, duration, timingFunc, delay || 0, maxCompensate || 3, interval || 1000 / 60, through || true);
  }
  
  
  CompensateInterval.prototype.start = CompensateInterval_start;
  CompensateInterval.create          = CompensateInterval_create;
  
  this.CompensateInterval = CompensateInterval;
}) ();

//___________________________________________



var canvas = Graffiti.create (document.querySelector ('#hoge'));

function hoge (y, c) {
  var scl = 300;
  console.log(c.toFixed(4) + " | "+ y +"|"+ (new Date() - time) + "ms");
  canvas.pset (c*scl, scl-y*scl, [255, 0, 0]);
}

var time = new Date;

var fuga = CompensateInterval.create (hoge, 1000, 'linear', 0, 4, 100, false);
fuga.start();


</script>