//#include "Obj.h" CLoadObj::CLoadObj(){m_bJustReadAFace = false;m_bObjectHasUV = false;}
// 下面的函数的功能是将obj文件的信息读入指定的模型中 bool CLoadObj::ImportObj(t3DModel *pModel, char *strFileName) { char strMessage[255] = {0}; // 用于显示错误信息 // 判断是否是一个合法的模型和文件类型 if(!pModel || !strFileName) return false; // 以只读方式打开文件,返回文件指针 m_FilePointer = fopen(strFileName, "r"); // 判断文件指针是否正确 if(!m_FilePointer) { // 如果文件指针不正确,则显示错误信息 sprintf(strMessage, "Unable to find or open the file: %s", strFileName); MessageBox(NULL, strMessage, "Error", MB_OK); return false; } // 读入文件信息 ReadObjFile(pModel); // 计算顶点的法向量,用于光照 ComputeNormals(pModel); // 关闭打开的文件 fclose(m_FilePointer); return true; } // 读入obj文件中的对象到模型中 void CLoadObj::ReadObjFile(t3DModel *pModel) { char strLine[255] = {0}; char ch = 0; while(!feof(m_FilePointer)) { float x = 0.0f, y = 0.0f, z = 0.0f; // 获得obj文件中的当前行的第一个字符 ch = fgetc(m_FilePointer); switch(ch) { case 'v': // 读入的是'v' (后续的数据可能是顶点/法向量/纹理坐标) // 如果在前面读入的是面的行,那么现在进入的是另一个对象,因此在读入下一个对象之前, // 需要将最后对象的数据写入到模型结构体中 if(m_bJustReadAFace) { // 将最后对象的信息保存到模型结构中 FillInObjectInfo(pModel); } // 读入点的信息,要区分顶点 ("v")、法向量 ("vn")、纹理坐标 ("vt") ReadVertexInfo(); break; case 'f': // 读入的是'f' // 读入面的信息 ReadFaceInfo(); break; default: // 略过该行的内容 fgets(strLine, 100, m_FilePointer); break; } } // 保存最后读入的对象 FillInObjectInfo(pModel); } // 下面的函数读入顶点信息('v'是指顶点,'vt'指UV坐标) void CLoadObj::ReadVertexInfo() { CVector3 vNewVertex = {0}; CVector2 vNewTexCoord = {0}; char strLine[255] = {0}; char ch = 0; // 读入第一个字符,判断读入的是否顶点/法向量/UV坐标 ch = fgetc(m_FilePointer); if(ch == ' ') // 如果是空格,则必是顶点("v") { // 读入顶点坐标,格式是"v x y z" fscanf(m_FilePointer, "%f %f %f", &vNewVertex.x, &vNewVertex.y, &vNewVertex.z); // 读入该行中余下的内容,则文件指针到下一行 fgets(strLine, 100, m_FilePointer); // 添加一个新的顶点到顶点链表中 m_pVertices.push_back(vNewVertex); } else if(ch == 't') // 如果是't',则必定是纹理坐标("vt") { // 读入纹理坐标,格式是"vt u v" fscanf(m_FilePointer, "%f %f", &vNewTexCoord.x, &vNewTexCoord.y); // 读入该行余下的内容,则文件指针指向下一行 fgets(strLine, 100, m_FilePointer); // 添加一个新的纹理坐标到链表中 m_pTextureCoords.push_back(vNewTexCoord); // 设置对象具有纹理坐标为true m_bObjectHasUV = true; } else // 否则可能是法向量("vn") { // 由于在最后计算各点的法向量,在这里略过 fgets(strLine, 100, m_FilePointer); } } // 下面的函数读入面信息 void CLoadObj::ReadFaceInfo() { tFace newFace = {0}; char strLine[255] = {0}; int vNewNorm[4] = {0};// 读入对象的顶点和纹理坐标索引,格式是"顶点1/纹理坐标1 顶点2/纹理坐标2 顶点3/纹理坐标3" fscanf(m_FilePointer, "%d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d", &newFace.vertIndex[0], &newFace.coordIndex[0],&vNewNorm[0], &newFace.vertIndex[1], &newFace.coordIndex[1],&vNewNorm[1], &newFace.vertIndex[2], &newFace.coordIndex[2],&vNewNorm[2], &newFace.vertIndex[2], &newFace.coordIndex[2],&vNewNorm[3]);
判断对象是否具有纹理坐标 //if(m_bObjectHasUV ) //{ // // 读入对象的顶点和纹理坐标索引,格式是"顶点1/纹理坐标1 顶点2/纹理坐标2 顶点3/纹理坐标3" // fscanf(m_FilePointer, "%d/%d %d/%d %d/%d", // &newFace.vertIndex[0], &newFace.coordIndex[0],/* &vNewNorm[0],*/// &newFace.vertIndex[1], &newFace.coordIndex[1],/* &vNewNorm[1],*/// &newFace.vertIndex[2], &newFace.coordIndex[2]); /* &vNewNorm[2]);*/ //} //else // 对象无纹理坐标 //{ // // 读入对象的顶点索引,格式是"顶点1 顶点2 顶点3" // fscanf(m_FilePointer, "%d %d %d", &newFace.vertIndex[0], // &newFace.vertIndex[1], // &newFace.vertIndex[2]); //} // 读入该行余下的内容,则文件指针指向下一行 fgets(strLine, 100, m_FilePointer); // 添加一个新面到面链表中 m_pFaces.push_back(newFace); // 设置刚才读入的是面 m_bJustReadAFace = true; } void CLoadObj::GetKeyWord(char* keyword){//char ch = 0; //ch = fgetc(m_FilePointer);
跳过每行开头的所有空格//if(ch == ' ')//{// while( fgetc(m_FilePointer) == ' ')// ;//}
//if ( ch != NULL )//{// keyword[0] = ch;// ch = fgetc(m_FilePointer);// while( ch != ' ' || ch != '/n' || ch != '/a')// ;//}}// 下面的函数将读入对象的信息写入模型结构体中 void CLoadObj::FillInObjectInfo(t3DModel *pModel) { t3DObject newObject = {0}; int textureOffset = 0, vertexOffset = 0; int i = 0; // 模型中对象计数器递增 pModel->numOfObjects++; // 添加一个新对象到模型的对象链表中 pModel->pObject.push_back(newObject); // 获得当前对象的指针 t3DObject *pObject = &(pModel->pObject[pModel->numOfObjects - 1]); // 获得面的数量、顶点的数量和纹理坐标的数量 pObject->numOfFaces = m_pFaces.size(); pObject->numOfVerts = m_pVertices.size(); pObject->numTexVertex = m_pTextureCoords.size(); // 如果读入了面 if(pObject->numOfFaces) { // 分配保存面的存储空间 pObject->pFaces = new tFace [pObject->numOfFaces]; } // 如果读入了点 if(pObject->numOfVerts) { // 分配保存点的存储空间 pObject->pVerts = new CVector3 [pObject->numOfVerts]; } // 如果读入了纹理坐标 if(pObject->numTexVertex) { pObject->pTexVerts = new CVector2 [pObject->numTexVertex]; pObject->bHasTexture = true; } // 遍历所有的面 for(i = 0; i < pObject->numOfFaces; i++) { // 拷贝临时的面链表到模型链表中 pObject->pFaces[i] = m_pFaces[i]; // 判断是否是对象的第一个面 if(i == 0) { // 如果第一索引不是1,则必须略过第一个对象 if(pObject->pFaces[0].vertIndex[0] != 1) { vertexOffset = pObject->pFaces[0].vertIndex[0] - 1; // 对于纹理坐标,也进行同样的操作 if(pObject->numTexVertex > 0) { // 当前的索引剪去1 textureOffset = pObject->pFaces[0].coordIndex[0] - 1; } } } for(int j = 0; j < 3; j++) { // 对于每一个索引,必须将其减去1 pObject->pFaces[i].vertIndex[j] -= 1 + vertexOffset; pObject->pFaces[i].coordIndex[j] -= 1 + textureOffset; } } // 遍历对象中的所有点 for(i = 0; i < pObject->numOfVerts; i++) { // 将当前的顶点从临时链表中拷贝到模型链表中 pObject->pVerts[i] = m_pVertices[i]; } // 遍历对象中所有的纹理坐标 for(i = 0; i < pObject->numTexVertex; i++) { // 将当前的纹理坐标从临时链表中拷贝到模型链表中 pObject->pTexVerts[i] = m_pTextureCoords[i]; } // 由于OBJ文件中没有材质,因此将materialID设置为-1,必须手动设置材质 pObject->materialID = -1; pObject->bHasTexture = false;
AddMaterial(pModel, "defautl", NULL);pObject->materialID = 0;// 清除所有的临时链表 m_pVertices.clear(); m_pFaces.clear(); m_pTextureCoords.clear(); // 设置所有的布尔值为false m_bObjectHasUV = false; m_bJustReadAFace = false; } // 下面的函数为对象序列中的对象赋予具体的材质 void CLoadObj::SetObjectMaterial(t3DModel *pModel, int whichObject, int materialID) { // 确保模型合法 if(!pModel) return; // 确保对象合法 if(whichObject >= pModel->numOfObjects) return; // 给对象赋予材质ID pModel->pObject[whichObject].materialID = materialID; } // 下面的函数给模型手动添加材质 void CLoadObj::AddMaterial(t3DModel *pModel, char *strName, char *strFile, int r, int g, int b) { tMaterialInfo newMaterial = {0}; // 设置材质的RGB值[0 - RED 1 - GREEN 2 - BLUE] newMaterial.color[0] = r; newMaterial.color[1] = g; newMaterial.color[2] = b; // 如果具有文件名称,则将其拷贝到材质结构体中 if(strFile) { strcpy(newMaterial.strFile, strFile); } // 如果具有材质名称,则将其拷贝到材质结构体中 if(strName) { strcpy(newMaterial.strName, strName); } // 将材质加入到模型链表中 pModel->pMaterials.push_back(newMaterial); // 材质数量递增 pModel->numOfMaterials++; } // 下面的这些函数主要用来计算顶点的法向量,顶点的法向量主要用来计算光照 // 下面的宏定义计算一个矢量的长度 #define Mag(Normal) (sqrt(Normal.x*Normal.x + Normal.y*Normal.y + Normal.z*Normal.z)) // 下面的函数求两点决定的矢量 CVector3 Vector(CVector3 vPoint1, CVector3 vPoint2) { CVector3 vVector; vVector.x = vPoint1.x - vPoint2.x; vVector.y = vPoint1.y - vPoint2.y; vVector.z = vPoint1.z - vPoint2.z; return vVector; } // 下面的函数两个矢量相加 CVector3 AddVector(CVector3 vVector1, CVector3 vVector2) { CVector3 vResult; vResult.x = vVector2.x + vVector1.x; vResult.y = vVector2.y + vVector1.y; vResult.z = vVector2.z + vVector1.z; return vResult; } // 下面的函数处理矢量的缩放 CVector3 DivideVectorByScaler(CVector3 vVector1, float Scaler) { CVector3 vResult; vResult.x = vVector1.x / Scaler; vResult.y = vVector1.y / Scaler; vResult.z = vVector1.z / Scaler; return vResult; } // 下面的函数返回两个矢量的叉积 CVector3 Cross(CVector3 vVector1, CVector3 vVector2) { CVector3 vCross; vCross.x = ((vVector1.y * vVector2.z) - (vVector1.z * vVector2.y)); vCross.y = ((vVector1.z * vVector2.x) - (vVector1.x * vVector2.z)); vCross.z = ((vVector1.x * vVector2.y) - (vVector1.y * vVector2.x)); return vCross; } // 下面的函数规范化矢量 CVector3 Normalize(CVector3 vNormal) { double Magnitude; Magnitude = Mag(vNormal); // 获得矢量的长度 vNormal.x /= (float)Magnitude; vNormal.y /= (float)Magnitude; vNormal.z /= (float)Magnitude; return vNormal; } // 下面的函数用于计算对象的法向量 void CLoadObj::ComputeNormals(t3DModel *pModel) { CVector3 vVector1, vVector2, vNormal, vPoly[3]; // 如果模型中没有对象,则返回 if(pModel->numOfObjects <= 0) return; // 遍历模型中所有的对象 for(int index = 0; index < pModel->numOfObjects; index++) { // 获得当前的对象 t3DObject *pObject = &(pModel->pObject[index]); // 分配需要的存储空间 CVector3 *pNormals = new CVector3 [pObject->numOfFaces]; CVector3 *pTempNormals = new CVector3 [pObject->numOfFaces]; pObject->pNormals = new CVector3 [pObject->numOfVerts]; // 遍历对象的所有面 for(int i=0; i < pObject->numOfFaces; i++) { vPoly[0] = pObject->pVerts[pObject->pFaces[i].vertIndex[0]]; vPoly[1] = pObject->pVerts[pObject->pFaces[i].vertIndex[1]]; vPoly[2] = pObject->pVerts[pObject->pFaces[i].vertIndex[2]]; // 计算面的法向量 vVector1 = Vector(vPoly[0], vPoly[2]); // 获得多边形的矢量 vVector2 = Vector(vPoly[2], vPoly[1]); // 获得多边形的第二个矢量 vNormal = Cross(vVector1, vVector2); // 获得两个矢量的叉积 pTempNormals[i] = vNormal; // 保存非规范化法向量 vNormal = Normalize(vNormal); // 规范化获得的叉积 pNormals[i] = vNormal; // 将法向量添加到法向量列表中 } // 下面求顶点法向量 CVector3 vSum = {0.0, 0.0, 0.0}; CVector3 vZero = vSum; int shared=0; // 遍历所有的顶点 for (int i = 0; i < pObject->numOfVerts; i++) { for (int j = 0; j < pObject->numOfFaces; j++) // 遍历所有的三角形面 { // 判断该点是否与其它的面共享 if (pObject->pFaces[j].vertIndex[0] == i || pObject->pFaces[j].vertIndex[1] == i || pObject->pFaces[j].vertIndex[2] == i) { vSum = AddVector(vSum, pTempNormals[j]); shared++; } } pObject->pNormals[i] = DivideVectorByScaler(vSum, float(-shared)); // 规范化最后的顶点法向 pObject->pNormals[i] = Normalize(pObject->pNormals[i]); vSum = vZero; shared = 0; } // 释放存储空间,开始下一个对象 delete [] pTempNormals; delete [] pNormals; } }
