Segment Segment交叉代码的精度问题

时间:2011-12-21 05:26:28

标签: geometry floating-point precision computational-geometry

我正在调试我编写的算法,该算法通过识别所有交叉点并在外围周围走动,将复杂的自相交多边形转换为简单多边形。

我写了一系列随机数据生成压力测试,在这一次,我遇到了一个有趣的情况,导致我的算法失败,正确地工作了数千次。

double fRand(double fMin, double fMax)
{
    double f = (double)rand() / RAND_MAX; // On my machine RAND_MAX = 2147483647
    return fMin + f * (fMax - fMin);
}
// ... testing code below:  
srand(1);
for(int j=3;j<5000;j+=5) {
    std::vector<E_Point> geometry;
    for(int i=0;i<j;i++) {
        double radius = fRand(0.6,1.0);
        double angle = fRand(0,2*3.1415926535);
        E_Point pt(cos(angle),sin(angle));
        pt *= radius;
        geometry.push_back(pt); // sending in a pile of shit
    }
    // run algorithm on this geometry

这概述了这是如何相关的,以及我如何到达现在的位置。我遗漏了更多细节。

我能够做的就是将问题缩小到我正在使用的段段交叉码:

bool intersect(const E_Point& a0, const E_Point& a1,
               const E_Point& b0, const E_Point& b1,
               E_Point& intersectionPoint) {

    if (a0 == b0 || a0 == b1 || a1 == b0 || a1 == b1) return false;
    double x1 = a0.x; double y1 = a0.y;
    double x2 = a1.x; double y2 = a1.y;
    double x3 = b0.x; double y3 = b0.y;
    double x4 = b1.x; double y4 = b1.y;

    //AABB early exit
    if (b2Max(x1,x2) < b2Min(x3,x4) || b2Max(x3,x4) < b2Min(x1,x2) ) return false;
    if (b2Max(y1,y2) < b2Min(y3,y4) || b2Max(y3,y4) < b2Min(y1,y2) ) return false;

    float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3));
    float ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3));
    float denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
    // check against epsilon (lowest normalized double value)
    if (fabs(denom) < DBL_EPSILON) {
        //Lines are too close to parallel to call
        return false;
    }
    ua /= denom;
    ub /= denom;

    if ((0 < ua) && (ua < 1) && (0 < ub) && (ub < 1)) {
        intersectionPoint.x = (x1 + ua * (x2 - x1));
        intersectionPoint.y = (y1 + ua * (y2 - y1));
        return true;
    }
    return false;
}

发生的事情是我有两个交叉点,这个函数返回完全相同的交叉点值。以下是相关几何体的放大视图:

enter image description here

垂直线由点定义 (0.3871953044519425, -0.91857980824611341), (0.36139704793723609, 0.91605957361605106)

(0.8208980020500205, 0.52853407296583088), (0.36178501611208552, 0.88880385168617226)

的绿色近水平线

(0.36178501611208552, 0.88880385168617226), (-0.43211245441046209, 0.68034202227710472)

的白线

正如您所看到的,最后两行确实有共同点。

我的功能为我提供了(0.36178033094571277, 0.88880245640159794)的解决方案 这两个交叉路口(其中一个你在图片中看到的是一个红点)。

这是一个大问题的原因是我的周边算法依赖于对每条边上的交叉点进行排序。由于这两个交点都被计算为具有相同的精确值,因此排序使它们处于错误的方向。周边的路径来自上方,沿着白线左边,而不是左边的绿线,这意味着我不再跟随多边形的外围。

要解决这个问题,我可能会做很多事情但是我不想搜索所有的交叉点列表来检查其他点以查看位置是否相等。更好的解决方案是尝试提高交叉口功能的准确性。

所以我要问的是,为什么解决方案指向如此不准确?是因为其中一条线几乎是垂直的吗?我应该先进行某种转型吗?在这两种情况下,几乎垂直的行都是a0a1

更新:嘿,看看这个:

TEST(intersection_precision_test) {
    E_Point problem[] = {

    {0.3871953044519425, -0.91857980824611341}, // 1559
    {0.36139704793723609, 0.91605957361605106}, // 1560
    {-0.8208980020500205, 0.52853407296583088}, // 1798
    {0.36178501611208552, 0.88880385168617226}, // 1799
    {-0.43211245441046209, 0.6803420222771047} // 1800
    };
    std::cout.precision(16);
    E_Point result;
    intersect(problem[0],problem[1],problem[2],problem[3],result);
    std::cout << "1: " << result << std::endl;
    intersect(problem[0],problem[1],problem[3],problem[2],result);
    std::cout << "2: " << result << std::endl;
    intersect(problem[1],problem[0],problem[2],problem[3],result);
    std::cout << "3: " << result << std::endl;
    intersect(problem[1],problem[0],problem[3],problem[2],result);
    std::cout << "4: " << result << std::endl;

    intersect(problem[2],problem[3],problem[0],problem[1],result);
    std::cout << "rev: " << result << std::endl;
    intersect(problem[3],problem[2],problem[0],problem[1],result);
    std::cout << "revf1: " << result << std::endl;
    intersect(problem[2],problem[3],problem[1],problem[0],result);
    std::cout << "revf2: " << result << std::endl;
    intersect(problem[3],problem[2],problem[1],problem[0],result);
    std::cout << "revfboth: " << result << std::endl;
}

输出:

Starting Test intersection_precision_test, at Polygon.cpp:1830
1: <0.3617803309457128,0.8888024564015979>
2: <0.3617803309457128,0.8888024564015979>
3: <0.3617803314022162,0.8888024239374175>
4: <0.3617803314022162,0.8888024239374175>
rev: <0.3617803635476076,0.8888024344185281>
revf1: <0.3617803313928456,0.8888024246235207>
revf2: <0.3617803635476076,0.8888024344185281>
revfboth: <0.3617803313928456,0.8888024246235207>

我实际上是用完了尾数位还是用智能算法做得更好?

这里的问题是我没有简单的方法来确定何时设置顶点真的非常接近另一条线。我不介意移动它,甚至完全没有它,因为这些都不会搞砸我的那种!

1 个答案:

答案 0 :(得分:2)

如果你将浮动中间体(ua,ub,denom)改为双打并打印ua值(除法后),你会得到这些:

0x1.f864ab6b36458p-1 in the first case
0x1.f864af01f2037p-1 in the second case

我用十六进制打印它们,以便于查看这些位。这两个值在前22位(1.f864a加上bf的高位)一致。一个浮点只有23位有效数字!毫不奇怪,如果你在浮点数中计算你的中间体,他们会得到同样的答案。

在这种情况下,您可以通过使用双精度而不是浮点数计算中间体来解决问题。 (我的结果是使用x和y的结果。如果你使用浮点数,我不知道计算双打中间体是否有帮助。)

然而,垂直线段更接近两个水平线段的交叉点的情况可能仍然需要比双线段更精确的情况。

如果垂直线段完全通过水平线段的共享端点,您会怎么做?我假设你正确处理了这个案子。

如果你看一下用双打计算的ub值(除法后),你会得到这些:

0.9999960389052315
5.904388076838819e-06

这意味着交叉点非常非常接近水平线段的共享端点。

所以这就是我认为你能做到的。每次计算交叉点时,请查看ub。如果它足够接近1,则移动将端点作为交叉点。然后将交叉点视为首先是一个精确的传递案例。您实际上不必更改数据,但必须将端点视为共享端点的两个段的移动,这意味着当前的交叉点测试和下一个线段的测试。