了解了 md2文件的结构后,我们可以来构造一个加载 md2模型的类 CLoadMD2。
class CLoadMD2
{
public:
CLoadMD2();
~CLoadMD2();
// 加载MD2 模型
bool ImportMD2(Model3D_t *pModel, char *strFileName, char *strTexture);
private:
// 从MD2 文件读取数据并存放在成员变量中
void ReadMD2Data();
// 将成员变量数据转换成我们的模型结构
void ConvertDataStructures(Model3D_t *pModel);
// 当使用光照时,计算顶点法向量
void ComputeNormals(Model3D_t *pModel);
void CleanUp();
FILE *m_FilePointer; // 本MD2 文件指针
md2_header_t m_Header; // 文件头
md2skin *m_pSkins; // 皮肤数据
textureCoordinate_t *m_pTexCoords; // 纹理坐标
md2triangle_t *m_pTriangles; // 三角形索引数据
postframe_t *m_pFrames; // 帧数据
};
CLoadMD2类中使用了一个 Model3D_t的结构,它是我们用于渲染 MD2模型的结构,其定义如下:
typedef struct
{
int numOfObjects; // 模型中3D 物体的数目
int numOfMaterials; // 模型中材质的数目
int *m_glCommandBuffer; //gl 命令缓存
vector<tMaterialInfo> pMaterials; // 材质信息,包括纹理、颜色
// 这里的定义用到了STL
vector<Object3D_t> pObject; // 模型中3D 物体列表
} Model3D_t;
3D物体的结构定义如下
typedef struct
{
int numOfVerts; // 模型中顶点的数目
int numOfFaces; // 模型中三角形的数目
int numTexVertex; // 纹理坐标的数目
int materialID; // 纹理ID
bool bHasTexture; // 是否有纹理
char strName[255]; // 3D 物体名字
Vector3 *pVerts; // 物体的顶点数据
Vector3 *pNormals; // 物体的法向量数据
Vector2 *pTexVerts; // 纹理的坐标数据
face_t *pFaces; // 物体的三角形信息
} Object3D_t;
接下来看加载 MD2的最重要的函数 ImportMD2,函数的说明包含在代码的注释中。
bool CLoadMD2::ImportMD2(Model3D_t *pModel, char *strFileName, char *strTexture)
{
char strMessage[255] = {0};
// 首先打开一个md2 文件
m_FilePointer = fopen(strFileName, "rb");
if(!m_FilePointer)
{
MessageBox(NULL, “Unable to find the MD2 file!”, "Error", MB_OK);
return false;
}
// 读取文件头
fread(&m_Header, 1, sizeof(md2_header_t), m_FilePointer);
// 检查文件版本
if(m_Header.version != 8)
{
MessageBox(NULL, “Invalid file format strMessage”, "Error", MB_OK);
return false;
}
// 读取md2 文件中的其他数据
ReadMD2Data();
// 将数据转换成我们定义的模型格式
ConvertDataStructures(pModel);
// 计算顶点法向量
ComputeNormals(pModel);
// 检查是否有纹理文件
if(strTexture)
{
// 材质信息
tMaterialInfo texture;
strcpy(texture.strFile, strTexture);
// 对于一个md2 文件,只有一个纹理,因此其ID 一直是0
texture.texureId = 0;
// 纹理平铺的比例是1
texture.uTile = texture.uTile = 1;
// 这里的模型使用一个材质
pModel->numOfMaterials = 1;
// 将材质加入到我们自己的材质列表中
pModel->pMaterials.push_back(texture);
}
CleanUp();
return true;
}
<!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} p.MsoBodyText, li.MsoBodyText, div.MsoBodyText {margin-top:0cm; margin-right:0cm; margin-bottom:6.0pt; margin-left:0cm; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} p.MsoBodyTextFirstIndent, li.MsoBodyTextFirstIndent, div.MsoBodyTextFirstIndent {mso-style-update:auto; mso-style-parent:正文文本; mso-style-link:" Char Char"; margin:0cm; margin-bottom:.0001pt; text-indent:19.85pt; mso-pagination:none; font-size:10.5pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-ansi-language:ZH-CN;} p.a, li.a, div.a {mso-style-name:正文(首行不缩进); margin:0cm; margin-bottom:.0001pt; line-height:150%; mso-pagination:none; mso-layout-grid-align:none; text-autospace:none; font-size:10.5pt; mso-bidi-font-size:10.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体;} span.CharChar {mso-style-name:" Char Char"; mso-style-locked:yes; mso-style-link:正文首行缩进; mso-ansi-font-size:10.5pt; mso-bidi-font-size:10.5pt; font-family:宋体; mso-fareast-font-family:宋体; mso-ansi-language:ZH-CN; mso-fareast-language:ZH-CN; mso-bidi-language:AR-SA;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:36.0pt; mso-footer-margin:36.0pt; mso-paper-source:0;} div.Section1 {page:Section1;} -->
ImportMD2 是加载整个模型的过程 , 而 ReadMD2Data 则读取除了文件头之外其他数据 , 下面是 ReadMD2Data 的代码 , 通过这个函数 , 也可以更好地了解 md2 文件的格式。
void CLoadMD2::ReadMD2Data()
{
unsigned char buffer[MD2_MAX_FRAMESIZE]; //8320
int j = 0;
// 先根据文件头中的信息把内存分配好
m_pSkins = new md2skin [m_Header.numSkins];
m_pTexCoords = new textureCoordinate_t [m_Header.numTexCoords];
m_pTriangles = new md2triangle_t [m_Header.numTriangles];
m_pFrames = new postframe_t [m_Header.numFrames];
// 根据皮肤数来读取皮肤
fseek(m_FilePointer, m_Header.offsetSkins, SEEK_SET);
fread(m_pSkins, sizeof(md2skin), m_Header.numSkins, m_FilePointer);
// 读取纹理坐标
fseek(m_FilePointer, m_Header.offsetTexCoords, SEEK_SET);
fread(m_pTexCoords, sizeof(textureCoordinate_t), m_Header.numTexCoords,
m_FilePointer);
// 读取三角形数据,包括顶点和纹理坐标索引
fseek(m_FilePointer, m_Header.offsetTriangles, SEEK_SET);
fread(m_pTriangles, sizeof(md2triangle_t), m_Header.numTriangles, m_FilePointer);
fseek(m_FilePointer, m_Header.offsetFrames, SEEK_SET);
// 为帧缓存分配内存
frame_t *pFrame = (frame_t *) buffer;
m_pFrames[0].pVertices = new triangle_t [m_Header.numVertices];
// 读第一帧
fread(pFrame, 1, m_Header.frameSize, m_FilePointer);
strcpy(m_pFrames[0].strName, pFrame->name);
// 第一帧的顶点
triangle_t *pVertices = m_pFrames[0].pVertices;
// 对顶点数据进行变换成真实的坐标数据
for (j=0; j < m_Header.numVertices; j++)
{
pVertices[j].vertex[0] = pFrame->aliasVertices[j].vertex[0] * pFrame->scale[0]
+ pFrame->translate[0];
pVertices[j].vertex[2] = -1 * (pFrame->aliasVertices[j].vertex[1]
* pFrame->scale[1] + pFrame->translate[1]);
pVertices[j].vertex[1] = pFrame->aliasVertices[j].vertex[2] * pFrame->scale[2]
+ pFrame->translate[2];
}
}