计算几何——判断直线相交
判断直线相交问题,通常有多种手段解决,大部分都需要涉及浮点数运算。但是通过计算几何向量叉乘的方法可以避免使用浮点数来判断直线是否相交。
用向量叉乘判断向量的相对位置
假设 b ⃗ \vec{b} b在 a ⃗ \vec{a} a的左边,如图(1),根据右手定则, a ⃗ × b ⃗ \vec{a} \times \vec{b} a×b的 z z z坐标的值应该为正值。如果 b ⃗ \vec{b} b在 a ⃗ \vec{a} a的右边,如图(2),根据右手定则, a ⃗ × b ⃗ \vec{a} \times \vec{b} a×b的 z z z坐标的值应该为负值。
故两个向量叉乘得到的向量的 z z z坐标的正负决定了两个向量的相对位置。
线段的跨越
取线段 S 1 S_1 S1所在的直线,这条直线将平面分成了两个部分,若 S 2 S_2 S2的两个端点分别处在这两个部分,那么我们称线段 S 2 S_2 S2跨越了 S 1 S_1 S1。
我们可以通过向量叉乘判断
S
2
S_2
S2是否跨越
S
1
S_1
S1。我们将线段的端点进行标记,称向量
a
b
⃗
\vec{ab}
ab为轴向量,
a
c
⃗
\vec{ac}
ac和
a
d
⃗
\vec{ad}
ad为侧向量,我们分别计算
a
b
⃗
×
a
c
⃗
\vec{ab} \times \vec{ac}
ab×ac和
a
b
⃗
×
a
d
⃗
\vec{ab} \times \vec{ad}
ab×ad由图可以知,
S
2
S_2
S2跨越
S
1
S_1
S1的充要条件为两个侧向量和轴向量的相对位置不同,一个在轴向量的左侧,另一个在右侧。
我们可以得出线段相交的一个充分条件,即两个线段互相跨越,那么这两个线段必然相交。
边界条件
令外,线段相交还存在两个边界条件,其一为一个线段的端点在另外一个线段上,即下图。
另外一种情况即为共线,共线的情况其实为上述的一个特例,两种情况其实是一种情况。
在下述代码中,我们针对这两个情况进行处理。
代码实现
首先是向量类实现。
// 向量类
struct Vector
{
int x, y, z;
Vector(int xi, int yi, int zi) : x(xi), y(yi), z(zi) {}
Vector(int xi, int yi) : Vector(xi, yi, 0) {}
Vector operator+(const Vector &o) const
{
return Vector(x + o.x, y + o.y, z + o.z);
}
Vector operator-(const Vector &o) const
{
return Vector(x - o.x, y - o.y, z - o.z);
}
// 向量叉乘
Vector operator*(const Vector &o) const
{
return Vector(y * o.z - z * o.y, z * o.x - x * o.z, x * o.y - y * o.x);
}
};
其次是计算方向的辅助函数,和判断点是否在线段上的辅助函数。
// abc分别为三点,计算以a为公共顶点,ab与ac向量的叉乘的z坐标
int direction(Vector a, Vector b, Vector c)
{
return ((b - a) * (c - a)).z;
}
// 判断点c是否在线段ab上
bool on_segment(Vector a, Vector b, Vector c)
{
return c.x >= min(a.x, b.x) && c.x <= max(a.x, b.x) && c.y >= min(a.y, b.y) && c.y <= max(a.y, b.y);
}
最后是判断相交的代码。
// 判断两个线段是否相交, ab 为 S1 的两个端点, cd 为 S2 的两个端点
bool is_intersect(Vector a, Vector b, Vector c, Vector d)
{
// 计算轴向量和侧向量的叉乘值
int d1 = direction(a, b, c);
int d2 = direction(a, b, d);
int d3 = direction(c, d, a);
int d4 = direction(c, d, b);
if (d1 * d2 < 0 && d3 * d4 < 0) // 情况一:互相跨越
return true;
else if (d1 == 0 && on_segment(a, b, c)) // 情况二:存在一端点在线段上
return true;
else if (d2 == 0 && on_segment(a, b, d))
return true;
else if (d3 == 0 && on_segment(c, d, a))
return true;
else if (d4 == 0 && on_segment(c, d, b))
return true;
else
return false;
}