废话不多说,直接入正题。

误差

	const db eps=1e-9;
	inline int sgn(T x){
		return x<-eps?-1:x<eps?0:1;
	}

我们浮点数计算计算几何的时候是存在误差的,我们用上面这个函数来修正。

对于 $x < -eps$ ,我们认为它属于负数,返回 $-1$.

对于 $-eps \le x < eps$ ,我们认为它属于是零,返回 $0$

剩下的属于正数,返回 $1$.

这样我们先把比较运算写出来,然后移项成减法形式,再在外面套一层浮点数修正函数即可。

这个养成好习惯即可。

点(point)

我们已经非常熟悉的东西,由横纵坐标来组成。当然也有更高维的点,但是在这里我们暂且不提,只考虑二维的点。(不过高维的点也并不复杂,几维就是几个坐标)

我们这样存储:

struct node{
	db x,y;
	node(){}
	node(int _x,int _y){
		x=_x;y=_y;
	}
};
//这个db 是我 double 的宏定义,下同

向量(vector)

简言就是有方向的量。

我们可以认为向量是一条有向线段,但是它的其中一个点在坐标原点上,因此它既可以用来表示长度,还可以用来表示方向。

可以这么存储:

struct vect{
	db x,y;
	vect(){}
	vect(int _x,int _y){
		x=_x;y=_y;
	}
};

和物理里面的矢量差不多,好像就只有名字不一样。

所以显然,向量也是满足平行四边形法则的。具体来说就是对于两个向量,它们的和向量是满足其为两向量围成的平行四边形的对角线的。符号表示出来就是:

\[P_1(x_1,y_1)+P_2(x_2,y_2)=P_{sum}(x_1+x_2,y_1+y_2)\]

同理,有:

\[P_1(x_1,y_1)-P_2(x_2,y_2)=P_{sum}(x_1-x_2,y_1-y_2)\]

显然这种运算满足加法交换律,减法的结合律等。

那么我们可以为向量添加一些重载运算符。

	inline vect operator + (const vect &a){
		return (vect){x+a.x,y+a.y};
	}
	inline vect operator - (const vect &a){
		return (vect){x-a.x,y-a.y};
	}

加减介绍完了,现在介绍一下它的特殊运算。

点积(dot product)

先提出规定: $\lang A,B\rang$ 表示向量 $A$,$B$ 之间的夹角。

对于向量 $A(x_1,y_1),B(x_2,y_2)$

\[dot(A,B)=A\cdot B=x_1\cdot x_2+y_1\cdot y_2=\lvert A\rvert \cdot \lvert B\rvert \cdot cos\lang A,B \rang\]

那么这个玩意有什么用呢?你发现这个东西的符号和 $cos<A,B>$ 是同号的,所以可以判断两向量夹角是第几象限角。

如果点积为正,则夹角在第一、四象限

如果点积为负,那么在第二、三象限

浅显一点,可以用来判断夹角是钝角还是锐角还是直角。但是首先保证角度 $\alpha \in [0,\pi]$

如果点积为正,则夹角是锐角或者零角

如果点积为负,那么是钝角或平角

如果点积为 $0$,那么就是直接了

叉积(cross product)

\[cross(A,B)=A\times B=x_1\cdot y_2-x_2\cdot y_1=\vert A\vert \cdot \vert B\vert \cdot sin\lang A,B\rang\]

如果叉积为正,则夹角在第一、二象限

如果叉积为负,那么在第三、四象限

显然满足 $ P\times Q=-(Q\times P)$ 和 $P\times (-Q)=-(P\times Q)$

我们还有一个性质:

若 $P\times Q>0$ 时,$P$ 位于 $Q$ 的顺时针方向

若 $P\times Q=0$ 时,$P$ 与 $Q$ 共线,同向或反向

若 $P\times Q<0$ 时,$P$ 位于 $Q$ 的逆时针方向

然后我们发现根本不需要单独的一个点,向量可以完全代替它。为了方便,我们鸠占鹊巢,给向量的结构体改名为 $point$.

现在一个较为完整的向量结构体成型了。

struct point{
    db x,y;
    point(){}
    point(db _x,db _y){
        x=_x;y=_y;
    }
    inline point operator + (const point &a){
        return {x+a.x,y+a.y};
    }
    inline point operator - (const point &a){
        return {x-a.x,y-a.y};
    }
    inline bool operator == (const point &a){
        return sgn(x-a.x)==0 and sgn(y-a.y)==0;
    }
	inline db operator * (const point &a){
		return x*a.y-y*a.x;
	}
};
inline db dot(const point &a,const point &b){
    return a.x*b.x+a.y*b.y;
}

看起来挺不错的。

线(line)

对于线段,我们可以用两个向量(其端点)表示

对于直线和射线,我们考虑一个向量表示基础点,另外一个向量表示方向。但是直线不考虑正反,射线需要考虑。

然后我们判断两条线段是否相交。下面我们介绍一种方法。

设这两条线段为 $P_1P_2$ 和 $Q_1Q_2$

首先我们进行快速排斥实验,如果 $P_1P_2$ 对角线形成的矩形与 $Q_1Q_2$ 对角线形成的矩形没有交点,那么直接可以判断线段无交。这个很好判断,用矩形的左下角和右上角的点来判断即可。

显然如果两线段相交,会满足他们互相跨立对方。而如果 $P_1P_2$ 跨立 $Q_1Q_2$ 那么,肯定有 $(P_1-Q_1)$ 和 $(P_2-Q_1)$ 都在 $(Q_1-Q_2)$ 两侧,即 $[(P_1-Q_1)\times (Q_1-Q_2)]\times [(P_2-Q_1)\times (Q_1-Q_2)]<0$.等于 $0$ 的情况我们考虑一下,发现:在经过的快速排斥实验的情况下,$P_1P_2$ 的端点肯定在 $Q_1Q_2$ 上,所以有交点。

然后再同理判断 $Q_1Q_2$ 跨立 $P_1P_2$ 即可。然后可以判断两者相交。

好,这是点线相关的一些计算几何,就谈到这里。