从一个点在圆中找到切点
Find tangent points in a circle from a point
圆心:Cx,Cy
圆半径:a
我们需要绘制切线的点:Px,Py
我需要公式来找到上述所有条件下的两条切线 (t1x, t1y) 和 (t2x,t2y)。
编辑:有没有使用矢量代数之类的更简单的解决方案,而不是找到两条直线的方程,然后求解两条直线的方程分别找到两条切线?这个问题也不是题外话,因为我需要写一个代码来找到这个问题
嗯,这不是真正的算法问题(人们往往会弄错算法和方程式)如果你想写代码就写(你没有指定语言,也没有什么阻止你这样做,这就是票数接近的原因) ...如果没有此信息,您的 OP 只是在询问数学方程式,这在这里确实是题外话,通过回答这个问题,我也冒着(完全)否决票的风险(但是这个 is/was 在这里问了很多很多更少的信息和 4 票反对 1 票关闭使我决定重新开放并无论如何回答这个问题)。
你可以利用你在 2D 中的事实,因为在 2D 垂直向量到向量 a(x,y)
的计算方式如下这个:
c = (-y, x)
d = ( y,-x)
c = -d
所以你交换 x,y
并取反(哪个决定垂直向量是 CW 还是 CCW)。这实际上是一个旋转公式,但当我们旋转 90 度时,cos,sin
只是 +1
和 -1
.
现在圆上任意圆周点的法线n
位于通过该点和圆心的直线上。所以把所有这些放在一起你的切线是:
// normal
nx = Px-Cx
ny = Py-Cy
// tangent 1
tx = -ny
ty = +nx
// tangent 2
tx = +ny
ty = -nx
如果你想要单位向量而不只是除以半径 a
(不知道你为什么不像其他数学世界那样称呼它 r
)所以:
// normal
nx = (Px-Cx)/a
ny = (Py-Cy)/a
// tangent 1
tx = -ny
ty = +nx
// tangent 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
和
sin t = ±√(1-1/d²).
获取初始几何体中的切点,upscale,unrotate和平移。 (这些是简单的线性代数运算。)请注意,无需显式执行直接变换。您只需要d
,距离中心点与半径之比。
这是使用复数的另一种方法。
如果 a 是圆上切点从圆心 c 的方向(长度为 1 的复数),d 是沿切线到达 p 的(实数)长度,则(因为切线的方向是我*a)
p = c + r*a + d*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
这是使用三角函数的一种方法。如果你了解三角函数,这种方法很容易理解,尽管由于三角函数缺乏准确性,它可能不会在可能的情况下给出准确的正确答案。
给出了点 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)
有很明显的方法可以组合这些计算并使它们更优化一些,但我会把它留给你。也可以将三角学的角度加减公式与其他一些恒等式一起使用,以从计算中完全去除三角函数。然而,结果更复杂,更难理解。未经测试,我不知道哪种方法更 "optimized" 但这取决于您的目的。如果您需要这种其他方法,请告诉我,但这里的其他答案无论如何都会为您提供其他方法。
请注意,如果 a > b
那么 acos(a / b)
将抛出异常,但这意味着点 P
在 内部 圆没有切点。如果a == b
则点P
在上圆上只有一个切点,即点P
本身。我的代码适用于案例 a < b
。我将留给您编写其他情况的代码并确定所需的精度来确定 a
和 b
是否相等。
让我们来看看推导过程:
如您所见,如果正方形的内部小于 0,那是因为点在圆周的内部。当点在圆周之外时,有两种解决方案,具体取决于正方形的符号。
剩下的就简单了。拿 atan(solution)
并注意这里的标志,你最好做一些检查。
使用 (2) 然后撤消 (1) 转换,仅此而已。
c#实现dmuir的回答:
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));
}
圆心:Cx,Cy
圆半径:a
我们需要绘制切线的点:Px,Py
我需要公式来找到上述所有条件下的两条切线 (t1x, t1y) 和 (t2x,t2y)。
编辑:有没有使用矢量代数之类的更简单的解决方案,而不是找到两条直线的方程,然后求解两条直线的方程分别找到两条切线?这个问题也不是题外话,因为我需要写一个代码来找到这个问题
嗯,这不是真正的算法问题(人们往往会弄错算法和方程式)如果你想写代码就写(你没有指定语言,也没有什么阻止你这样做,这就是票数接近的原因) ...如果没有此信息,您的 OP 只是在询问数学方程式,这在这里确实是题外话,通过回答这个问题,我也冒着(完全)否决票的风险(但是这个 is/was 在这里问了很多很多更少的信息和 4 票反对 1 票关闭使我决定重新开放并无论如何回答这个问题)。
你可以利用你在 2D 中的事实,因为在 2D 垂直向量到向量 a(x,y)
的计算方式如下这个:
c = (-y, x)
d = ( y,-x)
c = -d
所以你交换 x,y
并取反(哪个决定垂直向量是 CW 还是 CCW)。这实际上是一个旋转公式,但当我们旋转 90 度时,cos,sin
只是 +1
和 -1
.
现在圆上任意圆周点的法线n
位于通过该点和圆心的直线上。所以把所有这些放在一起你的切线是:
// normal
nx = Px-Cx
ny = Py-Cy
// tangent 1
tx = -ny
ty = +nx
// tangent 2
tx = +ny
ty = -nx
如果你想要单位向量而不只是除以半径 a
(不知道你为什么不像其他数学世界那样称呼它 r
)所以:
// normal
nx = (Px-Cx)/a
ny = (Py-Cy)/a
// tangent 1
tx = -ny
ty = +nx
// tangent 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
和
sin t = ±√(1-1/d²).
获取初始几何体中的切点,upscale,unrotate和平移。 (这些是简单的线性代数运算。)请注意,无需显式执行直接变换。您只需要d
,距离中心点与半径之比。
这是使用复数的另一种方法。 如果 a 是圆上切点从圆心 c 的方向(长度为 1 的复数),d 是沿切线到达 p 的(实数)长度,则(因为切线的方向是我*a)
p = c + r*a + d*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
这是使用三角函数的一种方法。如果你了解三角函数,这种方法很容易理解,尽管由于三角函数缺乏准确性,它可能不会在可能的情况下给出准确的正确答案。
给出了点 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)
有很明显的方法可以组合这些计算并使它们更优化一些,但我会把它留给你。也可以将三角学的角度加减公式与其他一些恒等式一起使用,以从计算中完全去除三角函数。然而,结果更复杂,更难理解。未经测试,我不知道哪种方法更 "optimized" 但这取决于您的目的。如果您需要这种其他方法,请告诉我,但这里的其他答案无论如何都会为您提供其他方法。
请注意,如果 a > b
那么 acos(a / b)
将抛出异常,但这意味着点 P
在 内部 圆没有切点。如果a == b
则点P
在上圆上只有一个切点,即点P
本身。我的代码适用于案例 a < b
。我将留给您编写其他情况的代码并确定所需的精度来确定 a
和 b
是否相等。
让我们来看看推导过程:
如您所见,如果正方形的内部小于 0,那是因为点在圆周的内部。当点在圆周之外时,有两种解决方案,具体取决于正方形的符号。
剩下的就简单了。拿 atan(solution)
并注意这里的标志,你最好做一些检查。
使用 (2) 然后撤消 (1) 转换,仅此而已。
c#实现dmuir的回答:
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));
}