现在的位置: 首页 > 综合 > 正文

计算点、线、面等元素之间的交点、交线、封闭区域面积和闭合集(续9)

2012年09月22日 ⁄ 综合 ⁄ 共 4210字 ⁄ 字号 评论关闭
 

介绍完平面上凸包各种求取方法后,讲解三维顶点集合凸包的方法。

过程:

3DConvexHull( int argc, char *argv[] )

{  
    ReadVertices();                      //
读取所有顶点

    DoubleTriangle();                    //构造初始三角形

    ConstructHull();                     //构建三维顶点集合的凸包

}

    
    
//首先找到3个不共线的顶点,以相反的顺序建成三角形的两个面。接着找到不在三角面上的第四个顶点。在三角面上以逆时针顺序存储顶点,建立到新顶点的3个面。

void    DoubleTriangle( void )

{

    tVertex v0, v1, v2, v3, t;

    tFace    f0, f1 = NULL;

    tEdge    e0, e1, e2, s;

    int      vol;

    v0 = vertices;

    while ( Collinear( v0, v0->next, v0->next->next ) )//比较连续存储的3个顶点

        if ( ( v0 = v0->next ) == vertices )     //提取下一个顶点

            printf("所有顶点共线");

    v1 = v0->next;       //找到不共线的顶点

    v2 = v1->next;       //找到不共线的顶点

    v0->mark = PROCESSED;    //标记顶点处理过

    v1->mark = PROCESSED;    //标记顶点处理过

    v2->mark = PROCESSED;    //标记顶点处理过

    f0 = MakeFace( v0, v1, v2, f1 );

    f1 = MakeFace( v2, v1, v0, f0 );//以相反的顶点顺序构建第二个面

    f0->edge[0]->adjface[1] = f1;

    f0->edge[1]->adjface[1] = f1;

    f0->edge[2]->adjface[1] = f1;        //指出面的邻接面

    f1->edge[0]->adjface[1] = f0;

    f1->edge[1]->adjface[1] = f0;

    f1->edge[2]->adjface[1] = f0;        //指出面的邻接面

    //准备找到不在面上的第四个顶点来构建体

    v3 = v2->next;

    vol = VolumeSign( f0, v3 );      //通过计算体积来判断是否第四个顶点不在面上

    while ( !vol )   {               //当体积为0的话,继续循环

        if ( ( v3 = v3->next ) == v0 )

            printf("所有顶点是共面的");

        vol = VolumeSign( f0, v3 );

    }

    vertices = v3;                   //记录V3为现在被增加的顶点

}

//每次考察一个顶点,如果构成凸包中新顶点,则新建面和边,如处在现阶段的凸包之内,则不作其他处理;

void    ConstructHull( void )

{

    tVertex v, vnext;

    int         vol;

    bool        changed;

    v = vertices;

    do {

        vnext = v->next;     //取下一个顶点来处理

        if ( !v->mark ) {    //如果此顶点的标记为“还从未处理过”

            v->mark = PROCESSED;//将此顶点标记为“已经被处理过”

            changed = AddOne( v );//将新顶点加入,处在现凸包内,或构成新凸包的一个顶点

            CleanUp( &vnext );

        }

        v = vnext;

    } while ( v != vertices );

}

通过体积来计算此顶点与其他面的关系,若构成的体都为凹,则不予考虑。否则,可说此顶点位于某些面之外。若从此顶点能观察到某边的两个邻接面,则此边将被删除。若从此顶点只能观察到一个面,则将创建新面。

bool    AddOne( tVertex p )

{

    tFace f;

    tEdge e, temp;

    int       vol;

    bool     vis = FALSE;

    f = faces;

    do {

        vol = VolumeSign( f, p );        //计算每个面和新增加的顶点构成的锥体的体积

   

        if ( vol < 0 ) {

            f->visible = VISIBLE;  //此面被标记为可见

            vis = TRUE;              //标识顶点已处于面之外

        }

        f = f->next;             //取得下一个面

    } while ( f != faces );      //对所有面循环处理

   

    if ( !vis ) {                //没有一个面可见,则顶点位于体内,不将作为凸包顶点

        p->onhull = !ONHULL;    //顶点作标记

        return FALSE;

    }

    e = edges;

    do {

        temp = e->next;          //取下一条边

        if ( e->adjface[0]->visible && e->adjface[1]->visible )

            e->delete = REMOVED;//如果这条边的两个邻接面都可见,则标记为删除此条边

        else if ( e->adjface[0]->visible || e->adjface[1]->visible )

            e->newface = MakeConeFace( e, p ); //构建新面

        e = temp;

    } while ( e != edges );      //所有边循环处理

    return TRUE;

}

//计算体积,此结果的正负可判断顶点与面的“可见”关系。

int VolumeSign( tFace f, tVertex p )

{

    double vol;

    int     voli;

    double ax, ay, az, bx, by, bz, cx, cy, cz;

    ax = f->vertex[0]->v[X] - p->v[X];

    ay = f->vertex[0]->v[Y] - p->v[Y];

    az = f->vertex[0]->v[Z] - p->v[Z];       //顶点坐标相减构成向量

    bx = f->vertex[1]->v[X] - p->v[X];

    by = f->vertex[1]->v[Y] - p->v[Y];

    bz = f->vertex[1]->v[Z] - p->v[Z];

    cx = f->vertex[2]->v[X] - p->v[X];

    cy = f->vertex[2]->v[Y] - p->v[Y];

    cz = f->vertex[2]->v[Z] - p->v[Z];

    vol =   ax * (by*cz - bz*cy)

        + ay * (bz*cx - bx*cz)

        + az * (bx*cy - by*cx);

       //体积计算是通过法向量与一条边向量的点乘,因为点乘结果表示两个模与向量夹角的余弦,其中法向量的模是三角面上两条边的模乘上夹角的正弦,即为面积。而剩下的那个模乘上夹角的余弦,即为高。

    return vol;

}

                  
    当一条边的一个邻接面对新加入的顶点是“可见”的时候,构建一个新面和两条新边

tFace   MakeConeFace( tEdge e, tVertex p )

{

    tEdge new_edge[2];

    tFace new_face;

    int       i, j;

    //准备构建两条新边

    for ( i=0; i < 2; ++i )

        //如果新边已经被创建过,直接拷贝过来即可

        if ( !( new_edge[i] = e->endpts[i]->duplicate) ) {

            //如果新边没有被创建过,则构建

            new_edge[i] = MakeNullEdge();

            new_edge[i]->endpts[0] = e->endpts[i];

            new_edge[i]->endpts[1] = p;

            e->endpts[i]->duplicate = new_edge[i];// 顶点标记这条新边

        }

        //创建新的面

        new_face = MakeNullFace();  

        new_face->edge[0] = e;           //构成面的原来那条边

        new_face->edge[1] = new_edge[0];//构成面的第一条新边

        new_face->edge[2] = new_edge[1]; //构成面的第二条新边

        MakeCcw( new_face, e, p );       //以逆时针方向建立面上的顶点顺序

        //将新建立的边和面联系起来

        for ( i=0; i < 2; ++i )

            for ( j=0; j < 2; ++j

                     if ( !new_edge[i]->adjface[j] ) {

                     new_edge[i]->adjface[j] = new_face;

                     break;      //新边和新面一旦联系,就跳出循环

                 }

                 return new_face;//处理结束,返回新建立的面

}

抱歉!评论已关闭.