【问题标题】:Mass line segment intersection test - optimisation质量线段相交测试 - 优化
【发布时间】:2022-01-24 19:34:22
【问题描述】:

我有大量静态“墙”集合。这些被保存为 Vector3 点的两项数组,可以被视为线段(这是一个 2D 问题,所以我们只对 X 和 Z 感兴趣)。其中大约有 1000 个。

有一个大的点列表 - (以百万计)保存在一个名为 pointCollection 的列表中,还有一个更小的(数百个)Vector3 点保存在另一个名为 testCollection 的列表中。

结合 pointCollection 和 testCollection 的每一项,并针对墙集合测试这些段中的每一个,如果没有交叉点,则可以完成一些工作。

我目前的方法效果很好,但太慢了,因此如果没有一些改进就无法使用。

下面的工作(但速度很慢)代码显示了我在测试和交集函数中的初始方法。

//iterate over the point collection
foreach (Vector3 myPoint in pointCollection){

    //iterate over the test collection
    foreach (Vector3 myTest in testCollection){

        //assume no intersection until proven wrong
        bool pointClean = true;
        
        //test against all walls
        foreach (Vector3[] myWall in myWalls){
        if (intersects(myPoint.x, myPoint.z, myTest.x, myTest.z, myWall[0].x, myWall[0].z, myWall[1].x, myWall[1].z) == true){
            //Break to avoid unneeded tests - this point has already intersected
            pointClean = false;
            break;
        }
    }
    
    if (pointClean == true){
        //do some work if the point is clean
    }               
}

//tests the intersection of line segment (ab -> cd) vs (pq -> rs)
public bool intersects(float a, float b, float c, float d, float p, float q, float r, float s) {
    float det = (c - a) * (s - q) - (r - p) * (d - b);
    float gamma;
    float lambda;
        
    if (det == 0) {
        return false;
    } else {
        lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / det;
        gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / det;
        return (0 < lambda && lambda < 1) && (0 < gamma && gamma < 1);
    }
}

我觉得可能有一些可用的快捷方式“快速”通过使用矩形或排序列表排除了一些可能的重叠,但我正在绘制空白。

【问题讨论】:

  • 你的空间有界限吗?
  • 是的,两个轴都有明确的限制 - 因此可以按至少一个维度对列表进行排序。
  • 我认为您正在寻找线扫描算法,我已经使用了几次,效果非常好。 en.wikipedia.org/wiki/Sweep_line_algorithm
  • 另一种方法可以是分段树。 en.wikipedia.org/wiki/Segment_tree 在这里,您可以将 pointCollection 存储在分段树中,并将您的 testcollection 作为您想要获取 pointCollection 节点的“窗口”。
  • 我过去使用过四叉树,你可能会在那里找到一些东西。我会对此有所了解。

标签: c# algorithm optimization


【解决方案1】:

如果两堵墙可以相交,那么首先通过在这些交点处拆分墙使它们不相交。

鉴于数字,我将反转循环,并首先对较小的 (testCollection) 集合进行迭代。

对于其中的每个myTest,将墙转置为polar coordinates,以myTest 为原点,并按它们的角度(即它们的角坐标)对这些极点进行排序。迭代这些角度(和相关的边)总是保留最接近原点的墙(或者没有,当有一个没有任何墙的角段时),所以创建一个排序的墙段列表——从@987654326 的角度来看@ -- 不重叠。注意还要处理最大和最小角度之间的段(环绕)。

然后迭代较大的pointCollection,将每个myPoint点转换到同一个极坐标系。通过角度的二分搜索,您可以找到零或一堵墙来检查交叉点。

确保在交叉点有快速出口-检查:如果myPoint的距离(即径向极坐标)大于找到的线段的两个端点的距离,则存在交叉点。如果两者都小于,则不存在交集。对于剩下的情况,确定通过myPoint 的线相交的距离,并将其与myPoint 的实际距离进行比较。

通过使用极坐标的替代方法,使用slope 代替角度和距离的平方来避免平方根运算,可能会获得一些效率。

【讨论】:

    【解决方案2】:

    我尝试并发现 Trincot 的答案很好,并且确实取得了一些收益,但它们并没有改变游戏规则。但是,我能够通过关注问题的“快速失败”部分来解决问题(即尽早获得正交叉点以避免额外的工作)。

    基本上,所需的原则是,如果墙壁被“击中”,那么它很可能会被附近的点再次击中(它要么更接近测试点,要么可能只是更大)。

    当发生与墙的交叉点时,该墙可能会“提升”未来测试的优先级。这会创建一个积极的反馈循环,首先测试最有可能的交叉口,从而提高它们的排名 - 从而减少额外测试的需要。

    这使我的数据集的整体速度提高了大约 10 倍 - 需要注意维护按等级排序的墙壁列表并在适当的点重置/洗牌,这样排序算法本身就不会变得很重要。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-08-31
      • 2011-01-04
      • 1970-01-01
      • 2023-03-29
      • 2021-01-03
      • 2012-04-04
      • 1970-01-01
      相关资源
      最近更新 更多