1 | //***********************************************************************// |
---|
2 | // // |
---|
3 | // - "Talk to me like I'm a 3 year old!" Programming Lessons - // |
---|
4 | // // |
---|
5 | // $Author: DigiBen digiben@gametutorials.com // |
---|
6 | // // |
---|
7 | // $Program: 3DS Loader // |
---|
8 | // // |
---|
9 | // $Description: Demonstrates how to load a .3ds file format // |
---|
10 | // // |
---|
11 | // $Date: 10/6/01 // |
---|
12 | // // |
---|
13 | //***********************************************************************// |
---|
14 | |
---|
15 | #include "3ds.h" |
---|
16 | #include <assert.h> |
---|
17 | #include <math.h> |
---|
18 | |
---|
19 | // Global |
---|
20 | int gBuffer[50000] = {0}; // This is used to read past unwanted data |
---|
21 | |
---|
22 | // This file handles all of the code needed to load a .3DS file. |
---|
23 | // Basically, how it works is, you load a chunk, then you check |
---|
24 | // the chunk ID. Depending on the chunk ID, you load the information |
---|
25 | // that is stored in that chunk. If you do not want to read that information, |
---|
26 | // you read past it. You know how many bytes to read past the chunk because |
---|
27 | // every chunk stores the length in bytes of that chunk. |
---|
28 | |
---|
29 | ///////////////////////////////// CLOAD3DS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
30 | ///// |
---|
31 | ///// This constructor initializes the tChunk data |
---|
32 | ///// |
---|
33 | ///////////////////////////////// CLOAD3DS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
34 | |
---|
35 | CLoad3ds::CLoad3ds() |
---|
36 | { |
---|
37 | m_FilePointer = NULL; |
---|
38 | } |
---|
39 | |
---|
40 | ///////////////////////////////// IMPORT 3DS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
41 | ///// |
---|
42 | ///// This is called by the client to open the .3ds file, read it, then clean up |
---|
43 | ///// |
---|
44 | ///////////////////////////////// IMPORT 3DS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
45 | |
---|
46 | bool CLoad3ds::Import3DS(C3dModel *pModel, char *strFileName) |
---|
47 | { |
---|
48 | char strMessage[255] = {0}; |
---|
49 | tChunk currentChunk = {0}; |
---|
50 | |
---|
51 | // Open the 3DS file |
---|
52 | m_FilePointer = fopen(strFileName, "rb"); |
---|
53 | |
---|
54 | // Make sure we have a valid file pointer (we found the file) |
---|
55 | if(!m_FilePointer) |
---|
56 | { |
---|
57 | sprintf(strMessage, "Unable to find the file: %s!", strFileName); |
---|
58 | return false; |
---|
59 | } |
---|
60 | |
---|
61 | // Once we have the file open, we need to read the very first data chunk |
---|
62 | // to see if it's a 3DS file. That way we don't read an invalid file. |
---|
63 | // If it is a 3DS file, then the first chunk ID will be equal to PRIMARY (some hex num) |
---|
64 | |
---|
65 | // Read the first chuck of the file to see if it's a 3DS file |
---|
66 | ReadChunk(¤tChunk); |
---|
67 | |
---|
68 | // Make sure this is a 3DS file |
---|
69 | if (currentChunk.ID != PRIMARY) |
---|
70 | { |
---|
71 | sprintf(strMessage, "Unable to load PRIMARY chuck from file: %s!", strFileName); |
---|
72 | return false; |
---|
73 | } |
---|
74 | |
---|
75 | // Now we actually start reading in the data. ProcessNextChunk() is recursive |
---|
76 | |
---|
77 | // Begin loading objects, by calling this recursive function |
---|
78 | ProcessNextChunk(pModel, ¤tChunk); |
---|
79 | |
---|
80 | // After we have read the whole 3DS file, we want to calculate our own vertex normals. |
---|
81 | ComputeNormals(pModel); |
---|
82 | |
---|
83 | // Clean up after everything |
---|
84 | CleanUp(); |
---|
85 | |
---|
86 | return true; |
---|
87 | } |
---|
88 | |
---|
89 | ///////////////////////////////// CLEAN UP \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
90 | ///// |
---|
91 | ///// This function cleans up our allocated memory and closes the file |
---|
92 | ///// |
---|
93 | ///////////////////////////////// CLEAN UP \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
94 | |
---|
95 | void CLoad3ds::CleanUp() |
---|
96 | { |
---|
97 | if (m_FilePointer) { |
---|
98 | fclose(m_FilePointer); // Close the current file pointer |
---|
99 | m_FilePointer = NULL; |
---|
100 | } |
---|
101 | } |
---|
102 | |
---|
103 | |
---|
104 | ///////////////////////////////// PROCESS NEXT CHUNK\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
105 | ///// |
---|
106 | ///// This function reads the main sections of the .3DS file, then dives deeper with recursion |
---|
107 | ///// |
---|
108 | ///////////////////////////////// PROCESS NEXT CHUNK\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
109 | |
---|
110 | void CLoad3ds::ProcessNextChunk(C3dModel *pModel, tChunk *pPreviousChunk) |
---|
111 | { |
---|
112 | t3dObject newObject = {0}; // This is used to add to our object list |
---|
113 | tMaterialInfo newTexture = {0}; // This is used to add to our material list |
---|
114 | |
---|
115 | tChunk currentChunk = {0}; // The current chunk to load |
---|
116 | tChunk tempChunk = {0}; // A temp chunk for holding data |
---|
117 | |
---|
118 | // Below we check our chunk ID each time we read a new chunk. Then, if |
---|
119 | // we want to extract the information from that chunk, we do so. |
---|
120 | // If we don't want a chunk, we just read past it. |
---|
121 | |
---|
122 | // Continue to read the sub chunks until we have reached the length. |
---|
123 | // After we read ANYTHING we add the bytes read to the chunk and then check |
---|
124 | // check against the length. |
---|
125 | while (pPreviousChunk->bytesRead < pPreviousChunk->length) |
---|
126 | { |
---|
127 | // Read next Chunk |
---|
128 | ReadChunk(¤tChunk); |
---|
129 | |
---|
130 | // Check the chunk ID |
---|
131 | switch (currentChunk.ID) |
---|
132 | { |
---|
133 | case VERSION: // This holds the version of the file |
---|
134 | |
---|
135 | // If the file was made in 3D Studio Max, this chunk has an int that |
---|
136 | // holds the file version. Since there might be new additions to the 3DS file |
---|
137 | // format in 4.0, we give a warning to that problem. |
---|
138 | // However, if the file wasn't made by 3D Studio Max, we don't 100% what the |
---|
139 | // version length will be so we'll simply ignore the value |
---|
140 | |
---|
141 | // Read the file version and add the bytes read to our bytesRead variable |
---|
142 | currentChunk.bytesRead += fread(gBuffer, 1, currentChunk.length - currentChunk.bytesRead, m_FilePointer); |
---|
143 | |
---|
144 | // If the file version is over 3, give a warning that there could be a problem |
---|
145 | if ((currentChunk.length - currentChunk.bytesRead == 4) && (gBuffer[0] > 0x03)) { |
---|
146 | // This 3DS file is over version 3 so it may load incorrectly |
---|
147 | } |
---|
148 | break; |
---|
149 | |
---|
150 | case OBJECTINFO: // This holds the version of the mesh |
---|
151 | { |
---|
152 | // This chunk holds the version of the mesh. It is also the head of the MATERIAL |
---|
153 | // and OBJECT chunks. From here on we start reading in the material and object info. |
---|
154 | |
---|
155 | // Read the next chunk |
---|
156 | ReadChunk(&tempChunk); |
---|
157 | |
---|
158 | // Get the version of the mesh |
---|
159 | tempChunk.bytesRead += fread(gBuffer, 1, tempChunk.length - tempChunk.bytesRead, m_FilePointer); |
---|
160 | |
---|
161 | // Increase the bytesRead by the bytes read from the last chunk |
---|
162 | currentChunk.bytesRead += tempChunk.bytesRead; |
---|
163 | |
---|
164 | // Go to the next chunk, which is the object has a texture, it should be MATERIAL, then OBJECT. |
---|
165 | ProcessNextChunk(pModel, ¤tChunk); |
---|
166 | break; |
---|
167 | } |
---|
168 | case MATERIAL: // This holds the material information |
---|
169 | |
---|
170 | // This chunk is the header for the material info chunks |
---|
171 | |
---|
172 | // Increase the number of materials |
---|
173 | pModel->numOfMaterials++; |
---|
174 | |
---|
175 | // Add a empty texture structure to our texture list. |
---|
176 | // If you are unfamiliar with STL's "vector" class, all push_back() |
---|
177 | // does is add a new node onto the list. I used the vector class |
---|
178 | // so I didn't need to write my own link list functions. |
---|
179 | pModel->pMaterials.push_back(newTexture); |
---|
180 | |
---|
181 | // Proceed to the material loading function |
---|
182 | ProcessNextMaterialChunk(pModel, ¤tChunk); |
---|
183 | break; |
---|
184 | |
---|
185 | case OBJECT: // This holds the name of the object being read |
---|
186 | |
---|
187 | // This chunk is the header for the object info chunks. It also |
---|
188 | // holds the name of the object. |
---|
189 | |
---|
190 | // Increase the object count |
---|
191 | pModel->numOfObjects++; |
---|
192 | |
---|
193 | // Add a new tObject node to our list of objects (like a link list) |
---|
194 | pModel->pObject.push_back(newObject); |
---|
195 | |
---|
196 | // Initialize the object and all it's data members |
---|
197 | memset(&(pModel->pObject[pModel->numOfObjects - 1]), 0, sizeof(t3dObject)); |
---|
198 | |
---|
199 | // Get the name of the object and store it, then add the read bytes to our byte counter. |
---|
200 | currentChunk.bytesRead += GetString(pModel->pObject[pModel->numOfObjects - 1].strName); |
---|
201 | |
---|
202 | // Now proceed to read in the rest of the object information |
---|
203 | ProcessNextObjectChunk(pModel, &(pModel->pObject[pModel->numOfObjects - 1]), ¤tChunk); |
---|
204 | break; |
---|
205 | |
---|
206 | case EDITKEYFRAME: |
---|
207 | |
---|
208 | // Because I wanted to make this a SIMPLE tutorial as possible, I did not include |
---|
209 | // the key frame information. This chunk is the header for all the animation info. |
---|
210 | // In a later tutorial this will be the subject and explained thoroughly. |
---|
211 | |
---|
212 | //ProcessNextKeyFrameChunk(pModel, currentChunk); |
---|
213 | |
---|
214 | // Read past this chunk and add the bytes read to the byte counter |
---|
215 | currentChunk.bytesRead += fread(gBuffer, 1, currentChunk.length - currentChunk.bytesRead, m_FilePointer); |
---|
216 | break; |
---|
217 | |
---|
218 | default: |
---|
219 | |
---|
220 | // If we didn't care about a chunk, then we get here. We still need |
---|
221 | // to read past the unknown or ignored chunk and add the bytes read to the byte counter. |
---|
222 | currentChunk.bytesRead += fread(gBuffer, 1, currentChunk.length - currentChunk.bytesRead, m_FilePointer); |
---|
223 | break; |
---|
224 | } |
---|
225 | |
---|
226 | // Add the bytes read from the last chunk to the previous chunk passed in. |
---|
227 | pPreviousChunk->bytesRead += currentChunk.bytesRead; |
---|
228 | } |
---|
229 | } |
---|
230 | |
---|
231 | |
---|
232 | ///////////////////////////////// PROCESS NEXT OBJECT CHUNK \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
233 | ///// |
---|
234 | ///// This function handles all the information about the objects in the file |
---|
235 | ///// |
---|
236 | ///////////////////////////////// PROCESS NEXT OBJECT CHUNK \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
237 | |
---|
238 | void CLoad3ds::ProcessNextObjectChunk(C3dModel *pModel, t3dObject *pObject, tChunk *pPreviousChunk) |
---|
239 | { |
---|
240 | // The current chunk to work with |
---|
241 | tChunk currentChunk = {0}; |
---|
242 | |
---|
243 | // Continue to read these chunks until we read the end of this sub chunk |
---|
244 | while (pPreviousChunk->bytesRead < pPreviousChunk->length) |
---|
245 | { |
---|
246 | // Read the next chunk |
---|
247 | ReadChunk(¤tChunk); |
---|
248 | |
---|
249 | // Check which chunk we just read |
---|
250 | switch (currentChunk.ID) |
---|
251 | { |
---|
252 | case OBJECT_MESH: // This lets us know that we are reading a new object |
---|
253 | |
---|
254 | // We found a new object, so let's read in it's info using recursion |
---|
255 | ProcessNextObjectChunk(pModel, pObject, ¤tChunk); |
---|
256 | break; |
---|
257 | |
---|
258 | case OBJECT_VERTICES: // This is the objects vertices |
---|
259 | ReadVertices(pObject, ¤tChunk); |
---|
260 | break; |
---|
261 | |
---|
262 | case OBJECT_FACES: // This is the objects face information |
---|
263 | ReadVertexIndices(pObject, ¤tChunk); |
---|
264 | break; |
---|
265 | |
---|
266 | case OBJECT_MATERIAL: // This holds the material name that the object has |
---|
267 | |
---|
268 | // This chunk holds the name of the material that the object has assigned to it. |
---|
269 | // This could either be just a color or a texture map. This chunk also holds |
---|
270 | // the faces that the texture is assigned to (In the case that there is multiple |
---|
271 | // textures assigned to one object, or it just has a texture on a part of the object. |
---|
272 | // Since most of my game objects just have the texture around the whole object, and |
---|
273 | // they aren't multitextured, I just want the material name. |
---|
274 | |
---|
275 | // We now will read the name of the material assigned to this object |
---|
276 | ReadObjectMaterial(pModel, pObject, ¤tChunk); |
---|
277 | break; |
---|
278 | |
---|
279 | case OBJECT_UV: // This holds the UV texture coordinates for the object |
---|
280 | |
---|
281 | // This chunk holds all of the UV coordinates for our object. Let's read them in. |
---|
282 | ReadUVCoordinates(pObject, ¤tChunk); |
---|
283 | break; |
---|
284 | |
---|
285 | default: |
---|
286 | |
---|
287 | // Read past the ignored or unknown chunks |
---|
288 | currentChunk.bytesRead += fread(gBuffer, 1, currentChunk.length - currentChunk.bytesRead, m_FilePointer); |
---|
289 | break; |
---|
290 | } |
---|
291 | |
---|
292 | // Add the bytes read from the last chunk to the previous chunk passed in. |
---|
293 | pPreviousChunk->bytesRead += currentChunk.bytesRead; |
---|
294 | } |
---|
295 | } |
---|
296 | |
---|
297 | |
---|
298 | ///////////////////////////////// PROCESS NEXT MATERIAL CHUNK \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
299 | ///// |
---|
300 | ///// This function handles all the information about the material (Texture) |
---|
301 | ///// |
---|
302 | ///////////////////////////////// PROCESS NEXT MATERIAL CHUNK \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
303 | |
---|
304 | void CLoad3ds::ProcessNextMaterialChunk(C3dModel *pModel, tChunk *pPreviousChunk) |
---|
305 | { |
---|
306 | // The current chunk to work with |
---|
307 | tChunk currentChunk = {0}; |
---|
308 | |
---|
309 | // Continue to read these chunks until we read the end of this sub chunk |
---|
310 | while (pPreviousChunk->bytesRead < pPreviousChunk->length) |
---|
311 | { |
---|
312 | // Read the next chunk |
---|
313 | ReadChunk(¤tChunk); |
---|
314 | |
---|
315 | // Check which chunk we just read in |
---|
316 | switch (currentChunk.ID) |
---|
317 | { |
---|
318 | case MATNAME: // This chunk holds the name of the material |
---|
319 | |
---|
320 | // Here we read in the material name |
---|
321 | currentChunk.bytesRead += fread(pModel->pMaterials[pModel->numOfMaterials - 1].strName, 1, currentChunk.length - currentChunk.bytesRead, m_FilePointer); |
---|
322 | break; |
---|
323 | |
---|
324 | case MATDIFFUSE: // This holds the R G B color of our object |
---|
325 | ReadColorChunk(&(pModel->pMaterials[pModel->numOfMaterials - 1]), ¤tChunk); |
---|
326 | break; |
---|
327 | |
---|
328 | case MATMAP: // This is the header for the texture info |
---|
329 | |
---|
330 | // Proceed to read in the material information |
---|
331 | ProcessNextMaterialChunk(pModel, ¤tChunk); |
---|
332 | break; |
---|
333 | |
---|
334 | case MATMAPFILE: // This stores the file name of the material |
---|
335 | |
---|
336 | // Here we read in the material's file name |
---|
337 | currentChunk.bytesRead += fread(pModel->pMaterials[pModel->numOfMaterials - 1].strFile, 1, currentChunk.length - currentChunk.bytesRead, m_FilePointer); |
---|
338 | break; |
---|
339 | |
---|
340 | default: |
---|
341 | |
---|
342 | // Read past the ignored or unknown chunks |
---|
343 | currentChunk.bytesRead += fread(gBuffer, 1, currentChunk.length - currentChunk.bytesRead, m_FilePointer); |
---|
344 | break; |
---|
345 | } |
---|
346 | |
---|
347 | // Add the bytes read from the last chunk to the previous chunk passed in. |
---|
348 | pPreviousChunk->bytesRead += currentChunk.bytesRead; |
---|
349 | } |
---|
350 | } |
---|
351 | |
---|
352 | ///////////////////////////////// READ CHUNK \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
353 | ///// |
---|
354 | ///// This function reads in a chunk ID and it's length in bytes |
---|
355 | ///// |
---|
356 | ///////////////////////////////// READ CHUNK \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
357 | |
---|
358 | void CLoad3ds::ReadChunk(tChunk *pChunk) |
---|
359 | { |
---|
360 | // This reads the chunk ID which is 2 bytes. |
---|
361 | // The chunk ID is like OBJECT or MATERIAL. It tells what data is |
---|
362 | // able to be read in within the chunks section. |
---|
363 | pChunk->bytesRead = fread(&pChunk->ID, 1, 2, m_FilePointer); |
---|
364 | |
---|
365 | // Then, we read the length of the chunk which is 4 bytes. |
---|
366 | // This is how we know how much to read in, or read past. |
---|
367 | pChunk->bytesRead += fread(&pChunk->length, 1, 4, m_FilePointer); |
---|
368 | } |
---|
369 | |
---|
370 | ///////////////////////////////// GET STRING \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
371 | ///// |
---|
372 | ///// This function reads in a string of characters |
---|
373 | ///// |
---|
374 | ///////////////////////////////// GET STRING \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
375 | |
---|
376 | int CLoad3ds::GetString(char *pBuffer) |
---|
377 | { |
---|
378 | int index = 0; |
---|
379 | |
---|
380 | // Read 1 byte of data which is the first letter of the string |
---|
381 | fread(pBuffer, 1, 1, m_FilePointer); |
---|
382 | |
---|
383 | // Loop until we get NULL |
---|
384 | while (*(pBuffer + index++) != 0) { |
---|
385 | |
---|
386 | // Read in a character at a time until we hit NULL. |
---|
387 | fread(pBuffer + index, 1, 1, m_FilePointer); |
---|
388 | } |
---|
389 | |
---|
390 | // Return the string length, which is how many bytes we read in (including the NULL) |
---|
391 | return strlen(pBuffer) + 1; |
---|
392 | } |
---|
393 | |
---|
394 | |
---|
395 | ///////////////////////////////// READ COLOR \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
396 | ///// |
---|
397 | ///// This function reads in the RGB color data |
---|
398 | ///// |
---|
399 | ///////////////////////////////// READ COLOR \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
400 | |
---|
401 | void CLoad3ds::ReadColorChunk(tMaterialInfo *pMaterial, tChunk *pChunk) |
---|
402 | { |
---|
403 | tChunk tempChunk = {0}; |
---|
404 | |
---|
405 | // Read the color chunk info |
---|
406 | ReadChunk(&tempChunk); |
---|
407 | |
---|
408 | // Read in the R G B color (3 bytes - 0 through 255) |
---|
409 | tempChunk.bytesRead += fread(pMaterial->color, 1, tempChunk.length - tempChunk.bytesRead, m_FilePointer); |
---|
410 | |
---|
411 | // Add the bytes read to our chunk |
---|
412 | pChunk->bytesRead += tempChunk.bytesRead; |
---|
413 | } |
---|
414 | |
---|
415 | |
---|
416 | ///////////////////////////////// READ VERTEX INDECES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
417 | ///// |
---|
418 | ///// This function reads in the indices for the vertex array |
---|
419 | ///// |
---|
420 | ///////////////////////////////// READ VERTEX INDECES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
421 | |
---|
422 | void CLoad3ds::ReadVertexIndices(t3dObject *pObject, tChunk *pPreviousChunk) |
---|
423 | { |
---|
424 | unsigned short index = 0; // This is used to read in the current face index |
---|
425 | |
---|
426 | // In order to read in the vertex indices for the object, we need to first |
---|
427 | // read in the number of them, then read them in. Remember, |
---|
428 | // we only want 3 of the 4 values read in for each face. The fourth is |
---|
429 | // a visibility flag for 3D Studio Max that doesn't mean anything to us. |
---|
430 | |
---|
431 | // Read in the number of faces that are in this object (int) |
---|
432 | pPreviousChunk->bytesRead += fread(&pObject->iNumOfFaces, 1, 2, m_FilePointer); |
---|
433 | |
---|
434 | // Alloc enough memory for the faces and initialize the structure |
---|
435 | pObject->pFaces = new tFace [pObject->iNumOfFaces]; |
---|
436 | memset(pObject->pFaces, 0, sizeof(tFace) * pObject->iNumOfFaces); |
---|
437 | |
---|
438 | // Go through all of the faces in this object |
---|
439 | for(int i = 0; i < pObject->iNumOfFaces; i++) |
---|
440 | { |
---|
441 | // Next, we read in the A then B then C index for the face, but ignore the 4th value. |
---|
442 | // The fourth value is a visibility flag for 3D Studio Max, we don't care about this. |
---|
443 | for(int j = 0; j < 4; j++) |
---|
444 | { |
---|
445 | // Read the first vertice index for the current face |
---|
446 | pPreviousChunk->bytesRead += fread(&index, 1, sizeof(index), m_FilePointer); |
---|
447 | |
---|
448 | if(j < 3) |
---|
449 | { |
---|
450 | // Store the index in our face structure. |
---|
451 | pObject->pFaces[i].vertIndex[j] = index; |
---|
452 | } |
---|
453 | } |
---|
454 | } |
---|
455 | } |
---|
456 | |
---|
457 | |
---|
458 | ///////////////////////////////// READ UV COORDINATES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
459 | ///// |
---|
460 | ///// This function reads in the UV coordinates for the object |
---|
461 | ///// |
---|
462 | ///////////////////////////////// READ UV COORDINATES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
463 | |
---|
464 | void CLoad3ds::ReadUVCoordinates(t3dObject *pObject, tChunk *pPreviousChunk) |
---|
465 | { |
---|
466 | // In order to read in the UV indices for the object, we need to first |
---|
467 | // read in the amount there are, then read them in. |
---|
468 | |
---|
469 | // Read in the number of UV coordinates there are (int) |
---|
470 | pPreviousChunk->bytesRead += fread(&pObject->iNumTexVertex, 1, 2, m_FilePointer); |
---|
471 | |
---|
472 | // Allocate memory to hold the UV coordinates |
---|
473 | pObject->pTexVerts = new CVector2 [pObject->iNumTexVertex]; |
---|
474 | |
---|
475 | // Read in the texture coodinates (an array 2 float) |
---|
476 | pPreviousChunk->bytesRead += fread(pObject->pTexVerts, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer); |
---|
477 | } |
---|
478 | |
---|
479 | |
---|
480 | ///////////////////////////////// READ VERTICES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
481 | ///// |
---|
482 | ///// This function reads in the vertices for the object |
---|
483 | ///// |
---|
484 | ///////////////////////////////// READ VERTICES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
485 | |
---|
486 | void CLoad3ds::ReadVertices(t3dObject *pObject, tChunk *pPreviousChunk) |
---|
487 | { |
---|
488 | // Like most chunks, before we read in the actual vertices, we need |
---|
489 | // to find out how many there are to read in. Once we have that number |
---|
490 | // we then fread() them into our vertice array. |
---|
491 | |
---|
492 | // Read in the number of vertices (int) |
---|
493 | pPreviousChunk->bytesRead += fread(&(pObject->iNumOfVerts), 1, 2, m_FilePointer); |
---|
494 | |
---|
495 | // Allocate the memory for the verts and initialize the structure |
---|
496 | pObject->pVerts = new CVector3 [pObject->iNumOfVerts]; |
---|
497 | memset(pObject->pVerts, 0, sizeof(CVector3) * pObject->iNumOfVerts); |
---|
498 | |
---|
499 | // Read in the array of vertices (an array of 3 floats) |
---|
500 | pPreviousChunk->bytesRead += fread(pObject->pVerts, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer); |
---|
501 | |
---|
502 | // Now we should have all of the vertices read in. Because 3D Studio Max |
---|
503 | // Models with the Z-Axis pointing up (strange and ugly I know!), we need |
---|
504 | // to flip the y values with the z values in our vertices. That way it |
---|
505 | // will be normal, with Y pointing up. If you prefer to work with Z pointing |
---|
506 | // up, then just delete this next loop. Also, because we swap the Y and Z |
---|
507 | // we need to negate the Z to make it come out correctly. |
---|
508 | |
---|
509 | // Go through all of the vertices that we just read and swap the Y and Z values |
---|
510 | for(int i = 0; i < pObject->iNumOfVerts; i++) |
---|
511 | { |
---|
512 | // Store off the Y value |
---|
513 | float fTempY = pObject->pVerts[i].y; |
---|
514 | |
---|
515 | // Set the Y value to the Z value |
---|
516 | pObject->pVerts[i].y = pObject->pVerts[i].z; |
---|
517 | |
---|
518 | // Set the Z value to the Y value, |
---|
519 | // but negative Z because 3D Studio max does the opposite. |
---|
520 | pObject->pVerts[i].z = -fTempY; |
---|
521 | } |
---|
522 | } |
---|
523 | |
---|
524 | |
---|
525 | ///////////////////////////////// READ OBJECT MATERIAL \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
526 | ///// |
---|
527 | ///// This function reads in the material name assigned to the object and sets the materialID |
---|
528 | ///// |
---|
529 | ///////////////////////////////// READ OBJECT MATERIAL \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
530 | |
---|
531 | void CLoad3ds::ReadObjectMaterial(C3dModel *pModel, t3dObject *pObject, tChunk *pPreviousChunk) |
---|
532 | { |
---|
533 | char strMaterial[255] = {0}; // This is used to hold the objects material name |
---|
534 | |
---|
535 | // *What is a material?* - A material is either the color or the texture map of the object. |
---|
536 | // It can also hold other information like the brightness, shine, etc... Stuff we don't |
---|
537 | // really care about. We just want the color, or the texture map file name really. |
---|
538 | |
---|
539 | // Here we read the material name that is assigned to the current object. |
---|
540 | // strMaterial should now have a string of the material name, like "Material #2" etc.. |
---|
541 | pPreviousChunk->bytesRead += GetString(strMaterial); |
---|
542 | |
---|
543 | // Now that we have a material name, we need to go through all of the materials |
---|
544 | // and check the name against each material. When we find a material in our material |
---|
545 | // list that matches this name we just read in, then we assign the materialID |
---|
546 | // of the object to that material index. You will notice that we passed in the |
---|
547 | // model to this function. This is because we need the number of textures. |
---|
548 | // Yes though, we could have just passed in the model and not the object too. |
---|
549 | |
---|
550 | // Go through all of the textures |
---|
551 | for(int i = 0; i < pModel->numOfMaterials; i++) |
---|
552 | { |
---|
553 | // If the material we just read in matches the current texture name |
---|
554 | if(strcmp(strMaterial, pModel->pMaterials[i].strName) == 0) |
---|
555 | { |
---|
556 | // Set the material ID to the current index 'i' and stop checking |
---|
557 | pObject->materialID = i; |
---|
558 | |
---|
559 | // Now that we found the material, check if it's a texture map. |
---|
560 | // If the strFile has a string length of 1 and over it's a texture |
---|
561 | if(strlen(pModel->pMaterials[i].strFile) > 0) { |
---|
562 | |
---|
563 | // Set the object's flag to say it has a texture map to bind. |
---|
564 | pObject->bHasTexture = true; |
---|
565 | } |
---|
566 | break; |
---|
567 | } |
---|
568 | else |
---|
569 | { |
---|
570 | // Set the ID to -1 to show there is no material for this object |
---|
571 | pObject->materialID = -1; |
---|
572 | } |
---|
573 | } |
---|
574 | |
---|
575 | // Read past the rest of the chunk since we don't care about shared vertices |
---|
576 | // You will notice we subtract the bytes already read in this chunk from the total length. |
---|
577 | pPreviousChunk->bytesRead += fread(gBuffer, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer); |
---|
578 | } |
---|
579 | |
---|
580 | // *Note* |
---|
581 | // |
---|
582 | // Below are some math functions for calculating vertex normals. We want vertex normals |
---|
583 | // because it makes the lighting look really smooth and life like. You probably already |
---|
584 | // have these functions in the rest of your engine, so you can delete these and call |
---|
585 | // your own. I wanted to add them so I could show how to calculate vertex normals. |
---|
586 | |
---|
587 | ////////////////////////////// Math Functions ////////////////////////////////* |
---|
588 | |
---|
589 | // This computes the magnitude of a normal. (magnitude = sqrt(x^2 + y^2 + z^2) |
---|
590 | #define Mag(Normal) (sqrt(Normal.x*Normal.x + Normal.y*Normal.y + Normal.z*Normal.z)) |
---|
591 | |
---|
592 | // This calculates a vector between 2 points and returns the result |
---|
593 | CVector3 Vector(CVector3 vPoint1, CVector3 vPoint2) |
---|
594 | { |
---|
595 | CVector3 vVector; // The variable to hold the resultant vector |
---|
596 | |
---|
597 | vVector.x = vPoint1.x - vPoint2.x; // Subtract point1 and point2 x's |
---|
598 | vVector.y = vPoint1.y - vPoint2.y; // Subtract point1 and point2 y's |
---|
599 | vVector.z = vPoint1.z - vPoint2.z; // Subtract point1 and point2 z's |
---|
600 | |
---|
601 | return vVector; // Return the resultant vector |
---|
602 | } |
---|
603 | |
---|
604 | // This adds 2 vectors together and returns the result |
---|
605 | CVector3 AddVector(CVector3 vVector1, CVector3 vVector2) |
---|
606 | { |
---|
607 | CVector3 vResult; // The variable to hold the resultant vector |
---|
608 | |
---|
609 | vResult.x = vVector2.x + vVector1.x; // Add Vector1 and Vector2 x's |
---|
610 | vResult.y = vVector2.y + vVector1.y; // Add Vector1 and Vector2 y's |
---|
611 | vResult.z = vVector2.z + vVector1.z; // Add Vector1 and Vector2 z's |
---|
612 | |
---|
613 | return vResult; // Return the resultant vector |
---|
614 | } |
---|
615 | |
---|
616 | // This divides a vector by a single number (scalar) and returns the result |
---|
617 | CVector3 DivideVectorByScaler(CVector3 vVector1, float Scaler) |
---|
618 | { |
---|
619 | CVector3 vResult; // The variable to hold the resultant vector |
---|
620 | |
---|
621 | vResult.x = vVector1.x / Scaler; // Divide Vector1's x value by the scaler |
---|
622 | vResult.y = vVector1.y / Scaler; // Divide Vector1's y value by the scaler |
---|
623 | vResult.z = vVector1.z / Scaler; // Divide Vector1's z value by the scaler |
---|
624 | |
---|
625 | return vResult; // Return the resultant vector |
---|
626 | } |
---|
627 | |
---|
628 | // This returns the cross product between 2 vectors |
---|
629 | CVector3 Cross(CVector3 vVector1, CVector3 vVector2) |
---|
630 | { |
---|
631 | CVector3 vCross; // The vector to hold the cross product |
---|
632 | // Get the X value |
---|
633 | vCross.x = ((vVector1.y * vVector2.z) - (vVector1.z * vVector2.y)); |
---|
634 | // Get the Y value |
---|
635 | vCross.y = ((vVector1.z * vVector2.x) - (vVector1.x * vVector2.z)); |
---|
636 | // Get the Z value |
---|
637 | vCross.z = ((vVector1.x * vVector2.y) - (vVector1.y * vVector2.x)); |
---|
638 | |
---|
639 | return vCross; // Return the cross product |
---|
640 | } |
---|
641 | |
---|
642 | // This returns the normal of a vector |
---|
643 | CVector3 Normalize(CVector3 vNormal) |
---|
644 | { |
---|
645 | double Magnitude; // This holds the magitude |
---|
646 | |
---|
647 | Magnitude = Mag(vNormal); // Get the magnitude |
---|
648 | |
---|
649 | vNormal.x /= (float)Magnitude; // Divide the vector's X by the magnitude |
---|
650 | vNormal.y /= (float)Magnitude; // Divide the vector's Y by the magnitude |
---|
651 | vNormal.z /= (float)Magnitude; // Divide the vector's Z by the magnitude |
---|
652 | |
---|
653 | return vNormal; // Return the normal |
---|
654 | } |
---|
655 | |
---|
656 | ///////////////////////////////// COMPUTER NORMALS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
657 | ///// |
---|
658 | ///// This function computes the normals and vertex normals of the objects |
---|
659 | ///// |
---|
660 | ///////////////////////////////// COMPUTER NORMALS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* |
---|
661 | |
---|
662 | void CLoad3ds::ComputeNormals(C3dModel *pModel) |
---|
663 | { |
---|
664 | CVector3 vVector1, vVector2, vNormal, vPoly[3]; |
---|
665 | |
---|
666 | // If there are no objects, we can skip this part |
---|
667 | if(pModel->numOfObjects <= 0) |
---|
668 | return; |
---|
669 | |
---|
670 | // What are vertex normals? And how are they different from other normals? |
---|
671 | // Well, if you find the normal to a triangle, you are finding a "Face Normal". |
---|
672 | // If you give OpenGL a face normal for lighting, it will make your object look |
---|
673 | // really flat and not very round. If we find the normal for each vertex, it makes |
---|
674 | // the smooth lighting look. This also covers up blocky looking objects and they appear |
---|
675 | // to have more polygons than they do. Basically, what you do is first |
---|
676 | // calculate the face normals, then you take the average of all the normals around each |
---|
677 | // vertex. It's just averaging. That way you get a better approximation for that vertex. |
---|
678 | |
---|
679 | // Go through each of the objects to calculate their normals |
---|
680 | for(int index = 0; index < pModel->numOfObjects; index++) |
---|
681 | { |
---|
682 | // Get the current object |
---|
683 | t3dObject *pObject = &(pModel->pObject[index]); |
---|
684 | |
---|
685 | // Here we allocate all the memory we need to calculate the normals |
---|
686 | CVector3 *pNormals = new CVector3 [pObject->iNumOfFaces]; |
---|
687 | CVector3 *pTempNormals = new CVector3 [pObject->iNumOfFaces]; |
---|
688 | pObject->pNormals = new CVector3 [pObject->iNumOfVerts]; |
---|
689 | |
---|
690 | // Go though all of the faces of this object |
---|
691 | for(int i=0; i < pObject->iNumOfFaces; i++) |
---|
692 | { |
---|
693 | // To cut down LARGE code, we extract the 3 points of this face |
---|
694 | vPoly[0] = pObject->pVerts[pObject->pFaces[i].vertIndex[0]]; |
---|
695 | vPoly[1] = pObject->pVerts[pObject->pFaces[i].vertIndex[1]]; |
---|
696 | vPoly[2] = pObject->pVerts[pObject->pFaces[i].vertIndex[2]]; |
---|
697 | |
---|
698 | // Now let's calculate the face normals (Get 2 vectors and find the cross product of those 2) |
---|
699 | |
---|
700 | vVector1 = Vector(vPoly[0], vPoly[2]); // Get the vector of the polygon (we just need 2 sides for the normal) |
---|
701 | vVector2 = Vector(vPoly[2], vPoly[1]); // Get a second vector of the polygon |
---|
702 | |
---|
703 | vNormal = Cross(vVector1, vVector2); // Return the cross product of the 2 vectors (normalize vector, but not a unit vector) |
---|
704 | pTempNormals[i] = vNormal; // Save the un-normalized normal for the vertex normals |
---|
705 | vNormal = Normalize(vNormal); // Normalize the cross product to give us the polygons normal |
---|
706 | |
---|
707 | pNormals[i] = vNormal; // Assign the normal to the list of normals |
---|
708 | } |
---|
709 | |
---|
710 | //////////////// Now Get The Vertex Normals ///////////////// |
---|
711 | |
---|
712 | CVector3 vSum = {0.0, 0.0, 0.0}; |
---|
713 | CVector3 vZero = vSum; |
---|
714 | int shared=0; |
---|
715 | |
---|
716 | for (int i = 0; i < pObject->iNumOfVerts; i++) // Go through all of the vertices |
---|
717 | { |
---|
718 | for (int j = 0; j < pObject->iNumOfFaces; j++) // Go through all of the triangles |
---|
719 | { // Check if the vertex is shared by another face |
---|
720 | if (pObject->pFaces[j].vertIndex[0] == i || |
---|
721 | pObject->pFaces[j].vertIndex[1] == i || |
---|
722 | pObject->pFaces[j].vertIndex[2] == i) |
---|
723 | { |
---|
724 | vSum = AddVector(vSum, pTempNormals[j]);// Add the un-normalized normal of the shared face |
---|
725 | shared++; // Increase the number of shared triangles |
---|
726 | } |
---|
727 | } |
---|
728 | |
---|
729 | // Get the normal by dividing the sum by the shared. We negate the shared so it has the normals pointing out. |
---|
730 | pObject->pNormals[i] = DivideVectorByScaler(vSum, float(-shared)); |
---|
731 | |
---|
732 | // Normalize the normal for the final vertex normal |
---|
733 | pObject->pNormals[i] = Normalize(pObject->pNormals[i]); |
---|
734 | |
---|
735 | vSum = vZero; // Reset the sum |
---|
736 | shared = 0; // Reset the shared |
---|
737 | } |
---|
738 | |
---|
739 | // Free our memory and start over on the next object |
---|
740 | delete [] pTempNormals; |
---|
741 | delete [] pNormals; |
---|
742 | } |
---|
743 | } |
---|
744 | |
---|
745 | |
---|
746 | ///////////////////////////////////////////////////////////////////////////////// |
---|
747 | // |
---|
748 | // * QUICK NOTES * |
---|
749 | // |
---|
750 | // This was a HUGE amount of knowledge and probably the largest tutorial yet! |
---|
751 | // In the next tutorial we will show you how to load a text file format called .obj. |
---|
752 | // This is the most common 3D file format that almost ANY 3D software will import. |
---|
753 | // |
---|
754 | // Once again I should point out that the coordinate system of OpenGL and 3DS Max are different. |
---|
755 | // Since 3D Studio Max Models with the Z-Axis pointing up (strange and ugly I know! :), |
---|
756 | // we need to flip the y values with the z values in our vertices. That way it |
---|
757 | // will be normal, with Y pointing up. Also, because we swap the Y and Z we need to negate |
---|
758 | // the Z to make it come out correctly. This is also explained and done in ReadVertices(). |
---|
759 | // |
---|
760 | // CHUNKS: What is a chunk anyway? |
---|
761 | // |
---|
762 | // "The chunk ID is a unique code which identifies the type of data in this chunk |
---|
763 | // and also may indicate the existence of subordinate chunks. The chunk length indicates |
---|
764 | // the length of following data to be associated with this chunk. Note, this may |
---|
765 | // contain more data than just this chunk. If the length of data is greater than that |
---|
766 | // needed to fill in the information for the chunk, additional subordinate chunks are |
---|
767 | // attached to this chunk immediately following any data needed for this chunk, and |
---|
768 | // should be parsed out. These subordinate chunks may themselves contain subordinate chunks. |
---|
769 | // Unfortunately, there is no indication of the length of data, which is owned by the current |
---|
770 | // chunk, only the total length of data attached to the chunk, which means that the only way |
---|
771 | // to parse out subordinate chunks is to know the exact format of the owning chunk. On the |
---|
772 | // other hand, if a chunk is unknown, the parsing program can skip the entire chunk and |
---|
773 | // subordinate chunks in one jump. " - Jeff Lewis (werewolf@worldgate.com) |
---|
774 | // |
---|
775 | // In a short amount of words, a chunk is defined this way: |
---|
776 | // 2 bytes - Stores the chunk ID (OBJECT, MATERIAL, PRIMARY, etc...) |
---|
777 | // 4 bytes - Stores the length of that chunk. That way you know when that |
---|
778 | // chunk is done and there is a new chunk. |
---|
779 | // |
---|
780 | // So, to start reading the 3DS file, you read the first 2 bytes of it, then |
---|
781 | // the length (using fread()). It should be the PRIMARY chunk, otherwise it isn't |
---|
782 | // a .3DS file. |
---|
783 | // |
---|
784 | // Below is a list of the order that you will find the chunks and all the know chunks. |
---|
785 | // If you go to www.wosit.org you can find a few documents on the 3DS file format. |
---|
786 | // You can also take a look at the 3DS Format.rtf that is included with this tutorial. |
---|
787 | // |
---|
788 | // |
---|
789 | // |
---|
790 | // MAIN3DS (0x4D4D) |
---|
791 | // | |
---|
792 | // +--EDIT3DS (0x3D3D) |
---|
793 | // | | |
---|
794 | // | +--EDIT_MATERIAL (0xAFFF) |
---|
795 | // | | | |
---|
796 | // | | +--MAT_NAME01 (0xA000) (See mli Doc) |
---|
797 | // | | |
---|
798 | // | +--EDIT_CONFIG1 (0x0100) |
---|
799 | // | +--EDIT_CONFIG2 (0x3E3D) |
---|
800 | // | +--EDIT_VIEW_P1 (0x7012) |
---|
801 | // | | | |
---|
802 | // | | +--TOP (0x0001) |
---|
803 | // | | +--BOTTOM (0x0002) |
---|
804 | // | | +--LEFT (0x0003) |
---|
805 | // | | +--RIGHT (0x0004) |
---|
806 | // | | +--FRONT (0x0005) |
---|
807 | // | | +--BACK (0x0006) |
---|
808 | // | | +--USER (0x0007) |
---|
809 | // | | +--CAMERA (0xFFFF) |
---|
810 | // | | +--LIGHT (0x0009) |
---|
811 | // | | +--DISABLED (0x0010) |
---|
812 | // | | +--BOGUS (0x0011) |
---|
813 | // | | |
---|
814 | // | +--EDIT_VIEW_P2 (0x7011) |
---|
815 | // | | | |
---|
816 | // | | +--TOP (0x0001) |
---|
817 | // | | +--BOTTOM (0x0002) |
---|
818 | // | | +--LEFT (0x0003) |
---|
819 | // | | +--RIGHT (0x0004) |
---|
820 | // | | +--FRONT (0x0005) |
---|
821 | // | | +--BACK (0x0006) |
---|
822 | // | | +--USER (0x0007) |
---|
823 | // | | +--CAMERA (0xFFFF) |
---|
824 | // | | +--LIGHT (0x0009) |
---|
825 | // | | +--DISABLED (0x0010) |
---|
826 | // | | +--BOGUS (0x0011) |
---|
827 | // | | |
---|
828 | // | +--EDIT_VIEW_P3 (0x7020) |
---|
829 | // | +--EDIT_VIEW1 (0x7001) |
---|
830 | // | +--EDIT_BACKGR (0x1200) |
---|
831 | // | +--EDIT_AMBIENT (0x2100) |
---|
832 | // | +--EDIT_OBJECT (0x4000) |
---|
833 | // | | | |
---|
834 | // | | +--OBJ_TRIMESH (0x4100) |
---|
835 | // | | | | |
---|
836 | // | | | +--TRI_VERTEXL (0x4110) |
---|
837 | // | | | +--TRI_VERTEXOPTIONS (0x4111) |
---|
838 | // | | | +--TRI_MAPPINGCOORS (0x4140) |
---|
839 | // | | | +--TRI_MAPPINGSTANDARD (0x4170) |
---|
840 | // | | | +--TRI_FACEL1 (0x4120) |
---|
841 | // | | | | | |
---|
842 | // | | | | +--TRI_SMOOTH (0x4150) |
---|
843 | // | | | | +--TRI_MATERIAL (0x4130) |
---|
844 | // | | | | |
---|
845 | // | | | +--TRI_LOCAL (0x4160) |
---|
846 | // | | | +--TRI_VISIBLE (0x4165) |
---|
847 | // | | | |
---|
848 | // | | +--OBJ_LIGHT (0x4600) |
---|
849 | // | | | | |
---|
850 | // | | | +--LIT_OFF (0x4620) |
---|
851 | // | | | +--LIT_SPOT (0x4610) |
---|
852 | // | | | +--LIT_UNKNWN01 (0x465A) |
---|
853 | // | | | |
---|
854 | // | | +--OBJ_CAMERA (0x4700) |
---|
855 | // | | | | |
---|
856 | // | | | +--CAM_UNKNWN01 (0x4710) |
---|
857 | // | | | +--CAM_UNKNWN02 (0x4720) |
---|
858 | // | | | |
---|
859 | // | | +--OBJ_UNKNWN01 (0x4710) |
---|
860 | // | | +--OBJ_UNKNWN02 (0x4720) |
---|
861 | // | | |
---|
862 | // | +--EDIT_UNKNW01 (0x1100) |
---|
863 | // | +--EDIT_UNKNW02 (0x1201) |
---|
864 | // | +--EDIT_UNKNW03 (0x1300) |
---|
865 | // | +--EDIT_UNKNW04 (0x1400) |
---|
866 | // | +--EDIT_UNKNW05 (0x1420) |
---|
867 | // | +--EDIT_UNKNW06 (0x1450) |
---|
868 | // | +--EDIT_UNKNW07 (0x1500) |
---|
869 | // | +--EDIT_UNKNW08 (0x2200) |
---|
870 | // | +--EDIT_UNKNW09 (0x2201) |
---|
871 | // | +--EDIT_UNKNW10 (0x2210) |
---|
872 | // | +--EDIT_UNKNW11 (0x2300) |
---|
873 | // | +--EDIT_UNKNW12 (0x2302) |
---|
874 | // | +--EDIT_UNKNW13 (0x2000) |
---|
875 | // | +--EDIT_UNKNW14 (0xAFFF) |
---|
876 | // | |
---|
877 | // +--KEYF3DS (0xB000) |
---|
878 | // | |
---|
879 | // +--KEYF_UNKNWN01 (0xB00A) |
---|
880 | // +--............. (0x7001) ( viewport, same as editor ) |
---|
881 | // +--KEYF_FRAMES (0xB008) |
---|
882 | // +--KEYF_UNKNWN02 (0xB009) |
---|
883 | // +--KEYF_OBJDES (0xB002) |
---|
884 | // | |
---|
885 | // +--KEYF_OBJHIERARCH (0xB010) |
---|
886 | // +--KEYF_OBJDUMMYNAME (0xB011) |
---|
887 | // +--KEYF_OBJUNKNWN01 (0xB013) |
---|
888 | // +--KEYF_OBJUNKNWN02 (0xB014) |
---|
889 | // +--KEYF_OBJUNKNWN03 (0xB015) |
---|
890 | // +--KEYF_OBJPIVOT (0xB020) |
---|
891 | // +--KEYF_OBJUNKNWN04 (0xB021) |
---|
892 | // +--KEYF_OBJUNKNWN05 (0xB022) |
---|
893 | // |
---|
894 | // Once you know how to read chunks, all you have to know is the ID you are looking for |
---|
895 | // and what data is stored after that ID. You need to get the file format for that. |
---|
896 | // I can give it to you if you want, or you can go to www.wosit.org for several versions. |
---|
897 | // Because this is a proprietary format, it isn't a official document. |
---|
898 | // |
---|
899 | // I know there was a LOT of information blown over, but it is too much knowledge for |
---|
900 | // one tutorial. In the animation tutorial that I eventually will get to, some of |
---|
901 | // the things explained here will be explained in more detail. I do not claim that |
---|
902 | // this is the best .3DS tutorial, or even a GOOD one :) But it is a good start, and there |
---|
903 | // isn't much code out there that is simple when it comes to reading .3DS files. |
---|
904 | // So far, this is the best I have seen. That is why I made it :) |
---|
905 | // |
---|
906 | // I would like to thank www.wosit.org and Terry Caton (tcaton@umr.edu) for his help on this. |
---|
907 | // |
---|
908 | // Let me know if this helps you out! |
---|
909 | // |
---|
910 | // |
---|
911 | // Ben Humphrey (DigiBen) |
---|
912 | // Game Programmer |
---|
913 | // DigiBen@GameTutorials.com |
---|
914 | // Co-Web Host of www.GameTutorials.com |
---|
915 | // |
---|
916 | // |
---|