这里介绍的是“基元”之间的碰撞检测,所谓“基元”就是线段、三角形、矩形、平面、圆、椭圆等各种常见的、能用一两个数学公式表示的图形。“基元碰撞检测”是游戏开发中常用的手段,用数学公式求解碰撞结果,能让我们系统性的理解其中的原理。大家也不用担心,里面用到的数学公式,充其量高中、大一都学过,都属于“空间解析几何”范畴。


-----------------------------------------------   华丽的分割线   -----------------------------------------------


        前面讲了2D线段”与“2D线段”之间的碰撞检测,这次是“2D线段”与“2D圆”的碰撞检测。

先说结果:

离线段起始点最近的”那个点。

        2. 如果两者相切,就是两个交点在同一个位置,随便取一个即可。

        3. 在以下情况下认为没有交点:

                   圆心到线段的距离 > 圆的半径

                   线段很短、完全在圆内

                   线段与圆相交在延长线上

                  如果线段的两个端点重合,即线段是一个点,也认为没有交点。


开始推导:

假设在2D坐标系中,有一个圆,如下图所示:

        圆心为C,坐标为(Xc, Yc),半径为R

                                 

判断是不是ip java 判断是不是基元反应_线段

那么,对于圆周上任意一点P,到C的距离为R,两点之间的距离可以用如下公式计算: 

(Yp - Yc)^2) ) = R

用向量可以表示为:

                                    d = | P - C | = R


对于线段P1 -> Q1,前面讲过,对于该线段上任意一个点P,可以用如下“参数方程”表示:

                                    P = P1 + t * V1        t ∈ [0, 1] 

                                  

判断是不是ip java 判断是不是基元反应_圆_02


-----------------------------------------------   华丽的分割线   -----------------------------------------------


有了以上基础,我们就可以开始计算两者之间的碰撞点了。


假设两根线段相交于点P,那么联立方程组:

| P - C | = R

                                    P = P1 + t * V1        t ∈ [0, 1] 

                                

判断是不是ip java 判断是不是基元反应_线段_03

把P代进圆的方程:

| P1 + t * V1

                                    | t * V1 + (P1 - C) | = R

                                    | t * V1 + CP1 | = R

注意这里左边的“V1”、“CP1”都是向量,右边的R是标量。

两边平方去掉绝对值符号:

t * V1 + CP1)^2 = R^2

                                    V1^2 * t^2 + (2 * CP1 * V1) * t + CP1^2 = R^2

V1^2 * t^2 + (2 * CP1 * V1) * t + (CP1^2 - R^2) = 0

这里,向量的平方就是对自己点积,向量的乘法就是点积,我们做进一步计算:


                                    V1^2 = V1 * V1 = dot_product(V1, V1) = a

                                    2 * CP1 * V1 = 2 * dot_product(CP1, V1) = b



                                    CP1^2 - R^2 = CP1 * CP1 - R^2 = dot_product(CP1, CP1) - R^2

我们可以把方程简化为:

                                    a * t^2 + b * t + c = 0


这个方程,大家有没有很熟悉,初中里都学过,一元二次方程

它有一个专门的求根公式:

                                   

判断是不是ip java 判断是不是基元反应_数学_04

其中,

判断是不是ip java 判断是不是基元反应_碰撞检测_05


 Δ >= 0的时候,可以开根号,能求得两个“实数解”,线段与圆有1~2个交点。

      当 Δ < 0的时候,这种情况下,其实是“圆心到线段的距离”大于圆的半径,故不会相交。


求根公式图片是从baidu上找的,我们把 x 替换成 t,就能得到两个解:

                                    t1 = ( -b - sqrt( b^2 - 4ac ) ) / 2a

 = ( -b + sqrt( b^2 - 4ac ) ) / 2a

可以看到t1 <= t2。

至此,我们根据t1、t2的值就可以判断,线段与圆是否有效相交:

      t1、t2都在[0, 1]范围内时,即 0 <= t1 <= t2 <= 1,这两个都是有效解,相交于两个点。

      如果只有一个在[0, 1]范围内,则那个是有效解,即只相交一个点。

      如果t1 = t2,则线段与圆相切,线段所在直线是圆的切线,切点就是相交点,正好在圆周上。


-----------------------------------------------   华丽的分割线   -----------------------------------------------

原理讲完了,求解部分的伪代码如下:

return No Intersection;             // 线段两个端点重合

           Δ = b^2 - 4ac
           if  (Δ < 0 )     return No Intersection;

           t1 = ( -b - sqrt(Δ) ) / 2a
           if ( t1 >= 0 && t1 <= 1 )    
                  P = P1 + t1 * V1
                  return P;

           t2 = ( -b + sqrt(Δ) ) / 2a           if ( t2 >= 0 && t2 <= 1 )    
                  P = P1 + t2 * V1
                  return P;

          return No Intersection;


特别需要注意的是,如果V1的长度为0,线段两个端点重合,即a == 0,要特殊处理。

各种相交情况可以看以下这些图:

判断是不是ip java 判断是不是基元反应_判断是不是ip java_06

判断是不是ip java 判断是不是基元反应_圆_07

判断是不是ip java 判断是不是基元反应_碰撞检测_08

判断是不是ip java 判断是不是基元反应_碰撞检测_09


判断是不是ip java 判断是不是基元反应_圆_10

判断是不是ip java 判断是不是基元反应_圆_11