计算几何模板

本文介绍了计算几何中向量和直线的相关概念及操作方法,包括向量的封装、角度和线段处理等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前文:在计算几何中,向量是主要工具,而不是解析几何中的数值。

向量和直线的封装模板:

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const double eps=1e-8,pi=acos(-1);

int cmp(double a,double b)
{
    if(fabs(a-b)<eps) return 0;
    else if(a-b>eps) return 1;
    return -1;
}

struct vec
{
    double x,y;
    bool operator==(const vec& a) const {return fabs(x-a.x)<eps&&fabs(y-a.y)<eps;}
    vec operator+(const vec& a) const {return (vec){x+a.x,y+a.y};}
    vec operator-(const vec& a) const {return (vec){x-a.x,y-a.y};}
    vec operator*(const double& k) const {return (vec){x*k,y*k};}
    vec operator/(const double& k) const {return (vec){x/k,y/k};}
    double operator^(const vec& a) const {return x*a.y-y*a.x;}//获得叉积的模
    double operator*(const vec& a) const {return x*a.x+y*a.y;}
    friend istream& operator>>(istream& o,const vec& a)
    {
        scanf("%lf%lf",&a.x,&a.y);
        return o;
    }
    friend ostream& operator<<(ostream& o,const vec& a)
    {
        printf("%.10lf %.10lf ",a.x,a.y);
        return o;
    }
};

struct line
{
    vec base,a,b;//两条向量和方向向量
};

bool rui(const vec& a,const vec& b){return a*b>eps;}//判定是否是锐角
bool zhi(const vec& a,const vec& b){return fabs(a*b)<eps;}//是否直角
bool dun(const vec& a,const vec& b){return a*b<-eps;}//是否钝角
bool operator!=(const vec& a,const vec& b){return !(a==b);}
double S(const vec& a,const vec& b){return fabs(a^b)/2;}//面积,叉积的模/2
vec operator*(const double& k,const vec &a){return (vec){k*a.x,k*a.y};}
double getlen(const vec& a){return sqrt(a.x*a.x+a.y*a.y);}//模长
double getangle(const vec& a,const vec& b){return acos(a*b/(getlen(a)*getlen(b)));}//获得夹角
vec getvertical(const vec& a){return (vec){a.y,-a.x};}//外积求垂直向量
vec getsingle(const vec& a){return a/getlen(a);}//单位向量
vec rotate(const vec& a,double k){return (vec){a.x*cos(k)-a.y*sin(k),a.x*sin(k)+a.y*cos(k)};}//旋转k弧度
line trans(const vec& a,const vec& b){return (line){getsingle(b-a),a,b};}//两点确定一条直线
line trans(double a,double b,double x,double y){return (line){getsingle((vec){x-a,y-b}),(vec){a,b},(vec){x,y}};}//两点确定一条直线

struct circle
{
    vec o;
    double r;
    friend istream& operator>>(istream& o,const circle& O)
    {
        cin>>O.o; scanf("%lf",&O.r);
        return o;
    }
};

int main()
{

    return 0;
}

点相关:

点一般在计算几何内与向量等价,于是一般用向量表示一个点,所以在模板中PPP点写作P=OP→P=\overrightarrow{OP}P=OP

角度相关:

向量的旋转:

x⃗⋅y⃗\vec{x}\cdot\vec{y}xy即辐角相加,并且其中一个的原长乘另一个在这里的投影,因此可以要将x⃗\vec{x}x旋转θ\thetaθ,等价于乘以一个模为1的,辐角为θ\thetaθ的向量,可以是(cosθ,sinθ)(cos\theta,sin\theta)(cosθ,sinθ)

线相关:

直线和线段的向量表示:

表示一条直线只需要一个方向向量和直线上一个点,也就是两个向量;表示一个线段需要所在直线的方向向量与两个线段端点,需要三个线段。表示线段的方法也可以表示直线,于是我们采用三个向量表示一条直线或线段的方法,分别代表单位方向向量,直线/线段上的两个点A,BA,BA,B,并且单位方向向量取AB→\overrightarrow{AB}AB的单位向量。

投影向量:

先求投影长度,再乘投影到的直线的单位方向向量即可。求PPP在直线lll上的投影向量,就是AP→⋅l.base→⋅l.base→\overrightarrow{AP}\cdot\overrightarrow{l.base}\cdot\overrightarrow{l.base}APl.basel.base

点到直线的垂足坐标:

设现在要求PPPABABAB所在直线lll的投影坐标,那么首先取AAA作为起始点,然后AAA到垂足HHH的差向量即是AP→\overrightarrow{AP}APlll的投影z→\overrightarrow{z}z,那么P=A+AP→⋅l.base→⋅l.base→P=A+\overrightarrow{AP}\cdot\overrightarrow{l.base}\cdot\overrightarrow{l.base}P=A+APl.basel.base

角平分线:

菱形形对角线即是角平分线,那么a→+b→\overrightarrow{a}+\overrightarrow{b}a+b就是角平分线向量,直线取单位向量即可。

tips:注意是等长度向量相加
中垂线:

先找垂线的方向向量,利用点积为0;然后中点坐标,求出中点即可。

点关于直线的对称点:

求出点在直线的投影垂足后,用中点坐标公式即可

点与直线的位置关系:

点在直线的哪一侧可以用叉积解决。

右手定则:a→×b→\overrightarrow{a} \times\overrightarrow{b}a×b,右手指弯曲,先从a→\overrightarrow{a}a经过,此时大拇指的方向就是叉乘向量的正方向,此时模长为正,反之为负。

由右手定则,我们要判断在直线的哪一侧就只需要判断在方向向量的哪一侧,即AB→×AP→\overrightarrow{AB}\times\overrightarrow{AP}AB×AP的模正负性。

直线和直线的位置关系:

若内积为0,那么垂直;若叉积为0,那么共线(可能平行可能重合);反之是普通的相交。

点是否在线段上:

需要满足两个条件:

(1)共线,叉积为0

(2)PAPAPAPBPBPB夹角为钝角,点积小于0

线段和线段的位置关系:

如果是垂直或平行,只需要看所在直线是否垂直平行

接下来着重讨论相交这个关系:

判断线段相交,按顺序进行下面两个实验:

(1)快速排斥实验,看以两个线段为对角线且边与坐标轴平行的矩形是否有重合,如果没有,那么一定不重合,否则再进行第二项。判断是否有重合只需判断在两线段在xxx轴的投影是否有重合部分和在yyy轴投影是否有重合部分,如果任意一个没有,那么就一定没有。

(2)跨立实验:分别以两条线段为基准,看另一条线段的两个端点是否在基准线段的两侧,使用叉积判断。

主观上跨立实验足够充要证明是否相交,但不能判断线段共线且无相交的情况,第一个实验可以认为是特判这种情况的。

tips:注意跨立实验,端点可以在另一条线段上,也就是叉积可以为0
点到直线(线段)的距离:

如果是到直线的距离,那么利用等面积法,因为PA→×PB→=2S=∣AB∣⋅h\overrightarrow{PA}\times\overrightarrow{PB}=2S=|AB|\cdot hPA×PB=2S=ABh,变形即可求得hhh

如果是到线段的距离,需要判断点在线段所在直线上的投影是否在线段上,如果是,那么到所在直线的距离就是到线段的距离,否则就是点到线段的两个端点的距离的最小值。

线段和线段的距离:

如果两线段相交,距离为0;反之,为每个线段端点到另一条线段的距离的最小值。

直线和直线的交点:

应该建立在不共线的基础之上。

A,BA,BA,B确定指向l1l1l1C,DC,DC,D确定直线l2l2l2

不妨设交点为PPP,那么他一定可以由l1l1l1上的一点AAA加上kkk倍的l1l1l1的方向向量l1.basel1.basel1.base得到,不妨设P=A+k⋅l1.base→P=A+k\cdot\overrightarrow{l1.base}P=A+kl1.base,由于相交,那么CP→\overrightarrow{CP}CPl2.base→\overrightarrow{l2.base}l2.base共线,所以叉积为0,即(P−C)⋅l2.base→=0(P-C)\cdot\overrightarrow{l2.base}=0(PC)l2.base=0,叉积满足分配律,化简解出kkk即可。

多边形相关:

多边形的存储方法:

按照逆时针或顺时针存储点即可

多边形的面积:

S=∑i=1nOPi→⋅OPi%n+1→2S=\frac{\sum_{i=1}^{n}\overrightarrow{OP_{i}}\cdot\overrightarrow{OP_{i\%n+1}}}{2}S=2i=1nOPiOPi%n+1

多边形的凹凸性:
两种定义:

(1)当多边形没有一个内角是优角时,这个多边形是凸多边形,否则是凹多边形。

(2)若把任意一条边延长成直线,如果与其他边没相交,就是凸多边形,反之为凹。

一种充要的判定方法,选择一个特定顺序的三元组{a,b,c}\{a,b,c\}{a,b,c},如果每一个三元组都满足cccab→\overrightarrow{ab}ab的同一方向,那么这个多边形就是凸的,反之为凹。

圆相关:

圆和圆的位置关系:

设两圆心之间距离为disdisdis,两圆半径分别为r1,r2r_{1},r_{2}r1,r2,并且r1>r2r_{1}>r_{2}r1>r2

(1)相离,dis>r1+r2dis>r_{1}+r_{2}dis>r1+r2

(2)相切,dis=r1+r2dis=r_{1}+r_{2}dis=r1+r2

此时开始小圆圆心一定在大圆内

(3)内切,dis=r1−r2dis=r_{1}-r_{2}dis=r1r2

(4)相交,内切和外切之间即是相交,r1−r2<dis<r1+r2r1-r2<dis<r1+r2r1r2<dis<r1+r2

(5)否则即是内包含

圆和直线的交点坐标:

求出到弦的垂足hhh和一半的弦长,向量加减即可

圆和圆的交点坐标:

由两个圆心和一个交点组成的三角形都是已知的,求出一个以圆心为顶点的角,之后将圆心向量旋转即可。

过点PPP的切线与圆的切点:

切点,PPPOOO三点的直角三角形可以得到角度,旋转即可。

圆的公切线的切点:

找角度,旋转圆心向量即可,注意切线种类有两种,不经过两圆之间和经过两圆之间。

圆和多边形的面积并

圆和圆的面积并/交:

讨论圆和圆的位置关系即可

圆和多边形的面积交:

利用计算多边形面积的”有向面积法“,讨论每个边的情况:

(0)线段经过原点

(1)与圆有一个交点

(2)与圆有两个交点

(3)线段完全在圆内

(4)线段完全在圆外

判断情况是比较繁琐的,可以先判断简单的,然后在else区域内再进一步判断,会显得容易一些。

对于情况(1)(2),用余弦定理是非常麻烦的,只要直线交圆,就一定有垂径定理的三角形,那么就有角度,利用角度运算,会简单许多。

情况(1)(2)中θ\thetaθ为弦与半径的夹角,然后运算即可。

int gettot(vec p1,vec p2,double len,double dis,double len1,double len2,double len3)
{
	//两个交点
	if(cmp(len,dis)==0&&cmp(len1,s.r)>-1&&cmp(len2,s.r)>-1&&cmp(len,s.r)==-1) return 2;
	if(cmp(len2,s.r)<1&&cmp(len1,s.r)>-1) return 1;
	return 0;
}

double solve(vec p1,vec p2)
{
	if(on_seg(trans(p1,p2))) return 0;
	double len1=getlen(p1),len2=getlen(p2),len3=getlen(p2-p1);
	if(cmp(len1,len2)==-1) swap(len1,len2);
	double dis=fabs(p1^p2)/len3,len=getdis({0,0},trans(p1,p2));
	int tot=gettot(p1,p2,len,dis,len1,len2,len3),base=(p1^p2)>0?1:-1;
	if(tot==1)
	{
		double theta=asin(dis/s.r),alpha=acos(getcos(len2,len3,len1)),beta=acos(getcos(len1,len2,len3));
		return base*((beta+alpha+theta-pi)*s.r*s.r/2+sin(pi-theta-alpha)*s.r*len2/2);
	}
	else if(tot==2)
	{
		double theta=asin(dis/s.r),alpha=asin(dis/len1),beta=asin(dis/len2);
		double ret1=(theta-alpha)*s.r*s.r/2,ret2=(theta-beta)*s.r*s.r/2,ret3=s.r*s.r*sin(pi-2*theta)/2;
		return base*(ret1+ret2+ret3);
	}
	if(cmp(len1,s.r)==-1&&cmp(len2,s.r)==-1) return base*S(p1,p2);//线段完全在内部
	return base*acos(getcos(len1,len2,len3))*s.r*s.r/2;//线段在外部
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值