美文网首页
谁说数学没用——基元碰撞检测(1)

谁说数学没用——基元碰撞检测(1)

作者: ayu兄 | 来源:发表于2018-03-26 16:48 被阅读0次

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

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

    好接下来开始最简单的,“2D线段”与“2D线段”之间的碰撞检测。注意这里是“线段”,不是“直线”或者“射线”,如果搞不清的,建议先搜索一下。

    先说结果

            1. 两根“线段”之间如果有正常的“相交”,那么结果应该是一个点。

            2. 在以下情况下没有交点:平行、但不重合;在很远处相交,不在线段范围内。

            3. 如果重合,我们需要特殊处理。

    推导过程:

    假设在2D坐标系中,有一条线段,如下图所示:

            起点为P1,坐标为(Xp1, Yp1)

            终点为Q1,坐标为(Xq1,Yq1)

    V1 = Q1 - P1

    那么从P1到Q1有一个向量V1,可以表示为 V1 = Q1 - P1,反过来,Q1 = P1 + V1。这个应该很好理解。

    好,接下来,对于该线段上任意一个点P,我们可以用如下“参数方程”表示:

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

    把V1 = Q1 - P1替代进去,我们可以得到:

                                        P = P1 + t * (Q1 - P1)        t ∈ [0, 1]

    见下图

    P = P1 + t * V1

    其中 t 为变参,它的范围是0~1,两边都是闭区间,我们可以把它理解为0~100的一个百分比。

            当 t=0 的时候,P = P1 + 0 * V1, ==> P = P1,即P点与P1点重合。

            当 t=1 的时候,P = P1 + 1 * V1, ==> P = P1 + V1 = P1 + (Q1 - P1) = Q1,即P点与Q1点重合。

            如果 t=0.5,P = P1 + 0.5 * (P2 - P1) = 0.5 * (P2 + P1),即线段的中点,就是50%的位置。

    如果 t 超出这个范围,那么点虽然在该线段所处的“直线”上,但“不在线段范围内”。如下图,t > 1。

    P = P1 + t * V1    ( t > 1)

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

    接下来我们把第二根线段放进去,P2 -> Q2,我们可以得到:

                                        V2 = Q2 - P2

                                        P = P2 + s * V2                  s ∈ [0, 1]

                                        P = P2 + s * (Q2 - P2)        s ∈ [0, 1]

    两线段相交于P点

    大家注意两根线段的“参数方程”有点小区别,我们在第一个方程中用了变参 t,在第二个方程中用了变参 s,以此表示两个不同的变量,切不可都写成 t,否则最后联立方程组的时候,会傻傻分不清楚。(仔细想想为什么?)

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

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

    假设两根线段相交于点P,那么:

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

                                        P = P2 + s * V2                  s ∈ [0, 1]

    两个等式左边都是P,那么我们可以令他们相等:

                                        P1 + V1 * t = P2 + V2 * s

    把等式处理一下,得到:

                                        t * V1 – s * V2 = P2 – P1

    到目前为止,我们还不能直接求解该方程,因为这是一个“二元一次方程”,有两个变量:t 和 s,却只有一个等式,所以是无法得到唯一解的(这可是常识哦),我们需要继续处理一下。

    因为每个点其实有两个坐标值(x,y),我们可以得到:

                                        t * V1.x – s * V2.x = (P2 – P1).x = P12.x

                                        t * V1.y – s * V2.y = (P2 – P1).y = P12.y

    哈,现在我们就有两个方程,构成一个方程组,来求解两个变量了。如果到这里,你已经会继续处理了(比如消元法、代入法),那就不用继续往下看了,下面我用行列式(线性代数)来求得 t 和 s 。

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

    根据以上方程,令:

                                          A = | V1.x   V2.x   |

                                                 | V1.y   V2.y   |

    如果线性代数还有点印象的话,对此应该能理解,行列式不是矩阵,行列式虽然写起来跟矩阵差不多、有一大堆值,但其实它的结果就是一个数值:

                                          A = V1.x * V2.y - V2.x * V1.y

    继续:

                 T = | P12.x   V2.x   |                                            S = | V1.x   P12.x   |

                        | P12.y   V2.y   |                                                  | V1.y   P12.y   |

                 T = P12.x * V2.y - V2.x * P12.y                           S = V1.x * P12.y - P12.x * V1.y

    最终结果:

                                          { t = T / A

                                          { s = S / A

    再回过来看方程,P = P1 + V1 * t = P2 + V2 * s,说明线段1在 t 比例 处与线段2相交,这个位置是线段2的 s 比例 处。

    貌似我们很容易就得到结果了,且慢!还有一些情况要判断:首先这里有除法,那除0是必须要处理的,如果A = 0,就无法求得 t 和 s 。这种情况下,其实是两根线段“平行”或者“重合”,我们来推导一下:

                                          A = V1.x * V2.y - V2.x * V1.y = 0

                                          V1.x * V2.y = V2.x * V1.y

                                          V2.y / V2.x = V1.y / V1.x

    y/x,结果就是这根线段或直线的“斜率”。如果两根线段的“斜率”相等,那可不就是“平行”或“重合”吗?所以,算完 A,先别着急算 T 和 S,如果A = 0,那函数可能就要返回False了,认为没有相交。

    如果A不等于0,就可以继续得到 t 和 s。接着我们要判断 t 和 s的范围,前面我们说过,t ∈ [0, 1]  &&  s ∈ [0, 1],才是相交在两个线段范围内,如果任一条件不满足,仍然不是“线段相交”,而是“直线相交”或“射线相交”。

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

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

                A = V1.x * V2.y - V2.x * V1.y

                if (A == 0)              return Parallel Or SuperPosition;

                T = P12.x * V2.y - V2.x * P12.y

                t = T / A

                if ( t < 0 || t > 1 )     return No Intersection;

                S = V1.x * P12.y - P12.x * V1.y

                s = S / A

                if ( s < 0 || s > 1 )    return No Intersection;

                P = P1 + t * V1

                return P;                  // P就是最终交点

    相关文章

      网友评论

          本文标题:谁说数学没用——基元碰撞检测(1)

          本文链接:https://www.haomeiwen.com/subject/pqurcftx.html