移动点和移动线段的撞击位置和时间连续碰撞检测

如何解决移动点和移动线段的撞击位置和时间连续碰撞检测

我正在研究一种2D对撞机系统,该系统将形状分解为一个可能的基元:由两点定义的不可穿透的线段。为了为该系统提供碰撞检测,我使用一种静态碰撞检测方法,该方法每帧计算一次段的边缘与当前处理的段之间的距离(点/线距离)。如果距离太小,则会在该帧期间触发冲突。这可以很好地工作,但是如果一个或多个物体表现出高速,则存在隧道问题。因此,我正在尝试其他选择。

现在,我想介绍在动态点/动态段上运行的连续碰撞检测(CCD)。我的问题是:我不知道怎么做。我确实知道如何在两个运动点(一个运动点和一个静态段)之间进行连续碰撞,但是不知道如何在一个运动点(由P点定义)和一个运动段(由U点和V点定义)之间进行CCD拍摄,完全自由移动)。

illustration of problem

我在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

enter image description here

更新

由于我犯了一个错误,请忽略以上大部分内容。 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>

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-