【问题标题】:Collision Detection between Accelerating Spheres加速球之间的碰撞检测
【发布时间】:2010-12-27 08:08:49
【问题描述】:

我正在编写一个物理引擎/模拟器,其中包含 3D 太空飞行、行星/恒星引力、船舶推力和相对论效应。到目前为止,一切进展顺利,但是,我需要帮助的一件事是碰撞检测算法的数学运算。

我使用的运动迭代模拟基本如下:

(注意:3D 向量全部大写。)

For each obj

    obj.ACC = Sum(all acceleration influences)

    obj.POS = obj.POS + (obj.VEL * dT) + (obj.ACC * dT^2)/2     (*EQ.2*)

    obj.VEL = obj.VEL + (obj.ACC * dT)

Next

地点:

obj.ACC is the acceleration vector of the object
obj.POS is the position or location vector of the object
obj.VEL is the velocity vector of the object

obj.Radius is the radius (scalar) of the object

dT is the time delta or increment

我基本上需要做的是从上面的 (EQ.2) 中为两个对象 (obj1, obj2) 找到一些有效的公式,并判断它们是否发生碰撞,如果是,在什么时候。我需要确切的时间,以便我可以确定它是否在这个特定的时间增量中(因为加速度在不同的时间增量中会有所不同),而且我可以找到确切的位置(我知道该怎么做,给定时间)

对于这个引擎,我将所有对象建模为球体,所有这个公式/算法需要做的就是弄清楚哪些点:

(obj1.POS - obj2.POS).Distance = (obj1.Radius + obj2.Radius)

其中 .Distance 是一个正标量值。 (如果这更容易,您也可以对两边取平方,以避免 .Distance 计算中隐含的平方根函数)。

(是的,我知道很多很多其他的碰撞检测问题,但是,他们的解决方案似乎都对他们的引擎和假设非常特殊,似乎没有一个符合我的条件:3D、球体和加速度应用于模拟递增。如果我错了,请告诉我。)


一些说明:

1) 在时间增量之前和之后检查两个球体的 交集 是不够的。在许多情况下,它们的速度和位置变化将远远超过它们的半径。

2) RE:效率,在确定可能的碰撞候选者方面,我不需要帮助(无论如何),我想我已经涵盖了。


另一个澄清,似乎经常出现:

3) 我的增量运动方程 (EQ.2) 是一个二次方程,它同时应用了速度加速度:

obj.POS = obj.POS + (obj.VEL * dT) + (obj.ACC * dT^2)/2

在我见过的物理引擎中,(当然还有我听说过的所有游戏引擎)只有线性增量运动方程应用速度:

obj.POS = obj.POS + (obj.VEL * dT)

这就是为什么我不能使用 StackOverflow、Wikipedia 和整个 Web 上常见的碰撞检测解决方案,例如找到两条线段的交点/最近的方法。我的模拟处理对结果至关重要的可变加速度,所以我需要的是两个 抛物线 线段的交点/最接近方法。

【问题讨论】:

    标签: math 3d simulation physics collision-detection


    【解决方案1】:

    好像你想要Closest Point of Approach (CPA)。如果它小于半径之和,则发生碰撞。链接中有示例代码。您可以使用当前速度计算每一帧,并检查 CPA 时间是否小于您的刻度大小。您甚至可以缓存 cpa 时间,并且仅在对任一项目应用加速时才更新。

    【讨论】:

    • 是的,我知道这种方法。不幸的是,AFAIK 它不能解决我的方程 2(即加速度),它只能解决对物体的速度增量应用。我的引擎对物体使用了速度和加速度的增量应用,因此需要一个不同的(高阶)公式来解决它。
    • 您的刻度 (dT) 有多长?对于我见过的大多数模拟,它足够小,以至于 (obj.ACC * dT^2)/2 术语对于任何单个刻度都可以忽略不计,至少对于碰撞检测而言。
    • 现在的刻度是 1/10 秒,但这是可参数化的并且可能会改变。更糟糕的是,超过 100 G 的加速度既可能,有时也可能发生。
    【解决方案2】:

    在每个球体的开始位置和结束位置之间画一条线。如果生成的线段与球体相交,那么球体肯定会在某个点发生碰撞,并且一些聪明的数学可以找到碰撞发生的时间。还要确保检查线段之间的最小距离(如果它们不相交)是否小于 2*radius。这也表明发生了碰撞。

    从那里您可以倒退您的增量时间以准确地发生在碰撞时,这样您就可以正确计算力。

    您是否考虑过使用已经可以做到这一点的物理库?许多库使用更先进、更稳定(更好的积分器)系统来求解您正在使用的方程组。 Bullet Physics 浮现在脑海中。

    【讨论】:

    • 再次,“线段”假设我的增量运动方程是:(obj.POS = obj.POS * (obj.VEL * dT)},即线性. 我的增量运动方程是二次,它需要求解“抛物线段”的最小分离。它需要一个完全不同(并且更难)的公式。
    • 我不考虑使用其他人的物理库,因为我的主要目标之一是自己做这件事。此外,我对开源物理引擎的简短调查并没有发现任何能够充分处理随距离的平方反比变化的物体加速度(即行星重力,它们最多都假设恒定加速度)或速度-有限的影响和可见性传播(即光速/相对论),也没有任何合理的方式将它们添加进去。这两个都是我的模拟所必需的。
    • 我不知道您要做到多准确...对于足够小的 dt 可以将增量位置视为线段吗?
    【解决方案3】:

    在AShelley提到的网页上,最近点方法是针对两个物体匀速运动的情况而开发的。但是,我相信在两个物体都以恒定的非零加速度(二次时间相关)移动的情况下,可以使用相同的矢量微积分方法得出结果。

    在这种情况下,距离平方函数的时间导数是 3 阶(三次)而不是 1 阶。因此,对于最近接近时间将有 3 种解决方案,这并不奇怪,因为两个对象的路径都是弯曲的,因此可能有多个交叉点。对于此应用程序,您可能希望使用当前模拟步骤定义的时间间隔内的最早的 t 值(如果存在这样的时间)。

    我算出了应该给出最接近时间的导数方程:

    0 = |D_ACC|^2 * t^3 + 3 * dot(D_ACC, D_VEL) * t^2 + 2 * [ |D_VEL|^2 + dot(D_POS, D_ACC) ] * t + 2 * dot(D_POS, D_VEL)

    地点:

    D_ACC = ob1.ACC-obj2.ACC

    D_VEL = ob1.VEL-obj2.VEL(更新前)

    D_POS = ob1.POS-obj2.POS(也在更新前)

    dot(A, B) = A.x*B.x + A.y*B.y + A.z*B.z

    (请注意,|A|^2 幅度的平方可以使用 dot(A, A) 计算)

    要为 t 解决这个问题,您可能需要使用像 found on Wikipedia 这样的公式。

    当然,这只会给你最接近的时刻。此时您需要测试距离(使用公式 2 之类的方法)。如果它大于(obj1.Radius + obj2.Radius),则可以忽略(即没有冲突)。但是,如果距离更小,则意味着球体在此之前发生碰撞。然后,您可以使用迭代搜索在更早的时间测试距离。也有可能提出另一种(甚至更复杂的)推导,它考虑了大小,或者可能找到其他一些解析解,而不诉诸迭代求解。

    编辑:由于更高阶,方程的一些解实际上是最远分离的时刻。我相信在所有情况下,3 个解决方案中的 1 个或 3 个解决方案中的 2 个都将是最远分离的时间。您可以通过评估关于时间的二阶导数(通过将一阶导数设置为零找到的 t 值)来分析测试您是处于最小值还是最大值:

    D''(t) = 3 * |D_ACC|^2 * t^2 + 6 * dot(D_ACC, D_VEL) * t + 2 * [ |D_VEL|^2 + dot(D_POS, D_ACC) ]

    如果二阶导数的计算结果为正数,那么您知道距离在给定时间 t 内处于最小值,而不是最大值。

    【讨论】:

    • 感谢 tcovo,这正是我所需要的一个好的开始。知道如何将维基百科上的三次方程解扩展到矢量系数吗?
    • 我写的三次方程有标量系数:两个向量的点积是标量,幅值的平方也是标量。我编辑了我的回复以详细说明这些内容。
    • 我在一些简单的情况下测试了数学并发现(1)一个错字和(2)导数方程的解可能会找到最远分离的时间,这不是我们正在寻找的对于(并不奇怪,因为将导数设置为零通常会找到极值)。我编辑了我的回复。
    • 考虑使用不等式保守地消除常见情况:如果 |p1-p0| - |v1-v0|t - |a1-a0|t^2/2 > r1+r2 则没有碰撞风险。
    • 回复。编辑:啊,这更有意义。我一直想知道三次方程的一阶项中奇怪的不对称性。
    【解决方案4】:

    op 询问碰撞时间。稍微不同的方法将精确计算它...

    记住位置投影方程是:

    NEW_POS=POS+VEL*t+(ACC*t^2)/2

    如果我们将POS 替换为D_POS=POS_A-POS_B,将VEL 替换为D_VEL=VEL_A-VEL_B,并将ACC=ACC_A-ACC_B 替换为对象AB,我们得到:

    $D_NEW_POS=D_POS+D_VEL*t+(D_ACC*t^2)/2

    这是对象之间矢量距离的公式。为了得到它们之间的平方标量距离,我们可以取这个方程的平方,展开后是这样的:

    distsq(t) = D_POS^2+2*dot(D_POS,D_VEL)*t + (dot(D_POS, D_ACC)+D_VEL^2)*t^2 + dot(D_VEL,D_ACC)*t^3 + D_ACC^2*t^4/4

    为了找出碰撞发生的时间,我们可以将方程设为半径和的平方,求解t

    0 = D_POS^2-(r_A+r_B)^2 + 2*dot(D_POS,D_VEL)*t + (dot(D_POS, D_ACC)+D_VEL^2)*t^2 + dot(D_VEL,D_ACC)*t^3 + D_ACC^2*t^4/4

    现在,我们可以使用quartic formula 求解方程。

    四次公式将产生 4 个根,但我们只对 实数 根感兴趣。如果存在双实根,则两个对象恰好在一个时间点接触边缘。如果有两个真正的根,则对象在根 1 和根 2 之间连续重叠(即根 1 是碰撞开始的时间,根 2 是碰撞停止的时间)。四个实根意味着对象碰撞两次,在根对 1,2 和 3,4 之间连续碰撞。

    在R中,我使用polyroot()解决如下:

    # initial positions
    POS_A=matrix(c(0,0),2,1)
    POS_B=matrix(c(2,0),2,1)
    # initial velocities
    VEL_A=matrix(c(sqrt(2)/2,sqrt(2)/2),2,1)
    VEL_B=matrix(c(-sqrt(2)/2,sqrt(2)/2),2,1)
    # acceleration
    ACC_A=matrix(c(sqrt(2)/2,sqrt(2)/2),2,1)
    ACC_B=matrix(c(0,0),2,1)
    # radii
    r_A=.25
    r_B=.25
    # deltas
    D_POS=POS_B-POS_A
    D_VEL=VEL_B-VEL_A
    D_ACC=ACC_B-ACC_A
    
    
    # quartic coefficients
    z=c(t(D_POS)%*%D_POS-r*r, 2*t(D_POS)%*%D_VEL, t(D_VEL)%*%D_VEL+t(D_POS)%*%D_ACC, t(D_ACC)%*%D_VEL, .25*(t(D_ACC)%*%D_ACC))
    # get roots
    roots=polyroot(z)
    # In this case there are only two real roots...
    root1=as.numeric(roots[1])
    root2=as.numeric(roots[2])
    
    # trajectory over time
    pos=function(p,v,a,t){
      T=t(matrix(t,length(t),2))
      return(t(matrix(p,2,length(t))+matrix(v,2,length(t))*T+.5*matrix(a,2,length(t))*T*T))
    }
    
    # plot A in red and B in blue
    t=seq(0,2,by=.1) # from 0 to 2 seconds.
    a1=pos(POS_A,VEL_A,ACC_A,t)
    a2=pos(POS_B,VEL_B,ACC_B,t)
    plot(a1,type='o',col='red')
    lines(a2,type='o',col='blue')
    
    # points of a circle with center 'p' and radius 'r'
    circle=function(p,r,s=36){
      e=matrix(0,s+1,2)
      for(i in 1:s){
        e[i,1]=cos(2*pi*(1/s)*i)*r+p[1]
        e[i,2]=sin(2*pi*(1/s)*i)*r+p[2]
      }
      e[s+1,]=e[1,]
      return(e)
    }
    
    # plot circles with radius r_A and r_B at time of collision start in black
    lines(circle(pos(POS_A,VEL_A,ACC_A,root1),r_A))
    lines(circle(pos(POS_B,VEL_B,ACC_B,root1),r_B))
    # plot circles with radius r_A and r_B at time of collision stop in gray
    lines(circle(pos(POS_A,VEL_A,ACC_A,root2),r_A),col='gray')
    lines(circle(pos(POS_B,VEL_B,ACC_B,root2),r_B),col='gray')
    

    对象A 遵循从左下角到右上角的红色轨迹。对象B 遵循从右下角到左上角的蓝色轨迹。两个物体在时间 0.9194381 和时间 1.167549 之间不断碰撞。两个黑色圆圈刚刚接触,表明重叠的开始 - 重叠会及时持续,直到对象到达灰色圆圈的位置。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-08-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-07
      相关资源
      最近更新 更多