【问题标题】:What's the best way to implement one-dimensional collision detection?实现一维碰撞检测的最佳方法是什么?
【发布时间】:2011-03-01 20:47:15
【问题描述】:

我正在编写一个模拟软件,需要一种有效的方法来测试沿线的碰撞。

模拟的是一列火车穿过轨道上的几个道岔。当轮子靠近开关 N 英寸时,开关打开,然后在轮子离开时关闭。由于所有车轮的尺寸相同,并且所有开关的尺寸相同,因此我可以将它们表示为沿轨道的单个坐标 X。一旦设置,开关距离和车轮距离就不会相互改变。

当通过将 X 坐标放在列表中并遍历它们通过蛮力完成时,这是一个相当微不足道的问题,但我需要一种有效的方法,因为它需要非常准确,即使火车正在移动在高速。有大量关于 2D 碰撞检测的教程,但我不确定处理这种独特的 1D 场景的最佳方法。


显然我的数据看起来有些混乱。

我模拟的是单个站点,而不是整个区域。火车可以是任意长度,有不同类型的车厢,但只有一列火车。我的火车数据格式为{48,96,508,556,626,674,...},表示火车前部(0)到车轴中心的距离。

(火车数据更有可能以有序列表的形式出现在我面前Car对象,每个对象都有一个长度和一个表示轴距的整数列表那辆车的前部,但所有这些都汇总到一个列表中,因为所有车轴对我来说都是一样的。)

我的开关都在几百英尺之内,而且经常会被火车完全覆盖,开关可以相距数百英尺到几英寸的任何间隔,并且与火车的形式相同:@987654324 @,表示站点起点到开关中心的距离。

最后,我知道了车轮启动开关的距离,以英寸为单位。

例如,使用上面的示例数据和 8 英寸的激活距离,当火车到达 X=40 时,X=0 处的第一个开关将激活,这意味着火车进入站点 40 英寸。当火车到达 X=48 时,X=8 处的开关也被激活。在 X=56 时,第一个开关断开,在 X=64 时,第二个开关也断开。不同的车轴在穿过场地时打开和关闭不同的开关。

火车通常以低于 10 英里/小时的速度运行,但可以走得更高。 (目前我们的模拟速度上限为 30 mph,但更高会更好。)

【问题讨论】:

  • 嗯......(对我来说)显而易见的答案是采用 2D 解决方案并对其进行调整 - 最简单的方法是始终使一个维度保持不变(所有事物的 y 坐标都为 0)。这些解决方案不能轻易适应是有原因的吗?
  • 那么你的数据集是什么样的?你只是有位置(到一个点的绝对距离)还是你有可以掩盖的东西?
  • 我在上面添加了一些示例数据。你是什​​么意思“面具反对”?我可以将列表中的数据转换为其他结构。
  • @FrustratedWithFormsDesigner:鉴于数据的静态性质和单一维度,我认为 2D 解决方案完全是矫枉过正。
  • 轴之间的距离可以小于激活的距离吗?

标签: c# .net algorithm simulation collision-detection


【解决方案1】:

拥有所有开关坐标的排序列表,并在列表中使用二分搜索来查找最近的开关。然后检查它有多远以及是否发生碰撞。

O(log n)

另一种选择是利用火车沿着轨道行驶并且只能靠近两个道岔,一个在后面,一个在前面。

构造一个所有开关的双向链表,并放置一个额外的节点来表示列车在链表中的正确位置。然后只检查火车前往的开关是否接近。

O(1)

为了节省内存,请将排序后的坐标存储在一个数组中,并简单地跟踪火车位于哪些索引之间。

【讨论】:

  • 第一个解决方案我会担心两件事。数据的“团块”。在美国中西部,铁路线上的有趣点可能相距数百英里,导致二分搜索在高密度地方停滞不前。大多数情况下,搜索结果都是空的,这是二分搜索的最慢结果。
  • 这正是我提供更好解决方案的原因。我会删除这个糟糕的建议。
  • 你误解了我的数据。我添加了一些示例 - 如果您有任何其他问题,请告诉我。
  • (虽然这将是一个很好的解决方案,因为火车总是完全在两个开关之间。)
【解决方案2】:

将您的开关位置和灵敏度范围预处理为轨道段列表。每个段都有一个长度,每个段之间有一组开关“开”或“关”事件。

switch_on ( 0 ), ( length: 8 ), switch_on ( 1 ), // x = zero here 
segment ( length: 8 ), switch_off ( 0 ),
segment ( length: 8 ), switch_off ( 1 ),
segment ( length: 488 ), switch_on ( 2 ),
segment ( length: 8 ), switch_on ( 3 ),
segment ( length: 8 ), switch_off ( 2 ),
segment ( length: 8 ), switch_off ( 3 ),
...

对于每个车轴,还显示其当前位置及其所在的轨道段。

如果您要进行基于事件的模拟,则应根据从车轴到当前轨道段末端的距离的最小值安排下一个事件。这与火车速度无关,而且准确(如果火车开得更快,您不会错过开关)。如有必要,将事件存储在堆中(通常少于 30 个左右不值得,如有必要,请分析事件调度)。

处理一个事件将是 O(no-of-axles)。大多数步骤将涉及一两个开关状态更改和位置更新。在每个事件中,一个轴将导致一个开关打开或关闭(根据数据同时的开关会导致两个事件,相隔零时间),并且需要比较所有轴到其段结束的时间。您可以假设所有轴以相同的速度行驶,也可以不假设;就处理事件而言无关紧要,它只计算到达特定于相关轴的下一个开关的时间。

如果您在一个固定的时间步模拟中,则处理在该时间步结束之前可能发生的所有事件,然后处理一个事件以将轴移动到它们在结束时到达的点步。

【讨论】:

  • 对于相同的数据 - 6 个轴,4 个传感器 - 它将使用基于列表的调度程序(线性搜索下一个事件)在 0.09 毫秒内生成 90 个事件,对于基于二进制堆的调度程序( O(logN) 搜索下一个事件),对于大多数应用程序来说应该足够快。如果你的火车真的是几倍长(未决事件的数量等于轴的数量,并且与传感器的数量无关),那么基于堆的调度程序可能会更好。
【解决方案3】:

按照 Ben 的指示将开关列表存储为双向链表。

在轮子对象(或结构,假设有一个)中保留一个指向下一个开关和上一个开关相对于您当前位置的指针。当轮子放在轨道上时初始化这些。

当您在每个开关上移动时,将轮子对象中的“下一个”和“上一个”开关换成新的“下一个”和“上一个”开关,可以通过检查双向链表快速获得。

这避免了所有搜索,除了可能最初放置的轮子。

此外,“开关”结构可用于将接近指针返回到将其列为“上一个”或“下一个”的所有轮子。 (这里有一个互斥锁,所以要小心谁更新它。)这可以提供一个快速更新谁正在接近任何给定的开关以及他们与它的距离。

【讨论】:

    【解决方案4】:

    假设 Axle-to-Axle 距离总是大于激活距离,并且火车进入后路线不会频繁变化,您应该能够通过预先计算加快速度。基本上,对于每个开关,计算它会切换的火车行驶距离列表,然后随着火车的前进遍历列表。

    伪代码:

    axles  = {48,96,508,556,626,674,...}
    switches ={0,8,512,520,...}
    activate = 8
    float toggledist[num_switches]
    boolean switchState[num_switches]={false,false,false,...}
    int idx[num_switches]
    
    for (i in switches)
      n = 0
      for (a in axles)
        toggledist[n++] = switches[i]+axles[a]-activate
        toggledist[n++] = switches[i]+axles[a]+activate
    
    travel= 0.0f;
    
    each (cycle)
      travel += TrainVelocity*time;
      for (i in switches)
        while (trigger>=toggledist[idx[i]])
          switchState[i]=!switchState[i];
          //additional processing for switch change here, if needed
          idx[i]++;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-29
      • 1970-01-01
      • 2023-03-21
      • 2014-01-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-23
      相关资源
      最近更新 更多