OpenGL一个重要应用就是能够读取外部的3D模型文件,比如OBJ,MD2,MD3,3DS等。在我之前的日志里已经写过一篇关于OpenGL读取Obj格式的类,详情可以看“
Qt下学习OpenGL之OBJ模型”。而这次我要介绍的是3DS文件的读取。
接下来要贴出的代码已经在Qt4下成功运行,不过里面需要依赖一个CBMPLoader的类,这个可以从《OpenGL游戏编程》这本书里看到,也可以在我博客里找到对应的代码。
最后,还补充一下个人的看法。对于3DS格式,相比OBJ的话,其多了光照、贴图、材质等信息,但如果是工业标准,并且对于贴图和材质没有特别的要求的话,使用代码给每个不同物体写上一个单一颜色就好了。这样速度很快,速度是很重要的因素啊。
好了,贴代码了。
3DSLoader.h内容:
001 | #ifndef __3DSLOADER_H__ |
002 | #define __3DSLOADER_H__ |
003 | |
004 | #include "Stdafx.h" |
005 | #include "Vector.h" |
006 | #include "CBMPLoader.h" |
007 | |
008 | /* 下面是定义一些块的ID号 */ |
009 | |
010 | /* 基本块(Primary Chunk),位于文件的开始 */ |
011 | #define PRIMARY 0x4D4D |
012 | |
013 | /* 主块(Main Chunks) */ |
014 | #define OBJECTINFO 0x3D3D /* 网格对象的版本号 */ |
015 | #define VERSION 0x0002 /* .3ds文件的版本 */ |
016 | #define EDITKEYFRAME 0xB000 /* 所有关键帧信息的头部 */ |
017 | |
018 | /* 对象的次级定义 */ |
019 | #define MATERIAL 0xAFFF /* 纹理信息 */ |
020 | #define OBJECT 0x4000 /* 对象的面、顶点等信息 */ |
021 | |
022 | /* 材质的次级定义 */ |
023 | #define MATNAME 0xA000 /* 材质名称 */ |
024 | #define MATDIFFUSE 0xA020 /* 对象/材质的颜色 */ |
025 | #define MATMAP 0xA200 /* 新材质的头部 */ |
026 | #define MATMAPFILE 0xA300 /* 保存纹理的文件名 */ |
027 | #define OBJ_MESH 0x4100 /* 新的网格对象 */ |
028 | |
029 | /* 网格对象的次级定义 */ |
030 | #define OBJ_VERTICES 0x4110 /* 对象顶点 */ |
031 | #define OBJ_FACES 0x4120 /* 对象的面 */ |
032 | #define OBJ_MATERIAL 0x4130 /* 对象的材质 */ |
033 | #define OBJ_UV 0x4140 /* 对象的UV纹理坐标 */ |
034 | |
035 | /* 面的结构定义 */ |
036 | struct tFace |
037 | { |
038 | int vertIndex[3]; /* 顶点索引 */ |
039 | int coordIndex[3]; /* 纹理坐标索引 */ |
040 | }; |
041 | |
042 | /* 材质信息结构体 */ |
043 | struct tMatInfo |
044 | { |
045 | char strName[255]; /* 纹理名称 */ |
046 | char strFile[255]; /* 纹理文件名称 */ |
047 | unsigned char color[3]; /* 对象的RGB颜色 */ |
048 | int texureId; /* 纹理ID */ |
049 | float uTile; /* u 重复 */ |
050 | float vTile; /* v 重复 */ |
051 | float uOffset; /* u 纹理偏移 */ |
052 | float vOffset; /* v 纹理偏移 */ |
053 | } ; |
054 | |
055 | /* 对象信息结构体 */ |
056 | struct t3DObject |
057 | { |
058 | int numOfVerts; /* 模型中顶点的数目 */ |
059 | int numOfFaces; /* 模型中面的数目 */ |
060 | int numTexVertex; /* 模型中纹理坐标的数目 */ |
061 | int materialID; /* 纹理ID */ |
062 | bool bHasTexture; /* 是否具有纹理映射 */ |
063 | char strName[255]; /* 对象的名称 */ |
064 | Vector3 *pVerts; /* 对象的顶点 */ |
065 | Vector3 *pNormals; /* 对象的法向量 */ |
066 | Vector2 *pTexVerts; /* 纹理UV坐标 */ |
067 | tFace *pFaces; /* 对象的面信息 */ |
068 | }; |
069 | |
070 | /* 模型信息结构体 */ |
071 | struct t3DModel |
072 | { int numOfObjects; /* 模型中对象的数目 */ |
073 | int numOfMaterials; /* 模型中材质的数目 */ |
074 | QVectorpMaterials; /* 材质链表信息 */ |
075 | QVector pObject; /* 模型中对象链表信息 */ |
076 | }; |
077 | |
078 | /* 块信息的结构 */ |
079 | struct tChunk |
080 | { |
081 | unsigned short int ID; /* 块的ID */ |
082 | unsigned int length; /* 块的长度 */ |
083 | unsigned int bytesRead; /* 需要读的块数据的字节数 */ |
084 | }; |
085 | |
086 | #define MAX_TEXTURES 100 |
087 | |
088 | /* 3DS文件载入类 */ |
089 | class C3DSLoader |
090 | { |
091 | public : |
092 | /* 构造函数 */ |
093 | C3DSLoader(); |
094 | virtual ~C3DSLoader(); |
095 | void Draw(); //显示3ds模型 |
096 | void Init( char *filename); |
097 | |
098 | private : |
099 | /* 读一个字符串 */ |
100 | int GetString( char *); |
101 | |
102 | /* 装载3ds文件到模型结构中 */ |
103 | bool Import3DS(t3DModel *pModel, char *strFileName); |
104 | |
105 | /* 从文件中创建纹理 */ |
106 | void LoadTexture( char * filename, GLuint textureArray[], GLuint textureID); |
107 | |
108 | /* 读取一个块 */ |
109 | void ReadChunk(tChunk *); |
110 | |
111 | /* 读取下一个块 */ |
112 | void ReadNextChunk(t3DModel *pModel, tChunk *); |
113 | |
114 | /* 读取下一个对象 */ |
115 | void ReadNextObjChunk(t3DModel *pModel,t3DObject *pObject,tChunk *); |
116 | |
117 | /* 读取下一个材质块 */ |
118 | void ReadNextMatChunk(t3DModel *pModel, tChunk *); |
119 | |
120 | /* 读取对象颜色的RGB值 */ |
121 | void ReadColor(tMatInfo *pMaterial, tChunk *pChunk); |
122 | |
123 | /* 读取对象的顶点信息 */ |
124 | void ReadVertices(t3DObject *pObject, tChunk *); |
125 | |
126 | /* 读取对象的面信息 */ |
127 | void ReadVertexIndices(t3DObject *pObject,tChunk *); |
128 | |
129 | /* 读取对象的纹理坐标 */ |
130 | void ReadUVCoordinates(t3DObject *pObject,tChunk *); |
131 | |
132 | /* 读取赋予对象的材质 */ |
133 | void ReadObjMat(t3DModel *pModel,t3DObject *pObject,tChunk *pPreChunk); |
134 | |
135 | /* 计算对象顶点的法向量 */ |
136 | void ComputeNormals(t3DModel *pModel); |
137 | |
138 | /* 释放内存,关闭文件 */ |
139 | void CleanUp(); |
140 | |
141 | FILE *m_FilePointer; /*< 文件指针 */ |
142 | tChunk *m_CurrentChunk; /*< 读取过程中当前块 */ |
143 | tChunk *m_TempChunk; /*< 临时块 */ |
144 | GLuint m_textures[MAX_TEXTURES]; /*< 纹理 */ |
145 | t3DModel m_3DModel; /*< 模型 */ |
146 | CBMPLoader m_BMPTexture; /* 载入位图 */ |
147 | |
148 | }; |
149 | |
150 | #endif |
3DSLoader.cpp内容:
001 | #include "Stdafx.h" |
002 | #include "3DSLoader.h" |
003 | |
004 | /* 构造函数 */ |
005 | C3DSLoader::C3DSLoader() |
006 | { |
007 | m_CurrentChunk = new tChunk; /* 为当前块分配空间 */ |
008 | m_TempChunk = new tChunk; /* 为临时块分配空间 */ |
009 | m_3DModel.numOfObjects = 0; |
010 | m_3DModel.numOfMaterials = 0; |
011 | for ( int i=0;i0) |
012 | LoadTexture(m_3DModel.pMaterials[i].strFile,m_textures, i); /* 使用纹理文件名称来装入位图 */ |
013 | m_3DModel.pMaterials[i].texureId = i; /* 设置材质的纹理ID */ |
014 | } |
015 | } |
016 | |
017 | /* 显示3ds模型 */ |
018 | void C3DSLoader::Draw() |
019 | { |
020 | /*保存现有颜色属实性 */ |
021 | glPushAttrib(GL_CURRENT_BIT); |
022 | glDisable(GL_TEXTURE_2D); |
023 | //glEnable(GL_LIGHT0); |
024 | |
025 | /* 遍历模型中所有的对象 */ |
026 | for ( int i = 0; i < m_3DModel.numOfObjects; i++) |
027 | { |
028 | /* 如果对象的大小小于0,则退出 */ |
029 | if (m_3DModel.pObject.size() <= 0) break ; /* 获得当前显示的对象 */ t3DObject *pObject = &m_3DModel.pObject[i]; /* 判断该对象是否有纹理映射 */ if (pObject->bHasTexture) |
030 | { |
031 | glEnable(GL_TEXTURE_2D); /* 打开纹理映射 */ |
032 | glBindTexture(GL_TEXTURE_2D, m_textures[pObject->materialID]); |
033 | } |
034 | else |
035 | glDisable(GL_TEXTURE_2D); /* 关闭纹理映射 */ |
036 | |
037 | glColor3ub(255, 255, 255); |
038 | |
039 | /* 开始绘制 */ |
040 | |
041 | glBegin(GL_TRIANGLES); |
042 | |
043 | /* 遍历所有的面 */ |
044 | for ( int j = 0; j < pObject->numOfFaces; j++) |
045 | { |
046 | /* 遍历三角形的所有点 */ |
047 | for ( int tex = 0; tex < 3; tex++) { /* 获得面对每个点的索引 */ int index = pObject->pFaces[j].vertIndex[tex]; |
048 | |
049 | /* 给出法向量 */ |
050 | glNormal3f(pObject->pNormals[index].x,pObject->pNormals[index].y, |
051 | pObject->pNormals[index].z); |
052 | |
053 | /* 如果对象具有纹理 */ |
054 | if (pObject->bHasTexture) |
055 | { |
056 | /* 确定是否有UVW纹理坐标 */ |
057 | if (pObject->pTexVerts) |
058 | glTexCoord2f(pObject->pTexVerts[index].x,pObject->pTexVerts[index].y); |
059 | } |
060 | else |
061 | { |
062 | if (m_3DModel.pMaterials.size() && pObject->materialID>= 0) |
063 | { |
064 | unsigned char *pColor = m_3DModel.pMaterials[pObject->materialID].color; |
065 | glColor3ub(pColor[0],pColor[1],pColor[2]); |
066 | } |
067 | } |
068 | glVertex3f(pObject->pVerts[index].x,pObject->pVerts[index].y,pObject->pVerts[index].z); |
069 | } |
070 | } |
071 | glEnd(); |
072 | /* 绘制结束 */ |
073 | } |
074 | |
075 | //glDisable(GL_LIGHT0); |
076 | glEnable(GL_TEXTURE_2D); |
077 | |
078 | /* 恢复前一属性 */ |
079 | glPopAttrib(); |
080 | } |
081 | |
082 | void C3DSLoader::LoadTexture( char * filename, GLuint textureArray[], GLuint textureID) |
083 | { |
084 | if (!filename) |
085 | return ; |
086 | |
087 | if (! this ->m_BMPTexture.LoadBitmap(filename)) |
088 | { |
089 | QMessageBox::warning(0,QObject::tr( "Load Bitmap Error" ),QObject::tr( "Load Bitmap Error" )); |
090 | return ; |
091 | } |
092 | |
093 | glGenTextures(1,&m_textures[textureID]); |
094 | |
095 | glPixelStorei (GL_UNPACK_ALIGNMENT, 1); |
096 | glBindTexture(GL_TEXTURE_2D, m_textures[textureID]); |
097 | |
098 | /* 控制滤波 */ |
099 | glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); |
100 | glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR); |
101 | |
102 | /* 创建纹理 */ |
103 | gluBuild2DMipmaps(GL_TEXTURE_2D, 3, m_BMPTexture.imageWidth,m_BMPTexture.imageHeight, GL_RGB, |
104 | GL_UNSIGNED_BYTE, this ->m_BMPTexture.image); |
105 | |
106 | } |
107 | |
108 | /* 载入3DS文件到模型结构中 */ |
109 | bool C3DSLoader::Import3DS(t3DModel *pModel, char *strFileName) |
110 | { |
111 | char strMessage[255] = {0}; |
112 | |
113 | /* 打开一个3ds文件 */ |
114 | m_FilePointer = fopen (strFileName, "rb" ); |
115 | |
116 | /* 检查文件指针 */ |
117 | if (!m_FilePointer) |
118 | { |
119 | sprintf (strMessage, "Cann't find the file: %s!" , strFileName); |
120 | QMessageBox::information(NULL, |
121 | "Error" , |
122 | strMessage, |
123 | QMessageBox::Yes | QMessageBox::No, |
124 | QMessageBox::Yes); |
125 | return false ; |
126 | } |
127 | |
128 | /* 将文件的第一块读出并判断是否是3ds文件 */ |
129 | ReadChunk(m_CurrentChunk); |
130 | |
131 | /* 确保是3ds文件 */ |
132 | if (m_CurrentChunk->ID != PRIMARY) |
133 | { |
134 | QMessageBox::information(NULL, |
135 | "Error" , |
136 | "Cann't Loading the Function" , |
137 | QMessageBox::Yes | QMessageBox::No, |
138 | QMessageBox::Yes); |
139 | return false ; |
140 | } |
141 | |
142 | /* 递归读出对象数据 */ |
143 | ReadNextChunk(pModel, m_CurrentChunk); |
144 | |
145 | /* 计算顶点的法线 */ |
146 | ComputeNormals(pModel); |
147 | |
148 | /* 释放内存空间 */ |
149 | CleanUp(); |
150 | |
151 | return true ; |
152 | } |
153 | |
154 | /* 读入一个字符串 */ |
155 | int C3DSLoader::GetString( char *pBuffer) |
156 | { |
157 | int index = 0; |
158 | |
159 | /* 读入一个字节的数据 */ |
160 | fread (pBuffer, 1, 1, m_FilePointer); |
161 | |
162 | /* 直到结束 */ |
163 | while (*(pBuffer + index++) != 0) |
164 | { |
165 | /* 读入一个字符直到NULL */ |
166 | fread (pBuffer + index, 1, 1, m_FilePointer); |
167 | } |
168 | |
169 | /* 返回字符串的长度 */ |
170 | return strlen (pBuffer) + 1; |
171 | } |
172 | |
173 | /* 读入块的ID号和它的字节长度 */ |
174 | void C3DSLoader::ReadChunk(tChunk *pChunk) |
175 | { |
176 | /* 读入块的ID号 */ |
177 | pChunk->bytesRead = fread (&pChunk->ID, 1, 2, m_FilePointer); |
178 | |
179 | /* 读入块占用的长度 */ |
180 | pChunk->bytesRead += fread (&pChunk->length, 1, 4, m_FilePointer); |
181 | |
182 | } |
183 | |
184 | /* 读出3ds文件的主要部分 */ |
185 | void C3DSLoader::ReadNextChunk(t3DModel *pModel, tChunk *pPreChunk) |
186 | { |
187 | t3DObject newObject = {0}; /* 用来添加到对象链表 */ |
188 | tMatInfo newTexture = {0}; /* 用来添加到材质链表 */ |
189 | unsigned int version = 0; /* 保存文件版本 */ |
190 | int buffer[50000] = {0}; /* 用来跳过不需要的数据 */ |
191 | m_CurrentChunk = new tChunk; /* 为新的块分配空间 */ |
192 | |
193 | /* 继续读入子块 */ |
194 | while (pPreChunk->bytesRead < pPreChunk->length) |
195 | { |
196 | /* 读入下一个块 */ |
197 | ReadChunk(m_CurrentChunk); |
198 | |
199 | /* 判断块的ID号 */ |
200 | switch (m_CurrentChunk->ID) |
201 | { |
202 | |
203 | /* 文件版本号 */ |
204 | case VERSION: |
205 | |
206 | /* 读入文件的版本号 */ |
207 | m_CurrentChunk->bytesRead += fread (&version, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); |
208 | |
209 | /* 如果文件版本号大于3,给出一个警告信息 */ |
210 | if (version > 0x03) |
211 | QMessageBox::information(NULL, |
212 | "Warning" , |
213 | "The 3DS File Version is wrong" , |
214 | QMessageBox::Yes | QMessageBox::No, |
215 | QMessageBox::Yes); |
216 | break ; |
217 | |
218 | /* 网格版本信息 */ |
219 | case OBJECTINFO: |
220 | |
221 | /* 读入下一个块 */ |
222 | ReadChunk(m_TempChunk); |
223 | |
224 | /* 获得网格的版本号 */ |
225 | m_TempChunk->bytesRead += fread (&version, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer); |
226 | |
227 | /* 增加读入的字节数 */ |
228 | m_CurrentChunk->bytesRead += m_TempChunk->bytesRead; |
229 | |
230 | /* 进入下一个块 */ |
231 | ReadNextChunk(pModel, m_CurrentChunk); |
232 | break ; |
233 | |
234 | /* 材质信息 */ |
235 | case MATERIAL: |
236 | |
237 | /* 材质的数目递增 */ |
238 | pModel->numOfMaterials++; |
239 | |
240 | /* 在纹理链表中添加一个空白纹理结构 */ |
241 | pModel->pMaterials.push_back(newTexture); |
242 | |
243 | /* 进入材质装入函数 */ |
244 | ReadNextMatChunk(pModel, m_CurrentChunk); |
245 | break ; |
246 | |
247 | /* 对象名称 */ |
248 | case OBJECT: |
249 | |
250 | /* 对象数递增 */ |
251 | pModel->numOfObjects++; |
252 | |
253 | /* 添加一个新的tObject节点到对象链表中 */ |
254 | pModel->pObject.push_back(newObject); |
255 | |
256 | /* 初始化对象和它的所有数据成员 */ |
257 | memset (&(pModel->pObject[pModel->numOfObjects - 1]), 0, sizeof (t3DObject)); |
258 | |
259 | /* 获得并保存对象的名称,然后增加读入的字节数 */ |
260 | m_CurrentChunk->bytesRead += GetString(pModel->pObject[pModel->numOfObjects - 1].strName); |
261 | |
262 | /* 进入其余的对象信息的读入 */ |
263 | ReadNextObjChunk(pModel, &(pModel->pObject[pModel->numOfObjects - 1]), m_CurrentChunk); |
264 | break ; |
265 | |
266 | /* 关键帧 */ |
267 | case EDITKEYFRAME: |
268 | |
269 | /* 跳过关键帧块的读入 */ |
270 | m_CurrentChunk->bytesRead += fread (buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); |
271 | break ; |
272 | |
273 | default : |
274 | /* 跳过所有忽略的块的内容的读入 */ |
275 | m_CurrentChunk->bytesRead += fread (buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); |
276 | break ; |
277 | } |
278 | |
279 | /* 增加从最后块读入的字节数 */ |
280 | pPreChunk->bytesRead += m_CurrentChunk->bytesRead; |
281 | } |
282 | |
283 | /* 释放当前块的内存空间 */ |
284 | delete m_CurrentChunk; |
285 | m_CurrentChunk = pPreChunk; |
286 | } |
287 | |
288 | /* 处理所有的文件中对象的信息 */ |
289 | void C3DSLoader::ReadNextObjChunk(t3DModel *pModel, t3DObject *pObject, tChunk *pPreChunk) |
290 | { |
291 | /* 用于读入不需要的数据 */ |
292 | int buffer[50000] = {0}; |
293 | |
294 | /* 对新的块分配存储空间 */ |
295 | m_CurrentChunk = new tChunk; |
296 | |
297 | /* 继续读入块的内容直至本子块结束 */ |
298 | while (pPreChunk->bytesRead < pPreChunk->length) |
299 | { |
300 | /* 读入下一个块 */ |
301 | ReadChunk(m_CurrentChunk); |
302 | |
303 | /* 区别读入是哪种块 */ |
304 | switch (m_CurrentChunk->ID) |
305 | { |
306 | /* 正读入的是一个新块 */ |
307 | case OBJ_MESH: |
308 | /* 使用递归函数调用,处理该新块 */ |
309 | ReadNextObjChunk(pModel, pObject, m_CurrentChunk); |
310 | break ; |
311 | |
312 | /* 读入是对象顶点 */ |
313 | case OBJ_VERTICES: |
314 | ReadVertices(pObject, m_CurrentChunk); |
315 | break ; |
316 | |
317 | /* 读入的是对象的面 */ |
318 | case OBJ_FACES: |
319 | ReadVertexIndices(pObject, m_CurrentChunk); |
320 | break ; |
321 | |
322 | /* 读入的是对象的材质名称 */ |
323 | case OBJ_MATERIAL: |
324 | /* 读入对象的材质名称 */ |
325 | ReadObjMat(pModel, pObject, m_CurrentChunk); |
326 | break ; |
327 | |
328 | /* 读入对象的UV纹理坐标 */ |
329 | case OBJ_UV: |
330 | /* 读入对象的UV纹理坐标 */ |
331 | ReadUVCoordinates(pObject, m_CurrentChunk); |
332 | break ; |
333 | |
334 | default : |
335 | /* 略过不需要读入的块 */ |
336 | m_CurrentChunk->bytesRead += fread (buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); |
337 | break ; |
338 | } |
339 | /* 添加从最后块中读入的字节数到前面的读入的字节中 */ |
340 | pPreChunk->bytesRead += m_CurrentChunk->bytesRead; |
341 | } |
342 | /* 释放当前块的内存空间,并把当前块设置为前面块 */ |
343 | delete m_CurrentChunk; |
344 | m_CurrentChunk = pPreChunk; |
345 | } |
346 | |
347 | /* 处理所有的材质信息 */ |
348 | void C3DSLoader::ReadNextMatChunk(t3DModel *pModel, tChunk *pPreChunk) |
349 | { |
350 | /* 用于读入不需要的数据 */ |
351 | int buffer[50000] = {0}; |
352 | |
353 | /* 给当前块分配存储空间 */ |
354 | m_CurrentChunk = new tChunk; |
355 | |
356 | /* 继续读入这些块 */ |
357 | while (pPreChunk->bytesRead < pPreChunk->length) |
358 | { |
359 | /* 读入下一块 */ |
360 | ReadChunk(m_CurrentChunk); |
361 | /* 判断读入的是什么块 */ |
362 | switch (m_CurrentChunk->ID) |
363 | { |
364 | case MATNAME: /* 材质的名称 */ |
365 | /* 读入材质的名称 */ |
366 | m_CurrentChunk->bytesRead += fread (pModel->pMaterials[pModel->numOfMaterials - 1].strName, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); |
367 | break ; |
368 | case MATDIFFUSE: /* 对象的R G B颜色 */ |
369 | ReadColor(&(pModel->pMaterials[pModel->numOfMaterials - 1]), m_CurrentChunk); |
370 | break ; |
371 | case MATMAP: /* 纹理信息的头部 */ |
372 | /* 下一个材质块信息 */ |
373 | ReadNextMatChunk(pModel, m_CurrentChunk); |
374 | break ; |
375 | case MATMAPFILE: /* 材质文件的名称 */ |
376 | /* 读入材质的文件名称 */ |
377 | m_CurrentChunk->bytesRead += fread (pModel->pMaterials[pModel->numOfMaterials - 1].strFile, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); |
378 | break ; |
379 | default : |
380 | /* 跳过不需要读入的块 */ |
381 | m_CurrentChunk->bytesRead += fread (buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); |
382 | break ; |
383 | } |
384 | /* 添加从最后块中读入的字节数 */ |
385 | pPreChunk->bytesRead += m_CurrentChunk->bytesRead; |
386 | } |
387 | /* 删除当前块,并将当前块设置为前面的块 */ |
388 | delete m_CurrentChunk; |
389 | m_CurrentChunk = pPreChunk; |
390 | } |
391 | |
392 | /* 读入RGB颜色 */ |
393 | void C3DSLoader::ReadColor(tMatInfo *pMaterial, tChunk *pChunk) |
394 | { |
395 | /* 读入颜色块信息 */ |
396 | ReadChunk(m_TempChunk); |
397 | /* 读入RGB颜色 */ |
398 | m_TempChunk->bytesRead += fread (pMaterial->color, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer); |
399 | /* 增加读入的字节数 */ |
400 | pChunk->bytesRead += m_TempChunk->bytesRead; |
401 | } |
402 | |
403 | /* 读入顶点索引 */ |
404 | void C3DSLoader::ReadVertexIndices(t3DObject *pObject, tChunk *pPreChunk) |
405 | { |
406 | unsigned short index = 0; /* 用于读入当前面的索引 */ |
407 | /* 读入该对象中面的数目 */ |
408 | pPreChunk->bytesRead += fread (&pObject->numOfFaces, 1, 2, m_FilePointer); |
409 | /* 分配所有面的存储空间,并初始化结构 */ |
410 | pObject->pFaces = new tFace [pObject->numOfFaces]; |
411 | memset (pObject->pFaces, 0, sizeof (tFace) * pObject->numOfFaces); |
412 | /* 遍历对象中所有的面 */ |
413 | for ( int i = 0; i < pObject->numOfFaces; i++) |
414 | { for ( int j = 0; j < 4; j++) { /* 读入当前面的第一个点 */ pPreChunk->bytesRead += fread (&index, 1, sizeof (index), m_FilePointer); |
415 | if (j < 3) { /* 将索引保存在面的结构中 */ pObject->pFaces[i].vertIndex[j] = index; |
416 | } |
417 | } |
418 | } |
419 | } |
420 | |
421 | /* 读入对象的UV坐标 */ |
422 | void C3DSLoader::ReadUVCoordinates(t3DObject *pObject, tChunk *pPreChunk) |
423 | { |
424 | /* 读入UV坐标的数量 */ |
425 | pPreChunk->bytesRead += fread (&pObject->numTexVertex, 1, 2, m_FilePointer); |
426 | |
427 | /* 分配保存UV坐标的内存空间 */ |
428 | pObject->pTexVerts = new Vector2[pObject->numTexVertex]; |
429 | |
430 | /* 读入纹理坐标 */ |
431 | pPreChunk->bytesRead += fread (pObject->pTexVerts, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer); |
432 | } |
433 | |
434 | /* 读入对象的顶点 */ |
435 | void C3DSLoader::ReadVertices(t3DObject *pObject, tChunk *pPreChunk) |
436 | { |
437 | /* 读入顶点的数目 */ |
438 | pPreChunk->bytesRead += fread (&(pObject->numOfVerts), 1, 2, m_FilePointer); |
439 | |
440 | /* 分配顶点的存储空间,然后初始化结构体 */ |
441 | pObject->pVerts = new Vector3 [pObject->numOfVerts]; |
442 | memset (pObject->pVerts, 0, sizeof (Vector3) * pObject->numOfVerts); |
443 | |
444 | /* 读入顶点序列 */ |
445 | pPreChunk->bytesRead += fread (pObject->pVerts, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer); |
446 | |
447 | /* 遍历所有的顶点将Y轴和Z轴交换,然后将Z轴反向 */ |
448 | for ( int i = 0; i < pObject->numOfVerts; i++) |
449 | { |
450 | /* 保存Y轴的值 */ |
451 | float fTempY = pObject->pVerts[i].y; |
452 | /* 设置Y轴的值等于Z轴的值 */ |
453 | pObject->pVerts[i].y = pObject->pVerts[i].z; |
454 | /* 设置Z轴的值等于-Y轴的值 */ |
455 | pObject->pVerts[i].z = -fTempY; |
456 | } |
457 | } |
458 | |
459 | /* 读入对象的材质名称 */ |
460 | void C3DSLoader::ReadObjMat(t3DModel *pModel, t3DObject *pObject, tChunk *pPreChunk) |
461 | { |
462 | char strMaterial[255] = {0}; /* 用来保存对象的材质名称 */ |
463 | int buffer[50000] = {0}; /* 用来读入不需要的数据 */ |
464 | |
465 | /* 读入赋予当前对象的材质名称 */ |
466 | pPreChunk->bytesRead += GetString(strMaterial); |
467 | |
468 | /* 遍历所有的纹理 */ |
469 | for ( int i = 0; i < pModel->numOfMaterials; i++) |
470 | { |
471 | /* 如果读入的纹理与当前的纹理名称匹配 */ |
472 | if ( strcmp (strMaterial, pModel->pMaterials[i].strName) == 0) |
473 | { |
474 | /* 设置材质ID */ |
475 | pObject->materialID = i; |
476 | |
477 | /* 判断是否是纹理映射 */ |
478 | if ( strlen (pModel->pMaterials[i].strFile) > 0) { |
479 | |
480 | /* 设置对象的纹理映射标志 */ |
481 | pObject->bHasTexture = true ; |
482 | } |
483 | break ; |
484 | } |
485 | else |
486 | { |
487 | /* 如果该对象没有材质,则设置ID为-1 */ |
488 | pObject->materialID = -1; |
489 | } |
490 | } |
491 | pPreChunk->bytesRead += fread (buffer, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer); |
492 | } |
493 | |
494 | /* 计算对象的法向量 */ |
495 | void C3DSLoader::ComputeNormals(t3DModel *pModel) |
496 | { |
497 | Vector3 vVector1, vVector2, vNormal, vPoly[3]; |
498 | |
499 | /* 如果模型中没有对象,则返回 */ |
500 | if (pModel->numOfObjects <= 0) |
501 | return ; |
502 | |
503 | /* 遍历模型中所有的对象 */ |
504 | for ( int index = 0; index < pModel->numOfObjects; index++) |
505 | { |
506 | /* 获得当前的对象 */ |
507 | t3DObject *pObject = &(pModel->pObject[index]); |
508 | |
509 | /* 分配需要的存储空间 */ |
510 | Vector3 *pNormals = new Vector3 [pObject->numOfFaces]; |
511 | Vector3 *pTempNormals = new Vector3 [pObject->numOfFaces]; |
512 | pObject->pNormals = new Vector3 [pObject->numOfVerts]; |
513 | |
514 | /* 遍历对象的所有面 */ |
515 | for ( int i=0; i < pObject->numOfFaces; i++) |
516 | { vPoly[0] = pObject->pVerts[pObject->pFaces[i].vertIndex[0]]; |
517 | vPoly[1] = pObject->pVerts[pObject->pFaces[i].vertIndex[1]]; |
518 | vPoly[2] = pObject->pVerts[pObject->pFaces[i].vertIndex[2]]; |
519 | |
520 | /* 计算面的法向量 */ |
521 | vVector1 = vPoly[0] - vPoly[2]; /* 获得多边形的矢量 */ |
522 | vVector2 = vPoly[2] - vPoly[1]; /* 获得多边形的第二个矢量 */ |
523 | vNormal = vVector1.crossProduct(vVector2); /* 计算两个矢量的叉积 */ |
524 | pTempNormals[i] = vNormal; |
525 | vNormal = vNormal.normalize(); /* 规一化叉积 */ |
526 | pNormals[i] = vNormal; /* 将法向量添加到法向量列表中 */ |
527 | } |
528 | |
529 | /* 计算顶点法向量 */ |
530 | Vector3 vSum(0.0,0.0,0.0); |
531 | Vector3 vZero = vSum; |
532 | int shared=0; |
533 | |
534 | /* 遍历所有的顶点 */ |
535 | for ( int i = 0; i < pObject->numOfVerts; i++) |
536 | { for ( int j = 0; j < pObject->numOfFaces; j++) /* 遍历所有的三角形面 */ |
537 | { /* 判断该点是否与其它的面共享 */ |
538 | if (pObject->pFaces[j].vertIndex[0] == i || |
539 | pObject->pFaces[j].vertIndex[1] == i || |
540 | pObject->pFaces[j].vertIndex[2] == i) |
541 | { |
542 | vSum = vSum + pTempNormals[j]; |
543 | shared++; |
544 | } |
545 | } |
546 | pObject->pNormals[i] = vSum / float (-shared); |
547 | |
548 | /* 规一化顶点法向 */ |
549 | pObject->pNormals[i] = pObject->pNormals[i].normalize(); |
550 | vSum = vZero; |
551 | shared = 0; |
552 | } |
553 | /* 释放存储空间,开始下一个对象 */ |
554 | delete [] pTempNormals; |
555 | delete [] pNormals; |
556 | } |
557 | } |
使用方法:
1 | //使用很简单,声明对象 |
2 | C3DSLoader m_3DS; |
3 | //初始化 |
4 | m_3DS.Init( "d:\plane.3ds" ); |
5 | //绘制模型 |
6 | m_3DS.Draw(); |