js过圆外一点的直线与圆相切的切点坐标计算

由圆外一点P1(x1,y1)向圆(x - a)2 + (y - b)2 = R2作切线,切线与圆相切的切点是P0(x0,y0)
image.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
image.png
那么经过点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

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)) <= 1e-10){
          // 再将坐标转换回来
           pointA.x += cx;
           pointA.y += cy;
           array.push(pointA);
       }

       if(Math.abs(pointB.x*(outsideX -pointB.x ) + pointB.y * (outsideY - pointB.y)) <= 1e-10){
           pointB.x += cx;
           pointB.y += cy;
           array.push(pointB);
       }
       if(Math.abs(pointC.x*(outsideX -pointC.x ) + pointC.y * (outsideY - pointC.y)) <= 1e-10){
           pointC.x += cx;
           pointC.y += cy;
           array.push(pointC);
       }
       if(Math.abs(pointD.x*(outsideX -pointD.x ) + pointD.y * (outsideY - pointD.y)) <= 1e-10){
           pointD.x += cx;
           pointD.y += cy;
           array.push(pointD);
       }
       return array;
   }

方法二:向量法

image.png
思路:只要知道P1点与水平线的角度就可以计算坐标,先计算OM与水平线的角度,角P1OM的余弦值就是P1O / OM
P1O 的长度就是半径r

let canvas =  document.getElementById("target");
   canvas.width = document.body.clientWidth;
   canvas.height = document.body.clientHeight;
   let ctx = canvas.getContext("2d");
   let isDrawing = false;
   let eventCircle = false;
   canvas.addEventListener('mousedown', e => {
       let x = e.offsetX;
       let y = e.offsetY;
       isDrawing = true;
       if(Math.pow(x - pointCenter.x,2) + Math.pow(y - pointCenter.y,2) <= radius*radius){
           eventCircle = true;
           pointCenter.x = x;
           pointCenter.y = y;
       }else{
           pointP.x = x;
           pointP.y = y;
       }
       update();
   });

   canvas.addEventListener('mousemove', e => {
       if (isDrawing === true) {
           let x = e.offsetX;
           let y = e.offsetY;
           if(eventCircle){
               pointCenter.x = x;
               pointCenter.y = y;
           }else{
               pointP.x = x;
               pointP.y = y;
           }
           update();
       }
   });

   canvas.addEventListener('mouseup', e => {
       if (isDrawing === true) {
           isDrawing = false;
           eventCircle = 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 p = calcQieDian2(pointCenter.x,pointCenter.y,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 calcQieDian2(cx,cy,radius,point) {
       //点到圆心的距离
       let d = Math.sqrt(Math.pow(cx - point.x,2) + Math.pow(cy - point.y,2));
       let vc1c2 = {x:point.x - cx,y:-point.y + cy}; //屏幕坐标系与笛卡尔坐标系是y轴是反着的
       let radC1C2 = Math.acos(vc1c2.x / Math.sqrt(Math.pow(vc1c2.x,2) + Math.pow(vc1c2.y,2)));
       let theta = Math.acos(radius/d);
       if(point.y < cy){
           let p1 = {x:cx + Math.cos(theta + radC1C2)*radius,y:cy - Math.sin(theta + radC1C2)*radius};
           let p2 = {x:cx + Math.cos(theta - radC1C2)*radius,y:cy + Math.sin(theta - radC1C2)*radius};
           return {p1:p1,p2:p2};
       }else{
           radC1C2 = Math.PI - radC1C2;
           let p1 = {x:cx + Math.cos(Math.PI - theta - radC1C2)*radius,y:cy + Math.sin(Math.PI - theta - radC1C2)*radius};
           let p2 = {x:cx + Math.cos(Math.PI - (theta - radC1C2))*radius,y:cy - Math.sin(Math.PI - (theta - radC1C2))*radius};
           return {p1:p1,p2:p2};
       }
   }

   function drawText(text,x,y) {
       ctx.beginPath();
       ctx.font="40px Arial";
       ctx.fillText(text,x,y);
       ctx.stroke();
   }

p

方法三 atan2

利用atan2计算,返回(-pi,pi]之间的值,从x轴正方向逆时针旋转到点(x,y)时经过的角度 ​

image.png

 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 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};
   }

完整代码

let canvas =  document.getElementById("target");
   canvas.width = document.body.clientWidth;
   canvas.height = document.body.clientHeight;
   let ctx = canvas.getContext("2d");
   let isDrawing = false;
   let eventCircle = false;
   canvas.addEventListener('mousedown', e => {
       let x = e.offsetX;
       let y = e.offsetY;
       isDrawing = true;
       if(Math.pow(x - pointCenter.x,2) + Math.pow(y - pointCenter.y,2) <= radius*radius){
           eventCircle = true;
           pointCenter.x = x;
           pointCenter.y = y;
       }else{
           pointP.x = x;
           pointP.y = y;
       }
       update();
   });

   canvas.addEventListener('mousemove', e => {
       if (isDrawing === true) {
           let x = e.offsetX;
           let y = e.offsetY;
           if(eventCircle){
               pointCenter.x = x;
               pointCenter.y = y;
           }else{
               pointP.x = x;
               pointP.y = y;
           }
           update();
       }
   });

   canvas.addEventListener('mouseup', e => {
       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();
   }
  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,我们需要确定两个的位置和半径。假设1的坐标为 (x1, y1),半径为 r1;2的坐标为 (x2, y2),半径为 r2。可以使用勾股定理计算出两个心之间的距离 d: ```python import math x1, y1, r1 = 1, 2, 3 x2, y2, r2 = -1, -2, 4 d = math.sqrt((x2 - x1)**2 + (y2 - y1)**2) ``` 接下来,我们需要判断两个是否相交或包含。如果两个相离,则它们之间不存在公共切线。如果一个包含另一个,则公共切线无限多。 ```python if d > r1 + r2: print("两个相离,不存在公共切线") elif d < abs(r1 - r2): print("一个包含另一个,公共切线无限多") ``` 对于两个相交的,我们可以通过计算它们的切点坐标来得到公共切线。假设切点分别为 P1 和 P2,我们可以将两个心连线的中垂线作为公共切线的法线,然后计算法线与心连线的夹角 α。根据三角函数的定义,切线与心连线的夹角为 90° - α。因此,我们可以计算出切线的斜率 k,然后使用两点式得到切线的方程。 ```python else: a = (r1**2 - r2**2 + d**2) / (2 * d) h = math.sqrt(r1**2 - a**2) x3 = x1 + a * (x2 - x1) / d y3 = y1 + a * (y2 - y1) / d x4_1 = x3 + h * (y2 - y1) / d y4_1 = y3 - h * (x2 - x1) / d x4_2 = x3 - h * (y2 - y1) / d y4_2 = y3 + h * (x2 - x1) / d print("切点坐标为 ({:.2f}, {:.2f}) 和 ({:.2f}, {:.2f})".format(x4_1, y4_1, x4_2, y4_2)) ``` 完整代码如下: ```python import math x1, y1, r1 = 1, 2, 3 x2, y2, r2 = -1, -2, 4 d = math.sqrt((x2 - x1)**2 + (y2 - y1)**2) if d > r1 + r2: print("两个相离,不存在公共切线") elif d < abs(r1 - r2): print("一个包含另一个,公共切线无限多") else: a = (r1**2 - r2**2 + d**2) / (2 * d) h = math.sqrt(r1**2 - a**2) x3 = x1 + a * (x2 - x1) / d y3 = y1 + a * (y2 - y1) / d x4_1 = x3 + h * (y2 - y1) / d y4_1 = y3 - h * (x2 - x1) / d x4_2 = x3 - h * (y2 - y1) / d y4_2 = y3 + h * (x2 - x1) / d print("切点坐标为 ({:.2f}, {:.2f}) 和 ({:.2f}, {:.2f})".format(x4_1, y4_1, x4_2, y4_2)) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值