如果是储存面索引的方法,那么就比原来的实现有些复杂,不过原理还是一样的。
假设所有的物体都是从3DS模型中读入的,我们首先要把物体的基本的数据结构搞清楚。
struct tFace //面
{
int vertIndex[3]; // 三个顶点
int coordIndex[3]; // 顶点的纹理坐标
};
struct t3DObject //物体
{
int numOfVerts; // 顶点总数
int numOfFaces; // 面的数目
int numTexVertex; // 纹理坐标的数目
int materialID; // 纹理ID, 通过引用纹理数组的索引引用
bool bHasTexture; //
char strName[255]; // 物体的名字
UINT *pIndices; // 物体的面索引,用于vertex arrays
CVector3 *pVerts; // 顶点的指针
CVector3 *pNormals; // 物体的法线
tVector2 *pTexVerts; // 纹理的UV 坐标
tFace *pFaces; // 面
}
struct tFaceList
{
vector<bool> pFaceList;
int totalFaceCount;
};
和前面一样,建立八叉树前先要获取世界的长宽高,然后把它作为根节点的信息传递给CreateNode函数。
由于我们读入的模型中可能包括很多物体,所以我们需要改变一下,把原来用于保存顶点的vector<bool>改成 vector<tFaceList>,其实也就多了一行代码。
vector<tFaceList> pList1(pWorld->numOfObjects); // TOP_LEFT_FRONT node
vector<tFaceList> pList2(pWorld->numOfObjects); // TOP_LEFT_BACK node
vector<tFaceList> pList3(pWorld->numOfObjects); // TOP_RIGHT_BACK node vector<tFaceList> pList4(pWorld->numOfObjects); // TOP_RIGHT_FRONT node
vector<tFaceList> pList5(pWorld->numOfObjects); // BOTTOM_LEFT_FRONT node
vector<tFaceList> pList6(pWorld->numOfObjects); // BOTTOM_LEFT_BACK node
vector<tFaceList> pList7(pWorld->numOfObjects); // BOTTOM_RIGHT_BACK node
vector<tFaceList> pList8(pWorld->numOfObjects); // BOTTOM_RIGHT_FRONT node
现在我们需要检测每个物体的每个三角形位于哪个位置,和前面的例子,这只是换汤不换药:
for(int i = 0; i < pWorld->numOfObjects; i++) //遍历世界的每一个物体
{
t3DObject *pObject = &(pWorld->pObject[i]); //当前物体
//调整容器的容量
pList1[i].pFaceList.resize(pObject->numOfFaces);
pList2[i].pFaceList.resize(pObject->numOfFaces);
pList3[i].pFaceList.resize(pObject->numOfFaces);
pList4[i].pFaceList.resize(pObject->numOfFaces);
pList5[i].pFaceList.resize(pObject->numOfFaces);
pList6[i].pFaceList.resize(pObject->numOfFaces);
pList7[i].pFaceList.resize(pObject->numOfFaces);
pList8[i].pFaceList.resize(pObject->numOfFaces);
for(int j = 0; j < pObject->numOfFaces; j++)//遍历该物体中所有的三角形
{
for(int whichVertex = 0; whichVertex < 3; whichVertex++)
{
// 当前顶点
CVector3 vPoint = pObject->pVerts[pObject->pFaces[j].vertIndex[whichVertex]];
// 看是否在 TOP LEFT FRONT node
if( (vPoint.x <= vCtr.x) && (vPoint.y >= vCtr.y) && (vPoint.z >= vCtr.z) )
pList1[i].pFaceList[j] = true;
//看是否在TOP LEFT BACK
if( (vPoint.x <= vCtr.x) && (vPoint.y >= vCtr.y) && (vPoint.z <= vCtr.z) )
pList2[i].pFaceList[j] = true;
//看是否在TOP RIGHT BACK
if( (vPoint.x >= vCtr.x) && (vPoint.y >= vCtr.y) && (vPoint.z <= vCtr.z) )
pList3[i].pFaceList[j] = true;
//看是否在TOP RIGHT FRONT
if( (vPoint.x >= vCtr.x) && (vPoint.y >= vCtr.y) && (vPoint.z >= vCtr.z) )
pList4[i].pFaceList[j] = true;
//看是否在BOTTOM LEFT FRONT
if( (vPoint.x <= vCtr.x) && (vPoint.y <= vCtr.y) && (vPoint.z >= vCtr.z) )
pList5[i].pFaceList[j] = true;
//看是否在BOTTOM LEFT BACK
if( (vPoint.x <= vCtr.x) && (vPoint.y <= vCtr.y) && (vPoint.z <= vCtr.z) )
pList6[i].pFaceList[j] = true;
//看是否在BOTTOM RIGHT BACK
if( (vPoint.x >= vCtr.x) && (vPoint.y <= vCtr.y) && (vPoint.z <= vCtr.z) )
pList7[i].pFaceList[j] = true;
//看是否在BOTTOM RIGHT FRONT
if( (vPoint.x >= vCtr.x) && (vPoint.y <= vCtr.y) && (vPoint.z >= vCtr.z) )
pList8[i].pFaceList[j] = true;
}
}
// 接下来我们要把每个小区域内部的三角形的数目数出来
pList1[i].totalFaceCount = 0; pList2[i].totalFaceCount = 0;
pList3[i].totalFaceCount = 0; pList4[i].totalFaceCount = 0;
pList5[i].totalFaceCount = 0; pList6[i].totalFaceCount = 0;
pList7[i].totalFaceCount = 0; pList8[i].totalFaceCount = 0;
}
知道了三角形的数目我们就可以调用CreateNewNode了:
for(i = 0; i < pWorld->numOfObjects; i++)
{
//便利所有物体的三角形
for(int j = 0; j < pWorld->pObject[i].numOfFaces; j++)
{
if(pList1[i].pFaceList[j]) { pList1[i].totalFaceCount++; triCount1++; }
if(pList2[i].pFaceList[j]) { pList2[i].totalFaceCount++; triCount2++; }
if(pList3[i].pFaceList[j]) { pList3[i].totalFaceCount++; triCount3++; }
if(pList4[i].pFaceList[j]) { pList4[i].totalFaceCount++; triCount4++; }
if(pList5[i].pFaceList[j]) { pList5[i].totalFaceCount++; triCount5++; }
if(pList6[i].pFaceList[j]) { pList6[i].totalFaceCount++; triCount6++; }
if(pList7[i].pFaceList[j]) { pList7[i].totalFaceCount++; triCount7++; }
if(pList8[i].pFaceList[j]) { pList8[i].totalFaceCount++; triCount8++; }
}
}
CreateNewNode(pWorld, pList1, triCount1, vCenter, width, TOP_LEFT_FRONT);
CreateNewNode(pWorld, pList2, triCount2, vCenter, width, TOP_LEFT_BACK);
CreateNewNode(pWorld, pList3, triCount3, vCenter, width, TOP_RIGHT_BACK);
CreateNewNode(pWorld, pList4, triCount4, vCenter, width, TOP_RIGHT_FRONT);
CreateNewNode(pWorld, pList5, triCount5, vCenter, width, BOTTOM_LEFT_FRONT);
CreateNewNode(pWorld, pList6, triCount6, vCenter, width, BOTTOM_LEFT_BACK);
CreateNewNode(pWorld, pList7, triCount7, vCenter, width, BOTTOM_RIGHT_BACK);
CreateNewNode(pWorld, pList8, triCount8, vCenter, width, BOTTOM_RIGHT_FRONT);
现在CreateNewNode有了较大的改变,参数还是一样,在调用CreateNode之前,我们需要做一些工作,这些工作是要确定面列表,到最后把它传给显示列表:
void COctree::CreateNewNode(t3DModel *pWorld, vector<tFaceList> pList, int triangleCount,
CVector3 vCenter, float width, int nodeID)
{
if(!triangleCount) return;
//临时变量,并初始化,保存叶结点中所有的物体
t3DModel *pTempWorld = new t3DModel;
memset(pTempWorld, 0, sizeof(t3DModel));
pTempWorld->numOfObjects = pWorld->numOfObjects;
// 遍历由参数传递进来的分区的所有物体
for(int i = 0; i < pWorld->numOfObjects; i++)
{
t3DObject *pObject = &(pWorld->pObject[i]);
// Create a new object, initialize it, then add it to our temp partition
t3DObject newObject;
memset(&newObject, 0, sizeof(t3DObject));
pTempWorld->pObject.push_back(newObject);
// Assign the new node's face count, material ID, texture boolean and
// vertices to the new object. Notice that it's not that pObject's face
// count, but the pList's. Also, we are just assigning the pointer to the
// vertices, not copying them.
pTempWorld->pObject[i].numOfFaces = pList[i].totalFaceCount;
pTempWorld->pObject[i].materialID = pObject->materialID;
pTempWorld->pObject[i].bHasTexture = pObject->bHasTexture;
pTempWorld->pObject[i].pVerts = pObject->pVerts;
pTempWorld->pObject[i].pFaces = new tFace [pTempWorld->pObject[i].numOfFaces];//建立空的面列表
int index = 0;
// 遍历所有的面,如果在节点内,就保存面到面列表
for(int j = 0; j < pObject->numOfFaces; j++)
{
if(pList[i].pFaceList[j])
{
pTempWorld->pObject[i].pFaces[index] = pObject->pFaces[j];
index++;
}
}
}
这里我们做的所有的工作都是为了这个pTempWorld,这是一个临时变量,保存了当前分区的所有物体,接下来要做的就是为新的节点分配内存
m_pOctreeNodes[nodeID] = new COctree;
CVector3 vNodeCenter = GetNewNodeCenter(vCenter, width, nodeID);
g_CurrentSubdivision++;
m_pOctreeNodes[nodeID]->CreateNode(pTempWorld, triangleCount, vNodeCenter, width / 2);
g_CurrentSubdivision--;
最后一步释放临时变量:
// 遍历临时变量的所有物体,释放前面创建的面列表
for(i = 0; i < pWorld->numOfObjects; i++)
{
if(pTempWorld->pObject[i].pFaces)
delete [] pTempWorld->pObject[i].pFaces;
}
delete pTempWorld;
到现在为止我们还没有把面索引的信息拷贝给叶结点,这个工作的思想是,对叶结点的分区的每一个物体遍历,把物体的每一个面(三角形)的三个顶点按顺序储存在一个数组里面,每一个物体都持有这样的一个数组
AssignTrianglesToNode(t3DModel *pWorld, int numberOfTriangles)
{
//我们把pWorld分区的信息传递给面列表,它保存了我们渲染需要的面索引,由于我们使用顶点数组的缘故我们不能使用tFace结构作为索引的值,我们需要建立这样一个数组,他连续的保存了面索引。这就是pIndices数组,他的型别是无符号整数,必须是无符号整数。
m_bSubDivided = false;
m_TriangleCount = numberOfTriangles;
m_pWorld = new t3DModel;
memset(m_pWorld, 0, sizeof(t3DModel));
// Assign the number of objects to our face index list
m_pWorld->numOfObjects = pWorld->numOfObjects;
// Go through all of the objects in the partition that was passed in
for(int i = 0; i < m_pWorld->numOfObjects; i++)
{
// Create a pointer to the current object
t3DObject *pObject = &(pWorld->pObject[i]);
// Create and init a new object to hold the face index information
t3DObject newObject;
memset(&newObject, 0, sizeof(t3DObject));
// If this object has face information, add it's index to our object index list
if(pObject->numOfFaces)
AddObjectIndexToList(i); //为每个物体建立索引
// Add our new object to our face index list
m_pWorld->pObject.push_back(newObject);
int numOfFaces = pObject->numOfFaces;
m_pWorld->pObject[i].numOfFaces = numOfFaces;
// 为面索引分配空间 pIndices将传递给顶点数组.
m_pWorld->pObject[i].pFaces = new tFace [numOfFaces];
m_pWorld->pObject[i].pIndices = new UINT [numOfFaces * 3];
// 初始化
memset(m_pWorld->pObject[i].pIndices, 0, sizeof(UINT) * numOfFaces * 3);
// 拷贝面信息
memcpy(m_pWorld->pObject[i].pFaces, pObject->pFaces, sizeof(tFace) * numOfFaces);
// 由于使用顶点数组,我们需要一个储存了所有的面信息的数组,这将作为参数传递给
// glDrawElements().
// Go through all the faces and assign them in a row to our pIndices array
for(int j = 0; j < numOfFaces * 3; j += 3)
{
m_pWorld->pObject[i].pIndices[j] = m_pWorld->pObject[i].pFaces[j / 3].vertIndex[0];
m_pWorld->pObject[i].pIndices[j + 1] = m_pWorld->pObject[i].pFaces[j / 3].vertIndex[1];
m_pWorld->pObject[i].pIndices[j + 2] = m_pWorld->pObject[i].pFaces[j / 3].vertIndex[2];
}
// 我们只需要使用pIndcies,所以释放不需要的变量
delete [] m_pWorld->pObject[i].pFaces;
m_pWorld->pObject[i].pFaces = NULL;
}
m_DisplayListID = g_EndNodeCount;
g_EndNodeCount++;
}
到此,每个物体的面索引就都储存在每个物体内部的pIndices数组内,这个数组最终将作为顶点数组渲染的对象。
为了使程序看上去完整,顺带把显示列表也提一下,我们将为每个叶结点分配一个显示列表的ID,每个叶结点中的物体保存了pIndices数组,在构造显示列表的时候用顶点数组来实现立即绘制:
CreateDisplayList(COctree *pNode, t3DModel *pRootWorld, int displayListOffset)
{
if(!pNode) return;
// 检查是不是叶结点,如果不是就深入
if(pNode->IsSubDivided())
{
// Recurse down to each one of the children until we reach the end nodes
CreateDisplayList(pNode->m_pOctreeNodes[TOP_LEFT_FRONT], pRootWorld, displayListOffset);
CreateDisplayList(pNode->m_pOctreeNodes[TOP_LEFT_BACK], pRootWorld, displayListOffset);
CreateDisplayList(pNode->m_pOctreeNodes[TOP_RIGHT_BACK], pRootWorld, displayListOffset);
CreateDisplayList(pNode->m_pOctreeNodes[TOP_RIGHT_FRONT], pRootWorld, displayListOffset);
CreateDisplayList(pNode->m_pOctreeNodes[BOTTOM_LEFT_FRONT], pRootWorld, displayListOffset);
CreateDisplayList(pNode->m_pOctreeNodes[BOTTOM_LEFT_BACK], pRootWorld, displayListOffset);
CreateDisplayList(pNode->m_pOctreeNodes[BOTTOM_RIGHT_BACK], pRootWorld, displayListOffset);
CreateDisplayList(pNode->m_pOctreeNodes[BOTTOM_RIGHT_FRONT],pRootWorld, displayListOffset);
}
else
{
if(!pNode->m_pWorld) return;
// 显示列表的ID偏移量
pNode->m_DisplayListID += displayListOffset;
//********************************建立显示列表开始*****************************************
// Start the display list and assign it to the end nodes ID
glNewList(pNode->m_DisplayListID,GL_COMPILE);
// 储存物体数量
int counter = 0;
// Store the object count and material count in some local variables for optimization
int objectCount = pNode->m_pObjectList.size(); //物体数量
int materialCount = pRootWorld->pMaterials.size(); //材质数量
// 遍历所有物体
while(counter < objectCount)
{
// Get the first object index into our root world
// 取得整个世界中第一个物体的索引
int i = pNode->m_pObjectList[counter];
// Store pointers to the current face list and the root object
// that holds all the data (verts, texture coordinates, normals, etc..)
// 保存指向现在的面列表的指针和整个世界的指针,他们包含了所有的数据
t3DObject *pObject = &pNode->m_pWorld->pObject[i];
t3DObject *pRootObject = &pRootWorld->pObject[i];
// Check to see if this object has a texture map, if so, bind the texture to it.
// 检查当前物体是否包括贴图,如果有,邦定贴图
if(pRootObject->bHasTexture)
{
// Turn on texture mapping and turn off color
glEnable(GL_TEXTURE_2D);
// Reset the color to normal again
glColor3ub(255, 255, 255);
glBindTexture(GL_TEXTURE_2D, g_Texture[pRootObject->materialID]);
}
else
{
glDisable(GL_TEXTURE_2D);
glColor3ub(255, 255, 255);
}
// Check to see if there is a valid material assigned to this object
// 检查材质
if(materialCount && pRootObject->materialID >= 0)
{
// Get and set the color that the object is, since it must not have a texture
BYTE *pColor = pRootWorld->pMaterials[pRootObject->materialID].color;
// Assign the current color to this model
glColor3ub(pColor[0], pColor[1], pColor[2]);
}
// Make sure we have texture coordinates to render
// 是否有纹理坐标,如果有,需要启用纹理坐标数组
if(pRootObject->pTexVerts)
{
// Turn on the texture coordinate state
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, 0, pRootObject->pTexVerts);
}
// Make sure we have vertices to render
// 确定是否有顶点等待渲染,如果有建立顶点数组
if(pRootObject->pVerts)
{
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, pRootObject->pVerts); // pRootObject->pVerts指向顶点数组
}
// Make sure we have normals to render
// 确定是否有法线需要渲染,如果有建立法线数组
if(pRootObject->pNormals)
{
// Turn on the normals state
glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer(GL_FLOAT, 0, pRootObject->pNormals);
}
// 注意,这里的pObject是 &pNode->m_pWorld->pObject[i];
glDrawElements(GL_TRIANGLES, pObject->numOfFaces * 3,
GL_UNSIGNED_INT, pObject->pIndices);
counter++;
}
// End the display list for this ID
glEndList();
//********************************建立显示列表结束****************************************
}
}
最后一步,把八叉树画出来,思想也很简单,首先深入到叶结点,然后执行显示列表,当然,在最开始还是要判断以下视景体所在的位置:
DrawOctree(COctree *pNode, t3DModel *pRootWorld)
{
if(!pNode) return;
if(!g_Frustum.CubeInFrustum(pNode->m_vCenter.x, pNode->m_vCenter.y,
pNode->m_vCenter.z, pNode->m_Width / 2) )
{
return;
}
if(pNode->IsSubDivided())
{
// Recurse to the bottom of these nodes and draw the end node's vertices
// Like creating the octree, we need to recurse through each of the 8 nodes.
DrawOctree(pNode->m_pOctreeNodes[TOP_LEFT_FRONT], pRootWorld);
DrawOctree(pNode->m_pOctreeNodes[TOP_LEFT_BACK], pRootWorld);
DrawOctree(pNode->m_pOctreeNodes[TOP_RIGHT_BACK], pRootWorld);
DrawOctree(pNode->m_pOctreeNodes[TOP_RIGHT_FRONT], pRootWorld);
DrawOctree(pNode->m_pOctreeNodes[BOTTOM_LEFT_FRONT], pRootWorld);
DrawOctree(pNode->m_pOctreeNodes[BOTTOM_LEFT_BACK], pRootWorld);
DrawOctree(pNode->m_pOctreeNodes[BOTTOM_RIGHT_BACK], pRootWorld);
DrawOctree(pNode->m_pOctreeNodes[BOTTOM_RIGHT_FRONT], pRootWorld);
}
else
{
// Increase the amount of nodes in our viewing frustum (camera's view)
g_TotalNodesDrawn++;
// Make sure we have valid data assigned to this node
if(!pNode->m_pWorld) return;
// Call the list with our end node's display list ID
glCallList(pNode->m_DisplayListID);
}
}