二次贝塞尔路径与垂直线的交点程序化计算问题
二次贝塞尔曲线与垂直线交点计算问题修复
问题重现
使用参考的二次贝塞尔曲线与直线交点计算代码时,测试数据能正常输出结果,但替换为以下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
相关产品推荐
相关产品推荐

