You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何无需高成本预计算即可实现贝塞尔曲线匀速运动

二次贝塞尔曲线没有直接通过长度百分比计算坐标的闭式解析公式,不过可以用高斯积分+牛顿迭代的方案完全替代你现在用的预计算查找表,性能、精度、内存占用都远好于现有方案。


核心实现逻辑

  • 首先实现弧长快速计算函数:用2阶或3阶高斯-勒让德积分,仅需3~5个采样点就能得到误差小于1e-6的弧长结果,比逐段累加1000次的效率高100倍以上。
  • 然后实现牛顿迭代求解t值:给定长度百分比s(01),通过迭代快速逼近对应的参数t,一般34次迭代就能达到视觉无差的精度。

修改后完整代码

let c = document.querySelector("canvas");
let ctx = c.getContext("2d");

class BezierCurve {
  constructor(x1, y1, cpX, cpY, x2, y2) {
    this.f = 0;
    this.x1 = x1;
    this.y1 = y1;
    this.cpX = cpX;
    this.cpY = cpY;
    this.x2 = x2;
    this.y2 = y2;
    // 仅预计算总长度,一次计算即可
    this.totalLength = this.calcArcLength(1);
  }

  calcX(t) { return (1 - t) * (1 - t) * this.x1 + 2 * (1 - t) * t * this.cpX + t * t * this.x2; }
  calcY(t) { return (1 - t) * (1 - t) * this.y1 + 2 * (1 - t) * t * this.cpY + t * t * this.y2; }

  // 计算t处一阶导数的模长(即弧长对t的导数)
  calcDerivativeMod(t) {
    const dx = 2 * (1 - t) * (this.cpX - this.x1) + 2 * t * (this.x2 - this.cpX);
    const dy = 2 * (1 - t) * (this.cpY - this.y1) + 2 * t * (this.y2 - this.cpY);
    return Math.sqrt(dx * dx + dy * dy);
  }

  // 3阶高斯-勒让德积分计算0到t的弧长
  calcArcLength(t) {
    const gaussPoints = [
      { x: -0.7745966692, w: 0.5555555556 },
      { x: 0, w: 0.8888888889 },
      { x: 0.7745966692, w: 0.5555555556 }
    ];
    let sum = 0;
    const halfT = t / 2;
    for (const p of gaussPoints) {
      const curT = halfT * (p.x + 1);
      sum += p.w * this.calcDerivativeMod(curT);
    }
    return halfT * sum;
  }

  // 输入长度百分比s(0~1),返回对应的贝塞尔参数t
  getTByLengthPercent(s) {
    if (s <= 0) return 0;
    if (s >= 1) return 1;
    let t = s;
    const targetLength = s * this.totalLength;
    // 迭代4次即可达到1e-6级精度
    for (let i = 0; i < 4; i++) {
      const currentLength = this.calcArcLength(t);
      const derivative = this.calcDerivativeMod(t);
      if (derivative < 1e-8) break;
      t -= (currentLength - targetLength) / derivative;
      t = Math.max(0, Math.min(1, t));
    }
    return t;
  }

  draw() {
    ctx.beginPath();
    ctx.moveTo(this.x1, this.y1);
    ctx.quadraticCurveTo(this.cpX, this.cpY, this.x2, this.y2);
    ctx.stroke();
  }

  tick(amount = 0.001) {
    this.f = this.f < 1 ? this.f + amount : 0;
  }
}

function drawCircle(x, y, r) {
  ctx.beginPath();
  ctx.arc(x, y, r, 0, 2 * Math.PI);
  ctx.fill();
}

let a = new BezierCurve(25, 25, 80, 250, 100, 50);
let b = new BezierCurve(225, 25, 280, 250, 300, 50);

function draw(curve, fraction) {
  let x = curve.calcX(fraction);
  let y = curve.calcY(fraction);

  curve.draw();
  drawCircle(x, y, 5);

  curve.tick();
}

function drawLoop(elapsed) {  
  c.width = 600;
  c.height = 600;
  
  draw(a, a.f);
  // 直接调用方法获取对应长度百分比的参数t
  draw(b, b.getTByLengthPercent(b.f));

  requestAnimationFrame(drawLoop);
}

drawLoop(0);
<canvas></canvas>

方案优势

  • 内存占用极低:仅存储一个总长度数值,不需要存储上千个映射点
  • 初始化开销几乎为0:实例化时仅计算一次总长度,3次采样即可完成,远低于原方案预计算1000次的开销
  • 精度可控:普通视觉场景3次迭代就足够,需要更高精度时增加迭代次数即可
  • 运行时性能高:单次查询仅需4次迭代,每次迭代只有3次采样计算,每帧调用上百次也不会有性能压力

你需要的50%长度位置坐标可以直接通过curve.calcX(curve.getTByLengthPercent(0.5))获取,完全符合需求。


内容的提问来源于stack exchange,提问作者Ryan Peschel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 03:24:00