三维凸包

一些前置知识

(鉴于博主没系统学过三维几何,若描述有错误欢迎提出)

  1. 法向量:垂直于平面的直线所表示的向量为该平面的法向量(右手螺旋定则)
    求法:同一平面内任选两个向量做叉积,得到模长为面积的法向量。
  2. 点到平面的距离:该点到平面上任意一点的向量与该平面法向量做点积(可以理解为平面上的投影思想)
  3. 判断一个点 D D D是否在该平面上:
    求法:在平面上任意找一个点,和该点构成向量,与法向量做点积判断是否为 0 0 0
    粗略证明:已知法向量 n ⃗ \vec n n 与整个平面垂直,从而 n ⃗ \vec n n 与平面 α \alpha α上任意一条线都垂直,所以只要该点 D D D在平面上,其与平面上任意一点的连线都与 n ⃗ \vec n n 垂直。若 D D D不在平面上,却又有 D A ⊥ n ⃗ DA\perp \vec n DAn ,则可以在 α \alpha% α上找另一点 D ′ D' D,使 D ′ A ⊥ n ⃗ D'A\perp \vec n DAn ,那么有 D ′ A / / D A D'A//DA DA//DA,与假设矛盾,假设不成立,证毕。
  4. 判断一个面是否可见
    为了判断有向体积,先一律规定法向量方向朝外,将该新增的点与需判断平面的一个顶点做叉积,判断有向体积是否大于零(

增量法找三维凸包 O ( n 2 ) O(n^2) O(n2)做法( O ( n l o g n ) O(nlogn) O(nlogn)做法咕咕咕,大概就是三维旋转卡壳思想,分治即可)
放一张来自这里的图便于大家理解
将新增的点与已有的点做投影,判断哪些面可见哪些不可见,并将可见面删除,在数组中增加新增点与可见面棱的面即可。
注意:

  1. 为了避免四点共面的情况,在读入时对每个点进行微小扰动处理(不会对后面结果造成影响)
  2. 初始要先构造两条边,再与第一个点结合起来构成一个基本的面,这样才能进行下一步,由于后面处理的原理,这个初始的面不会对其造成影响
  3. % 3 \%3 %3是为了头尾相接
  4. 判断面是否可见:计算 A D ⃗ \vec {AD} AD 与法向量点积,求出余弦值,余弦值为正的可见,否则不可见
    三维凸包_第1张图片
    上代码:
#include 
#define db double
using namespace std;
const int N=2100;
const db eps=1e-9;
int n,cnt,vis[N][N];
db reps(){return (rand()/(db)RAND_MAX-0.5)*eps;}
struct Pnt{
	db x,y,z;
	void shake(){x+=reps(),y+=reps(),z+=reps();}
	db len(){return sqrt(x*x+y*y+z*z);}
	Pnt operator -(Pnt a){return (Pnt){x-a.x,y-a.y,z-a.z};}
	Pnt operator *(Pnt a){return (Pnt){y*a.z-z*a.y,z*a.x-x*a.z,x*a.y-y*a.x};}
	db operator ^(Pnt a){return x*a.x+y*a.y+z*a.z;}
}A[N];
struct face{
	int v[3];
	Pnt Normal(){return (A[v[1]]-A[v[0]])*(A[v[2]]-A[v[0]]);}
	db area(){return Normal().len()/2.0;}
}f[N],C[N];
int see(face a,Pnt b) {return ((b-A[a.v[0]])^a.Normal())>0;}
void convex_3(){
	f[++cnt]=face{1,2,3};
	f[++cnt]=face{3,2,1};
	for(int i=4,cc=0;i<=n;++i){
		for(int j=1,v;j<=cnt;++j){
			if(!(v=see(f[j],A[i])))C[++cc]=f[j];
			for(int k=0;k<3;++k){vis[f[j].v[k]][f[j].v[(k+1)%3]]=v;}
		}
		for(int j=1;j<=cnt;++j){
			for(int k=0;k<3;++k){
				int x=f[j].v[k],y=f[j].v[(k+1)%3];
				if(vis[x][y]&&(!vis[y][x]))C[++cc]=face{x,y,i};
			}
		}
		for(int j=1;j<=cc;++j){f[j]=C[j];}
		cnt=cc,cc=0;
	}
}
inline int read(){
	int cnt=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){cnt=(cnt<<3)+(cnt<<1)+(c^48);c=getchar();}
	return cnt*f;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++) cin>>A[i].x>>A[i].y>>A[i].z,A[i].shake();
    convex_3();
    db ans=0;
    for(int i=1;i<=cnt;i++) ans+=f[i].area();
    printf("%.3lf\n",ans);
	return 0;
}

顺便实名吐槽一下洛谷2287,抖动精度要开到 1 e − 10 1e-10 1e10要不然只有 75 p t s 75pts 75pts,精度什么的好烦鸭>o<还是我脸黑?

你可能感兴趣的:(计算几何)