判断 2 个线段相交有很多方法,最直接的方法就是直接计算两条直线的交点,然后看看交点是否分别在这两条线段上。这样的方法很容易理解,但是代码实现比较麻烦。
还有一种常用的方法是通过向量叉积来判断的,这种方法不需要算出直线方程,在代码实现上比较简便。
用这种方法判别线段是否相交一般分为两步:
1. 快速排斥实验
2. 跨立实验
快速排斥实验
我们首先判断两条线段在 x 以及 y 坐标的投影是否有重合。
也就是判断下一个线段中 x 较大的端点是否小于另一个线段中 x 较小的段点,若是,则说明两个线段必然没有交点,同理判断下 y。
如上图所示,代码表示如下:
max(C.x,D.x).x,B.x) || max(C.y,D.y).y,B.y) ||
max(A.x,B.x).x,D.x) || max(A.y,B.y).y,C.y)
如果上面的四条判断有一个为真,则代表两线段必不可交,否则应该进行第二步判断。
显然,上图通不过快速排斥实验。
跨立实验
矢量叉积
计算矢量叉积是与直线和线段相关算法的核心部分。
设矢量 P = (x1, y1),Q = ( x2, y2 ),则矢量叉积定义为:P × Q = x1*y2 - x2*y1,其结果是一个矢量,与为 P Q 向量所在平面的法向量。显然有性质 P × Q = - ( Q × P ) 和 P × ( - Q ) = - ( P × Q )。
叉积的一个非常重要性质是可以通过它的符号判断两矢量相互之间的顺逆时针关系:
若 P × Q > 0 , 则 P 在 Q 的顺时针方向。
若 P × Q <0 , 则 P 在 Q 的逆时针方向。
若 P × Q &#61; 0 , 则 P 与 Q 共线&#xff0c;但可能同向也可能反向。
通过叉积来判断线段相交
如果两线段相交那么就意味着它们互相跨立&#xff0c;即如上图点 A 和 B 分别在线段 CD 两侧&#xff0c;点 C 和 D 分别在线 AB 两侧。
判断 A 点与 B 点是否在线段 DC 的两侧&#xff0c;即向量 A-D 与向量 B-D 分别在向量 C-D 的两端&#xff0c;也就是其叉积是异号的&#xff0c;即 (A−D)×(C−D)∗(B−D)×(C−D)<0(A−D)×(C−D)∗(B−D)×(C−D)<0。
同时也要证明 C 点与 D 点在线段 AB 的两端&#xff0c;两个同时满足&#xff0c;则表示线段相交。
然后我们来看看临界情况&#xff0c;也就是上式恰好等于 0 的情况下&#xff1a;
当出现如上图所示的情况的时候&#xff0c;(A−D)×(C−D)∗(B−D)×(C−D)&#61;0(A−D)×(C−D)∗(B−D)×(C−D)&#61;0&#xff0c;显然&#xff0c;这种情况是相交的。只要将等号直接补上即可。
再接得想一想&#xff0c;如果没有第一步的快速排斥实验&#xff0c;仅判断第二步&#xff0c;会出现什么问题&#xff1f;
当出现如上所示的情况的时候&#xff0c;叉积都为 0, 可以通过跨立实验&#xff0c;但是两个线段并没有交点。不过还好&#xff0c;这种情况在第一步快速排斥已经被排除了。
代码
struct Line {double x1;double y1;double x2;double y2;
};bool intersection(const Line &l1, const Line &l2)
{//快速排斥实验if ((l1.x1 > l1.x2 ? l1.x1 : l1.x2) <(l2.x1 l1.y2 ? l1.y1 : l1.y2) <(l2.y1 l2.x2 ? l2.x1 : l2.x2) <(l1.x1 l2.y2 ? l2.y1 : l2.y2) <(l1.y1 0 ||(((l2.x1 - l1.x1)*(l1.y2 - l1.y1) - (l2.y1 - l1.y1)*(l1.x2 - l1.x1))*((l2.x2 - l1.x1)*(l1.y2 - l1.y1) - (l2.y2 - l1.y1)*(l1.x2 - l1.x1))) > 0){return false;}return true;
}