同心圆外圆切线延长线与外圆交点坐标求解(JS Canvas)
同心圆切线交点求解(Canvas绘图用)
已知圆心在原点(0,0)的两个同心圆,外圆半径R,内圆半径r,外圆上一点A(x₀,y₀),求从A出发的两条内圆切线延长后与外圆的交点B、C坐标。
数学推导步骤
切线斜率求解
设切线斜率为k,直线方程为y - y₀ = k(x - x₀),圆心到直线的距离等于内圆半径r,据此列出方程:|y₀ - kx₀| / √(k² + 1) = r整理为关于k的二次方程:
(x₀² - r²)k² - 2x₀y₀k + (R² - x₀² - r²) = 0利用二次方程求根公式
k = [2x₀y₀ ± √Δ]/[2(x₀² - r²)],其中判别式化简后为:Δ = 4r²(R² - x₀²)代入后可得到两个斜率k₁、k₂,对应两条切线。
求外圆交点B、C
对每个切线斜率k,利用韦达定理求直线与外圆的另一交点(A点已在圆上):- x坐标:
x_B = [-2k(y₀ - kx₀)/(1 + k²)] - x₀ - y坐标:
y_B = k * x_B + (y₀ - kx₀)
同理可得C点坐标(x_C, y_C)。
- x坐标:
JavaScript实现代码(适配Canvas)
function getTangentIntersections(Ax, Ay, R, r) { // 容错:自动将A点归一化到外圆上 const distSq = Ax*Ax + Ay*Ay; if (Math.abs(distSq - R*R) > 1e-6) { const scale = R / Math.sqrt(distSq); Ax *= scale; Ay *= scale; } const x0 = Ax, y0 = Ay; const a = x0*x0 - r*r; const b = -2 * x0 * y0; const c = (R*R - x0*x0) - r*r; const delta = b*b - 4*a*c; // 无切线情况(r >= R) if (delta < -1e-6) return null; const sqrtDelta = Math.sqrt(Math.max(delta, 0)); // 计算交点的通用函数 const getPoint = (k) => { const denom = 1 + k*k; const numeratorX = -2*k*(y0 - k*x0); const x = (numeratorX / denom) - x0; const y = k*x + (y0 - k*x0); return { x: parseFloat(x.toFixed(6)), y: parseFloat(y.toFixed(6)) }; }; let points = []; // 处理斜率存在的情况 if (Math.abs(a) > 1e-6) { const k1 = (-b + sqrtDelta) / (2*a); const k2 = (-b - sqrtDelta) / (2*a); points.push(getPoint(k1)); points.push(getPoint(k2)); } else { // 处理垂直于x轴的切线(斜率不存在) const yVal = Math.sqrt(R*R - x0*x0); points.push({x: x0, y: y0 === yVal ? -yVal : yVal}); // 计算另一条斜率存在的切线交点 const k = -c/b; points.push(getPoint(k)); // 去重 points = points.filter((p, idx, arr) => idx === arr.findIndex(q => Math.abs(p.x - q.x) < 1e-6 && Math.abs(p.y - q.y) < 1e-6) ); } return points; } // Canvas绘图示例 const canvas = document.getElementById('myCanvas'); const ctx = canvas.getContext('2d'); const R = 100; const r = 50; const A = {x: 100, y: 0}; const intersections = getTangentIntersections(A.x, A.y, R, r); // 绘制同心圆 ctx.beginPath(); ctx.arc(0, 0, R, 0, Math.PI*2); ctx.stroke(); ctx.beginPath(); ctx.arc(0, 0, r, 0, Math.PI*2); ctx.stroke(); // 标记A点 ctx.fillStyle = 'red'; ctx.beginPath(); ctx.arc(A.x, A.y, 3, 0, Math.PI*2); ctx.fill(); // 绘制切线与交点 if (intersections) { intersections.forEach(point => { ctx.fillStyle = 'blue'; ctx.beginPath(); ctx.arc(point.x, point.y, 3, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.moveTo(A.x, A.y); ctx.lineTo(point.x, point.y); ctx.strokeStyle = 'green'; ctx.stroke(); }); }
注意事项
- 浮点精度:用1e-6作为误差阈值,避免浮点运算导致的逻辑错误。
- 特殊场景:处理了切线垂直于x轴(斜率不存在)的情况,同时覆盖了r=0(内圆退化为原点)、r=R(无切线)等边界情况。
内容的提问来源于stack exchange,提问作者JudgeRoyBean
相关产品推荐
相关产品推荐

