opengl匯入3DS檔案(帶紋理)之填坑
阿新 • • 發佈:2019-01-04
#ifndef _CLoad3DS_h_ #define _CLoad3DS_h_ #include #include #include #include #include #include #include #include #include #include #include #include #include //初始化OpenGL環境 #include #include //#include #include #pragma comment(lib,"opengl32.lib") #pragma comment(lib,"glu32.lib") //#pragma comment(lib,"glaux.lib") #define PICPATH "\\Data\\pic\\" //紋理資源的地址 // 基本塊(Primary Chunk),位於檔案的開始 #define PRIMARY 0x4D4D // 主塊(Main Chunks) #define OBJECTINFO 0x3D3D // 網格物件的版本號 #define VERSION 0x0002 // .3ds檔案的版本 #define EDITKEYFRAME 0xB000 // 所有關鍵幀資訊的頭部 // 物件的次級定義(包括物件的材質和物件) #define MATERIAL 0xAFFF // 儲存紋理資訊 #define OBJECT 0x4000 // 儲存物件的面、頂點等資訊 // 材質的次級定義 #define MATNAME 0xA000 // 儲存材質名稱 #define MATDIFFUSE 0xA020 // 物件/材質的顏色 #define MATMAP 0xA200 // 新材質的頭部 #define MATMAPFILE 0xA300 // 儲存紋理的檔名 #define OBJECT_MESH 0x4100 // 新的網格物件 // OBJECT_MESH的次級定義 #define OBJECT_VERTICES 0x4110 // 物件頂點 #define OBJECT_FACES 0x4120 // 物件的面 #define OBJECT_MATERIAL 0x4130 // 物件的材質 #define OBJECT_UV 0x4140 // 物件的UV紋理座標 // 下面的巨集定義計算一個向量的長度 #define Mag(Normal) (sqrt(Normal.x*Normal.x + Normal.y*Normal.y + Normal.z*Normal.z)) #define MAX_TEXTURES 100 // 最大的紋理數目 using namespace std; class NBVector3 { public: NBVector3() {} NBVector3(float X, float Y, float Z) { x = X; y = Y; z = Z; } inline NBVector3 operator+(NBVector3 vVector) { return NBVector3(vVector.x + x, vVector.y + y, vVector.z + z); } inline NBVector3 operator-(NBVector3 vVector) { return NBVector3(x - vVector.x, y - vVector.y, z - vVector.z); } inline NBVector3 operator-() { return NBVector3(-x, -y, -z); } inline NBVector3 operator*(float num) { return NBVector3(x * num, y * num, z * num); } inline NBVector3 operator/(float num) { return NBVector3(x / num, y / num, z / num); } inline NBVector3 operator^(const NBVector3 &rhs) const { return NBVector3(y * rhs.z - rhs.y * z, rhs.x * z - x * rhs.z, x * rhs.y - rhs.x * y); } union { struct { float x; float y; float z; }; float v[3]; }; }; // 定義2D點類,用於儲存模型的UV紋理座標 class CVector2 { public: float x, y; }; // 面的結構定義 struct tFace { int vertIndex[3]; // 頂點索引 int coordIndex[3]; // 紋理座標索引 }; // 材質資訊結構體 struct tMaterialInfo { char strName[255]; // 紋理名稱 char strFile[255]; // 如果存在紋理對映,則表示紋理檔名稱 BYTE color[3]; // 物件的RGB顏色 int texureId; // 紋理ID float uTile; // u 重複 float vTile; // v 重複 float uOffset; // u 紋理偏移 float vOffset; // v 紋理偏移 } ; // 物件資訊結構體 struct t3DObject { int numOfVerts; // 模型中頂點的數目 int numOfFaces; // 模型中面的數目 int numTexVertex; // 模型中紋理座標的數目 int materialID; // 紋理ID bool bHasTexture; // 是否具有紋理對映 char strName[255]; // 物件的名稱 NBVector3 *pVerts; // 物件的頂點 NBVector3 *pNormals; // 物件的法向量 CVector2 *pTexVerts; // 紋理UV座標 tFace *pFaces; // 物件的面資訊 }; // 模型資訊結構體 struct t3DModel { UINT texture[MAX_TEXTURES]; int numOfObjects; // 模型中物件的數目 int numOfMaterials; // 模型中材質的數目 vector pMaterials; // 材質連結串列資訊 vector pObject; // 模型中物件連結串列資訊 }; struct tIndices { unsigned short a, b, c, bVisible; }; // 儲存塊資訊的結構 struct tChunk { unsigned short int ID; // 塊的ID unsigned int length; // 塊的長度 unsigned int bytesRead; // 需要讀的塊資料的位元組數 }; typedef struct tagBoundingBoxStruct { NBVector3 BoxPosMaxVertex; NBVector3 BoxNegMaxVertex; } BoundingBoxVertex2; // 下面的函式求兩點決定的向量 NBVector3 Vector(NBVector3 vPoint1, NBVector3 vPoint2); // 下面的函式兩個向量相加 NBVector3 AddVector(NBVector3 vVector1, NBVector3 vVector2); // 下面的函式處理向量的縮放 NBVector3 DivideVectorByScaler(NBVector3 vVector1, float Scaler); // 下面的函式返回兩個向量的叉積 NBVector3 Cross(NBVector3 vVector1, NBVector3 vVector2); // 下面的函式規範化向量 NBVector3 Normalize(NBVector3 vNormal); void DrawModel(t3DModel& Model,bool touming=false); ////////////////////////////////////////////////////////////////////////// #define FRAND (((float)rand()-(float)rand())/RAND_MAX) #define Clamp(x, min, max) x = (xRelease(); // Decrements IPicture Reference Count return FALSE; // Return False (Failure) } glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glMaxTexDim); // Get Maximum Texture Size Supported pPicture->get_Width(&lWidth); // Get IPicture Width (Convert To Pixels) lWidthPixels = MulDiv(lWidth, GetDeviceCaps(hdcTemp, LOGPIXELSX), 2540); pPicture->get_Height(&lHeight); // Get IPicture Height (Convert To Pixels) lHeightPixels = MulDiv(lHeight, GetDeviceCaps(hdcTemp, LOGPIXELSY), 2540); // Resize Image To Closest Power Of Two if (lWidthPixels <= glMaxTexDim) // Is Image Width Less Than Or Equal To Cards Limit lWidthPixels = 1 << (int)floor((log((double)lWidthPixels)/log(2.0f)) + 0.5f); else // Otherwise Set Width To "Max Power Of Two" That The Card Can Handle lWidthPixels = glMaxTexDim; if (lHeightPixels <= glMaxTexDim) // Is Image Height Greater Than Cards Limit lHeightPixels = 1 << (int)floor((log((double)lHeightPixels)/log(2.0f)) + 0.5f); else // Otherwise Set Height To "Max Power Of Two" That The Card Can Handle lHeightPixels = glMaxTexDim; // Create A Temporary Bitmap BITMAPINFO bi = {0}; // The Type Of Bitmap We Request DWORD *pBits = 0; // Pointer To The Bitmap Bits bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); // Set Structure Size bi.bmiHeader.biBitCount = 32; // 32 Bit bi.bmiHeader.biWidth = lWidthPixels; // Power Of Two Width bi.bmiHeader.biHeight = lHeightPixels; // Make Image Top Up (Positive Y-Axis) bi.bmiHeader.biCompression = BI_RGB; // RGB Encoding bi.bmiHeader.biPlanes = 1; // 1 Bitplane // Creating A Bitmap This Way Allows Us To Specify Color Depth And Gives Us Imediate Access To The Bits hbmpTemp = CreateDIBSection(hdcTemp, &bi, DIB_RGB_COLORS, (void**)&pBits, 0, 0); if(!hbmpTemp) // Did Creation Fail? { DeleteDC(hdcTemp); // Delete The Device Context pPicture->Release(); // Decrements IPicture Reference Count return FALSE; // Return False (Failure) } SelectObject(hdcTemp, hbmpTemp); // Select Handle To Our Temp DC And Our Temp Bitmap Object // Render The IPicture On To The Bitmap pPicture->Render(hdcTemp, 0, 0, lWidthPixels, lHeightPixels, 0, lHeight, lWidth, -lHeight, 0); // Convert From BGR To RGB Format And Add An Alpha Value Of 255 for(long i = 0; i < lWidthPixels * lHeightPixels; i++) // Loop Through All Of The Pixels { BYTE* pPixel = (BYTE*)(&pBits[i]); // Grab The Current Pixel BYTE temp = pPixel[0]; // Store 1st Color In Temp Variable (Blue) pPixel[0] = pPixel[2]; // Move Red Value To Correct Position (1st) pPixel[2] = temp; // Move Temp Value To Correct Blue Position (3rd) // This Will Make Any Black Pixels, Completely Transparent (You Can Hardcode The Value If You Wish) if ((pPixel[0]==0) && (pPixel[1]==0) && (pPixel[2]==0)) // Is Pixel Completely Black pPixel[3] = 0; // Set The Alpha Value To 0 else // Otherwise pPixel[3] = 255; // Set The Alpha Value To 255 } glGenTextures(1, &texid); // Create The Texture // Typical Texture Generation Using Data From The Bitmap glBindTexture(GL_TEXTURE_2D, texid); // Bind To The Texture ID glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // (Modify This For The Type Of Filtering You Want) glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // (Modify This For The Type Of Filtering You Want) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, lWidthPixels, lHeightPixels, 0, GL_RGBA, GL_UNSIGNED_BYTE, pBits); // (Modify This If You Want Mipmaps) DeleteObject(hbmpTemp); // Delete The Object DeleteDC(hdcTemp); // Delete The Device Context pPicture->Release(); // Decrements IPicture Reference Count printf( "load %s!" , szPath ); return TRUE; // Return True (All Good) } // 建構函式的功能是初始化tChunk資料 CLoad3DS::CLoad3DS() { m_CurrentChunk = new tChunk; // 初始化併為當前的塊分配空間 m_TempChunk = new tChunk; // 初始化一個臨時塊並分配空間 } // 開啟一個3ds檔案,讀出其中的內容,並釋放記憶體 bool CLoad3DS::Import3DS(t3DModel *pModel, char *strFileName) { char strMessage[255] = {0}; // 開啟一個3ds檔案 m_FilePointer = fopen(strFileName, "rb"); // 確保所獲得的檔案指標合法 if(!m_FilePointer) { sprintf(strMessage, "Unable to find the file: %s!", strFileName); MessageBox(NULL, strMessage, "Error", MB_OK); return false; } // 當檔案開啟之後,首先應該將檔案最開始的資料塊讀出以判斷是否是一個3ds檔案 // 如果是3ds檔案的話,第一個塊ID應該是PRIMARY // 將檔案的第一塊讀出並判斷是否是3ds檔案 ReadChunk(m_CurrentChunk); // 確保是3ds檔案 if (m_CurrentChunk->ID != PRIMARY) { sprintf(strMessage, "Unable to load PRIMARY chuck from file: %s!", strFileName); MessageBox(NULL, strMessage, "Error", MB_OK); return false; } // 現在開始讀入資料,ProcessNextChunk()是一個遞迴函式 // 通過呼叫下面的遞迴函式,將物件讀出 ProcessNextChunk(pModel, m_CurrentChunk); // 在讀完整個3ds檔案之後,計算頂點的法線 ComputeNormals(pModel); // 釋放記憶體空間 CleanUp(); return true; } // 下面的函式釋放所有的記憶體空間,並關閉檔案 void CLoad3DS::CleanUp() { fclose(m_FilePointer); // 關閉當前的檔案指標 delete m_CurrentChunk; // 釋放當前塊 delete m_TempChunk; // 釋放臨時塊 } // 下面的函式讀出3ds檔案的主要部分 void CLoad3DS::ProcessNextChunk(t3DModel *pModel, tChunk *pPreviousChunk) { t3DObject newObject = {0}; // 用來新增到物件連結串列 tMaterialInfo newTexture = {0}; // 用來新增到材質連結串列 unsigned int version = 0; // 儲存檔案版本 int buffer[50000] = {0}; // 用來跳過不需要的資料 m_CurrentChunk = new tChunk; // 為新的塊分配空間 // 下面每讀一個新塊,都要判斷一下塊的ID,如果該塊是需要的讀入的,則繼續進行 // 如果是不需要讀入的塊,則略過 // 繼續讀入子塊,直到達到預定的長度 while (pPreviousChunk->bytesRead < pPreviousChunk->length) { // 讀入下一個塊 ReadChunk(m_CurrentChunk); // 判斷塊的ID號 switch (m_CurrentChunk->ID) { case VERSION: // 檔案版本號 // 在該塊中有一個無符號短整型數儲存了檔案的版本 // 讀入檔案的版本號,並將位元組數新增到bytesRead變數中 m_CurrentChunk->bytesRead += fread(&version, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); // 如果檔案版本號大於3,給出一個警告資訊 if (version > 0x03) MessageBox(NULL, "This 3DS file is over version 3 so it may load incorrectly", "Warning", MB_OK); break; case OBJECTINFO: // 網格版本資訊 // 讀入下一個塊 ReadChunk(m_TempChunk); // 獲得網格的版本號 m_TempChunk->bytesRead += fread(&version, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer); // 增加讀入的位元組數 m_CurrentChunk->bytesRead += m_TempChunk->bytesRead; // 進入下一個塊 ProcessNextChunk(pModel, m_CurrentChunk); break; case MATERIAL: // 材質資訊 // 材質的數目遞增 pModel->numOfMaterials++; // 在紋理連結串列中新增一個空白紋理結構 pModel->pMaterials.push_back(newTexture); // 進入材質裝入函式 ProcessNextMaterialChunk(pModel, m_CurrentChunk); break; case OBJECT: // 物件的名稱 // 該塊是物件資訊塊的頭部,儲存了物件了名稱 // 物件數遞增 pModel->numOfObjects++; // 新增一個新的tObject節點到物件連結串列中 pModel->pObject.push_back(newObject); // 初始化物件和它的所有資料成員 memset(&(pModel->pObject[pModel->numOfObjects - 1]), 0, sizeof(t3DObject)); // 獲得並儲存物件的名稱,然後增加讀入的位元組數 m_CurrentChunk->bytesRead += GetString(pModel->pObject[pModel->numOfObjects - 1].strName); // 進入其餘的物件資訊的讀入 ProcessNextObjectChunk(pModel, &(pModel->pObject[pModel->numOfObjects - 1]), m_CurrentChunk); break; case EDITKEYFRAME: // 跳過關鍵幀塊的讀入,增加需要讀入的位元組數 m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); break; default: // 跳過所有忽略的塊的內容的讀入,增加需要讀入的位元組數 m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); break; } // 增加從最後塊讀入的位元組數 pPreviousChunk->bytesRead += m_CurrentChunk->bytesRead; } // 釋放當前塊的記憶體空間 delete m_CurrentChunk; m_CurrentChunk = pPreviousChunk; } // 下面的函式處理所有的檔案中物件的資訊 void CLoad3DS::ProcessNextObjectChunk(t3DModel *pModel, t3DObject *pObject, tChunk *pPreviousChunk) { int buffer[50000] = {0}; // 用於讀入不需要的資料 // 對新的塊分配儲存空間 m_CurrentChunk = new tChunk; // 繼續讀入塊的內容直至本子塊結束 while (pPreviousChunk->bytesRead < pPreviousChunk->length) { // 讀入下一個塊 ReadChunk(m_CurrentChunk); // 區別讀入是哪種塊 switch (m_CurrentChunk->ID) { case OBJECT_MESH: // 正讀入的是一個新塊 // 使用遞迴函式呼叫,處理該新塊 ProcessNextObjectChunk(pModel, pObject, m_CurrentChunk); break; case OBJECT_VERTICES: // 讀入是物件頂點 ReadVertices(pObject, m_CurrentChunk); break; case OBJECT_FACES: // 讀入的是物件的面 ReadVertexIndices(pObject, m_CurrentChunk); break; case OBJECT_MATERIAL: // 讀入的是物件的材質名稱 // 該塊儲存了物件材質的名稱,可能是一個顏色,也可能是一個紋理對映。同時在該塊中也儲存了 // 紋理物件所賦予的面 // 下面讀入物件的材質名稱 ReadObjectMaterial(pModel, pObject, m_CurrentChunk); break; case OBJECT_UV: // 讀入物件的UV紋理座標 // 讀入物件的UV紋理座標 ReadUVCoordinates(pObject, m_CurrentChunk); break; default: // 略過不需要讀入的塊 m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); break; } // 新增從最後塊中讀入的位元組數到前面的讀入的位元組中 pPreviousChunk->bytesRead += m_CurrentChunk->bytesRead; } // 釋放當前塊的記憶體空間,並把當前塊設定為前面塊 delete m_CurrentChunk; m_CurrentChunk = pPreviousChunk; } // 下面的函式處理所有的材質資訊 void CLoad3DS::ProcessNextMaterialChunk(t3DModel *pModel, tChunk *pPreviousChunk) { int buffer[50000] = {0}; // 用於讀入不需要的資料 // 給當前塊分配儲存空間 m_CurrentChunk = new tChunk; // 繼續讀入這些塊,知道該子塊結束 while (pPreviousChunk->bytesRead < pPreviousChunk->length) { // 讀入下一塊 ReadChunk(m_CurrentChunk); // 判斷讀入的是什麼塊 switch (m_CurrentChunk->ID) { case MATNAME: // 材質的名稱 // 讀入材質的名稱 m_CurrentChunk->bytesRead += fread(pModel->pMaterials[pModel->numOfMaterials - 1].strName, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); break; case MATDIFFUSE: // 物件的R G B顏色 ReadColorChunk(&(pModel->pMaterials[pModel->numOfMaterials - 1]), m_CurrentChunk); break; case MATMAP: // 紋理資訊的頭部 // 進入下一個材質塊資訊 ProcessNextMaterialChunk(pModel, m_CurrentChunk); break; case MATMAPFILE: // 材質檔案的名稱 // 讀入材質的檔名稱 m_CurrentChunk->bytesRead += fread(pModel->pMaterials[pModel->numOfMaterials - 1].strFile, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); break; default: // 掠過不需要讀入的塊 m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); break; } // 新增從最後塊中讀入的位元組數 pPreviousChunk->bytesRead += m_CurrentChunk->bytesRead; } // 刪除當前塊,並將當前塊設定為前面的塊 delete m_CurrentChunk; m_CurrentChunk = pPreviousChunk; } // 下面函式讀入塊的ID號和它的位元組長度 void CLoad3DS::ReadChunk(tChunk *pChunk) { // 讀入塊的ID號,佔用了2個位元組。塊的ID號象OBJECT或MATERIAL一樣,說明了在塊中所包含的內容 pChunk->bytesRead = fread(&pChunk->ID, 1, 2, m_FilePointer); // 然後讀入塊佔用的長度,包含了四個位元組 pChunk->bytesRead += fread(&pChunk->length, 1, 4, m_FilePointer); } // 下面的函式讀入一個字串 int CLoad3DS::GetString(char *pBuffer) { int index = 0; // 讀入一個位元組的資料 fread(pBuffer, 1, 1, m_FilePointer); // 直到結束 while (*(pBuffer + index++) != 0) { // 讀入一個字元直到NULL fread(pBuffer + index, 1, 1, m_FilePointer); } // 返回字串的長度 return strlen(pBuffer) + 1; } // 下面的函式讀入RGB顏色 void CLoad3DS::ReadColorChunk(tMaterialInfo *pMaterial, tChunk *pChunk) { // 讀入顏色塊資訊 ReadChunk(m_TempChunk); // 讀入RGB顏色 m_TempChunk->bytesRead += fread(pMaterial->color, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer); // 增加讀入的位元組數 pChunk->bytesRead += m_TempChunk->bytesRead; } // 下面的函式讀入頂點索引 void CLoad3DS::ReadVertexIndices(t3DObject *pObject, tChunk *pPreviousChunk) { unsigned short index = 0; // 用於讀入當前面的索引 // 讀入該物件中面的數目 pPreviousChunk->bytesRead += fread(&pObject->numOfFaces, 1, 2, m_FilePointer); // 分配所有面的儲存空間,並初始化結構 pObject->pFaces = new tFace [pObject->numOfFaces]; memset(pObject->pFaces, 0, sizeof(tFace) * pObject->numOfFaces); // 遍歷物件中所有的面 for(int i = 0; i < pObject->numOfFaces; i++) { for(int j = 0; j < 4; j++) { // 讀入當前面的第一個點 pPreviousChunk->bytesRead += fread(&index, 1, sizeof(index), m_FilePointer); if(j < 3) { // 將索引儲存在面的結構中 pObject->pFaces[i].vertIndex[j] = index; } } } } // 下面的函式讀入物件的UV座標 void CLoad3DS::ReadUVCoordinates(t3DObject *pObject, tChunk *pPreviousChunk) { // 為了讀入物件的UV座標,首先需要讀入UV座標的數量,然後才讀入具體的資料 // 讀入UV座標的數量 pPreviousChunk->bytesRead += fread(&pObject->numTexVertex, 1, 2, m_FilePointer); // 分配儲存UV座標的記憶體空間 pObject->pTexVerts = new CVector2 [pObject->numTexVertex]; // 讀入紋理座標 pPreviousChunk->bytesRead += fread(pObject->pTexVerts, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer); } // 讀入物件的頂點 void CLoad3DS::ReadVertices(t3DObject *pObject, tChunk *pPreviousChunk) { // 在讀入實際的頂點之前,首先必須確定需要讀入多少個頂點。 // 讀入頂點的數目 pPreviousChunk->bytesRead += fread(&(pObject->numOfVerts), 1, 2, m_FilePointer); // 分配頂點的儲存空間,然後初始化結構體 pObject->pVerts = new NBVector3 [pObject->numOfVerts]; memset(pObject->pVerts, 0, sizeof(NBVector3) * pObject->numOfVerts); // 讀入頂點序列 pPreviousChunk->bytesRead += fread(pObject->pVerts, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer); // 現在已經讀入了所有的頂點。 // 因為3D Studio Max的模型的Z軸是指向上的,因此需要將y軸和z軸翻轉過來。 // 具體的做法是將Y軸和Z軸交換,然後將Z軸反向。 // 遍歷所有的頂點 for(int i = 0; i < pObject->numOfVerts; i++) { // 儲存Y軸的值 float fTempY = pObject->pVerts[i].y; // 設定Y軸的值等於Z軸的值 pObject->pVerts[i].y = pObject->pVerts[i].z; // 設定Z軸的值等於-Y軸的值 pObject->pVerts[i].z = -fTempY; } } // 下面的函式讀入物件的材質名稱 void CLoad3DS::ReadObjectMaterial(t3DModel *pModel, t3DObject *pObject, tChunk *pPreviousChunk) { char strMaterial[255] = {0}; // 用來儲存物件的材質名稱 int buffer[50000] = {0}; // 用來讀入不需要的資料 // 材質或者是顏色,或者是物件的紋理,也可能儲存了象明亮度、發光度等資訊。 // 下面讀入賦予當前物件的材質名稱 pPreviousChunk->bytesRead += GetString(strMaterial); // 遍歷所有的紋理 for(int i = 0; i < pModel->numOfMaterials; i++) { //如果讀入的紋理與當前的紋理名稱匹配 if(strcmp(strMaterial, pModel->pMaterials[i].strName) == 0) { // 設定材質ID pObject->materialID = i; // 判斷是否是紋理對映,如果strFile是一個長度大於1的字串,則是紋理 if(strlen(pModel->pMaterials[i].strFile) > 0) { //載入紋理 BuildTexture(pModel->pMaterials[i].strFile, pModel->texture[pObject->materialID]); // 設定物件的紋理對映標誌 pObject->bHasTexture = true; char strMessage[100]; sprintf(strMessage, "file name : %s!", pModel->pMaterials[i].strFile); printf( "%s\n" , strMessage ); // MessageBox(NULL, strMessage, "Error", MB_OK); } break; } else { // 如果該物件沒有材質,則設定ID為-1 pObject->materialID = -1; } } pPreviousChunk->bytesRead += fread(buffer, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer); } // 下面的這些函式主要用來計算頂點的法向量,頂點的法向量主要用來計算光照 // 下面的函式用於計算物件的法向量 void CLoad3DS::ComputeNormals(t3DModel *pModel) { NBVector3 vVector1, vVector2, vNormal, vPoly[3]; // 如果模型中沒有物件,則返回 if(pModel->numOfObjects <= 0) return; // 遍歷模型中所有的物件 for(int index = 0; index < pModel->numOfObjects; index++) { // 獲得當前的物件 t3DObject *pObject = &(pModel->pObject[index]); // 分配需要的儲存空間 NBVector3 *pNormals = new NBVector3 [pObject->numOfFaces]; NBVector3 *pTempNormals = new NBVector3 [pObject->numOfFaces]; pObject->pNormals = new NBVector3 [pObject->numOfVerts]; int i=0; // 遍歷物件的所有面 for(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; // 將法向量新增到法向量列表中 } // 下面求頂點法向量 NBVector3 vSum (0.0, 0.0, 0.0); NBVector3 vZero = vSum; int shared=0; // 遍歷所有的頂點 for (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; } } void changeObject(float trans[10]) { glTranslatef(trans[0],trans[1],trans[2]); glScalef(trans[3],trans[4],trans[5]); glRotatef(trans[6],trans[7],trans[8],trans[9]); } void drawModel(t3DModel Model,bool touming,bool outTex) { if( touming ){ glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4f(1,1,1,0.5); } // ±éàú?£Dí?D?ùóDμ????ó for(int i = 0; i < Model.numOfObjects; i++) { // ??μ?μ±?°??ê?μ????ó t3DObject *pObject = &Model.pObject[i]; // ?D???????óê?·?óD??àíó3é? if(!outTex) { if(pObject->bHasTexture) { // ′ò?a??àíó3é? glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, Model.texture[pObject->materialID]); } else { // 1?±???àíó3é? glDisable(GL_TEXTURE_2D); glColor3ub(255, 255, 255); } } // ?aê?ò?g_ViewMode?£ê????? glBegin(GL_TRIANGLES); // ±éàú?ùóDμ??? for(int j = 0; j < pObject->numOfFaces; j++) { // ±éàúèy??D?μ??ùóDμ? for(int whichVertex = 0; whichVertex < 3; whichVertex++) { // ??μ?????????μ?μ??÷òy int index = pObject->pFaces[j].vertIndex[whichVertex]; // ??3?·¨?òá? glNormal3f(pObject->pNormals[ index ].x, pObject->pNormals[ index ].y, pObject->pNormals[ index ].z); //è?1????ó??óD??àí if(pObject->bHasTexture) { // è·?¨ê?·?óDUVW??àí×?±ê if(pObject->pTexVerts) { glColor3f(1.0,1.0,1.0); glTexCoord2f(pObject->pTexVerts[ index ].x, pObject->pTexVerts[ index ].y); } } else{ if(Model.pMaterials.size() && pObject->materialID >= 0) { BYTE *pColor = Model.pMaterials[pObject->materialID].color; glColor3ub(pColor[0], pColor[1], pColor[2]); } } glVertex3f(pObject->pVerts[ index ].x, pObject->pVerts[ index ].y, pObject->pVerts[ index ].z); } } glEnd(); // ?????áê? } if( touming ) glDisable(GL_BLEND); }