【问题标题】:What is wrong with my intersection checking algorithm?我的交叉路口检查算法有什么问题?
【发布时间】:2016-09-10 13:33:44
【问题描述】:

我知道有很多网站解释了如何检查两条线的交叉点,但我发现仅仅复制和粘贴代码来完成如此简单的数学任务非常无聊。我越是让我的代码无法工作而感到沮丧。我知道“我的代码有什么问题?”的问题。很愚蠢,但我不知道我的数学/代码到底出了什么问题,我的代码也被很好地记录了(除了公认的错误变量命名),所以我想应该有人对它背后的数学感兴趣:

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,而 x 为 0 表示我们在第一个点 (a / c),而 x 为 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++ 代码时,它说它们不相交

【问题讨论】:

  • 调试器是解决此类问题的正确工具。 询问 Stack Overflow 之前,您应该逐行逐行检查您的代码。如需更多帮助,请阅读How to debug small programs (by Eric Lippert)。至少,您应该 [编辑] 您的问题,以包含一个重现您的问题的 Minimal, Complete, and Verifiable 示例,以及您在调试器中所做的观察。
  • 这不是我没有尝试自己调试它。我尝试调试,但半小时后没有任何线索,我最终放弃了。也许我只是误解了一些数学,这就是问题所在。真正尝试过自己解决后问有什么问题?
  • What is the problem of asking after genuinely having tried solving it by your own? 你仍然缺少minimal reproducible example
  • @m-s 我确实忘记了一个算法不起作用的点元组的例子。对于给您带来的不便,我深表歉意。我编辑了原始帖子以符合您的要求。现在您所要做的就是复制代码并在其上运行 g++ 或 clang++。我希望现在你能提供帮助:)

标签: c++ algorithm qt math intersection


【解决方案1】:

(你可以称我为书呆子,但术语很重要)

如果您想查看线 segments 是否相交,则依靠您的两个线段的参数表示,在两个参数中求解系统,并查看两者的解决方案是否参数属于[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 之间,t2cd 的交集也是如此。

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

  1. 段退化 - 一个或两个段的重合结束
  2. 具有非空重叠的共线段。存在单个重叠点的特殊情况(必要时,该点将是端点之一)
  3. 没有重叠的共线段
  4. 平行线段

【讨论】:

  • 我真的很喜欢你这样计算它的想法,即 s, t \in [0,1]。你忘了用 y btw 替换你的第二个方程中的 x。您的方程也不是那么简单,无法求解 s 或 t。因此,我使用了非常相似的方程 a.x+s*(b.x-a.x)=c.x+t*(d.x-c.x) (与 x 的 y 相同)。这个方程具有相同的性质,但我能够更快地求解 s。所以我只是实现了它并运行了代码,它就像一个魅力!谢谢你的好主意。尽管此实现较慢,因为它必须进行更多计算。但是,它正在工作,这就是我想要的
  • y 提供服务。关于另一种形式... doh,我并没有说要采用方程式并解决它们原样 - 我使用该表达式来表示一条线的参数repr,因为它更容易理解和记住它 - t1-t 权重末端之间的线性组合。
  • @Magnus2552 “虽然这个实现比较慢,因为它需要做更多的计算。”请记住我的说法:“没有什么比不工作的东西更慢。没有什么比高速产生错误的错误代码更具破坏性”。
  • 是的,我同意。早期优化总是一个不好的编程习惯。
【解决方案2】:

首先它通过计算deltay/deltax来计算线条的梯度。

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

看,你正在做的是将自己暴露在病态的情况下 - 在计算几何方面总是害怕分裂和与0.0的直接比较

替代方案:

  1. 两条线如果不平行就会相交
  2. 如果定义向量的叉积为零,则两条不同线将是平行的。

您的 (a,b) x (c,d) = (ax-bx)*(cy-dy)-(ay-by)*(cx-dx) 的叉积 - 如果它足够接近于零,则出于所有实际目的,您的线之间没有交叉点(交叉点太远了没关系)。

现在,还有什么要说的:

  • 需要有一个“这些线是否不同?”在计算叉积之前进行测试。更重要的是,您将需要处理退化情况(一条或两条线通过重合的末端减少到一个点 - 例如a==b 和/或c==d
  • 如果您不对定义向量进行归一化,“足够接近于零”的测试是不明确的 - 想象一个定义第一行的 1 光秒长度向量和另一行定义为 1 秒差距长度的向量(什么测试'proximity to zero' 你应该在这种情况下使用吗?)
    要规范化两个向量,只需应用除法... (你说的除法?我已经吓得发抖了) .. . 嗯.. 我是说用hypot(ax-bx, ay-by)*hypot(cx-dx,cy-dy) 来划分结果叉积(你明白为什么需要提前处理退化案例吗?)
  • 在归一化之后,再一次,对于结果叉积,什么是好的“接近零”测试?好吧,我想我可以再继续分析一个小时左右(例如,与 {a,b,c,d} 多边形的范围相比,交叉点的距离有多远),但是......因为交叉- 两个酉向量的乘积(归一化后)是sin(angle-between-versors),你可以用你的常识说'如果角度小于 1 度,这足以考虑两条线平行吗?不? 1 角秒呢?”

【讨论】:

    猜你喜欢
    • 2013-12-25
    • 2018-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-16
    • 1970-01-01
    • 1970-01-01
    • 2013-12-25
    相关资源
    最近更新 更多