如何实现基于折线路径与宽度生成走廊路径的函数?
实现折线走廊路径生成函数
getCorridorPath 需求说明
我们需要编写一个getCorridorPath函数,输入一维折线坐标数组(每两个元素代表一个点的x、y值)和走廊宽度,输出一个闭合的走廊路径坐标数组,实现示例中的走廊效果。
示例输入:
var coords = [50, 50, 150, 50, 250, 100, 220, 200, 350, 100]; var width = 10;
调用方式:
var corridorPath = getCorridorPath(coords, width);
预期输出是一个闭合的一维坐标数组,类似:
[40, 50, 50, 40, 150, 40, 157, 41, 255, 90, 260, 105, 240, 170, 340, 90, 360, 90, 360, 110, 220, 215, 205, 205, 233, 107, 147, 62, 50, 60, 40, 50]
实现思路
- 坐标格式转换:把一维坐标数组转成带
x、y属性的点对象数组,方便后续计算。 - 计算线段法向量:对每段折线,算出垂直方向的单位法向量,用来向两侧偏移生成走廊边界。
- 处理转折点:针对折线中间的转折点,计算相邻两段线段的角平分线方向,通过偏移得到走廊在拐角处的过渡点,避免出现尖锐的角。
- 拼接路径:把所有左侧边界点按顺序排列,再把右侧边界点逆序排列,最后首尾闭合,形成完整的走廊路径。
完整代码实现
function getCorridorPath(coords, width) { // 步骤1:将一维坐标数组转换为点对象数组 const points = []; for (let i = 0; i < coords.length; i += 2) { points.push({ x: coords[i], y: coords[i + 1] }); } const halfWidth = width / 2; const leftPoints = []; const rightPoints = []; // 步骤2:处理每一段线段,计算偏移点 for (let i = 0; i < points.length; i++) { const current = points[i]; const next = points[i + 1]; // 如果是最后一个点,停止处理线段 if (!next) break; // 计算线段的方向向量 const dx = next.x - current.x; const dy = next.y - current.y; // 计算线段长度 const length = Math.sqrt(dx * dx + dy * dy); // 计算单位法向量(左右两个方向) const normalLeft = { x: -dy / length, y: dx / length }; const normalRight = { x: dy / length, y: -dx / length }; // 计算当前线段的左右偏移点 const left = { x: current.x + normalLeft.x * halfWidth, y: current.y + normalLeft.y * halfWidth }; const right = { x: current.x + normalRight.x * halfWidth, y: current.y + normalRight.y * halfWidth }; // 处理转折点(非第一个点) if (i > 0) { const prev = points[i - 1]; // 计算前一段线段的方向向量 const prevDx = current.x - prev.x; const prevDy = current.y - prev.y; const prevLength = Math.sqrt(prevDx * prevDx + prevDy * prevDy); const prevNormalLeft = { x: -prevDy / prevLength, y: prevDx / prevLength }; const prevNormalRight = { x: prevDy / prevLength, y: -prevDx / prevLength }; // 前一段的结束偏移点 const prevLeftEnd = { x: current.x + prevNormalLeft.x * halfWidth, y: current.y + prevNormalLeft.y * halfWidth }; const prevRightEnd = { x: current.x + prevNormalRight.x * halfWidth, y: current.y + prevNormalRight.y * halfWidth }; // 计算两个左侧偏移线段的交点(转折点的左侧过渡点) const leftIntersection = getLineIntersection(prevLeftEnd, left, current, current); // 计算两个右侧偏移线段的交点(转折点的右侧过渡点) const rightIntersection = getLineIntersection(prevRightEnd, right, current, current); // 替换当前左侧点为交点 leftPoints.pop(); leftPoints.push(leftIntersection || left); // 替换当前右侧点为交点 rightPoints.pop(); rightPoints.push(rightIntersection || right); } else { // 第一个点直接加入 leftPoints.push(left); rightPoints.push(right); } // 加入下一个点的偏移点(最后一段的结束点) if (i === points.length - 2) { leftPoints.push({ x: next.x + normalLeft.x * halfWidth, y: next.y + normalLeft.y * halfWidth }); rightPoints.push({ x: next.x + normalRight.x * halfWidth, y: next.y + normalRight.y * halfWidth }); } } // 步骤3:拼接路径:左侧点顺序 + 右侧点逆序 + 第一个左侧点闭合 const path = []; leftPoints.forEach(p => path.push(p.x, p.y)); // 逆序遍历右侧点,排除最后一个(避免重复) for (let i = rightPoints.length - 1; i >= 0; i--) { path.push(rightPoints[i].x, rightPoints[i].y); } // 闭合路径,加入第一个左侧点 path.push(leftPoints[0].x, leftPoints[0].y); return path; } // 辅助函数:计算两条线段的交点 function getLineIntersection(p1, p2, p3, p4) { const denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y); if (denom === 0) return null; // 线段平行或重合 const ua = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom; const ub = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom; if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) { return { x: p1.x + ua * (p2.x - p1.x), y: p1.y + ua * (p2.y - p1.y) }; } return null; }
代码说明
getLineIntersection辅助函数用来计算两条线段的交点,处理折线转折点处的边界过渡,确保走廊在拐角处平滑连接。- 函数把走廊宽度分成一半,分别向折线两侧偏移,生成左右边界。
- 最终路径通过拼接左侧边界点、逆序的右侧边界点,并闭合路径,形成完整的走廊多边形。
内容的提问来源于stack exchange,提问作者rem99
相关产品推荐
相关产品推荐

