最佳包裹

求三维凸包用增量法求解

假设我们已经维护好了前面的点的凸包,对于新加入的点:

如果这个点在凸包内部显然那就不用管了

如果这个点在凸包外部,那么考虑如下情况:

将新点Pr当做光源,照的到的面全部删掉,照不到的面保留下来即可

如何判断一个面是否能够被照到:取多面体外侧为正方向,则对于某一个面,如果光源在其正侧那么能被照到,否则不行;具体判断的时候,在平面上取一个点a,则判断由a,Pr组成的向量与平面的法向量的叉积即可

维护面的时候,我们都是维护的三角形(如果一个面不是三角形我们对其进行三角剖分),存储三角形的三个点就好了(按照逆时针方向存储);一共有n个点,每个面对应三条边,但是每条边会被重复数两次,所以设面有m个,边有k个,则3m=2k,代入多面体欧拉定理得,m=2n4,k=3n6,可知时间复杂度为O(n2)

如何找出分界线:设g[i][j]表示由i指向j的这条向量是否是分界线,那么由于我们是按照逆时针方向存储的三个点,如下图

我们只需要判断ijji表示的两个面中,一个可以被照到,另一个不能被照到就好了

代码见下:

#include<bits/stdc++.h>
#define ll long long
#define Vector Point
using namespace std;
const int N=2010;
const double eps=1e-10;
double Rand()
{
	return rand()/(double)RAND_MAX;
	//RAND_MAX是宏定义,表示rand()能返回的最大整数 
}
double reps()
{
	return (Rand()-0.5)*eps;
}
struct Point
{
	double x,y,z;
	Point(double a=0,double b=0,double c=0):x(a),y(b),z(c){}
	void shake(){x+=reps(),y+=reps(),z+=reps();}
}p[N];
Vector Cross(Vector a,Vector b)
{
	return Vector{a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x};
}
bool vis[N][N];
int cnt,n;
Point operator - (Point a,Point b){return Point(a.x-b.x,a.y-b.y,a.z-b.z);}
double get_length(Vector a)
{
	return sqrt(a.x*a.x+a.y*a.y+a.z*a.z);
}
struct Face
{
	int v[3];//存储这一个面的三个点的编号,逆时针方向 
	double area(){return get_length(Cross(p[v[1]]-p[v[0]],p[v[2]]-p[v[0]]))/2;}
}f[N<<2],C[N<<2];
double operator * (Vector a,Vector b){return a.x*b.x+a.y*b.y+a.z*b.z;}
bool see(Face a,Point b)
{
	return (b-p[a.v[0]])*Cross(p[a.v[1]]-p[a.v[0]],p[a.v[2]]-p[a.v[0]])>0;//进行了微小扰动,不会出现等于0的情况
}
void Convex_3D()
{
	f[++cnt]={1,2,3},f[++cnt]={3,2,1};
	//对于前三个点的凸包,我们认为是两张一模一样的平面,但是方向不同
	//如果{1,2,3}从某一个方向看是顺时针,那么我们就认为我们从另一个方向看
	//总之可以找到一个方向使得{1,2,3}为逆时针,{3,2,1}是顺时针
	//于是此时认为这个方向为正方向,{3,2,1}的正方向就是这个方向的反方向 
	for(int i=4,cc=0;i<=n;i++)
	{
		bool v;
		for(int j=1;j<=cnt;j++)
		{
			v=see(f[j],p[i]);//判断是否看得见 
			if(!v) C[++cc]=f[j];
			for(int k=0;k<3;k++) 
			vis[f[j].v[k]][f[j].v[(k+1)%3]]=v;
			//vis[i][j]用来记录向量ij所表示的面(左手边)是否被看得见 
		} 
		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]={x,y,i};
			//添加新的面,注意三个点的顺序,要保证逆时针 
		 } 
		for(int j=1;j<=cc;j++) f[j]=C[j];
		cnt=cc;
		cc=0;
	}
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
    	scanf("%lf%lf%lf",&p[i].x,&p[i].y,&p[i].z);
    	p[i].shake();//这一步在随机微小扰动,为什么要下文说明 
	}
	Convex_3D();
	double ans=0;
	for(int i=1;i<=cnt;i++) ans+=f[i].area();
	printf("%.3f\n",ans);
    return 0;
}

说明一下为什么要微小扰动:这是为了防止出现四点共面的情况。如果出现了四点共面的情况,可能会导致各种错误。举一个例子,比如对于前四个点共面,那么在加入第四个点的时候,由上面的代码判断的是之前三个点形成的凸包的两个面都看不见,就都会算进去,而且现在不知道新加入的三角形如何剖分(剖分方式很多),所以不行

posted @   最爱丁珰  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示