【问题标题】:Point in polygon hit test algorithm多边形命中测试算法中的点
【发布时间】:2019-07-10 10:34:40
【问题描述】:

我需要测试一个点是否撞击带有孔和岛的多边形。我想了解我应该如何做到这一点。这没有记录在案,我找不到任何解释或示例。

我所做的是为每个外部多边形命中计数+1,为每个内部多边形命中计数-1。结果总和是:

  • > 0:命中;

HitData 类根据缠绕数分隔 路径,以避免不必要地重新计算 orientation。将Clipper.PointInPolygon() 应用于每个路径,总和很容易计算。

但有两个主要缺点:

  1. 我必须将Clipper.PointInPolygon() 应用于EVERY 路径
  2. 我无法利用 PolyTree 的层次结构。

有过 Clipper 实践经验的人 (@angus-johnson?) 能解决这个困惑吗?

再次,我的问题是:我应该如何实现这一点?我是否在重新发明轮子,而 Clipper 库中有现成的实际解决方案?

旁注:PolyTree 仍然需要测试 EVERY path 以确定该点在哪个 PolyNode 中。没有 Clipper.PointInPolyTree() 方法,因此, AFAIK PolyTree 没有帮助。

分隔外多边形和内多边形的结构:

public class HitData
{
    public List<List<IntPoint>> Outer, Inner;

    public HitData(List<List<IntPoint>> paths)
    {
        Outer = new List<List<IntPoint>>();
        Inner = new List<List<IntPoint>>();

        foreach (List<IntPoint> path in paths)
        {
            if (Clipper.Orientation(path))
            {
                Outer.Add(path);
            } else {
                Inner.Add(path);
            }
        }
    }
}

这是测试点的算法:

public static bool IsHit(HitData data, IntPoint point)
{
    int hits;

    hits = 0;

    foreach (List<IntPoint> path in data.Outer)
    {
        if (Clipper.PointInPolygon(point, path) != 0)
        {
            hits++;
        }
    }

    foreach (List<IntPoint> path in data.Inner)
    {
        if (Clipper.PointInPolygon(point, path) != 0)
        {
            hits--;
        }
    }

    return hits > 0;
}

【问题讨论】:

    标签: clipperlib


    【解决方案1】:

    有过 Clipper 实践经验的人 (@angus-johnson?) 能否解决这个困惑?

    我不清楚你的困惑是什么。正如您正确观察到的,Clipper 库没有提供确定一个点是否在多个路径内的函数。

    编辑(2019 年 9 月 13 日):

    好的,我现在创建了一个PointInPaths 函数(在 Delphi Pascal 中),它可以确定一个点是否在多个路径内。请注意,此函数适应不同的多边形填充规则。

    函数 CrossProduct(const pt1, pt2, pt3: TPointD): double; 变量 x1,x2,y1,y2:双倍; 开始 x1 := pt2.X - pt1.X; y1 := pt2.Y - pt1.Y; x2 := pt3.X - pt2.X; y2 := pt3.Y - pt2.Y; 结果 := (x1 * y2 - y1 * x2); 结尾; 函数 PointInPathsWindingCount(const pt: TPointD; 常量路径:TArrayOfArrayOfPointD):整数; 变量 i,j,len:整数; p:TArrayOfPointD; prevPt: TPointD; isAbove:布尔值; crossProd:双倍; 开始 //nb: 当pt在一行时返回MaxInt ((2^32)-1) 结果:= 0; for i := 0 to High(paths) do 开始 j := 0; p := 路径[i]; 长度:=长度(p); 如果 len 0 那么 prevPt := p[j -1]; crossProd := CrossProduct(prevPt, p[j], pt); 如果 crossProd = 0 那么 开始 结果:= MaxInt; 出口; 结尾 否则,如果 crossProd pt.Y) 做 inc(j); 如果 j = len 则中断 否则如果 j > 0 那么 prevPt := p[j -1]; crossProd := CrossProduct(prevPt, p[j], pt); 如果 crossProd = 0 那么 开始 结果:= MaxInt; 出口; 结尾 else if crossProd > 0 then inc(Result); 结尾; 公司(j); isAbove := 不是 isAbove; 结尾; 结尾; 结尾; 函数 PointInPaths(const pt: TPointD; 常量路径:TArrayOfArrayOfPointD;填充规则:TFillRule):布尔值; 变量 wc:整数; 开始 wc := PointInPathsWindingCount(pt, 路径); 案例填充规则 frEvenOdd: 结果 := Odd(wc); frNonZero: 结果 := (wc 0); 结尾; 结尾;



    关于利用 PolyTree 结构:

    PolyTree 中的顶部节点是外部节点,它们一起包含每个(嵌套)多边形。因此,您只需在这些顶级节点上执行PointInPolygon,直到找到肯定的结果。然后在该节点嵌套路径(如果有)上重复 PointInPolygon 以在那里寻找正匹配。显然,当外部节点在PointInPolygon 测试中失败时,它的嵌套节点(多边形)也会失败。外部节点将增加缠绕数,内部孔将减少缠绕数。

    【讨论】:

    • 尊敬的 Johnson 博士,您的第一个解决方案与 IsHit() 方法相同。没关系,我最终使用的是什么。我只是想知道是否有更快的算法。这个检查树的所有节点,而只有一条路径就足够了。我希望在一些易于实现的代码中可以减少检查的节点数量。也许从底部节点开始,然后向上移动到根?但是自从我有这个疑问以来已经过去了很长一段时间,“次优”的解决方案已经足够好了!不过,感谢您抽出宝贵时间回答我的问题 :)
    • @pid。是的,您是对的,我的解决方案 1 与您上面的 IsHit() 函数相同(显然我没有仔细查看)。另外,关于您的陈述:“我无法利用 PolyTree 的层次结构”,我认为可以利用 PolyTree ,因为您只需要检查顶级节点是否有 @ 987654327@ 并且仅在 PointInPolygon 返回肯定结果时深入到他们的孩子。
    • 另外,关于 PolyTree,我不建议仅仅为了构建 PolyTree 结构而强制执行剪裁操作,尤其是在您关心性能的情况下。因此,除非您对裁剪操作进行后期处理,否则我怀疑解决方案 2 将是两种解决方案中速度更快的解决方案。
    • 太棒了!我只是在找这个。您是否有机会将其添加到 Clipper 并发布新版本?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-27
    • 1970-01-01
    • 2011-12-19
    • 2013-03-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多