/** * 为正多边形添加圆角 * 将每个角转换为 segmentsPerCorner 条线段,线段的端点在圆角的圆弧上 * * 对每个角: * 1. 寻找圆心 * 2. 将圆心投影到两条边上 * 3. 以圆弧连接投影点 * @param vertices 多边形顶点数组(顺时针或逆时针) * @param cornerRadius 圆角半径 * @param segmentsPerCorner 每个圆角的分段数 * @returns 带圆角的多边形轮廓点 */ export function getRoundedPolygonPoints( vertices: [number, number][], cornerRadius: number, segmentsPerCorner: number = 4 ): [number, number][] { if (vertices.length < 3) { return [...vertices]; } const result: [number, number][] = []; for (let i = 0; i < vertices.length; i++) { const prev = vertices[(i - 1 + vertices.length) % vertices.length]; const curr = vertices[i]; const next = vertices[(i + 1) % vertices.length]; // 获取圆角圆心 const center = getTangentCircleCenter(prev, curr, next, cornerRadius); // 获取圆心到两条边的投影点(圆角的起点和终点) const start = getProjectedPoint(center, prev, curr); const end = getProjectedPoint(center, curr, next); // 计算起始角度和结束角度 const startAngle = Math.atan2(start[1] - center[1], start[0] - center[0]); const endAngle = Math.atan2(end[1] - center[1], end[0] - center[0]); // 判断方向(顺时针或逆时针) const cross = (curr[0] - prev[0]) * (next[1] - prev[1]) - (curr[1] - prev[1]) * (next[0] - prev[0]); const isClockwise = cross < 0; // 计算角度差,确保沿着正确的方向 let angleDiff = endAngle - startAngle; if (isClockwise) { if (angleDiff > 0) angleDiff -= Math.PI * 2; } else { if (angleDiff < 0) angleDiff += Math.PI * 2; } // 生成圆弧上的点 for (let j = 0; j <= segmentsPerCorner; j++) { const t = j / segmentsPerCorner; const angle = startAngle + angleDiff * t; const x = center[0] + cornerRadius * Math.cos(angle); const y = center[1] + cornerRadius * Math.sin(angle); result.push([x, y]); } } return result; } /** * 对于给定的角 abc,获取与 ab 和 bc 相切的半径为 radius 的圆的圆心位置。 * @param va * @param vb * @param vc * @param radius */ export function getTangentCircleCenter( va: [number, number], vb: [number, number], vc: [number, number], radius: number ): [number, number] { // 计算两条边的单位向量 const ba: [number, number] = [va[0] - vb[0], va[1] - vb[1]]; const bc: [number, number] = [vc[0] - vb[0], vc[1] - vb[1]]; const baLen = Math.sqrt(ba[0] ** 2 + ba[1] ** 2); const bcLen = Math.sqrt(bc[0] ** 2 + bc[1] ** 2); const baUnit: [number, number] = [ba[0] / baLen, ba[1] / baLen]; const bcUnit: [number, number] = [bc[0] / bcLen, bc[1] / bcLen]; // 角平分线方向 const bisector: [number, number] = [baUnit[0] + bcUnit[0], baUnit[1] + bcUnit[1]]; const bisectorLen = Math.sqrt(bisector[0] ** 2 + bisector[1] ** 2); const bisectorUnit: [number, number] = [bisector[0] / bisectorLen, bisector[1] / bisectorLen]; // 计算半角的正弦值 const halfAngle = Math.acos((baUnit[0] * bcUnit[0] + baUnit[1] * bcUnit[1])); const sinHalfAngle = Math.sin(halfAngle / 2); // 圆心到顶点的距离 const centerDist = radius / sinHalfAngle; // 圆心位置(从顶点沿角平分线向外) return [ vb[0] + bisectorUnit[0] * centerDist, vb[1] + bisectorUnit[1] * centerDist ]; } /** * 将 v 投影到 ab 线段上 * @param v * @param a * @param b */ export function getProjectedPoint( v: [number, number], a: [number, number], b: [number, number], ): [number, number] { const ab: [number, number] = [b[0] - a[0], b[1] - a[1]]; const av: [number, number] = [v[0] - a[0], v[1] - a[1]]; const abLenSq = ab[0] ** 2 + ab[1] ** 2; const t = (av[0] * ab[0] + av[1] * ab[1]) / abLenSq; return [ a[0] + ab[0] * t, a[1] + ab[1] * t ]; }