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

二次贝塞尔路径与垂直线的交点程序化计算问题

二次贝塞尔曲线与垂直线交点计算问题修复

问题重现

使用参考的二次贝塞尔曲线与直线交点计算代码时,测试数据能正常输出结果,但替换为以下SVG场景时无法得到正确交点:

  • 二次贝塞尔曲线:起点(200,300)、控制点(640,175)、终点(1080,300)
  • 垂直线:x=640,线段范围从(640,50)到(640,550)

问题原因

原代码在处理垂直线时会触发除以0的错误:
当直线为垂直线(a1.x == a2.x)时,计算得到的法向量normal.y = a2.x - a1.x = 0;同时该贝塞尔曲线的x方向二次项系数c2.x = p1.x - 2*p2.x + p3.x = 200 - 2*640 + 1080 = 0,导致a = normal.x*c2.x + normal.y*c2.y = 0。后续计算b和c时除以0,得到NaN值,无法正常求解交点。

修复方案

针对垂直线(x坐标固定)的场景,直接构建关于贝塞尔曲线参数t的二次方程求解,避开除以0的逻辑。修改后的完整代码如下:

修复后的JS代码

// 线性插值工具
var lerp = function(a, b, x) { return (a + x * (b - a)); };

// 当前SVG坐标点
var p1 = { x: 200, y: 300 };
var p2 = { x: 640, y: 175 };
var p3 = { x: 1080, y: 300 };
var a1 = { x: 640, y: 50 };
var a2 = { x: 640, y: 550 };

// 计算交点
var points = calcQLintersects(p1, p2, p3, a1, a2);

// 输出交点结果
var textPoints = '交点: ';
for (var i = 0; i < points.length; i++) {
  var p = points[i];
  textPoints += ' [' + parseInt(p.x) + ',' + parseInt(p.y) + ']';
}
console.log(textPoints);
console.log(points);

function calcQLintersects(p1, p2, p3, a1, a2) {
  var intersections = [];

  // 单独处理垂直线场景
  if (a1.x === a2.x) {
    var lineX = a1.x;
    // 构建二次贝塞尔曲线x(t) = lineX的方程:At² + Bt + C = 0
    var A = p1.x - 2*p2.x + p3.x;
    var B = 2*(p2.x - p1.x);
    var C = p1.x - lineX;

    var roots = [];
    if (A === 0) {
      // 退化为一次方程
      if (B !== 0) {
        var t = -C / B;
        if (t >= 0 && t <= 1) roots.push(t);
      }
    } else {
      var discriminant = B*B - 4*A*C;
      if (discriminant > 0) {
        var sqrtD = Math.sqrt(discriminant);
        roots.push((-B + sqrtD)/(2*A));
        roots.push((-B - sqrtD)/(2*A));
      } else if (discriminant === 0) {
        roots.push(-B/(2*A));
      }
    }

    // 验证交点是否在垂直线段范围内
    var minY = Math.min(a1.y, a2.y);
    var maxY = Math.max(a1.y, a2.y);
    for (var i = 0; i < roots.length; i++) {
      var t = roots[i];
      if (t >= 0 && t <= 1) {
        var y = (1-t)*(1-t)*p1.y + 2*t*(1-t)*p2.y + t*t*p3.y;
        if (y >= minY && y <= maxY) {
          intersections.push({x: lineX, y: y});
        }
      }
    }
    return intersections;
  }

  // 原逻辑处理非垂直线场景
  var normal = {
    x: a1.y - a2.y,
    y: a2.x - a1.x,
  };

  var c2 = {
    x: p1.x + p2.x * -2 + p3.x,
    y: p1.y + p2.y * -2 + p3.y
  };

  var c1 = {
    x: p1.x * -2 + p2.x * 2,
    y: p1.y * -2 + p2.y * 2,
  };

  var c0 = {
    x: p1.x,
    y: p1.y
  };

  var coefficient = a1.x * a2.y - a2.x * a1.y;
  var a = normal.x * c2.x + normal.y * c2.y;
  if (a === 0) return intersections;
  
  var b = (normal.x * c1.x + normal.y * c1.y) / a;
  var c = (normal.x * c0.x + normal.y * c0.y + coefficient) / a;

  var roots = [];
  var d = b * b - 4 * c;
  if (d > 0) {
    roots.push((-b + Math.sqrt(d)) / 2);
    roots.push((-b - Math.sqrt(d)) / 2);
  } else if (d == 0) {
    roots.push(-b / 2);
  }

  // 验证交点是否在线段范围内
  var minX = Math.min(a1.x, a2.x);
  var minY = Math.min(a1.y, a2.y);
  var maxX = Math.max(a1.x, a2.x);
  var maxY = Math.max(a1.y, a2.y);
  for (var i = 0; i < roots.length; i++) {
    var t = roots[i];
    if (t >= 0 && t <= 1) {
      var point = {
        x: lerp(lerp(p1.x, p2.x, t), lerp(p2.x, p3.x, t), t),
        y: lerp(lerp(p1.y, p2.y, t), lerp(p2.y, p3.y, t), t)
      };
      var x = point.x;
      var y = point.y;
      if (a1.y == a2.y && x >= minX && x <= maxX) {
        intersections.push(point);
      } else if (x >= minX && y >= minY && x <= maxX && y <= maxY) {
        intersections.push(point);
      }
    }
  }
  return intersections;
}

对应SVG代码

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 600" id="svg">
  <rect class="vBoxRect" width="1280" height="600" fill="#EFEFEF"></rect>
  <g id="controlPoint">
    <circle cx="640" cy="175" r="5" fill="green" fill-opacity=".5"></circle>    
  </g>
  <g id="qBez">
    <path d="M200,300Q640,175,1080,300" stroke="tomato" fill="none"></path>
  </g>
  <g id="limit">
    <circle id="upper" cx="640" cy="50" r="5" fill="red"></circle>
    <circle id="lower" cx="640" cy="550" r="5" fill="red"></circle>
    <text id="0" x="650" y="60">(640, 50)</text>
    <text id="1" x="650" y="560">(640, 550)</text>
  </g>
  <g id="vert">
    <line x1="640" y1="550" x2="640" y2="50" stroke="blue" stroke-dasharray="500,500"></line>
  </g>
</svg>

修复效果

运行修复后的代码,会正确输出交点[640,175],与SVG中贝塞尔曲线的控制点位置一致,符合图形实际情况。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 04:37:31