www.pudn.com > 3DSLoader.rar > 3DSLoader.cpp


//======================================================== 
/** 
*  @file      3DSLoader.h 
* 
*  项目描述: 3DS文件载入 
*  文件描述:  3DS文件载入类   
*  适用平台: Windows98/2000/NT/XP 
*   
*  作者:     WWBOSS 
*  电子邮件:  wwboss123@gmail.com 
*  创建日期: 2006-12-06	 
*  修改日期: 2006-12-12 
* 
*/      
//======================================================== 
 
#include "3DSLoader.h" 
#include "CBMPLoader.h" 
 
/** 构造函数 */ 
C3DSLoader::C3DSLoader() 
{ 
	m_CurrentChunk = new tChunk;	 /**< 为当前块分配空间 */ 
	m_TempChunk = new tChunk;		 /**< 为临时块分配空间 */ 
	m_3DModel.numOfObjects = 0; 
	m_3DModel.numOfMaterials = 0; 
	for (int i=0; i0)                 /**< 判断是否是一个文件名 */ 
		   LoadTexture(m_3DModel.pMaterials[i].strFile,m_textures, i);/**< 使用纹理文件名称来装入位图 */		  
	    
		/** 设置材质的纹理ID */ 
		m_3DModel.pMaterials[i].texureId = i;                      
	}  
} 
 
/** 显示3ds模型 */ 
void C3DSLoader::Draw()  
{ 
	glPushAttrib(GL_CURRENT_BIT); /**< 保存现有颜色属实性 */ 
	glDisable(GL_TEXTURE_2D); 
	 
	/**< 遍历模型中所有的对象 */ 
	for(int i = 0; i < m_3DModel.numOfObjects; i++) 
	{ 
		if(m_3DModel.pObject.size() <= 0)  
			break;                   /**< 如果对象的大小小于0,则退出 */ 
	  
		t3DObject *pObject = &m_3DModel.pObject[i];/**< 获得当前显示的对象 */ 
	  
		if(pObject->bHasTexture)                  /**< 判断该对象是否有纹理映射 */ 
		 {	 
			 glEnable(GL_TEXTURE_2D);             /**< 打开纹理映射 */ 
			 glBindTexture(GL_TEXTURE_2D, m_textures[pObject->materialID]); 
		 }  
	 else	 
		 glDisable(GL_TEXTURE_2D);                /**< 关闭纹理映射 */ 
	  
	 glColor3ub(255, 255, 255); 
 
	 /** 开始绘制 */ 
	 glBegin(GL_TRIANGLES); 
	 for(int j = 0; j < pObject->numOfFaces; j++)		/**< 遍历所有的面 */ 
	 {for(int tex = 0; tex < 3; tex++)					/**< 遍历三角形的所有点 */ 
		{ 
			int index = pObject->pFaces[j].vertIndex[tex];	/**< 获得面对每个点的索引 */ 
		  
			glNormal3f(pObject->pNormals[index].x,pObject->pNormals[index].y,   
			        pObject->pNormals[index].z);		/**< 给出法向量 */ 
		  
		 if(pObject->bHasTexture)						/**< 如果对象具有纹理 */ 
		 {	 
			 if(pObject->pTexVerts)						/**< 确定是否有UVW纹理坐标 */ 
				glTexCoord2f(pObject->pTexVerts[index].x,pObject->pTexVerts[index].y); 
		 } 
		 else 
		 {	 
			 if(m_3DModel.pMaterials.size() && pObject->materialID>= 0)  
			{	 
				BYTE *pColor = m_3DModel.pMaterials[pObject->materialID].color; 
				glColor3ub(pColor[0],pColor[1],pColor[2]); 
			} 
		 } 
		 glVertex3f(pObject->pVerts[index].x,pObject->pVerts[index].y,pObject->pVerts[index].z); 
		} 
	 } 
	glEnd(); /**< 绘制结束 */ 
	} 
	glEnable(GL_TEXTURE_2D); 
	 
	glPopAttrib();   /**< 恢复前一属性 */ 
} 
 
/** 转载纹理 */ 
void C3DSLoader::LoadTexture(char* filename, GLuint textureArray[], GLuint textureID) 
{ 
 
	if(!filename) 
		return; 
	 
	/** 载入位图 */ 
	if(!m_BMPTexture.LoadBitmap(filename)) 
	{ 
		MessageBox(NULL,"载入位图失败!","错误",MB_OK); 
		exit(0); 
	} 
	glGenTextures(1,&m_textures[textureID]); 
	 
	glPixelStorei (GL_UNPACK_ALIGNMENT, 1); 
	glBindTexture(GL_TEXTURE_2D, m_textures[textureID]); 
	 
	/** 控制滤波 */ 
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); 
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR); 
	 
	/** 创建纹理 */ 
	gluBuild2DMipmaps(GL_TEXTURE_2D, 3, m_BMPTexture.imageWidth, m_BMPTexture.imageHeight, GL_RGB,  
		              GL_UNSIGNED_BYTE, m_BMPTexture.image); 
		 
} 
 
/** 载入3DS文件到模型结构中 */ 
bool C3DSLoader::Import3DS(t3DModel *pModel, char *strFileName) 
{	 
	char strMessage[255] = {0}; 
	 
	/** 打开一个3ds文件 */ 
	m_FilePointer = fopen(strFileName, "rb"); 
	 
	/**< 检查文件指针 */ 
	if(!m_FilePointer)  
	{	 
		sprintf(strMessage, "找不到文件: %s!", strFileName); 
		MessageBox(NULL, strMessage, "Error", MB_OK); 
		return false; 
	} 
	 
	/** 将文件的第一块读出并判断是否是3ds文件 */ 
	ReadChunk(m_CurrentChunk); 
	 
	/** 确保是3ds文件 */ 
	if (m_CurrentChunk->ID != PRIMARY) 
	{	 
		MessageBox(NULL, "不能加载主块!", "Error", MB_OK); 
		return false; 
	} 
	 
	/** 递归读出对象数据 */ 
	ReadNextChunk(pModel, m_CurrentChunk); 
	 
	/** 计算顶点的法线 */ 
	ComputeNormals(pModel); 
	 
	/** 释放内存空间 */ 
	CleanUp(); 
	 
	return true; 
} 
 
/**  读入一个字符串 */ 
int C3DSLoader::GetString(char *pBuffer) 
{	 
	int index = 0; 
	 
	/** 读入一个字节的数据 */ 
	fread(pBuffer, 1, 1, m_FilePointer); 
	 
	/** 直到结束 */ 
	while (*(pBuffer + index++) != 0)  
	{ 
		/** 读入一个字符直到NULL */ 
		fread(pBuffer + index, 1, 1, m_FilePointer); 
	} 
	 
	/** 返回字符串的长度 */ 
	return strlen(pBuffer) + 1; 
} 
 
/** 读入块的ID号和它的字节长度 */ 
void C3DSLoader::ReadChunk(tChunk *pChunk) 
{	 
	// 读入块的ID号 */ 
	pChunk->bytesRead = fread(&pChunk->ID, 1, 2, m_FilePointer); 
	 
	/** 读入块占用的长度 */ 
	pChunk->bytesRead += fread(&pChunk->length, 1, 4, m_FilePointer); 
} 
 
 
/** 读出3ds文件的主要部分 */ 
void C3DSLoader::ReadNextChunk(t3DModel *pModel, tChunk *pPreChunk) 
{	 
	t3DObject newObject = {0};					/**< 用来添加到对象链表 */ 
	tMatInfo newTexture = {0};				    /**< 用来添加到材质链表 */ 
	unsigned int version = 0;					/**< 保存文件版本 */ 
	int buffer[50000] = {0};					/**< 用来跳过不需要的数据 */ 
	m_CurrentChunk = new tChunk;				/**< 为新的块分配空间		 */ 
	 
	/** 继续读入子块 */ 
	while (pPreChunk->bytesRead < pPreChunk->length) 
	{	 
		/** 读入下一个块 */ 
		ReadChunk(m_CurrentChunk); 
		 
		/** 判断块的ID号 */ 
		switch (m_CurrentChunk->ID) 
		{ 
		 
		/** 文件版本号 */ 
		case VERSION:							 
			 
			/** 读入文件的版本号 */ 
			m_CurrentChunk->bytesRead += fread(&version, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); 
			 
			/** 如果文件版本号大于3,给出一个警告信息 */ 
			if (version > 0x03) 
				MessageBox(NULL, " 该3DS文件版本大于3.0,可能不能正确读取", "警告", MB_OK); 
			break; 
		 
			/** 网格版本信息 */ 
		case OBJECTINFO:						 
			 
			/** 读入下一个块 */ 
			ReadChunk(m_TempChunk); 
			 
			/** 获得网格的版本号 */ 
			m_TempChunk->bytesRead += fread(&version, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer); 
			 
			/** 增加读入的字节数 */ 
			m_CurrentChunk->bytesRead += m_TempChunk->bytesRead; 
			 
			/** 进入下一个块 */ 
			ReadNextChunk(pModel, m_CurrentChunk); 
			break; 
		 
		/** 材质信息 */ 
		case MATERIAL:							 
			 
			/** 材质的数目递增 */ 
			pModel->numOfMaterials++; 
			 
			/** 在纹理链表中添加一个空白纹理结构 */ 
			pModel->pMaterials.push_back(newTexture); 
			 
			/** 进入材质装入函数 */ 
			ReadNextMatChunk(pModel, m_CurrentChunk); 
			break; 
 
		/** 对象名称 */ 
		case OBJECT:							 
			 
			/** 对象数递增 */ 
			pModel->numOfObjects++; 
			 
			/** 添加一个新的tObject节点到对象链表中 */ 
			pModel->pObject.push_back(newObject); 
			 
			/** 初始化对象和它的所有数据成员 */ 
			memset(&(pModel->pObject[pModel->numOfObjects - 1]), 0, sizeof(t3DObject)); 
			 
			/** 获得并保存对象的名称,然后增加读入的字节数 */ 
			m_CurrentChunk->bytesRead += GetString(pModel->pObject[pModel->numOfObjects - 1].strName); 
			 
			/** 进入其余的对象信息的读入 */ 
			ReadNextObjChunk(pModel, &(pModel->pObject[pModel->numOfObjects - 1]), m_CurrentChunk); 
			break; 
		 
		/** 关键帧 */ 
		case EDITKEYFRAME: 
			 
			/** 跳过关键帧块的读入 */ 
			m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); 
			break; 
		default:  
			 
			/**  跳过所有忽略的块的内容的读入 */ 
			m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); 
			break; 
		} 
		 
		/** 增加从最后块读入的字节数 */ 
		pPreChunk->bytesRead += m_CurrentChunk->bytesRead; 
	} 
	 
	/** 释放当前块的内存空间 */ 
	delete m_CurrentChunk; 
	m_CurrentChunk = pPreChunk; 
 
} 
/** 处理所有的文件中对象的信息 */ 
void C3DSLoader::ReadNextObjChunk(t3DModel *pModel, t3DObject *pObject, tChunk *pPreChunk) 
{	 
	int buffer[50000] = {0};					/** 用于读入不需要的数据 */ 
	 
	/** 对新的块分配存储空间 */ 
	m_CurrentChunk = new tChunk; 
	 
	/** 继续读入块的内容直至本子块结束 */ 
	while (pPreChunk->bytesRead < pPreChunk->length) 
	{	 
		/** 读入下一个块 */ 
		ReadChunk(m_CurrentChunk); 
		 
		/** 区别读入是哪种块 */ 
		switch (m_CurrentChunk->ID) 
		{ 
		case OBJ_MESH:					/**< 正读入的是一个新块 */ 
			 
			/** 使用递归函数调用,处理该新块 */ 
			ReadNextObjChunk(pModel, pObject, m_CurrentChunk); 
			break; 
		case OBJ_VERTICES:				/**< 读入是对象顶点 */ 
			ReadVertices(pObject, m_CurrentChunk); 
			break; 
		case OBJ_FACES:					/**< 读入的是对象的面 */ 
			ReadVertexIndices(pObject, m_CurrentChunk); 
			break; 
		case OBJ_MATERIAL:				/**< 读入的是对象的材质名称 */ 
			 
			/** 读入对象的材质名称 */ 
			ReadObjMat(pModel, pObject, m_CurrentChunk);			 
			break; 
		case OBJ_UV:						/**< 读入对象的UV纹理坐标 */ 
			/** 读入对象的UV纹理坐标 */ 
			ReadUVCoordinates(pObject, m_CurrentChunk); 
			break; 
		default:   
			/** 略过不需要读入的块 */ 
			m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); 
			break; 
		} 
		/** 添加从最后块中读入的字节数到前面的读入的字节中 */ 
		pPreChunk->bytesRead += m_CurrentChunk->bytesRead; 
	} 
	/** 释放当前块的内存空间,并把当前块设置为前面块 */ 
	delete m_CurrentChunk; 
	m_CurrentChunk = pPreChunk; 
} 
/** 处理所有的材质信息 */ 
void C3DSLoader::ReadNextMatChunk(t3DModel *pModel, tChunk *pPreChunk) 
{	 
	int buffer[50000] = {0};					/**< 用于读入不需要的数据 */ 
	/** 给当前块分配存储空间 */ 
	m_CurrentChunk = new tChunk; 
	/** 继续读入这些块 */ 
	while (pPreChunk->bytesRead < pPreChunk->length) 
	{	 
		/** 读入下一块 */ 
		ReadChunk(m_CurrentChunk); 
		/** 判断读入的是什么块 */ 
		switch (m_CurrentChunk->ID) 
		{ 
		case MATNAME:							/**< 材质的名称 */ 
			/** 读入材质的名称 */ 
			m_CurrentChunk->bytesRead += fread(pModel->pMaterials[pModel->numOfMaterials - 1].strName, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); 
			break; 
		case MATDIFFUSE:						/**< 对象的R G B颜色 */ 
			ReadColor(&(pModel->pMaterials[pModel->numOfMaterials - 1]), m_CurrentChunk); 
			break; 
		case MATMAP:							/**< 纹理信息的头部 */ 
			/** 下一个材质块信息 */ 
			ReadNextMatChunk(pModel, m_CurrentChunk); 
			break; 
		case MATMAPFILE:						/**< 材质文件的名称 */ 
			/** 读入材质的文件名称 */ 
			m_CurrentChunk->bytesRead += fread(pModel->pMaterials[pModel->numOfMaterials - 1].strFile, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); 
			break; 
		default:   
			/** 跳过不需要读入的块 */ 
			m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); 
			break; 
		} 
		/** 添加从最后块中读入的字节数 */ 
		pPreChunk->bytesRead += m_CurrentChunk->bytesRead; 
	} 
	/** 删除当前块,并将当前块设置为前面的块 */ 
	delete m_CurrentChunk; 
	m_CurrentChunk = pPreChunk; 
} 
 
 
/** 读入RGB颜色 */ 
void C3DSLoader::ReadColor(tMatInfo *pMaterial, tChunk *pChunk) 
{	 
	/** 读入颜色块信息 */ 
	ReadChunk(m_TempChunk); 
	/** 读入RGB颜色 */ 
	m_TempChunk->bytesRead += fread(pMaterial->color, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer); 
	/** 增加读入的字节数 */ 
	pChunk->bytesRead += m_TempChunk->bytesRead; 
} 
/** 读入顶点索引 */ 
void C3DSLoader::ReadVertexIndices(t3DObject *pObject, tChunk *pPreChunk) 
{	 
	unsigned short index = 0;					/**< 用于读入当前面的索引 */ 
	/** 读入该对象中面的数目 */ 
	pPreChunk->bytesRead += fread(&pObject->numOfFaces, 1, 2, m_FilePointer); 
	/** 分配所有面的存储空间,并初始化结构 */ 
	pObject->pFaces = new tFace [pObject->numOfFaces]; 
	memset(pObject->pFaces, 0, sizeof(tFace) * pObject->numOfFaces); 
	/** 遍历对象中所有的面 */ 
	for(int i = 0; i < pObject->numOfFaces; i++) 
	{	for(int j = 0; j < 4; j++) 
		{	 
			/** 读入当前面的第一个点  */ 
			pPreChunk->bytesRead += fread(&index, 1, sizeof(index), m_FilePointer); 
			if(j < 3) 
			{	 
				/** 将索引保存在面的结构中 */ 
				pObject->pFaces[i].vertIndex[j] = index; 
			} 
		} 
	} 
} 
 
/** 读入对象的UV坐标 */ 
void C3DSLoader::ReadUVCoordinates(t3DObject *pObject, tChunk *pPreChunk) 
{	 
	/** 读入UV坐标的数量 */ 
	pPreChunk->bytesRead += fread(&pObject->numTexVertex, 1, 2, m_FilePointer); 
	 
	/** 分配保存UV坐标的内存空间 */ 
	pObject->pTexVerts = new Vector2 [pObject->numTexVertex]; 
	 
	/** 读入纹理坐标 */ 
	pPreChunk->bytesRead += fread(pObject->pTexVerts, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer); 
} 
 
/**  读入对象的顶点 */ 
void C3DSLoader::ReadVertices(t3DObject *pObject, tChunk *pPreChunk) 
{	 
	/** 读入顶点的数目 */ 
	pPreChunk->bytesRead += fread(&(pObject->numOfVerts), 1, 2, m_FilePointer); 
	 
	/** 分配顶点的存储空间,然后初始化结构体 */ 
	pObject->pVerts = new Vector3 [pObject->numOfVerts]; 
	memset(pObject->pVerts, 0, sizeof(Vector3) * pObject->numOfVerts); 
	 
	/** 读入顶点序列 */ 
	pPreChunk->bytesRead += fread(pObject->pVerts, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer); 
	 
	/** 遍历所有的顶点将Y轴和Z轴交换,然后将Z轴反向 */ 
	for(int i = 0; i < pObject->numOfVerts; i++) 
	{	 
		/** 保存Y轴的值 */ 
		float fTempY = pObject->pVerts[i].y; 
		/** 设置Y轴的值等于Z轴的值 */ 
		pObject->pVerts[i].y = pObject->pVerts[i].z; 
		/** 设置Z轴的值等于-Y轴的值  */ 
		pObject->pVerts[i].z = -fTempY; 
	} 
} 
 
/** 读入对象的材质名称 */ 
void C3DSLoader::ReadObjMat(t3DModel *pModel, t3DObject *pObject, tChunk *pPreChunk) 
{	 
	char strMaterial[255] = {0};			/**< 用来保存对象的材质名称 */ 
	int buffer[50000] = {0};				/**< 用来读入不需要的数据 */ 
	 
	/** 读入赋予当前对象的材质名称 */ 
	pPreChunk->bytesRead += GetString(strMaterial); 
	 
	/** 遍历所有的纹理 */ 
	for(int i = 0; i < pModel->numOfMaterials; i++) 
	{	 
		/** 如果读入的纹理与当前的纹理名称匹配 */ 
		if(strcmp(strMaterial, pModel->pMaterials[i].strName) == 0) 
		{	 
			/** 设置材质ID */ 
			pObject->materialID = i; 
			 
			/** 判断是否是纹理映射 */ 
			if(strlen(pModel->pMaterials[i].strFile) > 0) { 
				 
				/** 设置对象的纹理映射标志 */ 
				pObject->bHasTexture = true; 
			}	 
			break; 
		} 
		else 
		{	 
			/** 如果该对象没有材质,则设置ID为-1 */ 
			pObject->materialID = -1; 
		} 
	} 
	pPreChunk->bytesRead += fread(buffer, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer); 
} 
 
 
/** 计算对象的法向量 */ 
void C3DSLoader::ComputeNormals(t3DModel *pModel) 
{	 
	Vector3 vVector1, vVector2, vNormal, vPoly[3]; 
	 
	/** 如果模型中没有对象,则返回 */ 
	if(pModel->numOfObjects <= 0) 
		return; 
	 
	/** 遍历模型中所有的对象 */ 
	for(int index = 0; index < pModel->numOfObjects; index++) 
	{	 
		/** 获得当前的对象 */ 
		t3DObject *pObject = &(pModel->pObject[index]); 
		 
		/** 分配需要的存储空间 */ 
		Vector3 *pNormals		= new Vector3 [pObject->numOfFaces]; 
		Vector3 *pTempNormals	= new Vector3 [pObject->numOfFaces]; 
		pObject->pNormals		= new Vector3 [pObject->numOfVerts]; 
		 
		/** 遍历对象的所有面 */ 
		for(int i=0; i < pObject->numOfFaces; i++) 
		{	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]]; 
			 
			/** 计算面的法向量 */ 
			vVector1 = vPoly[0] - vPoly[2];		        /**< 获得多边形的矢量 */ 
			vVector2 = vPoly[2] - vPoly[1];		        /**< 获得多边形的第二个矢量 */ 
			vNormal  = vVector1.crossProduct(vVector2);	/**< 计算两个矢量的叉积 */ 
			pTempNormals[i] = vNormal;					 
			vNormal  = vNormal.normalize();				/**< 规一化叉积 */ 
			pNormals[i] = vNormal;						/**< 将法向量添加到法向量列表中 */ 
		} 
		 
		/** 计算顶点法向量 */ 
		Vector3 vSum(0.0,0.0,0.0); 
		Vector3 vZero = vSum; 
		int shared=0; 
		 
		/** 遍历所有的顶点 */ 
		for (i = 0; i < pObject->numOfVerts; i++)			 
		{	for (int j = 0; j < pObject->numOfFaces; j++)	/**< 遍历所有的三角形面 */ 
			{												/**< 判断该点是否与其它的面共享 */ 
				if (pObject->pFaces[j].vertIndex[0] == i ||  
					pObject->pFaces[j].vertIndex[1] == i ||  
					pObject->pFaces[j].vertIndex[2] == i) 
				{	 
					vSum = vSum + pTempNormals[j]; 
					shared++;								 
				} 
			}       
			pObject->pNormals[i] = vSum / float(-shared); 
			 
			/** 规一化顶点法向 */ 
			pObject->pNormals[i] = pObject->pNormals[i].normalize();	 
			vSum = vZero;								 
			shared = 0;										 
		} 
		/** 释放存储空间,开始下一个对象 */ 
		delete [] pTempNormals; 
		delete [] pNormals; 
	} 
}