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

如何计算贝塞尔曲线上弧长中点及实现点沿曲线匀速运动?

贝塞尔曲线参数t与匀速移动解决方案

你当前使用的二次贝塞尔曲线计算公式完全符合数学定义,出现偏差的核心原因是贝塞尔曲线的参数t不属于弧长参数:t仅代表参数空间的插值进度,和沿曲线实际移动的路程长度没有线性对应关系,所以t=0.5对应的点不是曲线的几何中点,同时也会出现沿曲线变速移动的现象。

匀速移动实现方案

实现沿贝塞尔曲线匀速移动的通用方案是弧长重映射,通过预计算弧长查找表建立路程进度到参数t的映射关系,步骤如下:

1. 预生成弧长查找表

将t从0到1切分为足够多的采样段(通常100~200段即可满足普通场景精度需求),依次计算每个采样点的坐标,累加相邻两点的直线距离,得到每个采样t对应的累计弧长,存储为查找表。

// 采样精度,数值越高精度越高
const SAMPLE_COUNT = 100;
// 弧长查找表,每个元素结构为 { t: 采样t值, len: 累计弧长 }
let arcLengthTable = [];

function buildArcLengthTable() {
  arcLengthTable = [];
  let prevX = x1, prevY = y1;
  let totalLen = 0;
  arcLengthTable.push({ t: 0, len: 0 });
  for (let i = 1; i <= SAMPLE_COUNT; i++) {
    const t = i / SAMPLE_COUNT;
    const x = calcX(t);
    const y = calcY(t);
    // 累加当前段距离
    const dx = x - prevX;
    const dy = y - prevY;
    totalLen += Math.sqrt(dx*dx + dy*dy);
    arcLengthTable.push({ t, len: totalLen });
    prevX = x;
    prevY = y;
  }
}

2. 路程进度转参数t

当你需要获取沿曲线走了s比例(s范围0~1,0为起点,1为终点)的点时,先计算目标弧长为总弧长*s,再在查找表中匹配对应区间插值得到实际t值:

function getTByProgress(s) {
  const totalLength = arcLengthTable[arcLengthTable.length - 1].len;
  const targetLen = s * totalLength;
  // 二分查找目标弧长所在的区间
  let low = 0, high = arcLengthTable.length - 1;
  while (low < high) {
    const mid = Math.floor((low + high) / 2);
    if (arcLengthTable[mid].len < targetLen) {
      low = mid + 1;
    } else {
      high = mid;
    }
  }
  // 区间边界处理
  if (low === 0) return arcLengthTable[0].t;
  // 线性插值得到精确t值
  const prev = arcLengthTable[low - 1];
  const curr = arcLengthTable[low];
  const segmentLen = curr.len - prev.len;
  const segmentProgress = (targetLen - prev.len) / segmentLen;
  return prev.t + segmentProgress * (curr.t - prev.t);
}

3. 替换原有逻辑

在动画循环中,直接将进度s传入getTByProgress得到实际t,再计算坐标即可实现匀速移动,此时s=0.5对应的点就是曲线的几何中点,符合大众认知。
修改后的动画循环核心逻辑:

let progress = 0;
function drawLoop(elapsed) {  
  c.width = 600;
  c.height = 600;
  
  // 用进度获取映射后的t值
  const t = getTByProgress(progress);
  let x = calcX(t);
  let y = calcY(t);

  drawCurve();

  ctx.beginPath();
  ctx.rect(x, y, 3, 3);
  ctx.stroke();
  
  progress = progress < 1 ? progress + 0.001 : 0;
  document.querySelector(".debug").innerHTML = progress.toFixed(2);

  requestAnimationFrame(drawLoop);
}

// 初始化时先构建弧长表
buildArcLengthTable();
drawLoop(0);

优化提示

如果你的曲线控制点是动态变化的,每次控制点修改后重新调用buildArcLengthTable更新查找表即可。如果对性能要求极高,可以降低采样精度,或者使用近似公式计算弧长,普通场景下100次采样的性能损耗可以忽略不计。

内容的提问来源于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.28 00:54:04