我的交叉检查算法出了什么问题?

时间:2016-09-10 13:33:44

标签: c++ algorithm qt math intersection

我知道有很多网站可以解释如何检查两条线的交集,但我发现只需复制和粘贴代码就可以完成这么简单的数学任务。我越不能让我的代码无法工作。我知道“我的代码中有什么问题?”是愚蠢的,但我不知道我的数学/代码到底有什么问题,我的代码也很好地记录了(除了不可否认的变量命名),所以我猜应该有人对它背后的数学感兴趣:

bool segment::checkforIntersection(QPointF a, QPointF b) { //line 1: a+bx, line 2: c+dx, note that a and c are called offset and bx and dx are called gradients in this code
QPointF bx = b-a;
double firstGradient = bx.y() / bx.x(); //gradient of line 1
//now we have to calculate the offset of line 1: we have b from a+bx. Since QPointF a is on that line, it is:
//a + b * a.x = a.y with a as free variable, which yields a = a.y - b*a.x.
//One could also use the second point b for this calculation.
double firstOffset = a.y() - firstGradient * a.x();
double secondGradient, secondOffset;
for (int i = 0; i < poscount-3; i++) { //we dont check with the last line, because that could be the same line, as the one that emited intersection checking
    QPointF c = pos[i];
    QPointF d = pos[i+1];
    QPointF dx = d-c;
    secondGradient = dx.y() / dx.x(); //same formula as above
    secondOffset = c.y() - secondGradient * c.x();
    //a+bx=c+dx <=> a-c = (d-b)x <=> (a-c)/(d-b) = x
    double x = (firstOffset - secondOffset) / (secondGradient - firstGradient);
    //we have to check, if those lines intersect with a x \in [a.x,b.x] and x \in [c.x,d.x]. If this is the case, we have a collision
    if (x >= a.x() && x <= b.x() && x >= c.x() && x <= d.x()) {
        return true;
    }
}
return false;
}

这样做,它有4个点a,b,c,d(第1行:a - b,第2行:c - d)(忽略for循环),它具有绝对的x和y值。首先,它通过计算deltay / deltax来计算线的梯度。然后,它通过使用点a(或c)分别在线上的事实来计算偏移量。这样我们将4个点转换为这些线的数学表示,如等式a + bx,而ax为0意味着我们在第一个点(a / c),ax为1意味着我们在第二个点(b) / d)。接下来,我们计算这两条线(基本代数)的交集。之后我们检查交叉点的x值是否有效。据我所知,这一切都是正确的。有没有人看到错误?

根据经验检查这是不正确的。代码不会给出任何错误的肯定(假设有一个交叉点,当没有时),但是它给出了错误的否定(说实际上没有交叉点)。因此,当它说有一个交叉点时它是正确的,但是如果它说没有交叉点,你就不能总是相信我的算法。

再次,我在线检查,但算法不同(有一些方向技巧和其他东西),我只想提出自己的算法,如果有人可以提供帮助,我会很高兴。 :)

编辑:这是一个最小的可重复的不工作的例子,这次没有Qt但只有C ++:

#include <iostream>
#include <math.h>

using namespace std;
class Point {
private:
    double xval, yval;
public:
    // Constructor uses default arguments to allow calling with zero, one,
    // or two values.
    Point(double x = 0.0, double y = 0.0) {
        xval = x;
        yval = y;
    }

    // Extractors.
    double x() { return xval; }
    double y() { return yval; }

    Point sub(Point b)
    {
        return Point(xval - b.xval, yval - b.yval);
    }
};

bool checkforIntersection(Point a, Point b, Point c, Point d) { //line 1: a+bx, line 2: c+dx, note that a and c are called offset and bx and dx are called gradients in this code
    Point bx = b.sub(a);
    double firstGradient = bx.y() / bx.x(); //gradient of line 1
    //now we have to calculate the offset of line 1: we have b from a+bx. Since Point a is on that line, it is:
    //a + b * a.x = a.y with a as free variable, which yields a = a.y - b*a.x.
    //One could also use the second point b for this calculation.
    double firstOffset = a.y() - firstGradient * a.x();
    double secondGradient, secondOffset;
    Point dx = d.sub(c);
    secondGradient = dx.y() / dx.x(); //same formula as above
    secondOffset = c.y() - secondGradient * c.x();
    //a+bx=c+dx <=> a-c = (d-b)x <=> (a-c)/(d-b) = x
    double x = (firstOffset - secondOffset) / (secondGradient - firstGradient);
    //we have to check, if those lines intersect with a x \in [a.x,b.x] and x \in [c.x,d.x]. If this is the case, we have a collision
    if (x >= a.x() && x <= b.x() && x >= c.x() && x <= d.x()) {
        return true;
    }
    return false;
}

int main(int argc, char const *argv[]) {
    if (checkforIntersection(Point(310.374,835.171),Point(290.434,802.354), Point(333.847,807.232), Point(301.03,827.172)) == true) {
        cout << "These lines do intersect so I should be printed out\n";
    } else {
        cout << "The algorithm does not work, so instead I do get printed out\n";
    }
    return 0;
}

所以作为例子我拿点〜(310,835) - (290,802)和(333,807) - (301,827)。这些线相交:

\documentclass[crop,tikz]{standalone}
\begin{document}
\begin{tikzpicture}[x=.1cm,y=.1cm]
\draw (310,835) -- (290,802);
\draw (333,807) -- (301,827);
\end{tikzpicture}
\end{document}

Proof of intersection 但是,当运行上面的C ++代码时,它表示它们不相交

2 个答案:

答案 0 :(得分:1)

  

首先,它通过计算deltay / deltax来计算线的渐变。

当deltax非常接近零时会发生什么?

看,你正在做的是让自己暴露在病态的情况下 - 在计算几何时总是担心与0.0的分歧和直接比较。

替代方案:

  1. 如果它们不平行,则两条线相交
  2. 如果两个不同的行的定义向量将具有零交叉积,则它们将是并行的。
  3. (a,b) x (c,d) = (ax-bx)*(cy-dy)-(ay-by)*(cx-dx)的交叉乘积 - 如果它足够接近于零,那么在所有实际情况下,你的线之间没有交叉点(交点距离太远无关紧要)。

    现在,还有待说:

    • 需要一个“那些线是不同的?”在计算跨产品之前进行测试。更重要的是,您需要处理退化情况(一条或两条线被重合点缩小到某一点 - 例如a==b和/或c==d
    • 如果你没有规范化你的定义向量,那么“足够接近于零”测试是不明确的 - 想象一个定义第一行的1个光长度矢量和另一个的1个parsec长度矢量(什么测试'接近'如果你在这种情况下使用,你应该使用零吗?)为了使这两个向量标准化,只需应用一个除法...... (你说的是一个分支?我已经因恐惧而颤抖)。 .. mmm ..我是说用hypot(ax-bx, ay-by)*hypot(cx-dx,cy-dy)来划分产生的交叉产品(你知道为什么需要事先处理堕落案件吗?)
    • 在归一化之后,再一次,对于产生的交叉产品,什么是“接近零”测试?好吧,我想我可以继续进行另一个小时左右的分析(例如,与你的{a,b,c,d}多边形的范围相比,交叉点会有多远),但是......因为十字架 - 两个酉向量的乘积(归一化后)是sin(angle-between-versors),你可以使用你的常识并说'如果角度小于1度,这是否足以考虑两条线平行?没有? 1弧秒怎么样?“

答案 1 :(得分:1)

(你可以称我为学究,但术语很重要)

如果您想查看线段线是否相交,那么请依赖两个线段的参数表示,在两个参数中求解系统并查看两个参数的解决方案是否兼而有之参数属于[0,1]范围。

细分[ a b ]的参数化表示,按组件划分

  <{x, y}(t) = {(1-t)*ax+t*bx, (1-t)*ay+t*by} t范围内的[0,1]

快速检查 - t=0得到 a t=1得到 b t表达式为线性,所以你有它。

因此,您的(a,b)(c,d)交叉点问题变为:

// for some t1 and t2, the x coordinate is the same
(1-t1)*ax+t*bx=(1-t2)*cx+t2*dx; 
(1-t1)*ay+t*by=(1-t2)*cy+t2*dy; // and so is the y coordinate

t1t2中解决系统问题。如果t1位于[0,1]范围内,则交叉点位于ab之间,t2c相同和d

作为一种练习,读者可以研究在以下条件下对系统会产生什么影响,以及应该为强大的算法实施哪些检查:

  1. 段退化 - 一段或两段的重合结束
  2. 具有非空白重叠的共线段。特殊情况下,单点重叠(必要时,该点将是其中一个)
  3. 没有重叠的共线段
  4. 并行细分