计算几何基础知识——判断线段与线段相交

如何判断线段与线段是否相交。

方法: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用法,可以查看官方文档,下面给出链接

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;//因为直线可以无限延伸,所以只要判断有一个点在一线的两侧,就可以判断出是否相交
}

 

 

 

 

 

如有错误的地方,欢迎提出质疑

该博客为原创博客,可以转载但需要注明出处,谢谢合作!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值