www.pudn.com > OpenGLLoad3DS.zip > 3DS.CPP
// 3ds.cpp: implementation of the CLoad3DS class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "StepinGl.h"
#include "main.h"
#include "3ds.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CLoad3DS::CLoad3DS()
{
m_CurrentChunk = new tChunk; // Initialize and allocate our current chunk
m_TempChunk = new tChunk; // Initialize and allocate a temporary chunk
}
///////////////////////////////// IMPORT 3DS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This is called by the client to open the .3ds file, read it, then clean up
/////
///////////////////////////////// IMPORT 3DS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
bool CLoad3DS::Import3DS(t3DModel *pModel, char *strFileName)
{
char strMessage[255] = {0};
// Open the 3DS file
m_FilePointer = fopen(strFileName, "rb");
// Make sure we have a valid file pointer (we found the file)
if(!m_FilePointer)
{
sprintf(strMessage, "Unable to find the file: %s!", strFileName);
MessageBox(NULL, strMessage, "Error", MB_OK);
return false;
}
// Once we have the file open, we need to read the very first data chunk
// to see if it's a 3DS file. That way we don't read an invalid file.
// If it is a 3DS file, then the first chunk ID will be equal to PRIMARY (some hex num)
// Read the first chuck of the file to see if it's a 3DS file
ReadChunk(m_CurrentChunk);
// Make sure this is a 3DS file
if (m_CurrentChunk->ID != PRIMARY)
{
sprintf(strMessage, "Unable to load PRIMARY chuck from file: %s!", strFileName);
MessageBox(NULL, strMessage, "Error", MB_OK);
return false;
}
// Now we actually start reading in the data. ProcessNextChunk() is recursive
// Begin loading objects, by calling this recursive function
ProcessNextChunk(pModel, m_CurrentChunk);
// After we have read the whole 3DS file, we want to calculate our own vertex normals.
ComputeNormals(pModel);
// Clean up after everything
CleanUp();
return true;
}
///////////////////////////////// CLEAN UP \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This function cleans up our allocated memory and closes the file
/////
///////////////////////////////// CLEAN UP \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
void CLoad3DS::CleanUp()
{
fclose(m_FilePointer); // Close the current file pointer
delete m_CurrentChunk; // Free the current chunk
delete m_TempChunk; // Free our temporary chunk
}
///////////////////////////////// PROCESS NEXT CHUNK\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This function reads the main sections of the .3DS file, then dives deeper with recursion
/////
///////////////////////////////// PROCESS NEXT CHUNK\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
void CLoad3DS::ProcessNextChunk(t3DModel *pModel, tChunk *pPreviousChunk)
{
t3DObject newObject = {0}; // This is used to add to our object list
tMaterialInfo newTexture = {0}; // This is used to add to our material list
unsigned int version = 0; // This will hold the file version
int buffer[50000] = {0}; // This is used to read past unwanted data
m_CurrentChunk = new tChunk; // Allocate a new chunk
// Below we check our chunk ID each time we read a new chunk. Then, if
// we want to extract the information from that chunk, we do so.
// If we don't want a chunk, we just read past it.
// Continue to read the sub chunks until we have reached the length.
// After we read ANYTHING we add the bytes read to the chunk and then check
// check against the length.
while (pPreviousChunk->bytesRead < pPreviousChunk->length)
{
// Read next Chunk
ReadChunk(m_CurrentChunk);
// Check the chunk ID
switch (m_CurrentChunk->ID)
{
case VERSION: // This holds the version of the file
// This chunk has an unsigned short that holds the file version.
// Since there might be new additions to the 3DS file format in 4.0,
// we give a warning to that problem.
// Read the file version and add the bytes read to our bytesRead variable
m_CurrentChunk->bytesRead += fread(&version, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
// If the file version is over 3, give a warning that there could be a problem
if (version > 0x03)
MessageBox(NULL, "This 3DS file is over version 3 so it may load incorrectly", "Warning", MB_OK);
break;
case OBJECTINFO: // This holds the version of the mesh
// This chunk holds the version of the mesh. It is also the head of the MATERIAL
// and OBJECT chunks. From here on we start reading in the material and object info.
// Read the next chunk
ReadChunk(m_TempChunk);
// Get the version of the mesh
m_TempChunk->bytesRead += fread(&version, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer);
// Increase the bytesRead by the bytes read from the last chunk
m_CurrentChunk->bytesRead += m_TempChunk->bytesRead;
// Go to the next chunk, which is the object has a texture, it should be MATERIAL, then OBJECT.
ProcessNextChunk(pModel, m_CurrentChunk);
break;
case MATERIAL: // This holds the material information
// This chunk is the header for the material info chunks
// Increase the number of materials
pModel->numOfMaterials++;
// Add a empty texture structure to our texture list.
// If you are unfamiliar with STL's "vector" class, all push_back()
// does is add a new node onto the list. I used the vector class
// so I didn't need to write my own link list functions.
pModel->pMaterials.push_back(newTexture);
// Proceed to the material loading function
ProcessNextMaterialChunk(pModel, m_CurrentChunk);
break;
case OBJECT: // This holds the name of the object being read
// This chunk is the header for the object info chunks. It also
// holds the name of the object.
// Increase the object count
pModel->numOfObjects++;
// Add a new tObject node to our list of objects (like a link list)
pModel->pObject.push_back(newObject);
// Initialize the object and all it's data members
memset(&(pModel->pObject[pModel->numOfObjects - 1]), 0, sizeof(t3DObject));
// Get the name of the object and store it, then add the read bytes to our byte counter.
m_CurrentChunk->bytesRead += GetString(pModel->pObject[pModel->numOfObjects - 1].strName);
// Now proceed to read in the rest of the object information
ProcessNextObjectChunk(pModel, &(pModel->pObject[pModel->numOfObjects - 1]), m_CurrentChunk);
break;
case EDITKEYFRAME:
// Because I wanted to make this a SIMPLE tutorial as possible, I did not include
// the key frame information. This chunk is the header for all the animation info.
// In a later tutorial this will be the subject and explained thoroughly.
//ProcessNextKeyFrameChunk(pModel, m_CurrentChunk);
// Read past this chunk and add the bytes read to the byte counter
m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
break;
default:
// If we didn't care about a chunk, then we get here. We still need
// to read past the unknown or ignored chunk and add the bytes read to the byte counter.
m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
break;
}
// Add the bytes read from the last chunk to the previous chunk passed in.
pPreviousChunk->bytesRead += m_CurrentChunk->bytesRead;
}
// Free the current chunk and set it back to the previous chunk (since it started that way)
delete m_CurrentChunk;
m_CurrentChunk = pPreviousChunk;
}
///////////////////////////////// PROCESS NEXT OBJECT CHUNK \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This function handles all the information about the objects in the file
/////
///////////////////////////////// PROCESS NEXT OBJECT CHUNK \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
void CLoad3DS::ProcessNextObjectChunk(t3DModel *pModel, t3DObject *pObject, tChunk *pPreviousChunk)
{
int buffer[50000] = {0}; // This is used to read past unwanted data
// Allocate a new chunk to work with
m_CurrentChunk = new tChunk;
// Continue to read these chunks until we read the end of this sub chunk
while (pPreviousChunk->bytesRead < pPreviousChunk->length)
{
// Read the next chunk
ReadChunk(m_CurrentChunk);
// Check which chunk we just read
switch (m_CurrentChunk->ID)
{
case OBJECT_MESH: // This lets us know that we are reading a new object
// We found a new object, so let's read in it's info using recursion
ProcessNextObjectChunk(pModel, pObject, m_CurrentChunk);
break;
case OBJECT_VERTICES: // This is the objects vertices
ReadVertices(pObject, m_CurrentChunk);
break;
case OBJECT_FACES: // This is the objects face information
ReadVertexIndices(pObject, m_CurrentChunk);
break;
case OBJECT_MATERIAL: // This holds the material name that the object has
// This chunk holds the name of the material that the object has assigned to it.
// This could either be just a color or a texture map. This chunk also holds
// the faces that the texture is assigned to (In the case that there is multiple
// textures assigned to one object, or it just has a texture on a part of the object.
// Since most of my game objects just have the texture around the whole object, and
// they aren't multitextured, I just want the material name.
// We now will read the name of the material assigned to this object
ReadObjectMaterial(pModel, pObject, m_CurrentChunk);
break;
case OBJECT_UV: // This holds the UV texture coordinates for the object
// This chunk holds all of the UV coordinates for our object. Let's read them in.
ReadUVCoordinates(pObject, m_CurrentChunk);
break;
default:
// Read past the ignored or unknown chunks
m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
break;
}
// Add the bytes read from the last chunk to the previous chunk passed in.
pPreviousChunk->bytesRead += m_CurrentChunk->bytesRead;
}
// Free the current chunk and set it back to the previous chunk (since it started that way)
delete m_CurrentChunk;
m_CurrentChunk = pPreviousChunk;
}
///////////////////////////////// PROCESS NEXT MATERIAL CHUNK \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This function handles all the information about the material (Texture)
/////
///////////////////////////////// PROCESS NEXT MATERIAL CHUNK \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
void CLoad3DS::ProcessNextMaterialChunk(t3DModel *pModel, tChunk *pPreviousChunk)
{
int buffer[50000] = {0}; // This is used to read past unwanted data
// Allocate a new chunk to work with
m_CurrentChunk = new tChunk;
// Continue to read these chunks until we read the end of this sub chunk
while (pPreviousChunk->bytesRead < pPreviousChunk->length)
{
// Read the next chunk
ReadChunk(m_CurrentChunk);
// Check which chunk we just read in
switch (m_CurrentChunk->ID)
{
case MATNAME: // This chunk holds the name of the material
// Here we read in the material name
m_CurrentChunk->bytesRead += fread(pModel->pMaterials[pModel->numOfMaterials - 1].strName, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
break;
case MATDIFFUSE: // This holds the R G B color of our object
ReadColorChunk(&(pModel->pMaterials[pModel->numOfMaterials - 1]), m_CurrentChunk);
break;
case MATMAP: // This is the header for the texture info
// Proceed to read in the material information
ProcessNextMaterialChunk(pModel, m_CurrentChunk);
break;
case MATMAPFILE: // This stores the file name of the material
// Here we read in the material's file name
m_CurrentChunk->bytesRead += fread(pModel->pMaterials[pModel->numOfMaterials - 1].strFile, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
break;
default:
// Read past the ignored or unknown chunks
m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
break;
}
// Add the bytes read from the last chunk to the previous chunk passed in.
pPreviousChunk->bytesRead += m_CurrentChunk->bytesRead;
}
// Free the current chunk and set it back to the previous chunk (since it started that way)
delete m_CurrentChunk;
m_CurrentChunk = pPreviousChunk;
}
///////////////////////////////// READ CHUNK \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This function reads in a chunk ID and it's length in bytes
/////
///////////////////////////////// READ CHUNK \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
void CLoad3DS::ReadChunk(tChunk *pChunk)
{
// This reads the chunk ID which is 2 bytes.
// The chunk ID is like OBJECT or MATERIAL. It tells what data is
// able to be read in within the chunks section.
pChunk->bytesRead = fread(&pChunk->ID, 1, 2, m_FilePointer);
// Then, we read the length of the chunk which is 4 bytes.
// This is how we know how much to read in, or read past.
pChunk->bytesRead += fread(&pChunk->length, 1, 4, m_FilePointer);
}
///////////////////////////////// GET STRING \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This function reads in a string of characters
/////
///////////////////////////////// GET STRING \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
int CLoad3DS::GetString(char *pBuffer)
{
int index = 0;
// Read 1 byte of data which is the first letter of the string
fread(pBuffer, 1, 1, m_FilePointer);
// Loop until we get NULL
while (*(pBuffer + index++) != 0) {
// Read in a character at a time until we hit NULL.
fread(pBuffer + index, 1, 1, m_FilePointer);
}
// Return the string length, which is how many bytes we read in (including the NULL)
return strlen(pBuffer) + 1;
}
///////////////////////////////// READ COLOR \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This function reads in the RGB color data
/////
///////////////////////////////// READ COLOR \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
void CLoad3DS::ReadColorChunk(tMaterialInfo *pMaterial, tChunk *pChunk)
{
// Read the color chunk info
ReadChunk(m_TempChunk);
// Read in the R G B color (3 bytes - 0 through 255)
m_TempChunk->bytesRead += fread(pMaterial->color, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer);
// Add the bytes read to our chunk
pChunk->bytesRead += m_TempChunk->bytesRead;
}
///////////////////////////////// READ VERTEX INDECES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This function reads in the indices for the vertex array
/////
///////////////////////////////// READ VERTEX INDECES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
void CLoad3DS::ReadVertexIndices(t3DObject *pObject, tChunk *pPreviousChunk)
{
unsigned short index = 0; // This is used to read in the current face index
// In order to read in the vertex indices for the object, we need to first
// read in the number of them, then read them in. Remember,
// we only want 3 of the 4 values read in for each face. The fourth is
// a visibility flag for 3D Studio Max that doesn't mean anything to us.
// Read in the number of faces that are in this object (int)
pPreviousChunk->bytesRead += fread(&pObject->numOfFaces, 1, 2, m_FilePointer);
// Alloc enough memory for the faces and initialize the structure
pObject->pFaces = new tFace [pObject->numOfFaces];
memset(pObject->pFaces, 0, sizeof(tFace) * pObject->numOfFaces);
// Go through all of the faces in this object
for(int i = 0; i < pObject->numOfFaces; i++)
{
// Next, we read in the A then B then C index for the face, but ignore the 4th value.
// The fourth value is a visibility flag for 3D Studio Max, we don't care about this.
for(int j = 0; j < 4; j++)
{
// Read the first vertice index for the current face
pPreviousChunk->bytesRead += fread(&index, 1, sizeof(index), m_FilePointer);
if(j < 3)
{
// Store the index in our face structure.
pObject->pFaces[i].vertIndex[j] = index;
}
}
}
}
///////////////////////////////// READ UV COORDINATES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This function reads in the UV coordinates for the object
/////
///////////////////////////////// READ UV COORDINATES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
void CLoad3DS::ReadUVCoordinates(t3DObject *pObject, tChunk *pPreviousChunk)
{
// In order to read in the UV indices for the object, we need to first
// read in the amount there are, then read them in.
// Read in the number of UV coordinates there are (int)
pPreviousChunk->bytesRead += fread(&pObject->numTexVertex, 1, 2, m_FilePointer);
// Allocate memory to hold the UV coordinates
pObject->pTexVerts = new CVector2 [pObject->numTexVertex];
// Read in the texture coodinates (an array 2 float)
pPreviousChunk->bytesRead += fread(pObject->pTexVerts, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer);
}
///////////////////////////////// READ VERTICES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This function reads in the vertices for the object
/////
///////////////////////////////// READ VERTICES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
void CLoad3DS::ReadVertices(t3DObject *pObject, tChunk *pPreviousChunk)
{
// Like most chunks, before we read in the actual vertices, we need
// to find out how many there are to read in. Once we have that number
// we then fread() them into our vertice array.
// Read in the number of vertices (int)
pPreviousChunk->bytesRead += fread(&(pObject->numOfVerts), 1, 2, m_FilePointer);
// Allocate the memory for the verts and initialize the structure
pObject->pVerts = new CVector3 [pObject->numOfVerts];
memset(pObject->pVerts, 0, sizeof(CVector3) * pObject->numOfVerts);
// Read in the array of vertices (an array of 3 floats)
pPreviousChunk->bytesRead += fread(pObject->pVerts, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer);
// Now we should have all of the vertices read in. Because 3D Studio Max
// Models with the Z-Axis pointing up (strange and ugly I know!), we need
// to flip the y values with the z values in our vertices. That way it
// will be normal, with Y pointing up. If you prefer to work with Z pointing
// up, then just delete this next loop. Also, because we swap the Y and Z
// we need to negate the Z to make it come out correctly.
// Go through all of the vertices that we just read and swap the Y and Z values
for(int i = 0; i < pObject->numOfVerts; i++)
{
// Store off the Y value
float fTempY = pObject->pVerts[i].y;
// Set the Y value to the Z value
pObject->pVerts[i].y = pObject->pVerts[i].z;
// Set the Z value to the Y value,
// but negative Z because 3D Studio max does the opposite.
pObject->pVerts[i].z = -fTempY;
}
}
///////////////////////////////// READ OBJECT MATERIAL \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This function reads in the material name assigned to the object and sets the materialID
/////
///////////////////////////////// READ OBJECT MATERIAL \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
void CLoad3DS::ReadObjectMaterial(t3DModel *pModel, t3DObject *pObject, tChunk *pPreviousChunk)
{
char strMaterial[255] = {0}; // This is used to hold the objects material name
int buffer[50000] = {0}; // This is used to read past unwanted data
// *What is a material?* - A material is either the color or the texture map of the object.
// It can also hold other information like the brightness, shine, etc... Stuff we don't
// really care about. We just want the color, or the texture map file name really.
// Here we read the material name that is assigned to the current object.
// strMaterial should now have a string of the material name, like "Material #2" etc..
pPreviousChunk->bytesRead += GetString(strMaterial);
// Now that we have a material name, we need to go through all of the materials
// and check the name against each material. When we find a material in our material
// list that matches this name we just read in, then we assign the materialID
// of the object to that material index. You will notice that we passed in the
// model to this function. This is because we need the number of textures.
// Yes though, we could have just passed in the model and not the object too.
// Go through all of the textures
for(int i = 0; i < pModel->numOfMaterials; i++)
{
// If the material we just read in matches the current texture name
if(strcmp(strMaterial, pModel->pMaterials[i].strName) == 0)
{
// Set the material ID to the current index 'i' and stop checking
pObject->materialID = i;
// Now that we found the material, check if it's a texture map.
// If the strFile has a string length of 1 and over it's a texture
if(strlen(pModel->pMaterials[i].strFile) > 0) {
// Set the object's flag to say it has a texture map to bind.
pObject->bHasTexture = true;
}
break;
}
else
{
// Set the ID to -1 to show there is no material for this object
pObject->materialID = -1;
}
}
// Read past the rest of the chunk since we don't care about shared vertices
// You will notice we subtract the bytes already read in this chunk from the total length.
pPreviousChunk->bytesRead += fread(buffer, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer);
}
// *Note*
//
// Below are some math functions for calculating vertex normals. We want vertex normals
// because it makes the lighting look really smooth and life like. You probably already
// have these functions in the rest of your engine, so you can delete these and call
// your own. I wanted to add them so I could show how to calculate vertex normals.
////////////////////////////// Math Functions ////////////////////////////////*
// This computes the magnitude of a normal. (magnitude = sqrt(x^2 + y^2 + z^2)
#define Mag(Normal) (sqrt(Normal.x*Normal.x + Normal.y*Normal.y + Normal.z*Normal.z))
// This calculates a vector between 2 points and returns the result
CVector3 Vector(CVector3 vPoint1, CVector3 vPoint2)
{
CVector3 vVector; // The variable to hold the resultant vector
vVector.x = vPoint1.x - vPoint2.x; // Subtract point1 and point2 x's
vVector.y = vPoint1.y - vPoint2.y; // Subtract point1 and point2 y's
vVector.z = vPoint1.z - vPoint2.z; // Subtract point1 and point2 z's
return vVector; // Return the resultant vector
}
// This adds 2 vectors together and returns the result
CVector3 AddVector(CVector3 vVector1, CVector3 vVector2)
{
CVector3 vResult; // The variable to hold the resultant vector
vResult.x = vVector2.x + vVector1.x; // Add Vector1 and Vector2 x's
vResult.y = vVector2.y + vVector1.y; // Add Vector1 and Vector2 y's
vResult.z = vVector2.z + vVector1.z; // Add Vector1 and Vector2 z's
return vResult; // Return the resultant vector
}
// This divides a vector by a single number (scalar) and returns the result
CVector3 DivideVectorByScaler(CVector3 vVector1, float Scaler)
{
CVector3 vResult; // The variable to hold the resultant vector
vResult.x = vVector1.x / Scaler; // Divide Vector1's x value by the scaler
vResult.y = vVector1.y / Scaler; // Divide Vector1's y value by the scaler
vResult.z = vVector1.z / Scaler; // Divide Vector1's z value by the scaler
return vResult; // Return the resultant vector
}
// This returns the cross product between 2 vectors
CVector3 Cross(CVector3 vVector1, CVector3 vVector2)
{
CVector3 vCross; // The vector to hold the cross product
// Get the X value
vCross.x = ((vVector1.y * vVector2.z) - (vVector1.z * vVector2.y));
// Get the Y value
vCross.y = ((vVector1.z * vVector2.x) - (vVector1.x * vVector2.z));
// Get the Z value
vCross.z = ((vVector1.x * vVector2.y) - (vVector1.y * vVector2.x));
return vCross; // Return the cross product
}
// This returns the normal of a vector
CVector3 Normalize(CVector3 vNormal)
{
double Magnitude; // This holds the magitude
Magnitude = Mag(vNormal); // Get the magnitude
vNormal.x /= (float)Magnitude; // Divide the vector's X by the magnitude
vNormal.y /= (float)Magnitude; // Divide the vector's Y by the magnitude
vNormal.z /= (float)Magnitude; // Divide the vector's Z by the magnitude
return vNormal; // Return the normal
}
///////////////////////////////// COMPUTER NORMALS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
/////
///// This function computes the normals and vertex normals of the objects
/////
///////////////////////////////// COMPUTER NORMALS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*
void CLoad3DS::ComputeNormals(t3DModel *pModel)
{
CVector3 vVector1, vVector2, vNormal, vPoly[3];
// If there are no objects, we can skip this part
if(pModel->numOfObjects <= 0)
return;
// What are vertex normals? And how are they different from other normals?
// Well, if you find the normal to a triangle, you are finding a "Face Normal".
// If you give OpenGL a face normal for lighting, it will make your object look
// really flat and not very round. If we find the normal for each vertex, it makes
// the smooth lighting look. This also covers up blocky looking objects and they appear
// to have more polygons than they do. Basically, what you do is first
// calculate the face normals, then you take the average of all the normals around each
// vertex. It's just averaging. That way you get a better approximation for that vertex.
// Go through each of the objects to calculate their normals
for(int index = 0; index < pModel->numOfObjects; index++)
{
// Get the current object
t3DObject *pObject = &(pModel->pObject[index]);
// Here we allocate all the memory we need to calculate the normals
CVector3 *pNormals = new CVector3 [pObject->numOfFaces];
CVector3 *pTempNormals = new CVector3 [pObject->numOfFaces];
pObject->pNormals = new CVector3 [pObject->numOfVerts];
// Go though all of the faces of this object
for(int i=0; i < pObject->numOfFaces; i++)
{
// To cut down LARGE code, we extract the 3 points of this face
vPoly[0] = pObject->pVerts[pObject->pFaces[i].vertIndex[0]];
vPoly[1] = pObject->pVerts[pObject->pFaces[i].vertIndex[1]];
vPoly[2] = pObject->pVerts[pObject->pFaces[i].vertIndex[2]];
// Now let's calculate the face normals (Get 2 vectors and find the cross product of those 2)
vVector1 = Vector(vPoly[0], vPoly[2]); // Get the vector of the polygon (we just need 2 sides for the normal)
vVector2 = Vector(vPoly[2], vPoly[1]); // Get a second vector of the polygon
vNormal = Cross(vVector1, vVector2); // Return the cross product of the 2 vectors (normalize vector, but not a unit vector)
pTempNormals[i] = vNormal; // Save the un-normalized normal for the vertex normals
vNormal = Normalize(vNormal); // Normalize the cross product to give us the polygons normal
pNormals[i] = vNormal; // Assign the normal to the list of normals
}
//////////////// Now Get The Vertex Normals /////////////////
CVector3 vSum = {0.0, 0.0, 0.0};
CVector3 vZero = vSum;
int shared=0;
for (i = 0; i < pObject->numOfVerts; i++) // Go through all of the vertices
{
for (int j = 0; j < pObject->numOfFaces; j++) // Go through all of the triangles
{ // Check if the vertex is shared by another face
if (pObject->pFaces[j].vertIndex[0] == i ||
pObject->pFaces[j].vertIndex[1] == i ||
pObject->pFaces[j].vertIndex[2] == i)
{
vSum = AddVector(vSum, pTempNormals[j]);// Add the un-normalized normal of the shared face
shared++; // Increase the number of shared triangles
}
}
// Get the normal by dividing the sum by the shared. We negate the shared so it has the normals pointing out.
pObject->pNormals[i] = DivideVectorByScaler(vSum, float(-shared));
// Normalize the normal for the final vertex normal
pObject->pNormals[i] = Normalize(pObject->pNormals[i]);
vSum = vZero; // Reset the sum
shared = 0; // Reset the shared
}
// Free our memory and start over on the next object
delete [] pTempNormals;
delete [] pNormals;
}
}