平面凸多边形和空间凸包络体算法整理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rebornyp/article/details/84937140

最近毕设项目中用到了最大包络体求算算法,在这里进行简单的整理,为了以后更好的理解。

准备知识

  • 关于点的定义
//空间上任何一个点信息
struct Point {
	double x, y, z;
	Point(){} 
	Point(double xx,double yy,double zz):x(xx),y(yy),z(zz){}
	
	//两向量之差  
    Point operator -(const Point p1)  
    {  
        return Point(x-p1.x,y-p1.y,z-p1.z);  
    } 

	//两向量之和  
    Point operator +(const Point p1)  
    {  
        return Point(x+p1.x,y+p1.y,z+p1.z);  
    }  

    //叉乘  
    Point operator *(const Point p)  
    {  
        return Point(y*p.z-z*p.y,z*p.x-x*p.z,x*p.y-y*p.x);  
    } 
     
	// 数乘
    Point operator *(double d)  
    {  
        return Point(x*d,y*d,z*d);  
    }  

	// 数除
    Point operator / (double d)  
    {  
        return Point(x/d,y/d,z/d);  
    }  

    //点乘  
    double  operator ^(Point p)  
    {  
        return (x*p.x+y*p.y+z*p.z);  
    } 
};


平面凸多边形求算算法

给一系列处于同一平面的空间点,然后求出所有只在最外凸多边形上的所有点集;其实为实现该目标有多种具体的算法, 笔者将通过代码具体实现的方式将其中一种具体实现。

  • 算法步骤
    在这里插入图片描述
  • 代码实现
/*求平面内的最大包络多边形
参数解释:平面内所有点信息,用于存储多边形上下两半的二维数组,平面的法向量
*/
void dealWith(vector<Point> &allPoints, vector<Point> polygon[2], Point n1) {
	if(allPoints.size() < 2) return;
	Point a, b; //最小和最大两个极端顶点;
	a.x = allPoints[0].x;
	a.y = allPoints[0].y;
	a.z = allPoints[0].z;
	b.x = allPoints[0].x;
	b.y = allPoints[0].y;
	b.z = allPoints[0].z;
	for(int i=1; i<allPoints.size(); i++) {
		if(a.x - allPoints[i].x > eps) {
			a.x = allPoints[i].x;
			a.y = allPoints[i].y;
			a.z = allPoints[i].z;
		} else if(fabs(a.x - allPoints[i].x) < eps) {
			if(a.y - allPoints[i].y > eps) {
				a.x = allPoints[i].x;
				a.y = allPoints[i].y;
				a.z = allPoints[i].z;
			} else if(fabs(a.y - allPoints[i].y) < eps) {
				if(a.z - allPoints[i].z > eps) {
					a.x = allPoints[i].x;
					a.y = allPoints[i].y;
					a.z = allPoints[i].z;
				} 
			}
		}

		if(allPoints[i].x - b.x > eps) {
			b.x = allPoints[i].x;
			b.y = allPoints[i].y;
			b.z = allPoints[i].z;
		} else if(fabs(b.x - allPoints[i].x) < eps) {
			if(allPoints[i].y - b.y > eps) {
				b.x = allPoints[i].x;
				b.y = allPoints[i].y;
				b.z = allPoints[i].z;
			} else if(fabs(b.y - allPoints[i].y) < eps) {
				if(allPoints[i].z - b.z > eps) {
					b.x = allPoints[i].x;
					b.y = allPoints[i].y;
					b.z = allPoints[i].z;
				} 
			}
		}
	}

	if (fabs(a.x - b.x) + fabs(a.y - b.y) + fabs(a.z - b.z) < eps) {
		polygon[0].push_back(a);
		printf("两极值点相距过近,返回了直接");
		return;
	}

	polygon[0].push_back(a);
	polygon[0].push_back(b);
	polygon[1].push_back(a);
	polygon[1].push_back(b);
	vector<Point> p1, p2; // p1是直线左边所有点集合,p2是直线右边所有点集合
	Point mid ((a.x+b.x)/2, (a.y+b.y)/2, (a.z+b.z)/2); // 线段中点
	Point n2 (b.x-a.x, b.y-a.y, b.z-a.z); //两个极值点的线段所在的向量
	Point n3 (n1.y*n2.z-n2.y*n1.z, n2.x*n1.z-n1.x*n2.z, n1.x*n2.y-n2.x*n1.y); // 计算所在平面内的线段的法向量
	for (int i = 0; i < allPoints.size(); ++i)
	{
		Point temp (allPoints[i].x-mid.x, allPoints[i].y-mid.y, allPoints[i].z-mid.z); //点集合中任意一个点到直线中点的向量
		double value = n3.x*temp.x + n3.y*temp.y + n3.z*temp.z; //向量和平面内直线法向量的点积
		if(value > eps) p1.push_back(allPoints[i]);
		else if(value < -eps) p2.push_back(allPoints[i]);
	}
	FindPoint2(p1, a, b, mid, polygon[0], n1);
	FindPoint2(p2, a, b, mid, polygon[1], n1);
}

//平面求包主题算法
void FindPoint2(vector<Point> &p, Point a, Point b, Point mid, vector<Point> &polygon, Point &n) {
	if (p.size() == 0)
		return;
	Point pmax;
	pmax.x = p[0].x;
	pmax.y = p[0].y;
	pmax.z = p[0].z;
	double k, d;
	k = (b.y - a.y) / (b.x - a.x);
	d = a.y - k * a.x;
	double maxDis = DistanceOfPointToLine(&a, &b, &pmax), maxMid = distanceOfTwoPoints(pmax, mid);
	double newdist;
	for (int i = 1; i < p.size(); ++i)
	{
		newdist = DistanceOfPointToLine(&a, &b, &p[i]);
		if (newdist - maxDis > eps)
		{
			pmax.x = p[i].x;
			pmax.y = p[i].y;
			pmax.z = p[i].z;
			maxDis = newdist;
		}
		else if (fabs(newdist - maxDis) < eps)
		{	//选择距离线段ab中点最近的那个
			double dis1 = distanceOfTwoPoints(p[i], mid);
			if (dis1 < maxMid)
			{
				pmax.x = p[i].x;
				pmax.y = p[i].y;
				pmax.z = p[i].z;
				maxMid = dis1;
			}
		}
	}
	polygon.push_back(pmax);

	Point mid1 ((pmax.x+a.x)/2, (pmax.y+a.y)/2, (pmax.z+a.z)/2);
	Point mid2 ((pmax.x+b.x)/2, (pmax.y+b.y)/2, (pmax.z+b.z)/2);
	Point v1 (mid1.x-mid.x, mid1.y-mid.y, mid1.z-mid.z);
	Point v2 (mid2.x-mid.x, mid2.y-mid.y, mid2.z-mid.z);
	Point l1 (pmax.x-a.x, pmax.y-a.y, pmax.z-a.z); //两个极值点的线段所在的向量
	Point n1 (n.y*l1.z-l1.y*n.z, l1.x*n.z-n.x*l1.z, n.x*l1.y-l1.x*n.y); // 计算所在平面内的线段的法向量
	Point l2 (pmax.x-b.x, pmax.y-b.y, pmax.z-b.z); //两个极值点的线段所在的向量
	Point n2 (n.y*l2.z-l2.y*n.z, l2.x*n.z-n.x*l2.z, n.x*l2.y-l2.x*n.y); // 计算所在平面内的线段的法向量
	if(v1.x*n1.x+v1.y*n1.y+v1.z*n1.z < -eps) {
		n1.x *= -1;
		n1.y *= -1;
		n1.z *= -1;
	}
	double len = sqrt(n1.x*n1.x+n1.y*n1.y+n1.z*n1.z);
	n1.x /= len;
	n1.y /= len;
	n1.z /= len;
	if(v2.x*n2.x+v2.y*n2.y+v2.z*n2.z < -eps) {
		n2.x *= -1;
		n2.y *= -1;
		n2.z *= -1;
	}
	len = sqrt(n2.x*n2.x+n2.y*n2.y+n2.z*n2.z);
	n2.x /= len;
	n2.y /= len;
	n2.z /= len;

	/* 找出各自符合满足 Pmax,Pa 和 Pmax,Pb 的点 */
	vector<Point> p1, p2;
	for (int i = 0; i < p.size(); ++i)
	{
		Point temp1 (p[i].x-mid1.x, p[i].y-mid1.y, p[i].z-mid1.z);
		double value = temp1.x*n1.x+temp1.y*n1.y+temp1.z*n1.z;
		if(value > eps) p1.push_back(p[i]);
		else {
			Point temp2 (p[i].x-mid2.x, p[i].y-mid2.y, p[i].z-mid2.z);
			value = temp2.x*n2.x+temp2.y*n2.y+temp2.z*n2.z;
			if(value > eps) p2.push_back(p[i]);
		}

	}
	/* 递归寻找Pmax */
	FindPoint2(p1, pmax, a, mid1, polygon, n);
	FindPoint2(p2, pmax, b, mid2, polygon, n);
}

空间求凸包络体算法

空间凸包算法,是给定一系列三维空间点,然后求出最小凸包络体,其中凸包络体的顶点都来自给定的点,并且任意点都在凸包中。

  • 算法步骤
    在这里插入图片描述
  • 实现代码

struct CH3D  
{  
    struct face  
    {  
        //表示凸包一个面上的三个点的编号  
        int a,b,c;  
        //表示该面是否属于最终凸包上的面  
        bool ok;  
    };  

    //初始顶点数  
    int n;  

    //初始顶点  
    Point P[MAXN];  

    //凸包表面的三角形数  
    int num;  

    //凸包表面的三角形  
    face F[8*MAXN];  

    //凸包表面的三角形  
	//g[i][j]存储的是第i个点连接到第j个点的有向矢量所在的在F数组中的三角面的序号
    int g[MAXN][MAXN]; 

	//共面点集合,一维是集合数,二维是共面的点数
	vector<set<int>> count;
	vector<Point> polygons[MAXN][2];

    //向量长度  
    double vlen(Point a)  
    {  
        return sqrt(a.x*a.x+a.y*a.y+a.z*a.z);  
    }  

    //叉乘  
    Point cross(const Point &a,const Point &b,const Point &c)  
    {  
        return Point((b.y-a.y)*(c.z-a.z)-(b.z-a.z)*(c.y-a.y),  
                     (b.z-a.z)*(c.x-a.x)-(b.x-a.x)*(c.z-a.z),  
                     (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x)  
                     );  
    }  

    //三角形面积*2  
    double area(Point a,Point b,Point c)  
    {  
        return vlen((b-a)*(c-a));  
    }  

    //四面体有向体积*6  
    double volume(Point a,Point b,Point c,Point d)  
    {  
        return (b-a)*(c-a)^(d-a);  
    }  

    //正:点在面同向  返回的是点和面三点构成的体积,可正也可负
    double dblcmp(Point &p,face &f)  
    {  
        Point m=P[f.b]-P[f.a];  
        Point n=P[f.c]-P[f.a];  
        Point t=p-P[f.a];  
        return (m*n)^t;  
    }  

    void deal(int p,int a,int b)  
    {  
        int f=g[a][b];//搜索与该边相邻的另一个平面  
        face add;  
        if(F[f].ok)  
        {  
            if(dblcmp(P[p],F[f])>eps)  
              dfs(p,f);  
            else  
            {  
                add.a=p;  
                add.b=b;  
                add.c=a;//这里注意顺序,要成右手系  
                add.ok=true;  
                g[p][b]=g[a][p]=g[b][a]=num;  
                F[num++]=add;  
            }  
        }  
    }  

    void dfs(int p,int now)//递归搜索所有应该从凸包内删除的面  
    {  
         F[now].ok=0;  
         deal(p,F[now].b,F[now].a);  
         deal(p,F[now].c,F[now].b);  
         deal(p,F[now].a,F[now].c);  
    }  

	//F[s]和F[t]是否是共面;
    bool same(int s,int t)  
    {  
        Point &a=P[F[s].a];  
        Point &b=P[F[s].b];  
        Point &c=P[F[s].c];  
        return fabs(volume(a,b,c,P[F[t].a]))<eps &&  
               fabs(volume(a,b,c,P[F[t].b]))<eps &&  
               fabs(volume(a,b,c,P[F[t].c]))<eps;  
    }  

    //构建三维凸包  
    void create()  
    {  
        int i,j,tmp;  
        face add;  

        num=0;  
        if(n<4)return;  
    //**********************************************  
    //此段是为了保证前四个点不共面  
        bool flag=true;  
        for(i=1;i<n;i++)  
        {  
            if(vlen(P[0]-P[i])>eps)  
            {  
                swap(P[1],P[i]);  
                flag=false;  
                break;  
            }  
        }  
        if(flag) return;  //所有点都和P[0]点重合
        flag=true;  
        //使前三个点不共线  
        for(i=2;i<n;i++)  
        {  
            if(vlen((P[0]-P[1])*(P[1]-P[i]))>eps)  
            {  
                swap(P[2],P[i]);  
                flag=false;  
                break;  
            }  
        }  
        if(flag)return;  
        flag=true;  
        //使前四个点不共面  
        for(int i=3;i<n;i++)  
        {  
            if(fabs((P[0]-P[1])*(P[1]-P[2])^(P[0]-P[i]))>eps)  
            {  
                swap(P[3],P[i]);  
                flag=false;  
                break;  
            }  
        }  
        if(flag)return;  

    //*****************************************  
        for(i=0;i<4;i++)  
        {  
            add.a=(i+1)%4;  
            add.b=(i+2)%4;  
            add.c=(i+3)%4;  
            add.ok=true;  
            if(dblcmp(P[i],add)>0)
				swap(add.b,add.c);

            g[add.a][add.b]=g[add.b][add.c]=g[add.c][add.a]=num;  
            F[num++]=add;  
        }  
        for(i=4;i<n;i++)  
        {  
            for(j=0;j<num;j++)  
            {  
                if(F[j].ok && dblcmp(P[i],F[j])>eps)  
                {  
                    dfs(i,j);  
                    break;  
                }  
            }  
        }  
        tmp=num;  
        for(i=num=0;i<tmp;i++)  
          if(F[i].ok)  
            F[num++]=F[i];  
		polygon();
    }  

    //表面积  
    double area()  
    {  
        double res=0;  
        if(n==3)  
        {  
            Point p=cross(P[0],P[1],P[2]);  
            res=vlen(p)/2.0;  
            return res;  
        }  
        for(int i=0;i<num;i++)  
          res+=area(P[F[i].a],P[F[i].b],P[F[i].c]);  
        return res/2.0;  
    }  

	//计算凸多面体体积
    double volume()  
    {  
        double res=0;  
        Point tmp(0,0,0);  
        for(int i=0;i<num;i++)  
           res+=volume(tmp,P[F[i].a],P[F[i].b],P[F[i].c]);  
        return fabs(res/6.0);  
    }  

    //表面三角形个数  
    int triangle()  
    {  
        return num;  
    }  

    //表面多边形个数  
    int polygon()  
    {  
        int i,j,res,flag;  
		vector<int> index; //index[i]是第i个三角形是所在的count二维数组下标
		index.resize(num);
        for(i=res=0;i<num;i++)
		{

			flag = 1;
			for(j=0; j<i; j++) {
				if(same(i, j)) {
					count[index[j]].insert(F[i].a);
					count[index[j]].insert(F[i].b);
					count[index[j]].insert(F[i].c);
					index[i] = index[j];
					flag = 0;
					break;
				}
			}
			if(flag) {
				set<int> tempSet;
				tempSet.insert(F[i].a);
				tempSet.insert(F[i].b);
				tempSet.insert(F[i].c);
				count.push_back(tempSet);
				index[i] = count.size()-1;
			}
        }  
		return count.size();
    } 

    //三维凸包重心  
    Point barycenter()  
    {  
        Point ans(0,0,0),o(0,0,0);  
        double all=0;  
        for(int i=0;i<num;i++)  
        {  
            double vol=volume(o,P[F[i].a],P[F[i].b],P[F[i].c]);  
            ans=ans+(o+P[F[i].a]+P[F[i].b]+P[F[i].c])/4.0*vol;  
            all+=vol;  
        }  
        ans=ans/all;  
        return ans;  
    }  

    //点到面的距离  
    double ptoface(Point p,int i)  
    {  
        return fabs(volume(P[F[i].a],P[F[i].b],P[F[i].c],p)/vlen((P[F[i].b]-P[F[i].a])*(P[F[i].c]-P[F[i].a])));  
    }  
}; 

总结

后续的凸多面体算法的来自这篇博客,其实是源自一个竞赛题,里面涉及到了一点基础的数学知识,所以学好数学很重要。

猜你喜欢

转载自blog.csdn.net/rebornyp/article/details/84937140
今日推荐