1.前言
其实在此之前有遇到过需要求解直线与圆相交的交点,因为当时没有对向量进行足够深入的了解,所以当时是采用了二分法求解。二分法在某些情况下求解有它本身的优势,但是在求解直线与圆相交的交点时,就略显笨拙。
对向量进行了学习后,发现可以使用向量法轻松求解直线与圆的相交,亦可线与直线相交的交点,这一点在上一篇文章中直线与直线相交交点的两种求解方法已经介绍。本篇文章介绍下使用向量法求解平面中直线与圆的相交性检测。
2.向量法求解
已知条件:在同一平面中,直线上的两点 <math xmlns="http://www.w3.org/1998/Math/MathML"> P 1 ( x 1 , y 1 )、 P 2 ( x 2 , y 2 ) P1(x_1,y_1)、P2(x_2,y_2) </math>P1(x1,y1)、P2(x2,y2),圆心坐标 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( x 0 , y 0 ) O(x_0,y_0) </math>O(x0,y0)以及圆的半径R。直线与圆的相交,根据圆心到直线的距离来判断存在以下三种情况:
- 圆心到直线的距离大于圆的半径,此时直线与圆不相交;
- 圆心到直线的距离等于圆的半径,此时直线与圆有且只有一个交点,就是切点;
- 圆心到直线的距离小于圆的半径,此时直线与圆有两个交点;
下面我们分别来讨论这三种情况。
2.1判断直线与圆是否相交
2.1.1计算原理
首先需要求出圆心到直线的距离,以此来判断直线与圆的相交情况,怎么求直线到圆的距离 <math xmlns="http://www.w3.org/1998/Math/MathML"> C P r CP_r </math>CPr呢?可以用投影向量求得,投影向量的公式如下:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> v 1 = n v 1 ∙ n ( ∥ n ∥ ) 2 v_1=n \frac { v_1 \bullet n}{(\lVert n \rVert)^2} </math>v1=n(∥n∥)2v1∙n
为了方便计算,我们将向量n设置为单位向量,则投影向量公式为:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> v 1 = n ( v 1 ∙ n ) v_1=n ( v_1 \bullet n) </math>v1=n(v1∙n)
根据上图,我们设:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> P 1 P 2 → = P 2 − P 1 P 1 C → = C − P 1 P 1 P 2 → N o r m = P 1 P 2 → ∥ P 1 P 2 → ∥ \overrightarrow{P_1P_2}=P_2-P_1\\ \overrightarrow{P_1C}=C-P_1\\ \overrightarrow{P_1P_2}_{Norm}=\frac{\overrightarrow{P_1P_2}} {\lVert \overrightarrow{P_1P_2} \rVert} \\ </math>P1P2 =P2−P1P1C =C−P1P1P2 Norm=∥P1P2 ∥P1P2
<math xmlns="http://www.w3.org/1998/Math/MathML"> P 1 P 2 → N o r m \overrightarrow{P_1P_2}_{Norm} </math>P1P2 Norm就是单位向量 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n, <math xmlns="http://www.w3.org/1998/Math/MathML"> P 1 C → \overrightarrow{P_1C} </math>P1C 就是向量 <math xmlns="http://www.w3.org/1998/Math/MathML"> v v </math>v。根据直角三角形的勾股定理可知,圆心到直线的距离就是线段 <math xmlns="http://www.w3.org/1998/Math/MathML"> P 1 C P_1C </math>P1C的平方减去投影向量 <math xmlns="http://www.w3.org/1998/Math/MathML"> v 1 v_1 </math>v1的模长的平方的平方根。
若距离 <math xmlns="http://www.w3.org/1998/Math/MathML"> C P r CP_r </math>CPr大于半径,直线与圆不相交,否则直线与圆则相交。
2.1.1c#代码实现
在Unity中C#代码实现如下:
c#
/// <summary>
/// 判断直线与圆是否相交.
/// </summary>
/// <param name="center">圆心坐标.</param>
/// <param name="point1">直线上的点1.</param>
/// <param name="point2">直线上的点2.</param>
/// <param name="radius">圆的半径.</param>
/// <returns>The <see cref="bool"/>.</returns>
private bool IsLineIntersectCircle(Vector3 center, Vector3 point1, Vector3 point2, float radius)
{
var d1 = center - point1;
var d2 = point2 - point1;
var d2Norm = Vector3.Normalize(d2); //d2的单位向量
var projection = Vector3.Dot(d1, d2Norm) * d2Norm; //d1在d2Norm上的投影向量
//直角三角形勾股定理求圆心到直线的距离
var distance = Mathf.Sqrt(d1.sqrMagnitude - projection.sqrMagnitude);
if (distance <= radius)
{
return true;
}
return false;
}
2.2直线与圆相交情况
2.2.1计算原理
无交点的情况就不讨论,接下来讨论直线与圆有交点的情况,即 <math xmlns="http://www.w3.org/1998/Math/MathML"> C P r < = R CP_r<=R </math>CPr<=R。圆心到直线的距离可以由上面的代码一样求出,投影点 <math xmlns="http://www.w3.org/1998/Math/MathML"> P r P_r </math>Pr的坐标也是可以通过投影向量求出的,设:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> P 1 P 2 → = P 2 − P 1 P 1 C → = C − P 1 P 1 P 2 → N o r m = P 1 P 2 → ∥ P 1 P 2 → ∥ \overrightarrow{P_1P_2}=P_2-P_1\\ \overrightarrow{P_1C}=C-P_1\\ \overrightarrow{P_1P_2}_{Norm}=\frac{\overrightarrow{P_1P_2}} {\lVert \overrightarrow{P_1P_2} \rVert} \\ </math>P1P2 =P2−P1P1C =C−P1P1P2 Norm=∥P1P2 ∥P1P2
要求两个交点 <math xmlns="http://www.w3.org/1998/Math/MathML"> I 1 I_1 </math>I1、 <math xmlns="http://www.w3.org/1998/Math/MathML"> I 2 I_2 </math>I2,我们必须求出线段 <math xmlns="http://www.w3.org/1998/Math/MathML"> P 1 P r P_1P_r </math>P1Pr以及 <math xmlns="http://www.w3.org/1998/Math/MathML"> I 2 P r I_2P_r </math>I2Pr的长度,由几何关系可知,其中:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> I 2 P r = I 1 P r = R 2 − C P r 2 I_2P_r=I_1P_r=\sqrt{R^2-CP_r^2} </math>I2Pr=I1Pr=R2−CPr2
根据向量知识的求出 <math xmlns="http://www.w3.org/1998/Math/MathML"> I 1 I_1 </math>I1、 <math xmlns="http://www.w3.org/1998/Math/MathML"> I 2 I_2 </math>I2坐标:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> I 1 = P r + ( − P 1 P 2 → N o r m ) I 1 P r I 2 = P r + P 1 P 2 → N o r m I 2 P r I_1=P_r+(-\overrightarrow{P_1P_2}{Norm})I_1P_r\\ I_2=P_r+\overrightarrow{P_1P_2}{Norm}I_2P_r </math>I1=Pr+(−P1P2 Norm)I1PrI2=Pr+P1P2 NormI2Pr
2.2.2c#代码实现
在Unity中,实现上述向量计算的c#代码实现如下:
c#
/// <summary>
/// 计算直线与圆的交点.
/// </summary>
/// <param name="center">圆心坐标.</param>
/// <param name="point1">直线上的点1.</param>
/// <param name="point2">直线上的点2.</param>
/// <param name="point3">点3,用于确定需要返回的交点.</param>
/// <param name="radius">圆的半径.</param>
/// <returns>The <see cref="Vector3"/>.</returns>
private Vector3 IntersectionOfLineAndCircle(
Vector3 center,
Vector3 point1,
Vector3 point2,
Vector3 point3,
float radius)
{
var d1 = center - point1;
var d2 = point2 - point1;
var d2Norm = Vector3.Normalize(d2); //d2的单位向量
var length1 = Vector3.Dot(d1, d2Norm); //d1在d2Norm上的投影向量的模长
var projectionPoint = point1 + (length1 * d2Norm); //投影点
var projection = projectionPoint - point1; //投影向量
//直角三角形勾股定理求圆心到直线的距离
var length2 = Mathf.Sqrt(d1.sqrMagnitude - projection.sqrMagnitude); //圆心到直线的距离长度
var length3 = Mathf.Sqrt((radius * radius) - (length2 * length2)); //交点到投影点的距离长度
var intersection1 = projectionPoint - (d2Norm * length3);
var intersection2 = projectionPoint + (d2Norm * length3);
Debug.Log($"采用向量法求得直线与圆的交点1坐标为({intersection1.x},{intersection1.y},{intersection1.z})");
Debug.Log($"采用向量法求得直线与圆的交点2坐标为({intersection2.x},{intersection2.y},{intersection2.z})");
var distance1 = Vector3.Distance(intersection1, point3);
var distance2 = Vector3.Distance(intersection2, point3);
return distance1 > distance2 ? intersection2 : intersection1;
}
上述代码中point3的作用是比较两个交点的距离,返回距离较近的一个交点,当然你也可以根据需求返回两个交点的坐标值。
2.2.3一个例子
c#
void Start()
{
Vector3 o = new Vector3(10, 0, 10);
float radius = 10;
//圆心为(10, 0, 10),半径为10的圆与原点、圆心坐标连成的直线的交点
if (IsLineIntersectCircle(o, Vector3.zero, o, radius))
{
IntersectionOfLineAndCircle(o, Vector3.zero, o, Vector3.zero, radius);
}
}
输出结果如下:
3.结语
其实直线与圆的相交,同直线与直线相交一样,同样可以使用二分法求解,也可以用解方程组的方法去求解,但是二分法的缺点也是一样的。这里与直线不同的是圆的方程为二元二次方程,采用解方程组的方法就稍微棘手,但是通过上述向量法求解可以看出,优势明显。