这篇文章讲的是二维几何中点和直线的相关问题。
直线的参数表示。直线可以用直线上的一点P0和方向向量v表示(虽然这个向量的大小没什么用处)。直线上所有点P满足P=P0+tv。其中t称为参数。如果已知直线上的两个不同点A和B,则方向向量为B-A,所以参数方程为A+(B-A)t。
参数方程最方便的地方在于直线、射线和线段的方程形式是一样的。区别仅仅在于参数。直线的t没有范围限制。射线的t>0,线段的t在0~1之间,这样,很多对于直线适用的公式可以很方便的用在射线和线段上。
有向直线的存储结构定义:
//有向直线。它的左边就是对应的半平面
struct Line
{
Point p; //直线上任意一点
Vector v; // 方向向量,左边就是对应的半平面
double ang; //极角
Line(){}
Line(Point P, Vector v):p(p), v(v){ang = angle(v);}
//根据参数,求直线上的点
Point point(double t)
{
return p + v*t;
}
//直线的移动 方向是向量的左方向,即上方
Line move(double d)
{
return Line(p + Normal(v)*d, v);
}
bool operator < (const Line &L) const //排序用的比较运算符
{
return ang < L.ang;
}
};
直线交点:
设直线分别为P+tv和Q+tw,设向量u=P-Q,交点在第一条直线的参数为t1,第二条直线上的参数为t2,则x和y坐标可以列出一个方程解得:
t1=cross(w,u) / cross(v,w)
t2 = cross(v,u) / cross(v,w)
调用前请确保两条直线P+tv和Q+tw有唯一交点。当且仅当Cross(v,w)非0
如果P,v,Q,w的各个分量均为有理数,则交点坐标也是有理数。在精度要求极高的情况下,可以考虑自定义分数类。
Point GetLineIntersection(Point P, Vector v, Point Q, Vector w)
{
Vector u;
double t;
u = P - Q;
t = Cross(w, u) / Cross(v, w);
return P + v * t;
}
点到直线的距离:
点到直线的距离是一个常用的函数。可以用叉积算出。即用平行四边形的面积除以底。
double DistanceToLine(Point P, Point A, Point B)
{
//如果A==B
if(A == B)
return Length(P-A);
Vector v1, v2;
v1 = B - A;
v2 = P - A;
return fabs(Cross(v1, v2)) / Length(v1); //如果不取绝对值,得到的是有向距离
}
点到线段的距离。
设投影点为Q,如果Q在线段AB上,则所求距离就是P点到直线AB的距离;如果Q在射线BA上,则所求距离为PA距离;否则为PB距离,判断Q的位置可以用点积进行(参见上篇 :两个向量的为位置关系)。
double DistanceToSegment(Point P, Point A, Point B)
{
Vector v1, v2, v3;
v1 = B - A;
v2 = P - A;
v3 = P - B;
if(A == B) //A点等于B点
return Length(v2);
if(dcmp(Dot(v1, v2)) < 0) //Q在射线BA上
return Length(v2);
else if(dcmp(Dot(v1, v3)) > 0) //Q在射线AB上
return Length(v3);
else
return fabs(Cross(v1, v2)) / Length(v1);
}
点在直线上的投影。
我们把直线AB写成参数式A+tv(v为向量AB),并且设Q的参数为t0,那么Q=A+t0v,根据PQ垂直与AB,两个向量的点积应该为0。
因此Dot(v,P-(A+t0v))=0.根据分配率,有Dot(v,P-A)-t0*Dot(v,v)=0,这样就可以解出t0,从而得到Q点。
Point GetLineProjection(Point P, Point A, Point B)
{
Vector v;
v = B - A;
return A + v * (Dot(v, P-A) / Dot(v, v));
}
.线段相交判定。给定两条线段,判断是否相交。我们定义规范相交为两线段恰好有一个公共点,且不在任何一条线段的端点。线段规范相交的充要条件是:每条线段的两个端点都在另一条线段的两侧(这里的两侧是指叉积的符号不同)
bool SegmentProperIntersection(Point a1, Point a2, Point b1, Point b2)
{
double c1, c2, c3, c4;
c1 = Cross(a2-a1, b1-a1);
c2 = Cross(a2-a1, b2-a1);
c3 = Cross(b2-b1, a1-b1);
c4 = Cross(b2-b1, a2-b1);
return (dcmp(c1) * dcmp(c2) < 0) && (dcmp(c3) * dcmp(c4) < 0);
}
如果允许在端点处相交,情况就比较复杂了。如果c1和c2都是0,表示两线段共线,这时可能会有部分重叠的情况。如果c1和c2不都是0,则只有一种相交方法,即某个端点在另外一条线段上,为了判断上述情况是否发生,还需要判断一下点是否在一条线段(不包含端点)。
bool OnSegment(Point P, Point a1, Point a2)
{
return (dcmp(Cross(a1-P, a2-P)) == 0) && (dcmp(Dot(a1-P, a2-P)) < 0);
}
判断一个点是否在一条线段上.首先判断是否是端点,不是的话判断是否在线段上。
bool isPointOnSegment(Point p, Point a1, Point a2)
{
return (p==a1) || (p== a2) || OnSegment(p, a1, a2);
}