问题描述
我正在研究一种2D对撞机系统,该系统将形状分解为一个可能的基元:由两点定义的不可穿透的线段。为了为该系统提供碰撞检测,我使用一种静态碰撞检测方法,该方法每帧计算一次段的边缘与当前处理的段之间的距离(点/线距离)。如果距离太小,则会在该帧期间触发冲突。这可以很好地工作,但是如果一个或多个物体表现出高速,则存在隧道问题。因此,我正在尝试其他选择。
现在,我想介绍在动态点/动态段上运行的连续碰撞检测(CCD)。我的问题是:我不知道怎么做。我确实知道如何在两个运动点(一个运动点和一个静态段)之间进行连续碰撞,但是不知道如何在一个运动点(由P点定义)和一个运动段(由U点和V点定义)之间进行CCD拍摄,完全自由移动)。
我在SO和其他平台上也遇到过类似的问题,但是并没有这些确切的要求:
- 点和线段都在移动
- 段可以旋转和拉伸(因为U和V自由移动)
- 需要在两帧(CCD,无静态碰撞测试)之间准确找到碰撞时间和碰撞点
- 如果可能的话,我更喜欢数学上完美的解法(无迭代逼近算法,扫掠体积)
- 请注意:由于U,V点(see image)的自由度,扫掠线的形状并不总是凸多边形。
- 注意:用扫掠体积测试进行碰撞测试是不准确的,因为与多边形的碰撞点并不意味着实际运动中的碰撞点(see image,一旦实际碰撞,该点将离开多边形段已经越过该点的轨迹)
到目前为止,我想出了以下方法,已知:
- sP(帧开始处的P)
- eP(帧末尾的P)
- sU(帧开始处的U),
- eU(帧末尾的U),
- sV(帧开始处的V)
- eV(帧尾处的V)
问题:它们会碰撞吗?如果是,何时何地?
要回答“ if”问题,我发现本文很有用:https://www.cs.ubc.ca/~rbridson/docs/brochu-siggraph2012-ccd.pdf(第3.1节),但我无法得出“ when”和“ where”的答案。我还在这里找到了对该问题的另一种解释:http://15462.courses.cs.cmu.edu/fall2018/article/13(第三个问题)
解决方案:
将一帧中每个点的时间轨迹建模为线性运动( 0 的线轨迹)
- P(t)= sP *(1-t)+ eP * t
- U(t)= sU *(1-t)+ eU * t
- V(t)= sV *(1-t)+ eV * t
( 0 表示由U和V定义的线段上的位置)
- UV(a,t)= U(t)*(1-a)+ V(t)* a
通过等价于点和线段方程来模拟碰撞:
- P(t)= UV(a,t)
- P(t)= U(t)*(1-a)+ V(t)* a
推导从点P到线段(see picture of F)上的点的向量的函数:
- F(a,t)= P(t)-(1-a)* U(t)-a * V(t)
现在要找到一个碰撞,需要找到 a 和 t ,这样 F(a,t)=(0,0)和 a,t in [0,1] 。可以将其建模为具有2个变量的寻根问题。
将时间轨迹方程式插入 F(a,t):
- F(a,t)=(sP *(1-t)+ eP * t)-(1-a)*(sU *(1-t)+ eU * t)-a *( sV *(1- t)+ eV * t)
按维度(x和y)分开时间轨迹方程:
-
Fx(a,t)=(sP.x *(1- t)+ eP.x * t)-(1-a)*(sU.x *(1- t) + eU.x * t)-a *(sV.x *(1-t)+ eV.x * t)
-
Fy(a,t)=(sP.y *(1- t)+ eP.y * t)-(1-a)*(sU.y *(1- t) + eU.y * t)-a *(sV.y *(1-t)+ eV.y * t)
现在我们有两个方程和两个变量要求解(分别为 Fx,Fy 和 a , t ),因此我们应该能够使用求解器来获取 a 和 t 仅然后检查它们是否位于[0,1]之内。对吗?
当我将其插入Python sympy进行解决时:
from sympy import symbols,Eq,solve,nsolve
def main():
sxP = symbols("sxP")
syP = symbols("syP")
exP = symbols("exP")
eyP = symbols("eyP")
sxU = symbols("sxU")
syU = symbols("syU")
exU = symbols("exU")
eyU = symbols("eyU")
sxV = symbols("sxV")
syV = symbols("syV")
exV = symbols("exV")
eyV = symbols("eyV")
a = symbols("a")
t = symbols("t")
eq1 = Eq((sxP * (1 - t) + exP * t) - (1 - a) * (sxU * (1 - t) + exU * t) - a * (sxV * (1 - t) + exV * t))
eq2 = Eq((syP * (1 - t) + eyP * t) - (1 - a) * (syU * (1 - t) + eyU * t) - a * (syV * (1 - t) + eyV * t))
sol = solve((eq1,eq2),(a,t),dict=True)
print(sol)
if __name__ == "__main__":
main()
我得到的解决方案规模巨大,需要5分钟才能完成评估。 我不能在实际的引擎代码中使用如此大的表达方式,而这种解决方案对我而言似乎并不正确。
我想知道的是: 我在这里想念什么吗?我认为这个问题似乎很容易理解,但是我无法找到一种数学上准确的方法来找到动态点的冲击解决方案的时间( t )和点( a ) /动态细分。非常感谢您的帮助,即使有人告诉我 不可能那样做。
解决方法
TLDR
我确实读过“ ...喜欢5分钟就能评估...”
不要太久,这是许多线和点的实时解决方案。
抱歉,这不是一个完整的答案(我没有合理化和简化方程式),无法找到我要留给您的拦截点。
我还可以看到几种解决方案的方法,因为它绕着一个三角形(见图)旋转,而三角形是平坦的。接近波纹管会找到三角形的长边等于较短的两个整数之和的时间点。
求解 u (时间)
这可以作为一个简单的二次函数,使用从3个起点(每个点的单位时间内的向量)得出的系数来完成。解决你的问题
下面的图片提供了更多细节。
- 点 P 是点的起始位置
- L1 , L2 是线段的起点。
- 向量 V1 用于单位时间内的点(沿绿线)。
- 矢量 V2 , V3 用于单位时间内的行结束。
- u 是单位时间
- A 是点(蓝色), B 和 C 是线终点(红色)
在某个时间点( u ),其中 A 在 B , C 。此时, AB ( a )和 AC ( c )行的总和等于 BC 行的长度(如 b )(橙色行)。
这意味着当 b-(a + c)== 0 时,点在直线上。在图像中,点是平方的,因为这使它稍微简化了一点。 b 2 -(a 2 + c 2 )== 0
图像的底部是根据 u,P,L1,L2,V1,V2,V3 的方程式(二次方程式)。
该方程需要重新排列,以便得到(???)u 2 +(???)u +(???)= 0
抱歉,手动执行此操作非常繁琐,而且很容易出错。我没有手头的工具,也没有使用python,所以我不知道您使用的数学库。但是,它应该可以帮助您找到如何计算(???)u 2 +(???)u +(???)= 0
更新
由于我犯了一个错误,请忽略以上大部分内容。 b-(a + c)== 0 与 b 2 -((a 2 + c 2 )== 0 。第一个是需要的那个,这在处理部首时是个问题(请注意,仍然可以使用a + bi == sqrt(a^2 + b^2)
,其中i
是虚数)。
另一种解决方案
所以我探索了其他选项。
最简单的有一点缺陷。它将返回拦截时间。但是,必须对此进行验证,因为它在截取直线而不是线段 BC
时也会返回截取时间因此,如果找到结果,则可以通过将找到的点和线段的点积除以线段长度的平方来测试它。请参见测试代码段中的功能isPointOnLine
。
要解决这个问题,我使用了以下事实:当 BC 行与从 B 到 A 的向量的叉积为0时,点就在线上。
一些重命名
使用上面的图片,我对变量进行了重命名,以便我可以更轻松地完成所有复杂的操作。
/*
point P is {a,b}
point L1 is {c,d}
point L2 is {e,f}
vector V1 is {g,h}
vector V2 is {i,j}
vector V3 is {k,l}
Thus for points A,B,C over time u */
Ax = (a+g*u)
Ay = (b+h*u)
Bx = (c+i*u)
By = (d+j*u)
Cx = (e+k*u)
Cy = (f+l*u)
/* Vectors BA and BC at u */
Vbax = ((a+g*u)-(c+i*u))
Vbay = ((b+h*u)-(d+j*u))
Vbcx = ((e+k*u)-(c+i*u))
Vbcy = ((f+l*u)-(d+j*u))
/*
thus Vbax * Vbcy - Vbay * Vbcx == 0 at intercept
*/
这给出了二次方
0 = ((a+g*u)-(c+i*u)) * ((f+l*u)-(d+j*u)) - ((b+h*u)-(d+j*u)) * ((e+k*u)-(c+i*u))
重新安排我们得到
0 = -((i*l)-(h*k)+g*l+i*h+(i+k)*j-(g+i)*j)*u* u -(d*g-c*l-k*b-h*e+l*a+g*f+i*b+c*h+(i+k)*d+(c+e)*j-((f+d)*i)-((a+c)*j))*u +(c+e)*d-((a+c)*d)+a*f-(c*f)-(b*e)+c*b
因此系数为
A = -((i*l)-(h*k)+g*l+i*h+(i+k)*j-(g+i)*j)
B = -(d*g-c*l-k*b-h*e+l*a+g*f+i*b+c*h+(i+k)*d+(c+e)*j-((f+d)*i)-((a+c)*j))
C = (c+e)*d-((a+c)*d)+a*f-(c*f)-(b*e)+c*b
我们可以使用二次公式求解(请参见右上图)。
注意,可能有两种解决方案。在示例中,我忽略了第二种解决方案。但是,由于第一个解决方案可能不在线段上,因此如果第一个解决方案失败,则需要将第二个解决方案保持在 0 范围内。您还需要验证该结果。
测试
为避免错误,我必须测试解决方案
下面是一个片段,该片段生成随机的随机线对,然后生成随机的线,直到找到截距为止。
感兴趣的功能是
-
movingLineVPoint
,如果有的话,它会返回第一次拦截的单位时间。 -
isPointOnLine
以验证结果。
const ctx = canvas.getContext("2d");
canvas.addEventListener("click",test);
const W = 256,H = W,D = (W ** 2 * 2) ** 0.5;
canvas.width = W; canvas.height = H;
const rand = (m,M) => Math.random() * (M - m) + m;
const Tests = 300;
var line1,line2,path,count = 0;
setTimeout(test,0);
// creating P point L line
const P = (x,y) => ({x,y,get arr() {return [this.x,this.y]}});
const L = (l1,l2) => ({l1,l2,vec: P(l2.x - l1.x,l2.y - l1.y),get arr() {return [this.l1,this.l2]}});
const randLine = () => L(P(rand(0,W),rand(0,H)),P(rand(0,H)));
const isPointOnLine = (p,l) => {
const x = p.x - l.l1.x;
const y = p.y - l.l1.y;
const u = (l.vec.x * x + l.vec.y * y) / (l.vec.x * l.vec.x + l.vec.y * l.vec.y);
return u >= 0 && u <= 1;
}
// See answer illustration for names
// arguments in order Px,Py,L1x,l1y,l2x,l2y,V1x,V1y,V2x,V2y,V3x,V3y
function movingLineVPoint(a,b,c,d,e,f,g,h,i,j,k,l) {
var A = -(i*l)-(h*k)+g*l+i*h+(i+k)*j-(g+i)*j;
var B = -d*g-c*l-k*b-h*e+l*a+g*f+i*b+c*h+(i+k)*d+(c+e)*j-((f+d)*i)-((a+c)*j)
var C = +(c+e)*d-((a+c)*d)+a*f-(c*f)-(b*e)+c*b
// Find roots if any. Could be up to 2
// Using the smallest root >= 0 and <= 1
var u,D,u1,u2;
// if A is tiny we can ignore
if (Math.abs(A) < 1e-6) {
if (B !== 0) {
u = -C / B;
if (u < 0 || u > 1) { return } // !!!! no solution !!!!
} else { return } // !!!! no solution !!!!
} else {
B /= A;
D = B * B - 4 * (C / A);
if (D > 0) {
D **= 0.5;
u1 = 0.5 * (-B + D);
u2 = 0.5 * (-B - D);
if ((u1 < 0 || u1 > 1) && (u2 < 0 || u2 > 1)) { return } // !!!! no solution !!!!
if (u1 < 0 || u1 > 1) { u = u2 } // is first out of range
else if (u2 < 0 || u2 > 1) { u = u1 } // is second out of range
else if (u1 < u2) { u = u1 } // first is smallest
else { u = u2 }
} else if (D === 0) {
u = 0.5 * -B;
if (u < 0 || u > 1) { return } // !!!! no solution !!!!
} else { return } // !!!! no solution !!!!
}
return u;
}
function test() {
if (count> 0) { return }
line1 = randLine();
line2 = randLine();
count = Tests
subTest();
}
function subTest() {
path = randLine()
ctx.clearRect(0,W,H);
drawLines();
const u = movingLineVPoint(
path.l1.x,path.l1.y,line1.l1.x,line1.l1.y,line2.l1.x,line2.l1.y,path.vec.x,path.vec.y,line1.vec.x,line1.vec.y,line2.vec.x,line2.vec.y
);
if (u !== undefined) { // intercept found maybe
pointAt = P(path.l1.x + path.vec.x * u,path.l1.y + path.vec.y * u);
lineAt = L(
P(line1.l1.x + line1.vec.x * u,line1.l1.y + line1.vec.y * u),P(line2.l1.x + line2.vec.x * u,line2.l1.y + line2.vec.y * u)
);
const isOn = isPointOnLine(pointAt,lineAt);
if (isOn) {
drawResult(pointAt,lineAt);
count = 0;
info.textContent = "Found at: u= " + u.toFixed(4) + ". Click for another";
return;
}
}
setTimeout((--count < 0 ? test : subTest),18);
}
function drawLine(line,col = "#000",lw = 1) {
ctx.lineWidth = lw;
ctx.strokeStyle = col;
ctx.beginPath();
ctx.lineTo(...line.l1.arr);
ctx.lineTo(...line.l2.arr);
ctx.stroke();
}
function markPoint(p,size = 3,lw = 1) {
ctx.lineWidth = lw;
ctx.strokeStyle = col;
ctx.beginPath();
ctx.arc(...p.arr,size,Math.PI * 2);
ctx.stroke();
}
function drawLines() {
drawLine(line1);
drawLine(line2);
markPoint(line1.l1);
markPoint(line2.l1);
drawLine(path,"#0B0",1);
markPoint(path.l1,2,2);
}
function drawResult(pointAt,lineAt) {
ctx.clearRect(0,H);
drawLines();
markPoint(lineAt.l1,"red",1.5);
markPoint(lineAt.l2,1.5);
markPoint(pointAt,"blue",3);
drawLine(lineAt,"#BA0",2);
}
div {position: absolute; top: 10px; left: 12px}
canvas {border: 2px solid black}
<canvas id="canvas" width="1024" height="1024"></canvas>
<div><span id="info">Click to start</span></div>