我正在编写一个物理引擎/模拟器,它结合了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
我基本上需要做的是为两个对象(obj1,obj2)找到一个从( EQ.2 )派生的有效公式,并告诉他们是否碰撞,如果碰到,在什么时候。我需要确切的时间,以便我可以确定它是否在这个特定的时间增量(因为加速器在不同的时间增量会有所不同),并且还可以找到确切的位置(我知道怎么做,给定时间)
对于这个引擎,我将所有对象建模为球体,所有这些公式/ algortithim需要做的是弄清楚在哪些点:
(obj1.POS - obj2.POS).Distance = (obj1.Radius + obj2.Radius)
其中.Distance是一个正标量值。 (如果这更容易,你也可以将两边平方,以避免.Distance计算中隐含的平方根函数。)
(是的,我知道许多其他碰撞检测问题,但是,他们的解决方案似乎都非常特别针对他们的引擎和假设,并且似乎没有一个符合我的条件:3D,球体和加速度应用于模拟增量。如果我错了,请告诉我。)
一些澄清:
1)在时间增量之前和之后检查两个球体的*交点*是不够的。在许多情况下,它们的速度和位置变化将远远超过它们的半径。
2)RE:效率,我不需要帮助(此时无论如何)确定可能的碰撞候选人,我认为我已经覆盖了这一点。
另一个澄清,似乎出现了很多:
3)我的增量运动方程( EQ.2 )是一个二次方程,它同时应用了Velocity 和加速度:
obj.POS = obj.POS + (obj.VEL * dT) + (obj.ACC * dT^2)/2
我见过的物理引擎(当然还有我听说过的每个游戏引擎)只有线性增量运动方程只适用 Velocity:
obj.POS = obj.POS + (obj.VEL * dT)
这就是为什么我不能在StackOverflow,维基百科和整个网络上使用常见发布的碰撞检测解决方案,例如找到两个线段的交点/最接近的方法。我的模拟处理的是可变加速度,这是结果的基础,所以我需要的是两个抛物线段的交点/最近方法。
答案 0 :(得分:4)
在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内距离是最小值,而不是最大值。
答案 1 :(得分:2)
在每个球体的起始位置和结束位置之间画一条线。如果得到的线段与球体相交,则在某些点上肯定会发生碰撞,并且一些聪明的数学可以找到碰撞发生的时间。还要确保检查各段之间的最小距离(如果它们不相交)是否小于2 *半径。这也将表明发生碰撞。
从那里你可以将你的增量时间后退到恰好在碰撞时发生,这样你就可以正确计算力。
您是否考虑使用已经完成此项工作的物理库?许多图书馆使用更先进,更稳定(更好的集成商)系统来解决您正在使用的方程组。想到Bullet Physics。
答案 2 :(得分:1)
好像你想要Closest Point of Approach (CPA)。如果它小于半径之和,则发生碰撞。链接中有示例代码。您可以使用当前速度计算每个帧,并检查CPA时间是否小于您的刻度大小。您甚至可以缓存cpa时间,并且仅在加速度应用于任一项时更新。
答案 3 :(得分:1)
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
替换为A
,将B
替换为$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
:
polyroot()
现在,我们可以使用quartic formula来求解方程。
四次公式将产生4个根,但我们仅对 real 根感兴趣。如果存在双实根,则两个对象恰好在一个时间点接触边缘。如果存在两个真实根,则对象在根1和根2之间连续重叠(即根1是碰撞开始的时间,根2是碰撞停止的时间)。四个实根表示对象两次碰撞,在根对1,2和3,4之间连续发生碰撞。
在R中,我使用# 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
从左下角到右上角遵循红色轨迹。对象restart
从右下方到左上方遵循蓝色轨迹。两个对象在时间0.9194381和时间1.167549之间连续碰撞。两个黑色圆圈刚好接触,显示出重叠的开始-重叠会一直持续直到对象到达灰色圆圈的位置。