使用基数样条曲线的运动路径

时间:2016-04-05 05:37:56

标签: javascript html5 canvas spline

我重新格式化了一些我发现使用基数样条曲线的代码并绘制了一条曲线,给出了一组可与我的Canvas库一起使用的点,它工作得非常好,但后来我还想使用所述技术沿给定的集合移动对象点 - 一条路。 SO有几个与我的问题有关的问题,我试图实现this问题的最后一个答案,但老实说,我不知道他的代码中的一半变量是什么意思。这是我的librarycurve object本身:

Art.prototype.modules.display.Base.extend({
  constructor: function (options) {

    // Declare variables for brevity.

    var extend = Art.prototype.modules.utility.extend,
      defaults = {
        points: [],
        tension: 0.5,
        closed: false
      };

    // Extend the object with the defaults overwritten by the options.

    extend(this, extend(defaults, options));

  },
  id: 'curve',
  draw: function () {

    // Declare variables for brevity.

    var t = this,
      graphics = Art.prototype.modules.display.curve.core.graphics,
      controls = [],
      n = t.points.length,
      getControlPoints = function (a, b, c, d, e, f, tension) {
        var x = {
          x: Math.sqrt(Math.pow(c - a, 2) + Math.pow(d - b, 2)),
          y: Math.sqrt(Math.pow(e - c, 2) + Math.pow(f - d, 2))
        };
        var y = {
          x: tension * x.x / (x.x + x.y)
        };
        y.y = tension - y.x;
        var z = {
          x: c + y.x * (a - e),
          y: d + y.x * (b - f)
        };
        var $z = {
          x: c - y.y * (a - e),
          y: d - y.y * (b - f)
        };
        return [z.x, z.y, $z.x, $z.y];
      };

    graphics.strokeStyle = t.stroke;

    graphics.lineWidth = t.lineWidth;

    if (t.closed) {
      t.points.push(t.points[0], t.points[1], t.points[2], t.points[3]);
      t.points.unshift(t.points[n - 1]);
      t.points.unshift(t.points[n - 1]);
      for (var p = 0; p < n; p += 2) {
        controls = controls.concat(getControlPoints(t.points[p], t.points[p + 1], t.points[p + 2], t.points[p + 3], t.points[p + 4], t.points[p + 5], t.tension));
      }
      controls = controls.concat(controls[0], controls[1]);
      for (var $p = 2; $p < n + 2; $p += 2) {
        graphics.beginPath();
        graphics.moveTo(t.points[$p], t.points[$p + 1]);
        graphics.bezierCurveTo(controls[2 * $p - 2], controls[2 * $p - 1], controls[2 * $p], controls[2 * $p + 1], t.points[$p + 2], t.points[$p + 3]);
        graphics.stroke();
        graphics.closePath();
      }
    } else {
      for (var p = 0; p < n - 4; p += 2) {
        controls = controls.concat(getControlPoints(t.points[p], t.points[p + 1], t.points[p + 2], t.points[p + 3], t.points[p + 4], t.points[p + 5], t.tension));
      }
      for (var $p = 2; $p < t.points.length - 5; $p += 2) {
        graphics.beginPath();
        graphics.moveTo(t.points[$p], t.points[$p + 1]);
        graphics.bezierCurveTo(controls[2 * $p - 2], controls[2 * $p - 1], controls[2 * $p], controls[2 * $p + 1], t.points[$p + 2], t.points[$p + 3]);
        graphics.stroke();
        graphics.closePath();
      }
      graphics.beginPath();
      graphics.moveTo(t.points[0], t.points[1]);
      graphics.quadraticCurveTo(controls[0], controls[1], t.points[2], t.points[3]);
      graphics.stroke();
      graphics.closePath();
      graphics.beginPath();
      graphics.moveTo(t.points[n - 2], t.points[n - 1]);
      graphics.quadraticCurveTo(controls[2 * n - 10], controls[2 * n - 9], t.points[n - 4], t.points[n - 3]);
      graphics.stroke();
      graphics.closePath();
    }

    return this;

  }
});

我不一定希望代码在银盘上交给我(虽然......那会很好) - 相反,我想学习所涉及的数学,但最好是用伪代码和相对简单的术语。我联系到的SO答案的解释会特别有用,因为它很好用。

1 个答案:

答案 0 :(得分:1)

使用替代实现(https://gitlab.com/epistemex/cardinal-spline-js免责声明:我是作者)将以简单的方式在您需要的路径上生成所有点。

  • 您现在可以计算总长度
  • 在返回的数组中找到相应的段
  • 规范化余数以找到路径上的确切位置

实施例

在将点作为样条点阵列获得之后,主函数将遍历数组以找到所需位置所在的两个点的段。接下来,它将在这些位置之间插值以获得最终(x,y)位置(这里有足够的优化空间):

这允许我们以均匀的速度沿着样条移动 -

function getXY(points, pos, length) {

  var len = 0,             // total length so far
      lastLen,             // last segment length
      i,                   // iterator
      l = points.length;   // length of point array

  // find segment
  for(i = 2; i < l; i += 2) {

    // calculate length of this segment
    lastLen = dist(points[i], points[i+1], points[i-2], points[i-1]);

    // add to total length for now    
    len += lastLen;

    // are we inside a segment?
    if (pos < len && lastLen) {
      len -= lastLen;     // to back to beginning
      pos -= len;         // adjust position so we can normalize

      return {
        // interpolate  prev X + (next X - prev X) * normalized
        x: points[i-2] + (points[i] - points[i-2])   * (pos / lastLen),
        y: points[i-1] + (points[i+1] - points[i-1]) * (pos / lastLen)
      }
    }
  }
}

完整示例

var ctx = document.querySelector("canvas").getContext("2d"),
    points = [
      10,  10,    // x,y pairs
      100, 50,
      500, 100,
      600, 200,
      400, 220,
      200, 90
    ],
    spline = getCurvePoints(points),
    length = getLength(spline),
    t = 0,
    dx = 3;  // number of pixels to move object

// move along path:
(function loop() {

  // make t ping-pong, and clamp t to [0, (length-1)]
  t += dx;
  if (t < 0 || t >= length) dx = -dx;
  t = Math.max(0, Math.min(length - 1, t));

  // find segment in points which t is inside:
  var pos = getXY(spline, t, length);

  // redraw
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  render();

  // show marker
  ctx.fillRect(pos.x - 3, pos.y - 3, 6, 6);

  requestAnimationFrame(loop)
})();

function render(points) {
  ctx.beginPath();
  ctx.moveTo(spline[0], spline[1]);
  for(var i = 2; i < spline.length; i+=2)
    ctx.lineTo(spline[i], spline[i+1]);
  ctx.stroke()
}

function getXY(points, pos, length) {

  var len = 0, lastLen, i, l = points.length;

  // find segment
  for(i = 2; i < l; i += 2) {
    lastLen = dist(points[i], points[i+1], points[i-2], points[i-1]);

    len += lastLen;
    if (pos < len && lastLen) {
      len -= lastLen;
      pos -= len;

      return {
        x: points[i-2] + (points[i] - points[i-2]) * (pos / lastLen),
        y: points[i-1] + (points[i+1] - points[i-1]) * (pos / lastLen)
      }
    }
  }

  return null
}

function getLength(points) {
  for(var len = 0, i = 0, dx, dy; i < points.length - 2; i+=2) {
    len += dist(points[i+2], points[i+3], points[i], points[i+1])
  }
  return len
}

function dist(x1, y1, x2, y2) {
  var dx = x2 - x1,
      dy = y2 - y1;
  return Math.sqrt(dx*dx + dy*dy)
}