平面中直线与圆相交交点的向量法求解

1,229 阅读4分钟

1.前言

其实在此之前有遇到过需要求解直线与圆相交的交点,因为当时没有对向量进行足够深入的了解,所以当时是采用了二分法求解。二分法在某些情况下求解有它本身的优势,但是在求解直线与圆相交的交点时,就略显笨拙。

对向量进行了学习后,发现可以使用向量法轻松求解直线与圆的相交,亦可线与直线相交的交点,这一点在上一篇文章中直线与直线相交交点的两种求解方法已经介绍。本篇文章介绍下使用向量法求解平面中直线与圆的相交性检测。

2.向量法求解

已知条件:在同一平面中,直线上的两点P1x1,y1)、P2(x2,y2)P1(x_1,y_1)、P2(x_2,y_2),圆心坐标O(x0,y0)O(x_0,y_0)以及圆的半径R。直线与圆的相交,根据圆心到直线的距离来判断存在以下三种情况:

  1. 圆心到直线的距离大于圆的半径,此时直线与圆不相交;
  2. 圆心到直线的距离等于圆的半径,此时直线与圆有且只有一个交点,就是切点;
  3. 圆心到直线的距离小于圆的半径,此时直线与圆有两个交点;

下面我们分别来讨论这三种情况。

2.1判断直线与圆是否相交

2.1.1计算原理

image.png 首先需要求出圆心到直线的距离,以此来判断直线与圆的相交情况,怎么求直线到圆的距离CPrCP_r呢?可以用投影向量求得,投影向量的公式如下:

v1=nv1n(n)2v_1=n \frac { v_1 \bullet n}{(\lVert n \rVert)^2}

为了方便计算,我们将向量n设置为单位向量,则投影向量公式为:

v1=n(v1n)v_1=n ( v_1 \bullet n)

根据上图,我们设:

P1P2=P2P1P1C=CP1P1P2Norm=P1P2P1P2\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} \\

P1P2Norm\overrightarrow{P_1P_2}_{Norm}就是单位向量nn,P1C\overrightarrow{P_1C}就是向量vv。根据直角三角形的勾股定理可知,圆心到直线的距离就是线段P1CP_1C的平方减去投影向量v1v_1的模长的平方的平方根。

若距离CPrCP_r大于半径,直线与圆不相交,否则直线与圆则相交。

2.1.1c#代码实现

在Unity中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计算原理

无交点的情况就不讨论,接下来讨论直线与圆有交点的情况,即CPr<=RCP_r<=R。圆心到直线的距离可以由上面的代码一样求出,投影点PrP_r的坐标也是可以通过投影向量求出的,设:

P1P2=P2P1P1C=CP1P1P2Norm=P1P2P1P2\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} \\

要求两个交点I1I_1I2I_2,我们必须求出线段P1PrP_1P_r以及I2PrI_2P_r的长度,由几何关系可知,其中:

I2Pr=I1Pr=R2CPr2I_2P_r=I_1P_r=\sqrt{R^2-CP_r^2}

根据向量知识的求出I1I_1I2I_2坐标:

I1=Pr+(P1P2Norm)I1PrI2=Pr+P1P2NormI2PrI_1=P_r+(-\overrightarrow{P_1P_2}_{Norm})I_1P_r\\ I_2=P_r+\overrightarrow{P_1P_2}_{Norm}I_2P_r

2.2.2c#代码实现

在Unity中,实现上述向量计算的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一个例子

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

输出结果如下:

image.png

3.结语

其实直线与圆的相交,同直线与直线相交一样,同样可以使用二分法求解,也可以用解方程组的方法去求解,但是二分法的缺点也是一样的。这里与直线不同的是圆的方程为二元二次方程,采用解方程组的方法就稍微棘手,但是通过上述向量法求解可以看出,优势明显。