long fileSize = inputFile.tellg();
inputFile.seekg(0, ios::beg);
The above code determines the size of the file in bytes.
byte *pBuffer = new byte[fileSize];
inputFile.read(pBuffer, fileSize);
inputFile.close();
Then the file is read into a temporary buffer in its entirety.
const byte *pPtr = pBuffer;
MS3DHeader *pHeader = ( MS3DHeader* )pPtr;
pPtr += sizeof( MS3DHeader );
if (strncmp(pHeader->m_ID, "MS3D000000", 10) != 0) return false; // "Not A Valid Milkshape3D Model File."
if (pHeader->m_version < 3 || pHeader->m_version > 4) return false; // "Unhandled File Version. Only Milkshape3D Version 1.3 And 1.4 Is Supported."
Now, a pointer is acquired to out current position in the file, pPtr. A pointer to the header is saved, and then the pointer is advanced past the header. You will notice several MS3D… structures being used here. These are declared at the top of MilkshapeModel.cpp, and come directly from the file format specification. The fields of the header are checked to make sure that this is a valid file we are reading.
int nVertices = *( word* )pPtr;
m_numVertices = nVertices;
m_pVertices = new Vertex[nVertices];
pPtr += sizeof( word );
int i;
for (i = 0; i < nVertices; i++) {
MS3DVertex *pVertex = ( MS3DVertex* )pPtr;
m_pVertices[i].m_boneID = pVertex->m_boneID;
memcpy( m_pVertices[i].m_location, pVertex->m_vertex, sizeof( float )*3 );
pPtr += sizeof( MS3DVertex );
}
The above code reads each of the vertex structures in the file. First memory is allocated in the model for the vertices, and then each is parsed from the file as the pointer is advanced. Several calls to memcpy will be used in this function, which copies the contents of the small arrays easily. The m_boneID member can still be ignored for now – its for skeletal animation!
int nTriangles = *( word* )pPtr;
m_numTriangles = nTriangles;
m_pTriangles = new Triangle[nTriangles];
pPtr += sizeof( word );
for ( i = 0; i < nTriangles; i++ ) {
MS3DTriangle *pTriangle = ( MS3DTriangle* )pPtr;
int vertexIndices[3] = { pTriangle->m_vertexIndices[0], pTriangle->m_vertexIndices[1], pTriangle->m_vertexIndices[2] };
float t[3] = { 1.0f-pTriangle->m_t[0], 1.0f-pTriangle->m_t[1], 1.0f-pTriangle->m_t[2] };
memcpy( m_pTriangles[i].m_vertexNormals, pTriangle->m_vertexNormals, sizeof(float)*3*3 );
memcpy( m_pTriangles[i].m_s, pTriangle->m_s, sizeof(float)*3 );
memcpy( m_pTriangles[i].m_t, t, sizeof(float)*3 );
memcpy( m_pTriangles[i].m_vertexIndices, vertexIndices, sizeof(int)*3 );
pPtr += sizeof( MS3DTriangle );
}
As for the vertices, this part of the function stores all of the triangles in the model. While most of it involves just copying the arrays from one structure to another, you'll notice the difference for the vertexIndices and t arrays. In the file, the vertex indices are stores as an array of word values, but in the model they are int values for consistency and simplicity (no nasty casting needed). So this just converts the 3 values to integers. The t values are all set to 1.0-(original value). The reason for this is that OpenGL uses a lower-left coordinate system, whereas Milkshape uses an upper-left coordinate system for its texture coordinates. This reverses the y coordinate.
int nGroups = *( word* )pPtr;
m_numMeshes = nGroups;
m_pMeshes = new Mesh[nGroups];
pPtr += sizeof( word );
for ( i = 0; i < nGroups; i++ ) {
pPtr += sizeof( byte ); // Flags
pPtr += 32; // Name
word nTriangles = *( word* )pPtr;
pPtr += sizeof( word );
int *pTriangleIndices = new int[nTriangles];
for (int j = 0; j < nTriangles; j++) {
pTriangleIndices[j] = *( word* )pPtr;
pPtr += sizeof( word );
}
char materialIndex = *( char* )pPtr;
pPtr += sizeof( char );
m_pMeshes[i].m_materialIndex = materialIndex;
m_pMeshes[i].m_numTriangles = nTriangles;
m_pMeshes[i].m_pTriangleIndices = pTriangleIndices;
}
The above code loads the mesh data structures (also called groups in Milkshape3D). Since the number of triangles varies from mesh to mesh, there is no standard structure to read. Instead, they are taken field by field. The memory for the triangle indices is dynamically allocated within the mesh and read one at a time.
int nMaterials = *( word* )pPtr;
m_numMaterials = nMaterials;
m_pMaterials = new Material[nMaterials];
pPtr += sizeof( word );
for (i = 0; i < nMaterials; i++) {
MS3DMaterial *pMaterial = ( MS3DMaterial* )pPtr;
memcpy( m_pMaterials[i].m_ambient, pMaterial->m_ambient, sizeof(float)*4 );
memcpy( m_pMaterials[i].m_diffuse, pMaterial->m_diffuse, sizeof(float)*4 );
memcpy( m_pMaterials[i].m_specular, pMaterial->m_specular, sizeof(float)*4 );
memcpy( m_pMaterials[i].m_emissive, pMaterial->m_emissive, sizeof(float)*4 );
m_pMaterials[i].m_shininess = pMaterial->m_shininess;
m_pMaterials[i].m_pTextureFilename = new char[strlen(pMaterial->m_texture)+1];
strcpy( m_pMaterials[i].m_pTextureFilename, pMaterial->m_texture );
pPtr += sizeof( MS3DMaterial );
}
reloadTextures();
Lastly, the material information is taken from the buffer. This is done in the same way as those above, copying each of the lighting coefficients into the new structure. Also, new memory is allocated for the texture filename, and it is copied into there. The final call to reloadTextures is used to actually load the textures and bind them to OpenGL texture objects. That function, from the Model base class, is described later.
delete[] pBuffer;
return true;
}
The last fragment frees the temporary buffer now that all the data has been copied and returns successfully.
So at this point, the protected member variables of the Model class are filled with the model information. You'll note also that this is the only code in MilkshapeModel because it is the only code specific to Milkshape3D. Now, before the model can be rendered, it is necessary to load the textures for each of its materials. This is done with the following code:
void Modeclass="underline" :reloadTextures() {
for (int i = 0; i < m_numMaterials; i++) if (strlen(m_pMaterials[i].m_pTextureFilename) > 0) m_pMaterials[i].m_texture = LoadGLTexture(m_pMaterials[i].m_pTextureFilename);
else m_pMaterials[i].m_texture = 0;
}
For each material, the texture is loaded using a function from NeHe's base code (slightly modified from it's previous version). If the texture filename was an empty string, then it is not loaded, and instead the texture object identifier is set to 0 to indicate there is no texture.