绕线算法和浮点误差

如何解决绕线算法和浮点误差

我已经编写了一个 Java 实现的缠绕算法来解决多边形问题中的点。

这是代码

public boolean isPointInsidepolyline(final Vec3D pt) {
    boolean isCw = this.ispolylineClockwise();
    float count = 0;
    for (Line line : this.getLines()) { // gets all the line segments of the polyline
        if (line.contains(pt)) { // checks if the point is located on the line
            return false;
        }
        Vec3D pt1 = line.getStart();
        Vec3D pt2 = line.getEnd();
        Vec3D dir1 = pt1.sub(pt).normalize();
        Vec3D dir2 = pt2.sub(pt).normalize();
        float angle = dir1.angleBetween(dir2);
        if (dir1.dot(dir2) < 1) {
            if (angle != 0) {
                if (Points.isPointSequenceClockwise(
                    Arrays.asList(pt,pt1,pt2)) == isCw
                ) {
                    count += angle;
                } else {
                    count -= angle;
                }
            }
        }
    }
    return count > eps; // eps = 0.001f;
}

所使用的载体库来自toxiclibs。

整个算法似乎运行良好,但我遇到过由于计算中的浮点错误而失败的边缘情况。仅仅改变 eps 值似乎不是一个可靠的解决方案,因为 epsilon 值不是基于某些东西。

我想问一下遇到类似问题的其他人是否已经设法找到更好的解决方案来减少浮点错误

示例数据以演示以下失败的示例。

折线(用x、y、z坐标点描述):

-199.21376f,-2.1003783E-5f,-204.51433f,-1.302136E-4f,-204.79602f,-6.259075E-6f,-212.74092f,7.808674E-8f,-223.72076f,1.8734062E-6f,-224.89467f,-4.392134E-6f,-225.87766f,1.9165287E-7f,-327.65207f,1.855702E-5f,-355.3976f,3.3298825E-6f,-366.83472f,3.8695987E-5f,-367.7069f,-2.677933E-5f,-368.92123f,1.2206072E-4f,-370.22495f,2.6854828E-5f,-371.65765f,3.9196784E-5f,-399.56082f,3.5489844E-5f,-400.69135f,0.013866073f,-401.04166f,0.0067931935f,-399.67267f,48.833225f,-398.56805f,84.82277f,-397.50436f,92.38736f,-397.18918f,93.921135f,-397.0501f,94.37826f,-395.92285f,97.33734f,-393.09155f,104.53852f,-392.62076f,105.47283f,-387.8274f,113.93879f,-387.5292f,114.346664f,-386.7448f,115.26469f,-379.80237f,122.86422f,-374.12714f,127.701904f,-372.0301f,129.37302f,-370.6804f,130.21136f,-365.25098f,133.43129f,-363.58215f,134.21275f,-357.24515f,136.94856f,-352.32858f,138.49944f,-348.04324f,139.74101f,-344.63644f,140.3836f,-340.69135f,141.17427f,-339.6991f,142.09425f,-337.85547f,143.91138f,-336.91998f,150.82086f,-336.6831f,151.84744f,-335.75406f,151.8479f,-335.27155f,151.84789f,-327.8963f,151.84781f,-290.45605f,151.84741f,-286.98816f,138.05928f,-285.6575f,132.89183f,-282.18118f,120.361824f,-280.883f,116.26616f,-281.07944f,113.260635f,-281.1565f,105.80201f,-281.10834f,103.23026f,-280.75845f,92.780136f,-237.04051f,92.7801f,-215.375f,92.77999f,-199.18573f,92.78001f,-199.20132f,65.52554f,-199.23079f,8.813348f,-199.21376f,-2.1003798E-5f,0

我检查包含的点有坐标 (201.28201f,2.0f,0)

仅通过查看最小和最大 x 坐标,我们可以轻松看出该点不在折线内,但我编写的单元测试仍然返回 true。

解决方法

由于浮点表示,多边形中的点问题一直很棘手。特别是,当点非常接近时,测试点位于边缘的哪一侧是不明确的。使用容差确实不是解决方案,因为值的选择是任意的(您事先不知道顶点本身有多准确),而这只是解决了问题:接近容差限制的点也是不明确的。测试点对齐困难。

另一方面,我不喜欢绕数算法有两个原因:

  • 它使用了一个代价高昂的反三角函数,
  • 错误分析很困难。

我更喜欢简单的半线方法:

  • 考虑测试点旁的水平线;
  • 找到所有穿过这条线的多边形边。测试简单快速,基于纵坐标比较;
  • 对于所有交叉边,确定测试点位于与线相交的左侧还是右侧。这需要进行 2x2 行列式计算(无需显式计算交集);
  • 如果交叉点数为奇数,则该点在内部。

我并没有声称这种方法可以避免歧义问题(这是永远存在的)。无论如何,它提供了更好的安全级别,更容易解释,并且成本低。如果您通过为每个顶点分配“上方”或“下方”状态(但没有“开启”状态)来检测交叉,那么可以保证交叉边的数量是偶数,无论配置如何。该算法永远不会失败(而当测试点为顶点时,绕组数无法得出结论)。


更新:

抱歉,我没有注意到您正在解决 3D 问题。 (在 3D 中,多边形实际上并不是完全平坦的,这可能会导致额外的歧义:一个点不能被归类为倾斜多边形的内部或外部)。您可以通过将所有点投影到一个平面(例如坐标平面或最佳拟合平面)来解决此问题。

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

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?