Algorithm 从一个点查找圆中的切点

Algorithm 从一个点查找圆中的切点,algorithm,math,geometry,algebra,Algorithm,Math,Geometry,Algebra,圆心:Cx,Cy 圆半径:a 我们需要画一条切线的点:Px,Py 我需要一个公式来找到上面给出的两条切线(t1x,t1y)和(t2x,t2y) 编辑:有没有更简单的解决方案,使用向量代数或其他方法,而不是先求两条直线的方程,然后再解两条直线的方程,分别求出两条切线?同样,这个问题也不是离题的,因为我需要编写一个代码,以找到最佳的嗯,这不是一个真正的算法问题(人们往往会误解算法和方程),如果你想编写一个代码,那么就这样做(你没有指定语言,也没有指定什么阻止你这样做,这是投票结果接近的原因)。。。如

圆心:Cx,Cy

圆半径:a

我们需要画一条切线的点:Px,Py

我需要一个公式来找到上面给出的两条切线(t1x,t1y)和(t2x,t2y)


编辑:有没有更简单的解决方案,使用向量代数或其他方法,而不是先求两条直线的方程,然后再解两条直线的方程,分别求出两条切线?同样,这个问题也不是离题的,因为我需要编写一个代码,以找到最佳的

嗯,这不是一个真正的算法问题(人们往往会误解算法和方程),如果你想编写一个代码,那么就这样做(你没有指定语言,也没有指定什么阻止你这样做,这是投票结果接近的原因)。。。如果没有这些信息,你的OP只是要求数学方程式,这确实是离题的,回答这个问题,我也会冒(右满)否决票的风险(但这个问题在这里被问了很多次,信息要少得多,4票重新开放,1票关闭,我的决定权在重新开放和回答这个问题上)

您可以利用这样一个事实,即您处于2D中,正如在2D中,垂直于向量的向量
a(x,y)
的计算如下:

c = (-y, x)
d = ( y,-x)
c = -d
因此,交换
x,y
并取反(哪一个确定垂直向量是CW还是CCW)。这实际上是一个旋转公式,但当我们旋转90度时,
cos,sin
就是
+1
-1

现在,圆上任何圆周点的法线位于穿过该点和圆中心的直线上。所以把所有这些放在一起,你的切线是:

//正常
nx=Px-Cx
ny=Py-Cy
//切线1
tx=-ny
ty=+nx
//切线2
tx=+ny
ty=-nx
如果你想要单位向量,而不是仅仅除以半径
a
(不知道为什么你不把它称为
r
,就像其他数学世界一样),那么:

//正常
nx=(Px-Cx)/a
ny=(Py-Cy)/a
//切线1
tx=-ny
ty=+nx
//切线2
tx=+ny
ty=-nx

将圆移动到原点,旋转使点位于
X
上,并按
R
缩小比例以获得单位圆

现在,当原点
(0,0)
、给定的(减少的)点
(d,0)
和单位圆
(cos t,sin t)
上的任意点形成直角三角形时,即可实现相切

cos t (cos t - d) + sin t sin t = 1 - d cos t = 0
从这个,你画

cos t = 1 / d

要获取初始几何体中的切点,请选择“高比例”、“不旋转”和“不平移”。(这些是简单的线性代数运算。)请注意,不需要显式执行直接变换。您只需要
d
,距离中心点与半径的比率


这里有另一种使用复数的方法。 如果a是圆上切点从中心c开始的方向(长度1的复数),d是沿着切线到达p的(实际)长度,那么(因为切线的方向是I*a)

重新安排

(r+I*d)*a = p-c
但是a的长度是1,所以取我们得到的长度

|r+I*d| = |p-c|
除了d,我们什么都知道,所以我们可以解d:

d = +- sqrt( |p-c|*|p-c| - r*r)
然后找到a和圆上的点,每个点对应上面d的每个值:

a = (p-c)/(r+I*d)
q = c + r*a

这里有一种使用三角学的方法。如果您了解trig,这种方法很容易理解,尽管由于trig函数的不精确性,在可能的情况下,它可能无法给出准确的答案

给出了点
C=(Cx,Cy)
p=(Px,Py)
,以及半径
a
。半径在我的图表中显示了两次,分别为
a1
a2
。您可以轻松计算点
P
C
之间的距离
b
,您可以看到线段
b
形成两个直角三角形的斜边,边
a
。角度
theta
(在我的图表中也显示了两次)在斜边和相邻边
a
之间,因此可以用反余弦计算。从点
C
到点
P
的矢量方向角也很容易通过反正切找到。切点的方向角是原始方向角与计算出的三角形角的和与差。最后,我们可以使用这些方向角和距离
a
来找到这些切点的坐标

下面是Python 3中的代码

# Example values
(Px, Py) = (5, 2)
(Cx, Cy) = (1, 1)
a = 2

from math import sqrt, acos, atan2, sin, cos

b = sqrt((Px - Cx)**2 + (Py - Cy)**2)  # hypot() also works here
th = acos(a / b)  # angle theta
d = atan2(Py - Cy, Px - Cx)  # direction angle of point P from C
d1 = d + th  # direction angle of point T1 from C
d2 = d - th  # direction angle of point T2 from C

T1x = Cx + a * cos(d1)
T1y = Cy + a * sin(d1)
T2x = Cx + a * cos(d2)
T2y = Cy + a * sin(d2)
有一些明显的方法可以将这些计算结合起来,使它们更加优化,但我将把这留给你们。也可以使用三角函数的角度加减公式和其他一些恒等式,从计算中完全删除三角函数。然而,结果更为复杂和难以理解。没有测试,我不知道哪种方法更“优化”,但这取决于您的目的。如果您需要其他方法,请告诉我,但这里的其他答案会为您提供其他方法


请注意,如果
a>b
,则
acos(a/b)
将抛出异常,但这意味着点
p
位于圆内,没有切点。如果
a==b
则点
P
位于圆上,并且只有一个切点,即点
P
本身。我的代码用于案例
a
。我将让您对其他情况进行编码,并确定所需的精度,以确定
a
b
是否相等。

让我们来完成推导过程:

正如你所看到的,如果正方形的内部小于0,那是因为该点位于圆周的内部。当点位于圆周外时,根据正方形的符号,有两种解决方案。 剩下的很简单。拿着
atan(解决方案)
仔细看这里的标志,你可能会更好
a = (p-c)/(r+I*d)
q = c + r*a
# Example values
(Px, Py) = (5, 2)
(Cx, Cy) = (1, 1)
a = 2

from math import sqrt, acos, atan2, sin, cos

b = sqrt((Px - Cx)**2 + (Py - Cy)**2)  # hypot() also works here
th = acos(a / b)  # angle theta
d = atan2(Py - Cy, Px - Cx)  # direction angle of point P from C
d1 = d + th  # direction angle of point T1 from C
d2 = d - th  # direction angle of point T2 from C

T1x = Cx + a * cos(d1)
T1y = Cy + a * sin(d1)
T2x = Cx + a * cos(d2)
T2y = Cy + a * sin(d2)
static void FindTangents(Vector2 point, Vector2 circle, float r, out Line l1, out Line l2)
{
    var p = new Complex(point.x, point.y);
    var c = new Complex(circle.x, circle.y);
    var cp = p - c;
    var d = Math.Sqrt(cp.Real * cp.Real + cp.Imaginary * cp.Imaginary - r * r);
    var q = GetQ(r, cp, d, c);
    var q2 = GetQ(r, cp, -d, c);

    l1 = new Line(point, new Vector2((float) q.Real, (float) q.Imaginary));
    l2 = new Line(point, new Vector2((float) q2.Real, (float) q2.Imaginary));
}

static Complex GetQ(float r, Complex cp, double d, Complex c)
{
    return c + r * (cp / (r + Complex.ImaginaryOne * d));
}