如何判断线段与线段是否相交。
方法:1. 矩形实验
2. 跨立实验
知识点解析:
1. 矩形实验是为了将相距距离比较远的线段直接排除(因为这样,他们不能相交),具体做法也很简单,就是两条线段横坐标与纵坐标的区间不会有交集
2. 跨立实验是为了判断,两线段坐标区间有交集的线段之间的关系。具体思路为:一条线段的两个点在另一条线段的两侧,则满足跨立实验,具体代码实现为,利用向量与向量之间的叉乘的正负性,来做出判断。
下面先给出代码,后进行代码分析:
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
#include <map>
#include <vector>
#include <set>
#include <string>
#include <math.h>
using namespace std;
const double eps=1e-8;
int sgn(double x)//定义符号函数
{
if(fabs(x)<eps)
return 0;
else if(x<0)
return -1;
else
return 1;
}
struct Point
{
double x,y;
Point(){};//默认构造函数
Point(double _x,double _y)
{
x=_x;
y=_y;
}
Point operator-(const Point &b) const
{
return Point(x-b.x,y-b.y);
}
Point operator+(const Point &b) const
{
return Point(x+b.x,y+b.y);
}
double operator^(const Point &b) const//重载叉积
{
return x*b.y-y*b.x;
}
double operator*(const Point &b) const//点积
{
return x*b.x+y*b.y;
}
void transXY(double B)//绕原点旋转B弧度后,XY的值
{
double tx=x,ty=y;
x=tx*cos(B)-ty*sin(B);
y=tx*sin(B)+ty*cos(B);
}
};
struct Line
{
Point s,e;
double k;
Line(){}
Line(Point _s,Point _e)
{
s = _s;e = _e;
k = atan2(e.y - s.y,e.x - s.x);//斜率
}
//两条直线求交点,
//第一个值为0表示直线重合,为1表示平行,为0表示相交,为2是相交
//只有第一个值为2时,交点才有意义
pair<int,Point> operator &(const Line &b)const
{
Point res = s;
if(sgn((s-e)^(b.s-b.e)) == 0)//如果两条线的叉积等于0,那么要么具有平行关系,要么重合
{
if(sgn((s-b.e)^(b.s-b.e)) == 0)//分别取两条边的任意两个点,进行判断即可
return make_pair(0,res);//重合
else return make_pair(1,res);//平行
}
double t = ((s-b.s)^(b.s-b.e))/((s-e)^(b.s-b.e));//利用正弦定理:a/sinA=b/sinB=c/sinC,分析可知,利用叉乘可以得出sin值
res.x += (e.x-s.x)*t;
res.y += (e.y-s.y)*t;
return make_pair(2,res);
}
};
double dist(Point a,Point b)
{
return sqrt((a-b)*(a-b));
}
bool inter(Line l1,Line l2)
{
return
max(l1.s.x,l1.e.x) >= min(l2.s.x,l2.e.x) && //前4项为矩形实验,后两项为两次跨立实验,跨立实验的思想是,任取一个点 都在另一条边的两侧,利用叉积值
max(l2.s.x,l2.e.x) >= min(l1.s.x,l1.e.x) && //..的正负性来判断
max(l1.s.y,l1.e.y) >= min(l2.s.y,l2.e.y) &&
max(l2.s.y,l2.e.y) >= min(l1.s.y,l1.e.y) &&
sgn((l2.s-l1.s)^(l1.e-l1.s))*sgn((l1.e-l1.s)^(l2.e-l1.s))>=0&&
sgn((l1.s-l2.s)^(l2.e-l2.s))*sgn((l2.e-l2.s)^(l1.e-l2.s))>=0;
}
a/sinA=b/sinB=c/sinC,分析可知,利用叉乘可以得出sin值
res.x += (e.x-s.x)*t;
res.y += (e.y-s.y)*t;
return make_pair(2,res);
}
};
double dist(Point a,Point b)
{
return sqrt((a-b)*(a-b));
}
bool inter(Line l1,Line l2)
{
return
max(l1.s.x,l1.e.x) >= min(l2.s.x,l2.e.x) && //前4项为矩形实验,后两项为两次跨立实验,跨立实验的思想是,任取一个点 都在另一条边的两侧,利用叉积值
max(l2.s.x,l2.e.x) >= min(l1.s.x,l1.e.x) && //..的正负性来判断
max(l1.s.y,l1.e.y) >= min(l2.s.y,l2.e.y) &&
max(l2.s.y,l2.e.y) >= min(l1.s.y,l1.e.y) &&
sgn((l2.s-l1.s)^(l1.e-l1.s))*sgn((l1.e-l1.s)^(l2.e-l1.s))>=0&&
sgn((l1.s-l2.s)^(l2.e-l2.s))*sgn((l2.e-l2.s)^(l1.e-l2.s))>=0;
}
下面 我对上述代码进行分析解读:
这是解决计算几何问题的基本模板,首先定义点的结构体,再定义线的结构体,这样对以后的解题会有很大帮助
1. sgn函数,这个函数的目的是判断正负性,因为点的坐标可能是小数,所以这里对小于10的负8次方的数,定义为0.
2. Point结构体里的transXY函数,这里的证明用到了数学中解析几何的知识,我给出证明过程
3. Line结构体里面的求交点函数,非常值得去研究
首先先介绍pair的用法,它是一个结构体类型,它可以接受两个参数,first second,在这个函数中,我们用first来标记是否满足相交的条件,用second来返回具体的点坐标,make_pair()函数为pair结构体里的函数,可以创建一个pair对象,具体pair用法,可以查看官方文档,下面给出链接
接着比较难懂的就是求交点的公式,这个需要一定的数学基础知识,如果觉得难懂的话,可以直接记住公式,或者直接套用模板
下面给出证明:
利用向量的叉积为面积,再利用正弦定理,计算出从原来某条线段上的起点,应该移动的距离,即为新的坐标距离。
分析是要充分利用叉乘的几何意义, 结合下图,利用正弦定理,即可得出结论。
最后分析一下如何判断两线段是否相交的代码:
正如前文所说,首先用矩形实验先判断,最后再用跨立实验判断,矩阵实验简单明了,这里不再一一赘述,重点介绍跨立实验的使用,它的原理是利用两向量叉乘后的正负来判断两线段直接的位置关系,如果AB*CD>0表明,CD在AB的逆时针方向,等于0表示共线,小于0表示在顺时针方向,所以我们只需要选取同一层的三个点,构成三个向量,做叉乘即可得出结论
最后需要强调的是,跨立实验必须做两组及以上,目的是为了确保不会有特殊情况的发生,具体情况可以自己尝试画出。
下面给出一道例题的链接:
那么如何判断线段与直线是否相交呢? 这个判断就非常简单了,因为直线是可以无限延伸的,所以矩形实验不用做,并且只需要做一次跨立实验就行了。下面给出代码
bool seg_line(Line la,Line lb)
{
return sgn((l2.s-l1.s)^(l1.e-l1.s))*sgn((l1.e-l1.s)^(l2.e-l1.s))>=0;//因为直线可以无限延伸,所以只要判断有一个点在一线的两侧,就可以判断出是否相交
}
如有错误的地方,欢迎提出质疑
该博客为原创博客,可以转载但需要注明出处,谢谢合作!