计算几何(二):线段与直线,点与线,线与线的关系

写在前面

本文基于fxj巨佬的计算几何全家桶,并基于原文进行了自己的一些整理了经验补充,阅读本文前请前往支持巨佬fxj。

直线/线段的表示:

直线与线段的表示采用两点+方向向量的表示方法。
此外,一般来说,直线采用两点一点+方向向量表示,而线段必须用两点表示。

一般来说,方向向量用单位向量比较简单,因此下面的直线的方向向量默认为单位向量。

在以下算法中,如无特殊说明或者题目要求,不强调针对的是直线还是线段,毕竟算法原理是相通的。

struct line
{
	V d,a,b;	
};//存储

inline line trans(double a,double b,double c,double d)
{
	V dd(c-a,b-d),x(a,b),y(c,d);
	dd=dd/len(dd);
	return (line){dd,x,y};
}//将两点转化为一条线(直线或线段)

inline line trans(const V &a,const V &b)
{
	V dd(a-n);
	dd=dd/len(dd);
	return (line){dd,a,b};	
}//也是转化函数,存在的意义是用起来更方便(不必专门把点存为某一种格式后在转化)

inline void input(const line &l)
{
	double a=read(),b=read(),c=read(),d=read();
	l=trans(a,b,c,d);
	return;	
}//输入函数,模块化,用着方便

点与线

求点到直线的垂足

把线上的代表点移动投影的距离。
这里的投影是带符号的,符号体现移动的方向。

对于这部分内容,当你不能理解一些问题时,画图和特殊值带入能让你更好地理解它们。

计算几何(二):线段与直线,点与线,线与线的关系_第1张图片

inline V cui(const line &l,const V &o)
{
	return l.a+((o-l.a)*l.d)*l.d;
}

求点关于直线的对称点

求出垂足后用中点公式

inline V mid(const line &l,const V &o)
{
	V u=cui(l,o);
	return (V){2*u.x-o.x,2*u.y-o.y};	
}

判断点与直线或线段的位置关系

共线可以通过叉积等于0来判断。
不共线时,可以通过叉积的正负判断是在直线的哪一侧。
共线时,如果是线段或射线,还可以利用点积的正负判断与端点的位置关系。(点在线段的延长线还是反向延长线上)
(对于线段而言,可以直接通过模长判断是否在线段上)

inline bool on_line(const line &l,const V &o)
{
	return abs(l.d^(o-l.a))<eps;
}//共线

inline bool on_seg(const line &l,const V &o)
{
	return abs(len(o-l.a)+len(o-l.a)-len(l.a-l.b))<eps;	
}//在线段上

inline int pos(const line &l,const V &o)
{
	if(!on_line(l,o))
	{
		if((o-l.a)^l.d)>0) return 1;
		else return 2;
	}//不共线
	else
	{
		if(((o-l.a)*(o-l.b))<=0) return 5;
		else if(((o-l.a)*l.d)<0) return 3;
		else return 4;	
	}
}//1,2,5状态常用,3,4只在特定题目中使用

求点到直线或线段的距离

直线:叉积等面积法(叉积求出平行四边形面积 s s s d i s = s / 底 面 边 长 dis=s/底面边长 dis=s/
线段时,若垂足在线段上,相当于点到直线的距离;否则是到两端点距离的最小值。
垂足是否在线段上可以利用是否有钝角来判断。
计算几何(二):线段与直线,点与线,线与线的关系_第2张图片

inline bool dun(const V &a,const V &o,const V &b)
{
	V x=b-a,y=o-a;
	return (x*y)<-eps; 
}//之前出现过的判断钝角函数
inline double dis(const line &l,const V &o,int op=0) //op=0:dis on line,op=1:dis on segment
{
	if(op && (dun(l.a,o,l.b) || dun(l.b,o,l.a))) return min(len(o-l.a),len(o-l.b));
	else return abs((o-l.a)^(o-l.b)) / len(l.a-l.b);
}

线与线

直线与直线位置关系:

共线与垂直

共线
方向向量叉积为0则共线。

有时需判断平行还是重合,只需取一个直线的 l . a l.a l.a,求它到另一个直线的距离即可。
若距离为 0 0 0则重合,否则平行。

inline bool gongxian(const line &a,const line &b){return abs(a.d^b.d)<eps;}
inline bool chonghe(const line &a,const line &b){return abs(dis(a,b.a,0))<eps;}

垂直
方向向量点积为 0 0 0则垂直。

inline bool chuizhi(const line &a,const line &b){return abs(a.d*b.d)<eps;}

线段与线段:判断线段与线段是否相交

有两个步骤:

  1. 快速排斥实验:定义一条线段所在的矩形为边与坐标轴平行、能完全包含该线段的最小的矩形,如果两条线段所在的矩形没有公共部分,显然不可能有交点;否则,进入下一步跨立实验
  2. 跨立实验:如果l1的两端点在l2两侧,同时l2的两端点也在l1两侧(也可以在线段上),则得出两线段有公共交点,否则则没有公共交点。

实现上,快速排斥实验可以用坐标 min、max 的比较轻松解决,跨立实验则可以用前面讲的叉积求点与线的关系来实现,注意叉积为0(端点在线段上)是符合条件的。

一开始会感觉跨立实验足以判断线段与线段是否相交,但是事实上跨立实验在判断时认为叉积为 0 0 0是合法的,且本质上跨立实验相当于判断两点与直线的位置关系,因此就会出现如下图的问题。

计算几何(二):线段与直线,点与线,线与线的关系_第3张图片
而多出的两种情况:即共线且不相交,和两端点位于直线两侧,但与线段没有交点.
这两种情况都会被快速排斥实验ban掉,因此算法的正确性是有保证的。

ps:因此fxj在原文中所述的“快速排斥实验可以用特判共线替代”是不正确的(逃

更新:据fxj描述,最后一种情况不会被快速排斥实验ban掉,故必须尽量两次跨立,如函数最后一步。
因而”快速排斥实验可以被特判共线替代“是正确的,但是由于特判共线的实现难度大于快速排斥实验,
因此巨佬fxj推荐使用"快速排斥实验+两次跨立实验"的做法,即如下的函数中的内容。

inline bool jiao(const line &u,const line &v)
{
  if(min(u.a.x,u.b.x)>max(v.a.x,v.b.x)+eps||max(u.a.x,u.b.x)<min(v.a.x,v.b.x)-eps||
  min(u.a.y,u.b.y)>max(v.a.y,v.b.y)+eps||max(u.a.y,u.b.y)<min(v.a.y,v.b.y)-eps) return false;//rapid rejection test
  return ((u.a-v.a)^v.d)*((u.b-v.a)^v.d)<eps&&((v.a-u.a)^u.d)*((v.b-u.a)^u.d)<eps;//straddle test
}

求直线与直线的交点

首先应当保证两直线有交点:保证不共线(特判是否共线)
设两直线为 l 1 l_1 l1, l 2 l_2 l2,两直线上各有一个点 x 1 x_1 x1, x 2 x_2 x2,方向向量分别为 d 1 d_1 d1, d 2 d_2 d2.
设交点 p p p = = = x 1 + k d 1 x_1+kd_1 x1+kd1,则有 ( p − x 2 ) × d 2 = 0 (p-x_2) \times d2=0 (px2)×d2=0.

根据叉积的分配律,可得 k = ( x 2 − x 1 ) × d 2 d 1 × d 2 k=\frac{(x_2-x_1) \times d_2}{d_1 \times d_2} k=d1×d2(x2x1)×d2

再把求出的k带回原式即可。

inline V jiaodian(const line &u,const line &v)
{
	double k=((u.a-v.a)^v.d)/(u.d^v.d);
	return u.a+(k*u.d);
} 

杂项:

求角平分线:

已知三点 A A A, B B B, C C C,求 ∠ B A C \angle BAC BAC的角平分线

A B AB AB, A C AC AC的方向向量 d 1 d_1 d1, d 2 d_2 d2,角平分线的方向向量 d d d = = = d 1 + d 2 d_1+d_2 d1+d2,再放入点 A A A和点 A + d A+d A+d即可。

inline line pingfen(const V &a,const V &b,const V &c)
{
	V d1=(b-a)/len(b-a),d2=(c-a)/len(c-a),d=(d1+d2)/len(d1+d2);
	return (line){d,a,a+d};
}

求线段的中垂线

找到中点和与原线段垂直的方向向量即可。

inline line zhongcui(const V &a,const V &b)
{
	V x=min(a,b),d=danwei(cui(b-a));
	return (line){d,x,x+d};
}

你可能感兴趣的:(计算几何,模板,几何学,算法)