obj.cpp---------obj格式解析

    技术2026-03-27  5

     

     

    //#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; } }

    最新回复(0)