直线
我们以前学的直线的常用表示有:
- 一般式ax+by+c=0
- 点斜式y-y0=k(x-x0)
- 截距式y=kx+b
- 两点式(y-y1)/(y2-y1)=(x-x1)/(x2-x1)
但是很明显上面的形式保存的直线形式较为单一,因此我们使用直线的向量方程——点向式,用一个点和方向向量表示直线:ρ= p0+v·t
方向向量的求法:已知直线上的两点便可确定方向向量,进而确定一条直线
struct Line{
Point p,q; //传入两点,默认p是起点
Vector v; //由p,q确定的方向向量
Line(){}
Line(Point a,Point b){
p=a,q=b,v=q-p;
}
Point point(double t){ //点P=p+v*t
return p+v*t;
}
};
我们不难发现点向式不但表示了直线,当限制某一参数时,该直线就变成了射线或线段
由此我们也传入线段的几个常用函数,那么"Line"就是万能的线,并不局限直线
struct Line{
Point p,q; //默认p是起点
Vector v; //由p,q确定的方向向量
Line(){}
Line(Point a,Point b){ //构造函数
p=a,q=b,v=q-p;
}
Point point(double t){ //点P=p+v*t
return p+v*t;
}
Point spos(){ //线段起点
return p;
}
Point tpos(){ //线段终点
return q;
}
double length(){ //线段长度
return sqrt(dis(v));
}
void print(){
printf("Line:(%lf,%lf)->(%lf,%lf)\n",p.x,p.y,q.x,q.y);
}
};
点与线
点到直线的距离
一说到点到直线的距离,我们很容易想起这个公式:设有直线Ax+By+C=0,某坐标 (X0,Y0),那么该点到这直线的距离就为:
但是我们的直线是用点向式保存的,因此我们必须通过向量来求点到直线的距离。如图当我们以p点为起点向(x0,y0)构建一个向量,那么该向量与直线的方向向量叉乘绝对值的几何意义是如下平行四边形的面积,那么已知底边长度,所求的高h就是点到直线的距离:
double disToLine(Point p,Line l){
Vector v=p-l.p;
return fabs(l.v^v) / dis(l.v);
}
点到线段的距离
我们对线段做一个平行线,当p在平行线区域内时很明显直接利用点到直线的距离公式。但是当点在平行线外时,到线段的距离就是到A点的距离或者到B点的距离,但是要根据向量之间的夹角判断在哪边,我们作向量Ap,设它与v之间的夹角为α,同理设向量Bp和v的夹角为β,如下图红线均为向量Ap,蓝线均为向量Bp,那么当p点在A点左边时,一定有α>90°,其他两种情况均不是;同理当且仅当β在B点右边时,一定有β<90°,其他情况均不是。那么如果两种情况均不满足就是p1这样的情况
double disToSeg(Point p,Line l){
if(l.p==l.q) return dis(p-l.p);
Vector v1=p-l.p,v2=p-l.q;
if(dcmp(v1*l.v)<0) return dis(v1); //这时的p2在Line.p的左边
if(dcmp(v2*l.v)>0) return dis(v2); //这时p2在Line.q的右边
return disToLine(p,l);
}
点在直线上的投影点
利用向量数量积的几何意义:a·b为a在b上的投影,那么将该点与Line.p构造一个向量,利用数量积求得投影部分向量u·Line.v,再多乘一个Line.v并除以Line.v的模长,即可得到投影(Projection)向量u,点p+u得到投影点的坐标
Point getPro(Point p,Line l){
return l.p+l.v*(l.v*(p-l.p)/dis(l.v));
}
点是否在直线上
设点a,b和c是直线上的两点,那么令α=(a,b),β=(a,c)。利用三点共线的等价条件α×β=0判断是否在直线上
bool isOnLine(Point p,Line l){ //任取两个点
return dcmp((l.p-p)^(l.q-p))==0?true:false;
}
点是否在线段上
首先和上面一样,我们先利用叉乘判断点是否在直线上,接着利用两向量的数量积判断向量方向是否相符,相反则在线段上
bool isOnSeg(Point p,Line l){
return dcmp((l.p-p)^(l.q-p))==0 && dcmp((l.p-p)*(l.q-p))<=0;
}
线与线
计算两直线交点
首先要保证两直线相交,即a.v^b.v!=0
bool isLineInter(Line a,Line b){
return dcmp(a.v^b.v)==0?false:true;
}
接着我们看下面这张图直线L1和直线L2的交点为F,我们作向量 p1p2 ,然后将 v2 平行到和p1共起点.。由叉乘的几何意义,那么 p1p2×v2 就是两条向量构成平行四边形的面积,那么三角形p1p2A的面积就是该平行四边形面积的一半;同理三角形p1AB是向量 v1×v2 面积的一半。观察两个三角形我们不难发现有一条公共边 |p1A| ,因此我们分别对两三角形作高,发现对应的高刚好是 |EF| 和 |BC| ,那么两个三角形面积之比就是 |EF| : |BC| 。接着在三角形p1BC中,由三角形相似的知识,我们得出 |p1F| : |v1| = |EF| : |BC| 。那么F的坐标就是p1坐标加上向量 p1F,也就是比值t*v1
Point getLineInter(Line a,Line b){
Vector u=a.p-b.p;
double t=(b.v^u) / (a.v^b.v);
return a.point(t); //或者a.p+a.v*t;
}
判断两线段是否相交
首先需要两次跨立实验。就是加入两线段相交,那我们先固定其中一条线段,从另一条线段的两端点向固定线段的其中一个端点作向量,如下。那么由于 c1和 c2 肯定在 a.v 的两端,那么a.v 对 c1和c2 分别求向量积,根据向量积的定义来看,结果肯定是方向相反(结果符号相反),注意右手定则(当右手的四指从a以不超过180度的转角转向 b 时,竖起的大拇指指向是 c 的方向)
于是我们得到下面的判断方法:
double c1=a.v^b.p-a.p,c2=a.v^b.q-a.p;
double c3=b.v^a.p-b.p,c4=b.v^a.q-b.p;
return (dcmp(c1)*dcmp(c2)<0 && dcmp(c3)*dcmp(c4)<0);
但是我们发现,如果有一条线段的一个端点在另外一个线段的直线上时,那么c1、c2、c3、c4其中一个会出现0。因此,我们另外要分类讨论四个端点,如下:
bool isSegInter(Line a,Line b){
double c1=a.v^b.p-a.p,c2=a.v^b.q-a.p;
double c3=b.v^a.p-b.p,c4=b.v^a.q-b.p;
//判断两线段端点是否在另外一条线段上
if(!dcmp(c1) || !dcmp(c2) || !dcmp(c3) || !dcmp(c4)){
bool f1=isOnSeg(b.p,a);
bool f2=isOnSeg(b.q,a);
bool f3=isOnSeg(a.p,b);
bool f4=isOnSeg(a.q,b);
bool f=(f1|f2|f3|f4);
return f;
}
return (dcmp(c1)*dcmp(c2)<0 && dcmp(c3)*dcmp(c4)<0);
}
判断线段和直线是否相交
很明显一次跨立实验即可,但是这里是小于等于0
bool isLineSegInter(Line a,Line seg){
double c1=l.v^seg.p-l.p,c2=l.v^seg.q-l.p;
return dcmp(c1)*dcmp(c2)<=0;
}
简单模板
struct Line{
Point p,q; //默认p是起点
Vector v; //由p,q确定的方向向量p->q
Line(){}
Line(Point a,Point b){ //构造函数
p=a,q=b,v=b-a;
}
Point point(double t){ //点P=p+v*t
return p+v*t;
}
Point spos(){ //线段起点
return p;
}
Point tpos(){ //线段终点
return q;
}
double length(){ //线段长度
return sqrt(dis(v));
}
void print(){
printf("Line:(%lf,%lf)->(%lf,%lf)\n",p.x,p.y,q.x,q.y);
}
};
double disToLine(Point p,Line l){ //点到直线距离
Vector v=p-l.p;
return fabs(l.v^v) / dis(l.v);
}
double disToSeg(Point p,Line l){ //点到线段距离
if(l.p==l.q) return dis(p-l.p);
Vector v1=p-l.p,v2=p-l.q;
if(dcmp(v1*l.v)<0) return dis(v1);
if(dcmp(v2*l.v)>0) return dis(v2);
return disToLine(p,l);
}
Point getPro(Point p,Line l){ //点在直线投影
return l.p+l.v*(l.v*(p-l.p)/dis(l.v));
}
bool isOnLine(Point p,Line l){ //点是否在直线上
return dcmp((l.p-p)^(l.q-p))==0?true:false;
}
bool isOnSeg(Point p,Line l){ //点是否在线段上
return dcmp((l.p-p)^(l.q-p))==0 && dcmp((l.p-p)*(l.q-p))<0;
}
bool isLineInter(Line a,Line b){ //两直线是否相交
return dcmp(a.v^b.v)==0?false:true;
}
Point getLineInter(Line a,Line b){ //返回两直线交点
Vector u = a.p-b.p;
double t = (b.v^u) / (a.v^b.v);
return a.point(t); //或者a.p+a.v*t;
}
bool isSegInter(Line a,Line b){ //线段是否相交
double c1=a.v^b.p-a.p,c2=a.v^b.q-a.p;
double c3=b.v^a.p-b.p,c4=b.v^a.q-b.p;
//判断两线段端点是否在另外一条线段上
if(!dcmp(c1) || !dcmp(c2) || !dcmp(c3) || !dcmp(c4)){
bool f1=isOnSeg(b.p,a);
bool f2=isOnSeg(b.q,a);
bool f3=isOnSeg(a.p,b);
bool f4=isOnSeg(a.q,b);
bool f=(f1|f2|f3|f4);
return f;
}
return (dcmp(c1)*dcmp(c2)<0 && dcmp(c3)*dcmp(c4)<0);
}
bool isLineSegInter(Line l,Line seg){ //判断直线和线段是否相交
double c1=l.v^seg.p-l.p,c2=l.v^seg.q-l.p;
return dcmp(c1)*dcmp(c2)<=0;
}