js:使用规划算法思想生成平滑贝塞尔曲线

2023-12-17  本文已影响0人  时间煮鱼

最终效果路线(红色线)

ps: 这样做出来的,并不一定是最优的,姑且能用吧

1702890718753.png

先说问题:

当前有:两条带方向的直线和一条贝塞尔曲线,贝塞尔曲线的两端连接两条直线的各一端;
那么:有一辆车沿着这组成的线段行驶,如何将贝塞尔修改成最优的曲线使车最少进行原地旋转

解法思虑:

1、找到两条带方向直线的方向(航向)角度
2、沿起点线方向延长出n(我这边是12)个点&沿终止线反方向延长出n个点

1702889942576.png
将这12 * 12个点,两两组合再加上起点线的末端和终止线的首端生成144条贝塞尔:(这12 * 12是两个控制点)
3、将这144条贝塞尔,每一条切分m(我这边是100)份,计算出每一份的曲率,然后计算这m个曲率的方差
4、找到方差最小的一条就是最平滑的贝塞尔

部分核心代码

// 计算平滑的贝塞尔曲线
export function getSmoothBezier(startPoint, startAngle, endPoint, endAngle) {
  const len = Math.sqrt((startPoint.x - endPoint.x) ** 2 + (startPoint.y - endPoint.y) ** 2);
  const segs = 12; // 将起点和终点之间的距离分成12段
  const step = parseInt(len / segs);
  const allBeziers = []; // 存放所有生成的贝塞尔曲线
  // 起始线段&终点线段延长
  for (let i = 0; i < segs; i++) {
    for (let j = 0; j < segs; j++) {
      allBeziers.push({
        startPoint: { ...startPoint },
        startControlPoint: {
          x: startPoint.x + step * Math.cos(startAngle) * i,
          y: startPoint.y + step * Math.sin(startAngle) * i
        },
        endControlPoint: {
          x: endPoint.x - step * Math.cos(endAngle) * j,
          y: endPoint.y - step * Math.sin(endAngle) * j
        },
        endPoint: { ...endPoint }
      });
    }
  }
  const minBezier = getMinCurvatureBezier(allBeziers);
  return minBezier;
}

// 计算最小曲率的贝塞尔
export function getMinCurvatureBezier(allBeziers) {
  let minBezier = calculateCurvature(allBeziers[0]);
  let minBezierIndex = allBeziers[0];
  allBeziers.forEach(bezier => {
    const curvature = calculateCurvature(bezier);
    if (curvature < minBezier) {
      minBezierIndex = bezier;
      minBezier = curvature;
    }
  });
  return minBezierIndex;
}

// 贝塞尔曲线计算函数
function bezier(t, p0, p1, p2, p3) {
  const u = 1 - t;
  const tt = t * t;
  const uu = u * u;
  const uuu = uu * u;
  const ttt = tt * t;

  const p = {};
  p.x = uuu * p0.x + 3 * uu * t * p1.x + 3 * u * tt * p2.x + ttt * p3.x;
  p.y = uuu * p0.y + 3 * uu * t * p1.y + 3 * u * tt * p2.y + ttt * p3.y;

  return p;
}

// 计算贝塞尔曲线的切线向量
function tangentVector(t, p0, p1, p2, p3) {
  const dt = 0.001; // 微小的增量
  const p1n = bezier(t - dt, p0, p1, p2, p3);
  const p1p = bezier(t + dt, p0, p1, p2, p3);

  // 切线向量
  const tangent = {
    x: p1p.x - p1n.x,
    y: p1p.y - p1n.y
  };

  return tangent;
}

// 计算曲率
function curvature(t, p0, p1, p2, p3) {
  const tangent = tangentVector(t, p0, p1, p2, p3);
  const speed = Math.sqrt(tangent.x ** 2 + tangent.y ** 2);

  // 曲率公式
  const curvature = speed / Math.pow((1 + tangent.x ** 2 + tangent.y ** 2), 1.5);

  return curvature;
}

// 将贝塞尔曲线平分为100段并计算曲率
function calculateCurvature(bezier) {
  const { startPoint, startControlPoint, endControlPoint, endPoint } = bezier;
  const numOfSegments = 100;
  const result = [];

  for (let i = 0; i < numOfSegments; i++) {
    const t = i / numOfSegments;
    const curv = curvature(t, startPoint, startControlPoint, endControlPoint, endPoint);
    result.push(curv);
  }

  // 计算方差
  const variance = calculateVariance(result);
  return variance;
}

// 计算方差
function calculateVariance(result) {
  let sum = 0;
  let sumSquare = 0;
  result.forEach(v => {
    sum += v;
    sumSquare += v ** 2;
  });
  const mean = sum / result.length;
  const variance = sumSquare / result.length - mean ** 2;
  return variance;
}

代码引用

// 计算最优贝塞尔曲线的控制点
export function getOptimalBezier(startLine, endLine) {
  const startLineStartPoint = convertPointInfo(startLine.startPointPosition);
  const startLineEndPoint = convertPointInfo(startLine.endPointPosition);
  const endLineStartPoint = convertPointInfo(endLine.startPointPosition);
  const endLineEndPoint = convertPointInfo(endLine.endPointPosition);

  let startLineAngle = Math.atan2(startLineEndPoint.y - startLineStartPoint.y, startLineEndPoint.x - startLineStartPoint.x);
  let endLineAngle = Math.atan2(endLineEndPoint.y - endLineStartPoint.y, endLineEndPoint.x - endLineStartPoint.x);

  const smoothBezier = getSmoothBezier(startLineEndPoint, startLineAngle, endLineStartPoint, endLineAngle);

  return {
    startControlPoint: {
      x: toFixed1(smoothBezier.startControlPoint.x),
      y: toFixed1(smoothBezier.startControlPoint.y)
    },
    endControlPoint: {
      x: toFixed1(smoothBezier.endControlPoint.x),
      y: toFixed1(smoothBezier.endControlPoint.y)
    }
  };
}
上一篇 下一篇

猜你喜欢

热点阅读