直线
我们以前学的直线的常用表示有:
- 一般式 a x + b y + c = 0 ax+by+c=0 ax+by+c=0
- 点斜式 y − y 0 = k ( x − x 0 ) y-y_0=k(x-x_0) y−y0=k(x−x0)
- 截距式 y = k x + b y=kx+b y=kx+b
- 两点式 ( y − y 1 ) / ( y 2 − y 1 ) = ( x − x 1 ) / ( x 2 − x 1 ) (y-y_1)/(y_2-y_1)=(x-x_1)/(x_2-x_1) (y−y1)/(y2−y1)=(x−x1)/(x2−x1)
但是很明显上面的形式保存的直线形式较为单一,因此我们使用直线的向量方程——点向式,用一个点和方向向量表示直线: ρ = p 0 + v t ρ= p_0+vt ρ=p0+vt。
方向向量的求法:已知直线上的两点便可确定方向向量,进而确定一条直线
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;
}
};
我们不难发现点向式不但表示了直线,当限制某一参数时,该直线就变成了射线或线段
由此我们也传入线段的几个常用函数,那么" L i n e Line 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);
}
};
点与线
点到直线的距离
一说到点到直线的距离,我们很容易想起这个公式:设有直线
A
x
+
B
y
+
C
=
0
Ax+By+C=0
Ax+By+C=0,某坐标
(
X
0
,
Y
0
)
(X_0,Y_0)
(X0,Y0),那么该点到这直线的距离就为:
但是我们的直线是用点向式保存的,因此我们必须通过向量来求点到直线的距离。如图当我们以
p
p
p点为起点向
(
x
0
,
y
0
)
(x_0,y_0)
(x0,y0)构建一个向量,那么该向量与直线的方向向量叉乘绝对值的几何意义是如下平行四边形的面积,那么已知底边长度,所求的高
h
h
h就是点到直线的距离:
double disToLine(Point p,Line l){
Vector v=p-l.p;
return fabs(l.v^v) / dis(l.v);
}
点到线段的距离
我们对线段做一个平行线,当
p
p
p在平行线区域内时很明显直接利用点到直线的距离公式。但是当点在平行线外时,到线段的距离就是到
A
A
A点的距离或者到
B
B
B点的距离,但是要根据向量之间的夹角判断在哪边,我们作向量
A
p
Ap
Ap,设它与
v
v
v之间的夹角为
α
α
α,同理设向量
B
p
Bp
Bp和
v
v
v的夹角为
β
β
β,如下图红线均为向量
A
p
Ap
Ap,蓝线均为向量
B
p
Bp
Bp,那么当
p
p
p点在
A
A
A点左边时,一定有
α
>
90
α>90
α>90°,其他两种情况均不是;同理当且仅当
β
β
β在
B
B
B点右边时,一定有
β
<
90
β<90
β<90°,其他情况均不是。那么如果两种情况均不满足就是
p
1
p_1
p1这样的情况
double disToSeg(Point p, Line L) {
if (L.p == L.q) return dis(p - L.p);
Vector v1 = L.q - L.p, v2 = p - L.p, v3 = p - L.q;
if (dcmp(v1 * v2) < 0) return dis(v2);
else if (dcmp(v1 * v3) > 0) return dis(v3);
else return fabs((v1 ^ v2)) / dis(v1);
}
点在直线上的投影点
利用向量数量积的几何意义:
a
×
b
a \times b
a×b为
a
a
a在
b
b
b上的投影,那么将该点与
L
i
n
e
.
p
Line.p
Line.p构造一个向量,利用数量积求得投影部分向量
u
×
L
i
n
e
.
v
u \times Line.v
u×Line.v,再多乘一个
L
i
n
e
.
v
Line.v
Line.v并除以
L
i
n
e
.
v
Line.v
Line.v的模长,即可得到投影(Projection)向量
u
u
u,点
p
+
u
p+u
p+u得到投影点的坐标
Point getPro(Point p,Line l){
return l.p+l.v*(l.v*(p-l.p)/dis(l.v));
}
点是否在直线上
设点 a , b a,b a,b和 c c c是直线上的两点,那么令 α = ( a , b ) α=(a,b) α=(a,b), β = ( a , c ) β=(a,c) β=(a,c)。利用三点共线的等价条件 α × β = 0 α \times β=0 α×β=0判断是否在直线上
bool isOnLine(Point p,Line l){ //任取两个点
return dcmp((l.p-p)^(l.q-p))==0?true:false;
}
点是否在线段上
先利用叉乘判断点是否在直线上,接着比较点是否在线段扩展的矩形内。
bool isOnSeg(Point p, Line l) { //点是否在线段上
bool f1 = 0, f2 = 0, f3 = 0;
if (dcmp((l.p - p) ^ (l.q - p)) == 0) f1 = 1;
if (dcmp(p.x - min(l.p.x, l.q.x)) >= 0 && dcmp(p.x - max(l.p.x, l.q.x)) <= 0) f2 = 1;
if (dcmp(p.y - min(l.p.y, l.q.y)) >= 0 && dcmp(p.y - max(l.p.y, l.q.y)) <= 0) f3 = 1;
return f1 & f2 & f3;
}
线与线
计算两直线交点
首先要保证两直线相交,即 a . v a.v a.v ^ b . v ! = 0 b.v!=0 b.v!=0
bool isLineInter(Line a,Line b){
return dcmp(a.v^b.v)==0?false:true;
}
接着我们看下面这张图直线
L
1
L_1
L1和直线
L
2
L_2
L2的交点为
F
F
F,我们作向量
p
1
p
2
p_1p_2
p1p2 ,然后将
v
2
v_2
v2 平行到和
p
1
p_1
p1共起点.。由叉乘的几何意义,那么
p
1
p
2
×
v
2
p_1p_2 \times v_2
p1p2×v2 就是两条向量构成平行四边形的面积,那么三角形
p
1
p
2
A
p_1p_2A
p1p2A的面积就是该平行四边形面积的一半;同理三角形
p
1
A
B
p_1AB
p1AB是向量
v
1
×
v
2
v_1 \times v_2
v1×v2 面积的一半。观察两个三角形我们不难发现有一条公共边
∣
p
1
A
∣
|p~1~A|
∣p 1 A∣ ,因此我们分别对两三角形作高,发现对应的高刚好是
∣
E
F
∣
|EF|
∣EF∣ 和
∣
B
C
∣
|BC|
∣BC∣ ,那么两个三角形面积之比就是
∣
E
F
∣
:
∣
B
C
∣
|EF| : |BC|
∣EF∣:∣BC∣ 。接着在三角形
p
1
B
C
p_1BC
p1BC中,由三角形相似的知识,我们得出
∣
p
1
F
∣
:
∣
v
1
∣
=
∣
E
F
∣
:
∣
B
C
∣
|p_1F| : |v_1| = |EF| : |BC|
∣p1F∣:∣v1∣=∣EF∣:∣BC∣ 。那么
F
F
F的坐标就是
p
1
p_1
p1坐标加上向量
p
1
F
p_1F
p1F,也就是比值
t
∗
v
1
t*v_1
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;
}
判断两线段是否相交
首先需要两次跨立实验。就是加入两线段相交,那我们先固定其中一条线段,从另一条线段的两端点向固定线段的其中一个端点作向量,如下。那么由于
c
1
c_1
c1和
c
2
c_2
c2 肯定在
a
.
v
a.v
a.v 的两端,那么
a
.
v
a.v
a.v 对
c
1
c_1
c1和
c
2
c_2
c2 分别求向量积,根据向量积的定义来看,结果肯定是方向相反(结果符号相反),注意右手定则(当右手的四指从
a
a
a以不超过180度的转角转向
b
b
b 时,竖起的大拇指指向是
c
c
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);
但是我们发现,如果有一条线段的一个端点在另外一个线段的直线上时,那么 c 1 、 c 2 、 c 3 、 c 4 c_1、c_2、c_3、c_4 c1、c2、c3、c4其中一个会出现 0 0 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 = L.q - L.p, v2 = p - L.p, v3 = p - L.q;
if (dcmp(v1 * v2) < 0) return dis(v2);
else if (dcmp(v1 * v3) > 0) return dis(v3);
else return fabs((v1 ^ v2)) / dis(v1);
}
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) { //点是否在线段上
bool f1 = 0, f2 = 0, f3 = 0;
if (dcmp((l.p - p) ^ (l.q - p)) == 0) f1 = 1;
if (dcmp(p.x - min(l.p.x, l.q.x)) >= 0 && dcmp(p.x - max(l.p.x, l.q.x)) <= 0) f2 = 1;
if (dcmp(p.y - min(l.p.y, l.q.y)) >= 0 && dcmp(p.y - max(l.p.y, l.q.y)) <= 0) f3 = 1;
return f1 & f2 & f3;
}
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;
}