[2035] | 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 | // |
---|