bebraw
10/21/2013 - 11:59 AM

Arbitrary hermite curve thingy for Canvas.

Arbitrary hermite curve thingy for Canvas.

$(main);

function main() {
  var $c = $('#demo');
  var ctx = $c[0].getContext('2d');
  ctx.translate(0, $c.height());
  ctx.scale(1, -1);
  var points = [[0, 0], [55, 43], [94, 226], [255, 255]];
  
  drawCurve(ctx, hermite(points));
}

// http://en.wikibooks.org/wiki/Cg_Programming/Unity/Hermite_Curves
function hermite(points) {
  var pointsPerStep, pointsPerSegment;
  var i, len, p0, p1;
  var ret = [];
  
  for(i = 0, len = points.length - 1; i < len; i++) {
    p0 = points[i];
    p1 = points[i + 1];
    
    pointsPerSegment = (p1[0] - p0[0]) * 2;
    pointsPerStep = 1 / pointsPerSegment;
    
    if(i == points.length - 2) {
      // the last point of the last segment should reach p1
      pointsPerStep = 1 / (pointsPerSegment - 1);
    }
    
    ret.push({
      p0: p0,
      p1: p1,
      m0: Vec2D.mul(Vec2D.sub(p1, i > 0? points[i - 1]: p0), 0.5),
      m1: Vec2D.mul(Vec2D.sub(i < points.length - 2? points[i + 2]: p1, p0), 0.5),
      len: pointsPerSegment,
      step: pointsPerStep
    });
  }
  
  return ret;
}

function drawCurve(ctx, segments) {
  segments.forEach(drawSegment.bind(null, ctx));
}

function drawSegment(ctx, o) {
  var t, i, pos, p0, p1, m0, m1, fac0, fac1, fac2, fac3;
  
  for(i = 0; i < o.len; i++) {
    t = i * o.step;

    fac0 = 2.0 * t * t * t - 3.0 * t * t + 1.0;
    fac1 = t * t * t - 2.0 * t * t + t;
    fac2 = -2.0 * t * t * t + 3.0 * t * t;
    fac3 = t * t * t - t * t;

    pos = Vec2D.add(
      Vec2D.mul(o.p0, fac0),
      Vec2D.mul(o.m0, fac1),
      Vec2D.mul(o.p1, fac2),
      Vec2D.mul(o.m1, fac3)
    );
    
    ctx.strokeRect(pos[0], pos[1], 1, 1);
  }
}

var Vec2D = decorate({
    add: function(a, b) {return a + b;},
    sub: function(a, b) {return a - b;},
    div: function(a, b) {return a / b;},
    mul: function(a, b) {return a * b;}    
}, function(op) {
  return function() {
    var pos = arguments[0].slice();
    var i, len, arg;
    
    for(i = 1, len = arguments.length; i < len; i++) {
      arg = arguments[i];

      pos = update(pos, isNumber(arg)? [arg, arg]: arg);
    }
 
    return pos;
  };
  
  function update(pos, arg) {
    return pos.map(function(v, i) {
      return op(v, arg[i]);
    });
  }
});

function decorate(o, fn) {
  var ret = {};

  for(var k in o) {
    if(o.hasOwnProperty(k)) {
      ret[k] = fn(o[k]);
    }
  }
  
  return ret;
}

function isNumber(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}