深度探索DxFramework 10-3

Table of Contents

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com

第10章 进入三维的世界 3

10.5 无规矩不成方圆,C_BoundingBox类

10.5.1.包围盒的种类

DXFrameWork的包围盒是一个凸六面体,由6个面,8个顶点,12条边组成的闭合体。因此,DXFrameWork也使用了相关的数据结构来描述包围盒。代码如下:

class C_BoundingBox
{
    ...
public:
    D3DXVECTOR3 point[8]; //8个顶点
    D3DXPLANE     plane[6]; //6个表面
}

8个顶点和6个面和编号的示意图如下:

###10.5.2.包围盒的数据结构和初始化设置

//根据给定的图元计算出它的包围盒
void C_BoundingBox::FindBoundingBox(C_Primitive* p_Primitive) 
{
    unsigned i;

    //获取
    DWORD numVertices = p_Primitive->numVertices;

    // Lock the vertex buffer
    C_VertexManipulator vertexMemory;
    p_Primitive->LockVertexBuffer(&vertexMemory);

    // Find the mins and maxs for each X Y and Z axis
    D3DXVECTOR3 position;

    float minX;
    float minY;
    float minZ;
    float maxX;
    float maxY;
    float maxZ;

    position = vertexMemory[0].position;
    minX = maxX = position.x;
    minY = maxY = position.y;
    minZ = maxZ = position.z;

    //遍历每一个顶点,找出X,Y,Z值最大和最小值
    for (i = 1; i < numVertices; i++) {
        position = vertexMemory[i].position;
        if (position.x < minX) {
            minX = position.x;
        }
        if (position.y < minY) {
            minY = position.y;
        }
        if (position.z < minZ) {
            minZ = position.z;
        }
        if (position.x > maxX) {
            maxX = position.x;
        }
        if (position.y > maxY) {
            maxY = position.y;
        }
        if (position.z > maxZ) {
            maxZ = position.z;
        }
    }

    // Unlock the vertex buffer
    p_Primitive->UnlockVertexBuffer();

    //如果一些图元的包围盒是不是三维的话,比如,一个二维面片的包围盒。
    //那么,就要适当地调整一下,使得包围盒能变成三维的
    if (fabs(minX - maxX) < 0.01f) { 
        minX -= 0.1f;
        maxX += 0.1f; 
    }

    if (fabs(minY - maxY) < 0.01f) { 
        minY -= 0.1f;
        maxY += 0.1f; 
    }

    if (fabs(minZ - maxZ) < 0.01f) { 
        minZ -= 0.1f;
        maxZ += 0.1f; 
    }

    //创建组成三角形的八个顶点,从0到7的顺序,X,Y,Z值分别是:
    //0 右,下,近
    //1 右,上,近
    //2 左,上,近
    //3 右,上,近
    //4 右,下,远
    //5 右,上,远
    //6 左,上,远
    //7 左,下,远
    // 意思是,前4个
    point[0] = D3DXVECTOR3(maxX, minY, minZ); // Right Bottom Near
    point[1] = D3DXVECTOR3(maxX, maxY, minZ); // Right Top Near
    point[2] = D3DXVECTOR3(minX, maxY, minZ); // Left Top Near
    point[3] = D3DXVECTOR3(minX, minY, minZ); // Right Top Near
    point[4] = D3DXVECTOR3(maxX, minY, maxZ); // Right Bottom Far
    point[5] = D3DXVECTOR3(maxX, maxY, maxZ); // Right Top Far
    point[6] = D3DXVECTOR3(minX, maxY, maxZ); // Left Top Far
    point[7] = D3DXVECTOR3(minX, minY, maxZ); // Left Bottom Far

    // Obtain the 6 plane specifying the bounding box
    D3DXPlaneFromPoints(&plane[0], &point[2], &point[1], &point[0]); //Near
    D3DXPlaneFromPoints(&plane[1], &point[5], &point[6], &point[4]); //Far
    D3DXPlaneFromPoints(&plane[2], &point[7], &point[6], &point[2]); //Left
    D3DXPlaneFromPoints(&plane[3], &point[1], &point[5], &point[0]); //Right
    D3DXPlaneFromPoints(&plane[4], &point[4], &point[3], &point[0]); //Bottom
    D3DXPlaneFromPoints(&plane[5], &point[2], &point[5], &point[1]); //Top

    // Validate the bounding box
    valid = true;
}
//根据指定的包围盒的大小范围查找到对应的包围盒
void C_BoundingBox::FindBoundingBox(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {

    /* 当有些包围盒不是3D的话,比如其中有一维的值为0,则调整它变成真正三维的盒子*/
    if (fabs(minX - maxX) < 0.01f) { 
        minX -= 0.1f;
        maxX += 0.1f; 
    }

    if (fabs(minY - maxY) < 0.01f) { 
        minY -= 0.1f;
        maxY += 0.1f; 
    }

    if (fabs(minZ - maxZ) < 0.01f) { 
        minZ -= 0.1f;
        maxZ += 0.1f; 
    }

    point[0] = D3DXVECTOR3(maxX, minY, minZ); // Right Bottom Near
    point[1] = D3DXVECTOR3(maxX, maxY, minZ); // Right Top Near
    point[2] = D3DXVECTOR3(minX, maxY, minZ); // Left Top Near
    point[3] = D3DXVECTOR3(minX, minY, minZ); // Right Top Near
    point[4] = D3DXVECTOR3(maxX, minY, maxZ); // Right Bottom Far
    point[5] = D3DXVECTOR3(maxX, maxY, maxZ); // Right Top Far
    point[6] = D3DXVECTOR3(minX, maxY, maxZ); // Left Top Far
    point[7] = D3DXVECTOR3(minX, minY, maxZ); // Left Bottom Far

    // Obtain the 6 plane specifying the bounding box
    D3DXPlaneFromPoints(&plane[0], &point[2], &point[1], &point[0]); //Near
    D3DXPlaneFromPoints(&plane[1], &point[5], &point[6], &point[4]); //Far
    D3DXPlaneFromPoints(&plane[2], &point[7], &point[6], &point[2]); //Left
    D3DXPlaneFromPoints(&plane[3], &point[1], &point[5], &point[0]); //Right
    D3DXPlaneFromPoints(&plane[4], &point[4], &point[3], &point[0]); //Bottom
    D3DXPlaneFromPoints(&plane[5], &point[2], &point[5], &point[1]); //Top

    // Validate the bounding box
    valid = true;
}
 
void C_BoundingBox::SetBoundingBox(D3DXVECTOR3 point[8]) {     int i;

    for (i = 0; i < 8; i++) {
        this->point[i]=point[i];
    }

    // Obtain the 6 plane specifying the bounding box
    D3DXPlaneFromPoints(&plane[0], &point[2], &point[1], &point[0]); //Near
    D3DXPlaneFromPoints(&plane[1], &point[5], &point[6], &point[4]); //Far
    D3DXPlaneFromPoints(&plane[2], &point[7], &point[6], &point[2]); //Left
    D3DXPlaneFromPoints(&plane[3], &point[1], &point[5], &point[0]); //Right
    D3DXPlaneFromPoints(&plane[4], &point[4], &point[3], &point[0]); //Bottom
    D3DXPlaneFromPoints(&plane[5], &point[2], &point[5], &point[1]); //Top

    // 确认包围盒有效
    valid = true;
}
 
void C_BoundingBox::SetBoundingBox(D3DXVECTOR3 point[8], D3DXPLANE plane[6])
{
    int i;

    for (i = 0; i < 8; i++) {
        this->point[i] = point[i];
    }

    for (i = 0; i < 6; i++) {
        this->plane[i] = plane[i];
    }

    valid = true;
}

10.5.3.包围盒的相交种类和相交判定

在dxframework中,包围盒的相交种类分为四种,分别是:相离,相交,覆盖,被包含四种。

相离的情况如下,所谓相离是指,A包围盒和B包围盒之间,A包围盒中任意一个顶点,都不在B包围盒内部。或者B包围盒中任意一个顶点,都不在A包围盒的内部。示意图如下:

相交是指:B包围盒的一部分的顶点在A包围盒的内部,这时候称为A包围盒与B包围盒相交。示意图如下:

覆盖和被包含是指:B包围盒的全部顶点完全在A包围盒内部。这时候称为A包围盒“覆盖”了B包围盒。或者称为:B包围盒“被包含”于A包围盒。示意图如下:

C_BoundingBox类提供了包围盒相交检测的函数,如下:

//核心函数,判断本包围盒是否和p_BoundingBox包围盒相交
INTERSECTION_TYPE C_BoundingBox::IsIntersecting( C_BoundingBox *p_BoundingBox) 
{

    if ((p_BoundingBox == NULL) || (!p_BoundingBox->valid) || (!this->valid)) 
    {
        //两包围盒必须要有效,即各顶点信息和表明信息要计算好才能进行相交判断
        throw Error(_T("C_BoundingBox::IsIntersecting : Bounding box(es) do not exist or are not valid"));
    }

    //注释约定:first box指本包围盒(this指针所指向的包围盒对象),
    //second box指传递进来的参数指针所指向的包围盒。
    int i,j;

     //out[i]数据中的第k位表示是否第i个包围盒顶点是否在第k个平面的外测
    DWORD out[8]; 

    //检查second box的顶点是否在first box里面
    for (i = 0; i < 8; i++)    {
        out[i] = 0;
    }
    
    bool aPointInside = false;
    int sumOut = 0;

    for (i = 0; i < 8; i++) 
    {
        for (j = 0; j < 6; j++)
        {
            //调用工具函数,检查第i个second box是否在
            //first box的第j个表面正则。
            if (positiveSide(&p_BoundingBox->point[i], 
                             &plane[j]))
            {
                out[i] |=(1 << j);
            }
        }

        //如果第i个second box的顶点都在所有的first box的的负侧
        //表明表示这个第i个second box的顶点在first box的内部。
        if (!out[i]) 
        {
            aPointInside = true;
        }
        sumOut += out[i];
    }

    //如果second box至少有一个点在first box的内侧
    if (aPointInside) 
    {
        // sumOut等于0。表示所有的second box的顶点都在first box
        // 内部。所以first box覆盖second box
        if (sumOut == 0) 
        {
            return COMPLETELY_COVER;
        }
        return PARTIALY_INTERSECTING;
    }

    D3DXVECTOR3 intersectionPoint;
    if ((((((((out[0] & out[1]) & out[2]) & out[3]) 
        & out[4]) & out[5]) & out[6]) & out[7]) != 0) 
    {
        // 所有的顶点都在某一个面的正测,表示这两个包围盒是相离的
        return NOT_INTERSECTING;
    }

    //检查first box的某一边是否穿过second box
    {
        D3DXVECTOR3 edges[12][2] = {
        p_BoundingBox->point[0], p_BoundingBox->point[1],
        p_BoundingBox->point[1], p_BoundingBox->point[2],
        p_BoundingBox->point[2], p_BoundingBox->point[3],
        p_BoundingBox->point[3], p_BoundingBox->point[0],
        p_BoundingBox->point[0], p_BoundingBox->point[4],
        p_BoundingBox->point[1], p_BoundingBox->point[5],
        p_BoundingBox->point[2], p_BoundingBox->point[6],
        p_BoundingBox->point[3], p_BoundingBox->point[7],
        p_BoundingBox->point[4], p_BoundingBox->point[5],
        p_BoundingBox->point[5], p_BoundingBox->point[6],
        p_BoundingBox->point[6], p_BoundingBox->point[7],
        p_BoundingBox->point[7], p_BoundingBox->point[4]};

        D3DXVECTOR3 faces[6][4] = {
        point[3], point[2], point[1], point[0],//近Z,XY面
        point[5], point[6], point[7], point[4],//远Z,XY面
        point[3], point[7], point[6], point[2],//左X,YZ面
        point[1], point[5], point[4], point[0],//右X,YZ面    
        point[4], point[7], point[3], point[0],//底Y,XZ面
        point[2], point[6], point[5], point[1]};//顶Y,XZ面

        for (i = 0; i < 12; i++) 
        {
            for (j = 0; j < 6; j++)
            {
                //调用工具函数checkEdgeIntersects检查第i条first 
                //box的边是否穿过第j个second box的面
                if(checkEdgeIntersectsFace(edges[i],faces[j], 
                    4, &plane[j], &intersectionPoint)) 
                    { 
                        return PARTIALY_INTERSECTING;
                    }
                }
            }
    }

    //检查second box的某一边是否穿过first box
    {
        D3DXVECTOR3 edges[12][2] = {
                    point[0], point[1],
                    point[1], point[2],
                    point[2], point[3],
                    point[3], point[0],
                    point[0], point[4],
                    point[1], point[5],
                    point[2], point[6],
                    point[3], point[7],
                    point[4], point[5],
                    point[5], point[6],
                    point[6], point[7],
                    point[7], point[4]};

        D3DXVECTOR3 faces[6][4] = {
        p_BoundingBox->point[3], p_BoundingBox->point[2], 
        p_BoundingBox->point[1], p_BoundingBox->point[0],    
        p_BoundingBox->point[5], p_BoundingBox->point[6], 
        p_BoundingBox->point[7], p_BoundingBox->point[4],     
        p_BoundingBox->point[3], p_BoundingBox->point[7], 
        p_BoundingBox->point[6], p_BoundingBox->point[2],    
        p_BoundingBox->point[1], p_BoundingBox->point[5], 
        p_BoundingBox->point[4], p_BoundingBox->point[0],      
        p_BoundingBox->point[4], p_BoundingBox->point[7], 
        p_BoundingBox->point[3], p_BoundingBox->point[0],    
        p_BoundingBox->point[2], p_BoundingBox->point[6], 
        p_BoundingBox->point[5], p_BoundingBox->point[1]};   

        for (i = 0; i < 12; i++) 
        {
            for (j = 0; j < 6; j++)
            {
                if(checkEdgeIntersectsFace(edges[i],faces[j],
                                4, &p_BoundingBox->plane[j], 
                                &intersectionPoint)) 
                {
                      return PARTIALY_INTERSECTING;
                }
            }
        }
    }

    //检查一下是否first box的一点是否至少在second box的一个面的正侧,
    //如果是的话,表示两盒子相离。如果否的话,在上述的各种情况都检查过的
    //前提下,就表示,first box在second box的内部,即first box被
    //包含于second box
    for (i = 0; i < 6; i++)
    {
        if( positiveSide(&point[0], 
            &p_BoundingBox->plane[i])) 
        {
            return NOT_INTERSECTING; 
        }
    }

    return COMPLETELY_INSIDE;
}

上面的函数检查两包围盒相交与否是非常精确的。但是也可以看到,检测的计算非常耗时。尤其是后面判断first box是否被包含于second box的部分。如果我们能够预先知道first box的体积肯定大于second box的话。那么后面的“被包含”判断就可以省略的。因此,DXFrameWork提供了一个比较快速的相交判断函数:INTERSECTION_TYPE C_BoundingBox:: LazyIsIntersecting (C_BoundingBox *p_BoundingBox)。和IsIntersectiong函数相比,LazyIsIntersecting函数省略了检查“被包含”的判断。因此使用此函数的时候,要保证first box的体积大于second box才能得到正确的判断。

除了判断包围盒之间的相交检测之外,C_BoundingBox类还提供了包围盒和线段之间的相交判断函数,如下:

bool C_BoundingBox::IsPenetrated(D3DXVECTOR3 p_Edge[2]) 
{
    int j;
    D3DXVECTOR3 intersectionPoint;
    D3DXVECTOR3 faces[6][4] = {
        point[3], point[2], point[1], point[0],       //Near
        point[5], point[6], point[7], point[4],       //Far
        point[3], point[7], point[6], point[2],       //Left
        point[1], point[5], point[4], point[0],       //Right    
        point[4], point[7], point[3], point[0],       //Bottom
        point[2], point[6], point[5], point[1]};      //Top

    for (j = 0; j < 6; j++) 
    {
        if (checkEdgeIntersectsFace(p_Edge, faces[j], 4,&plane[j], &intersectionPoint)) 
        {
              return true;

        }
    }
    return false;
}

从上述代码看出,要检查某线段是否和某包围盒相交,就只是需要检查某线段是否和包围盒的任意一个表面相交即可。如果是则表示这线段和包围盒相交,否就表示不相交。

kumakoko avatar
kumakoko
pure coder
comments powered by Disqus