由圆外一点P1(x1,y1)向圆(x - a)2 + (y - b)2 = R2作切线,切线与圆相切的切点是P0(x0,y0) ![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80MTY5NjMwLThmZDQ2NGE4MGY2MzExYjQucG5n?x-oss-process=image/format,png)
方法一: 公式法
先求直线P0P1直线方程 因为P0P1⊥OP0 向量OP0 = (x0 - a,y0 - b ) 向量P0P1 = (x1 - x0, y1 - y0) 所以 向量P0P1 点乘 向量OP0 = 0 所以 (x0 - a)* (x1 - x0) + (y0 - b)(y1 - y0) = 0 并且 因为点P0是圆上的点,=> (x0 - a)2 + (y0 - b)2 = R2
解方程就可以求出P0(x0,y0)坐标,当然这样求法太复杂,我没有耐心解下去了,我们换个思维求我们把圆换成这样,相当于把整个系统做了一次相对位移,移动到0点 x2 + y2 = R2 那么经过点P0(x0,y0)这个圆的的切线方程就是 x0x1 + y0y1 = R2 并且x20 + y20 = R2 所以 (x21 + y21)x20 -2R2x1x0 + R4 - y21R2 = 0 这样就是求解一元二次方程 m = (x21 + y21) / R2
x0 = (-b±√(b2 - 4ac)) / (2a) = (x1 ± y1√(m-1)) / m y0 = (y1 ± x1√(m-1)) / m 至此切点是找到了, 然后再位移下就是真正的点了
![p](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80MTY5NjMwLWNhYWU5NmI4Mjc1MzUzMjcuZ2lm)
let canvas = document.getElementById("target");
canvas.width = document.body.clientWidth;
canvas.height = document.body.clientHeight;
let ctx = canvas.getContext("2d");
let isDrawing = false;
canvas.addEventListener('mousedown', e => {
let x = e.offsetX;
let y = e.offsetY;
isDrawing = true;
pointP = {x:x,y:y};
update();
});
canvas.addEventListener('mousemove', e => {
if (isDrawing === true) {
let x = e.offsetX;
let y = e.offsetY;
pointP = {x:x,y:y};
update();
}
});
canvas.addEventListener('mouseup', e => {
if (isDrawing === true) {
isDrawing = false;
}
});
let pointP = {x: 300,y: 100};
//圆心坐标
let pointCenter ={x:250,y:250};
//圆点半径
let radius = 100;
function update(){
ctx.clearRect(0,0,canvas.width,canvas.height);
//圆心
drawPoint(pointCenter.x,pointCenter.y,"#986923");
//圆
ctx.beginPath();
ctx.arc(pointCenter.x,pointCenter.y,radius,0,2*Math.PI,true);
ctx.stroke();
ctx.closePath();
drawPoint(pointP.x,pointP.y,"#986923");
let arrayQieDian = calcQieDian(pointCenter.x,pointCenter.y,radius,pointP);
arrayQieDian.forEach((point)=>{
drawPoint(point.x,point.y,"#f00");
drawLine(pointP,point);
drawLine(pointCenter,point);
});
}
update();
function drawPoint(x,y,color){
ctx.beginPath();
ctx.fillStyle=color;
ctx.arc(x,y,5,0,2*Math.PI,true);
ctx.fill();
}
function drawLine(pointStart,pointEnd) {
ctx.beginPath();
ctx.moveTo(pointStart.x,pointStart.y);
ctx.lineTo(pointEnd.x,pointEnd.y);
ctx.stroke();
}
function calcQieDian(cx,cy,radius,point) {
//将实际的点做一次转换,因为下面的计算都是暗转圆心都是在圆点计算的
let outsideX = point.x - cx;
let outsideY = point.y - cy;
let m = (Math.pow(outsideX,2)+Math.pow(outsideY,2))/Math.pow(radius,2);
//求出的结果将会有4种排列
let pointA = {
x:((outsideX+outsideY*Math.sqrt(m-1))/m),
y:((outsideY+outsideX*Math.sqrt(m-1))/m)
};
let pointB = {
x:((outsideX-outsideY*Math.sqrt(m-1))/m),
y:((outsideY-outsideX*Math.sqrt(m-1))/m)
};
let pointC = {
x:((outsideX+outsideY*Math.sqrt(m-1))/m),
y:((outsideY-outsideX*Math.sqrt(m-1))/m)
};
let pointD = {
x:((outsideX-outsideY*Math.sqrt(m-1))/m),
y:((outsideY+outsideX*Math.sqrt(m-1))/m)
};
let array = [];
//实际上只会有2个切点,利用向量垂直,点乘结果是0来判断哪个是有效的
//因为浮点数不能精确到0,所以这里用了1e-10
if(Math.abs(pointA.x*(outsideX -pointA.x ) + pointA.y * (outsideY - pointA.y)) {
if (isDrawing === true) {
isDrawing = false;
eventCircle = false;
}
});
let pointP = {x: 300,y: 100};
//圆心坐标
let pointCenter ={x:250,y:250};
//圆点半径
let radius = 100;
function angle(p1,p2) {
return Math.atan2(p1.y - p2.y, p1.x - p2.x);
}
function getVector(cx, cy, a, r) {
return {x:cx + r * Math.cos(a), y:cy + r * Math.sin(a)};
}
function update(){
ctx.clearRect(0,0,canvas.width,canvas.height);
//圆心
drawPoint(pointCenter.x,pointCenter.y,"#986923");
//圆
ctx.beginPath();
ctx.arc(pointCenter.x,pointCenter.y,radius,0,2*Math.PI,true);
ctx.stroke();
ctx.closePath();
drawPoint(pointP.x,pointP.y,"#986923");
let p = calcQieDian3(pointCenter,radius,pointP);
drawText("P1",p.p1.x,p.p1.y);
drawText("P2",p.p2.x,p.p2.y);
drawText("M",pointP.x,pointP.y);
drawText("O",pointCenter.x,pointCenter.y);
drawPoint(p.p1.x,p.p1.y,"#f00");
drawPoint(p.p2.x,p.p2.y,"#f00");
drawLine(pointCenter,p.p1);
drawLine(pointCenter,p.p2);
drawLine(pointCenter,pointP);
drawLine(pointP,p.p1);
drawLine(pointP,p.p2);
drawLine({x:0,y:pointCenter.y},{x:canvas.width,y:pointCenter.y});
}
update();
function drawPoint(x,y,color){
ctx.beginPath();
ctx.fillStyle=color;
ctx.arc(x,y,5,0,2*Math.PI,true);
ctx.fill();
}
function drawLine(pointStart,pointEnd) {
ctx.beginPath();
ctx.moveTo(pointStart.x,pointStart.y);
ctx.lineTo(pointEnd.x,pointEnd.y);
ctx.stroke();
}
function calcQieDian3(pointCircle,radius,point) {
let d = Math.sqrt(Math.pow(pointCircle.x - point.x,2) + Math.pow(pointCircle.y - point.y,2));
const angleBetweenCenters = angle(point,pointCircle);
const spread = Math.acos(radius/ d);
const angle1 = angleBetweenCenters + spread;
const angle2 = angleBetweenCenters - spread;
const p1 = getVector(pointCircle.x,pointCircle.y, angle1, radius);
const p2 = getVector(pointCircle.x,pointCircle.y, angle2, radius);
return {p1:p1,p2:p2};
}
function drawText(text,x,y) {
ctx.beginPath();
ctx.font="30px Arial";
ctx.fillText(text,x,y);
ctx.stroke();
}
|