Windows下Qt开发环境:OpenGL导入3DMax模型(.3DS)

参考:http://blog.csdn.net/cq361106306/article/details/41876541

效果:

Windows下Qt开发环境:OpenGL导入3DMax模型(.3DS)Windows下Qt开发环境:OpenGL导入3DMax模型(.3DS)Windows下Qt开发环境:OpenGL导入3DMax模型(.3DS)Windows下Qt开发环境:OpenGL导入3DMax模型(.3DS)

源代码:

解释:

CLoad3DS.h为加载3DMax模型的头文件,CLoad3DS.cpp为加载3DMax模型的实现文件,

nehewidget.h为Qt下使用OpenGL头文件,nehewidget.cpp为Qt下使用OpenGL实现文件。

注意:

1.3D模型和纹理图片资源需要放在源代码同一目录下的Data目录中,即/Data/3DS和/Data/pic下。

2.图标和其他纹理图片存放在Resources文件夹下。

CLoad3DS.h:

#ifndef _CLoad3DS_h_

#define _CLoad3DS_h_



#include <windows.h>

#include <cassert>

#include <cmath>

#include <string>

#include <cstdio>

#include <cstdlib>

#include <fstream>

#include <iostream>

#include <vector>                  



#include <olectl.h>              

#include <cmath>  

#include <ctime>

#include <algorithm>

#include <direct.h>



//初始化OpenGL环境

#include <gl/gl.h>

#include <gl/glu.h>

#include <gl/glut.h>



#pragma   comment(lib,"opengl32.lib")

#pragma	  comment(lib,"glu32.lib")



#define PICPATH "\\Data\\pic\\"  //纹理资源的地址



// 基本块(Primary Chunk),位于文件的开始

#define PRIMARY 0x4D4D



// 主块(Main Chunks)

#define OBJECTINFO 0x3D3D        // 网格对象的版本号

#define VERSION 0x0002           // .3ds文件的版本

#define EDITKEYFRAME 0xB000      // 所有关键帧信息的头部



// 对象的次级定义(包括对象的材质和对象)

#define MATERIAL   0xAFFF        // 保存纹理信息

#define OBJECT     0x4000        // 保存对象的面、顶点等信息



// 材质的次级定义

#define MATNAME 0xA000        // 保存材质名称

#define MATDIFFUSE 0xA020     // 对象/材质的颜色

#define MATMAP 0xA200         // 新材质的头部

#define MATMAPFILE 0xA300     // 保存纹理的文件名



#define OBJECT_MESH 0x4100    // 新的网格对象



// OBJECT_MESH的次级定义

#define OBJECT_VERTICES 0x4110      // 对象顶点

#define OBJECT_FACES    0x4120      // 对象的面

#define OBJECT_MATERIAL    0x4130   // 对象的材质

#define OBJECT_UV      0x4140       // 对象的UV纹理坐标



// 下面的宏定义计算一个矢量的长度

#define Mag(Normal) (sqrt(Normal.x*Normal.x + Normal.y*Normal.y + Normal.z*Normal.z))

#define MAX_TEXTURES 100            // 最大的纹理数目



using namespace std;

class NBVector3

{

public:

	NBVector3() {}

	NBVector3(float X, float Y, float Z) 

	{ 

		x = X; y = Y; z = Z;

	}

	inline NBVector3 operator+(NBVector3 vVector)

	{

		return NBVector3(vVector.x + x, vVector.y + y, vVector.z + z);

	}

	inline NBVector3 operator-(NBVector3 vVector)

	{

		return NBVector3(x - vVector.x, y - vVector.y, z - vVector.z);

	}

	inline NBVector3 operator-()

	{

		return NBVector3(-x, -y, -z);

	}

	inline NBVector3 operator*(float num)

	{

		return NBVector3(x * num, y * num, z * num);

	}

	inline NBVector3 operator/(float num)

	{

		return NBVector3(x / num, y / num, z / num);

	}

	inline NBVector3 operator^(const NBVector3 &rhs) const

	{

		return NBVector3(y * rhs.z - rhs.y * z, rhs.x * z - x * rhs.z, x * rhs.y - rhs.x * y);

	}

	union

	{

		struct

		{

			float x;

			float y;

			float z;

		};

		float v[3];

	};				

};



// 定义2D点类,用于保存模型的UV纹理坐标

class CVector2 

{

public:

	float x, y;

};



// 面的结构定义

struct tFace

{

	int vertIndex[3];      // 顶点索引

	int coordIndex[3];     // 纹理坐标索引

};



// 材质信息结构体

struct tMaterialInfo

{

	char strName[255];      // 纹理名称

	char strFile[255];      // 如果存在纹理映射,则表示纹理文件名称

	BYTE color[3];          // 对象的RGB颜色

	int texureId;           // 纹理ID

	float uTile;            // u 重复

	float vTile;            // v 重复

	float uOffset;          // u 纹理偏移

	float vOffset;          // v 纹理偏移

} ;



// 对象信息结构体

struct t3DObject 

{

	int numOfVerts;      // 模型中顶点的数目

	int numOfFaces;      // 模型中面的数目

	int numTexVertex;    // 模型中纹理坐标的数目

	int materialID;      // 纹理ID

	bool bHasTexture;    // 是否具有纹理映射

	char strName[255];   // 对象的名称

	NBVector3 *pVerts;   // 对象的顶点

	NBVector3 *pNormals; // 对象的法向量

	CVector2 *pTexVerts; // 纹理UV坐标

	tFace *pFaces;       // 对象的面信息

};



// 模型信息结构体

struct t3DModel 

{

	UINT texture[MAX_TEXTURES];

	int numOfObjects;                  // 模型中对象的数目

	int numOfMaterials;                // 模型中材质的数目

	vector<tMaterialInfo> pMaterials;  // 材质链表信息

	vector<t3DObject> pObject;         // 模型中对象链表信息

};



struct tIndices 

{              

	unsigned short a, b, c, bVisible;  

};



// 保存块信息的结构

struct tChunk

{

	unsigned short int ID;           // 块的ID    

	unsigned int length;             // 块的长度

	unsigned int bytesRead;          // 需要读的块数据的字节数

};



typedef struct tagBoundingBoxStruct

{

	NBVector3  BoxPosMaxVertex;

	NBVector3  BoxNegMaxVertex;

} BoundingBoxVertex2;



// 下面的函数求两点决定的矢量

NBVector3 Vector(NBVector3 vPoint1, NBVector3 vPoint2);

// 下面的函数两个矢量相加

NBVector3 AddVector(NBVector3 vVector1, NBVector3 vVector2);

// 下面的函数处理矢量的缩放

NBVector3 DivideVectorByScaler(NBVector3 vVector1, float Scaler);

// 下面的函数返回两个矢量的叉积

NBVector3 Cross(NBVector3 vVector1, NBVector3 vVector2);

// 下面的函数归一化矢量

NBVector3 Normalize(NBVector3 vNormal);



//////////////////////////////////////////////////////////////////////////

#define FRAND   (((float)rand()-(float)rand())/RAND_MAX)

#define Clamp(x, min, max)  x = (x<min  ? min : x<max ? x : max);

#define SQUARE(x)  (x)*(x)

struct vector3_t

{

	vector3_t(float x, float y, float z) : x(x), y(y), z(z) {}

	vector3_t(const vector3_t &v) : x(v.x), y(v.y), z(v.z) {}

	vector3_t() : x(0.0f), y(0.0f), z(0.0f) {}



	vector3_t& operator=(const vector3_t &rhs)

	{

		x = rhs.x;

		y = rhs.y;

		z = rhs.z;

		return *this;

	}



	// vector add

	vector3_t operator+(const vector3_t &rhs) const

	{

		return vector3_t(x + rhs.x, y + rhs.y, z + rhs.z);

	}



	// vector subtract

	vector3_t operator-(const vector3_t &rhs) const

	{

		return vector3_t(x - rhs.x, y - rhs.y, z - rhs.z);

	}



	// scalar multiplication

	vector3_t operator*(const float scalar) const

	{

		return vector3_t(x * scalar, y * scalar, z * scalar);

	}



	// dot product

	float operator*(const vector3_t &rhs) const

	{

		return x * rhs.x + y * rhs.y + z * rhs.z;

	}



	// cross product

	vector3_t operator^(const vector3_t &rhs) const

	{

		return vector3_t(y * rhs.z - rhs.y * z, rhs.x * z - x * rhs.z, x * rhs.y - rhs.x * y);

	}



	float& operator[](int index)

	{

		return v[index];

	}



	float Length()

	{

		float length = (float)sqrt(SQUARE(x) + SQUARE(y) + SQUARE(z));

		return (length != 0.0f) ? length : 1.0f;

	}



	/*****************************************************************************

	Normalize()



	Helper function to normalize vectors

	*****************************************************************************/

	vector3_t Normalize()

	{

		*this = *this * (1.0f/Length());

		return *this;

	}



	union

	{

		struct

		{

			float x;

			float y;

			float z;

		};

		float v[3];

	};

};



// CLoad3DS类处理所有的装入代码

class CLoad3DS

{

public:

	CLoad3DS();                // 初始化数据成员

	// 装入3ds文件到模型结构中

	bool Import3DS(t3DModel *pModel, char *strFileName);



private:

	// 读入一个纹理

	int BuildTexture(char *szPathName, GLuint &texid);

	// 读一个字符串

	int GetString(char *);

	// 读下一个块

	void ReadChunk(tChunk *);

	// 读下一个块

	void ProcessNextChunk(t3DModel *pModel, tChunk *);

	// 读下一个对象块

	void ProcessNextObjectChunk(t3DModel *pModel, t3DObject *pObject, tChunk *);

	// 读下一个材质块

	void ProcessNextMaterialChunk(t3DModel *pModel, tChunk *);

	// 读对象颜色的RGB值

	void ReadColorChunk(tMaterialInfo *pMaterial, tChunk *pChunk);

	// 读对象的顶点

	void ReadVertices(t3DObject *pObject, tChunk *);

	// 读对象的面信息

	void ReadVertexIndices(t3DObject *pObject, tChunk *);

	// 读对象的纹理坐标

	void ReadUVCoordinates(t3DObject *pObject, tChunk *);

	// 读赋予对象的材质名称

	void ReadObjectMaterial(t3DModel *pModel, t3DObject *pObject, tChunk *pPreviousChunk);

	// 计算对象顶点的法向量

	void ComputeNormals(t3DModel *pModel);

	// 关闭文件,释放内存空间

	void CleanUp();

	// 文件指针

	FILE *m_FilePointer;



	tChunk *m_CurrentChunk;

	tChunk *m_TempChunk;

};

void changeObject(float trans[10]);

void drawModel(t3DModel Model,bool touming,bool outTex);

#endif

CLoad3DS.cpp:

#include "CLoad3DS.h"



#pragma warning (disable: 4996) 



// 下面的函数求两点决定的矢量

NBVector3 Vector(NBVector3 vPoint1, NBVector3 vPoint2)

{

	NBVector3 vVector;              

	

	vVector.x = vPoint1.x - vPoint2.x;      

	vVector.y = vPoint1.y - vPoint2.y;      

	vVector.z = vPoint1.z - vPoint2.z;      

	

	return vVector;                

}



// 下面的函数两个矢量相加

NBVector3 AddVector(NBVector3 vVector1, NBVector3 vVector2)

{

	NBVector3 vResult;              

	vResult.x = vVector2.x + vVector1.x;    

	vResult.y = vVector2.y + vVector1.y;    

	vResult.z = vVector2.z + vVector1.z;    

	return vResult;                

}



// 下面的函数处理矢量的缩放

NBVector3 DivideVectorByScaler(NBVector3 vVector1, float Scaler)

{

	NBVector3 vResult;              

	vResult.x = vVector1.x / Scaler;      

	vResult.y = vVector1.y / Scaler;      

	vResult.z = vVector1.z / Scaler;     	

	return vResult;                

}



// 下面的函数返回两个矢量的叉积

NBVector3 Cross(NBVector3 vVector1, NBVector3 vVector2)

{

	NBVector3 vCross;                	

	vCross.x = ((vVector1.y * vVector2.z) - (vVector1.z * vVector2.y));

	vCross.y = ((vVector1.z * vVector2.x) - (vVector1.x * vVector2.z));	

	vCross.z = ((vVector1.x * vVector2.y) - (vVector1.y * vVector2.x));

	return vCross;                

}



// 下面的函数归一化矢量

NBVector3 Normalize(NBVector3 vNormal)

{

	double Magnitude;              	

	Magnitude = Mag(vNormal);          // 获得矢量的长度	

	vNormal.x /= (float)Magnitude;        

	vNormal.y /= (float)Magnitude;        

	vNormal.z /= (float)Magnitude;        	

	return vNormal;                

}



// 读入一个纹理

int CLoad3DS::BuildTexture(char *szPathName, GLuint &texid)

{

	HDC      hdcTemp;                        // The DC To Hold Our Bitmap

	HBITMAP    hbmpTemp;                     // Holds The Bitmap Temporarily

	LPPICTURE  pPicture;                     // IPicture Interface,此处较参考网址的博主的代码做了修改

	OLECHAR    wszPath[MAX_PATH+1];          // Full Path To Picture (WCHAR)

	char    szPath[MAX_PATH+1];         // Full Path To Picture

	long    lWidth;                          // Width In Logical Units

	long    lHeight;                         // Height In Logical Units

	long    lWidthPixels;                    // Width In Pixels

	long    lHeightPixels;                   // Height In Pixels

	GLint    glMaxTexDim ;                   // Holds Maximum Texture Size



	if (strstr(szPathName, "http://"))       // If PathName Contains http:// Then...

	{

		strcpy(szPath, szPathName);          // Append The PathName To szPath

	}

	else                                     // Otherwise... We Are Loading From A File

	{

		getcwd(szPath,MAX_PATH);              // Get Our Working Directory,此处较参考网址的博主的代码做了修改

		strcat(szPath, PICPATH);              // Append "\" After The Working Directory

		strcat(szPath, szPathName);           // Append The PathName

	}

	

	MultiByteToWideChar(CP_ACP, 0, szPath, -1, wszPath, MAX_PATH);    // Convert From ASCII To Unicode

	HRESULT hr = OleLoadPicturePath(wszPath, 0, 0, 0, IID_IPicture, reinterpret_cast<LPVOID *>(&pPicture));//此处较参考网址的博主的代码做了修改



	if(FAILED(hr))                             // If Loading Failed

		return FALSE;                          // Return False



	hdcTemp = CreateCompatibleDC(GetDC(0));    // Create The Windows Compatible Device Context

	if(!hdcTemp)                               // Did Creation Fail?

	{

		pPicture->Release();                   // Decrements IPicture Reference Count

		return FALSE;                          // Return False (Failure)

	}



	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glMaxTexDim);   // Get Maximum Texture Size Supported



	pPicture->get_Width(&lWidth);                       // Get IPicture Width (Convert To Pixels)

	lWidthPixels  = MulDiv(lWidth, GetDeviceCaps(hdcTemp, LOGPIXELSX), 2540);

	pPicture->get_Height(&lHeight);                     // Get IPicture Height (Convert To Pixels)

	lHeightPixels  = MulDiv(lHeight, GetDeviceCaps(hdcTemp, LOGPIXELSY), 2540);



	// Resize Image To Closest Power Of Two

	if (lWidthPixels <= glMaxTexDim) // Is Image Width Less Than Or Equal To Cards Limit

		lWidthPixels = 1 << (int)floor((log((double)lWidthPixels)/log(2.0f)) + 0.5f); 

	else // Otherwise Set Width To "Max Power Of Two" That The Card Can Handle

		lWidthPixels = glMaxTexDim;



	if (lHeightPixels <= glMaxTexDim) // Is Image Height Greater Than Cards Limit

		lHeightPixels = 1 << (int)floor((log((double)lHeightPixels)/log(2.0f)) + 0.5f);

	else // Otherwise Set Height To "Max Power Of Two" That The Card Can Handle

		lHeightPixels = glMaxTexDim;



	//  Create A Temporary Bitmap

	BITMAPINFO  bi = {0};                       // The Type Of Bitmap We Request

	DWORD    *pBits = 0;                        // Pointer To The Bitmap Bits



	bi.bmiHeader.biSize      = sizeof(BITMAPINFOHEADER); // Set Structure Size

	bi.bmiHeader.biBitCount  = 32;                       // 32 Bit

	bi.bmiHeader.biWidth     = lWidthPixels;             // Power Of Two Width

	bi.bmiHeader.biHeight    = lHeightPixels;            // Make Image Top Up (Positive Y-Axis)

	bi.bmiHeader.biCompression  = BI_RGB;                // RGB Encoding

	bi.bmiHeader.biPlanes    = 1;                        // 1 Bitplane



	//  Creating A Bitmap This Way Allows Us To Specify Color Depth And Gives Us Imediate Access To The Bits

	hbmpTemp = CreateDIBSection(hdcTemp, &bi, DIB_RGB_COLORS, (void**)&pBits, 0, 0);



	if(!hbmpTemp)                                 // Did Creation Fail?

	{

		DeleteDC(hdcTemp);                        // Delete The Device Context

		pPicture->Release();                      // Decrements IPicture Reference Count

		return FALSE;                             // Return False (Failure)

	}



	SelectObject(hdcTemp, hbmpTemp);              // Select Handle To Our Temp DC And Our Temp Bitmap Object



	// Render The IPicture On To The Bitmap

	pPicture->Render(hdcTemp, 0, 0, lWidthPixels, lHeightPixels, 0, lHeight, lWidth, -lHeight, 0);



	// Convert From BGR To RGB Format And Add An Alpha Value Of 255

	for(long i = 0; i < lWidthPixels * lHeightPixels; i++)        // Loop Through All Of The Pixels

	{

		BYTE* pPixel  = (BYTE*)(&pBits[i]);              // Grab The Current Pixel

		BYTE temp    = pPixel[0];                        // Store 1st Color In Temp Variable (Blue)

		pPixel[0]    = pPixel[2];                        // Move Red Value To Correct Position (1st)

		pPixel[2]    = temp;                             // Move Temp Value To Correct Blue Position (3rd)



		// This Will Make Any Black Pixels, Completely Transparent    (You Can Hardcode The Value If You Wish)

		if ((pPixel[0]==0) && (pPixel[1]==0) && (pPixel[2]==0))      // Is Pixel Completely Black

			pPixel[3]  = 0;                         // Set The Alpha Value To 0

		else                                        // Otherwise

			pPixel[3]  = 255;                       // Set The Alpha Value To 255

	}



	glGenTextures(1, &texid);                       // Create The Texture



	// Typical Texture Generation Using Data From The Bitmap

	glBindTexture(GL_TEXTURE_2D, texid);                            // Bind To The Texture ID

	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // (Modify This For The Type Of Filtering You Want)

	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // (Modify This For The Type Of Filtering You Want)

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, lWidthPixels, lHeightPixels, 0, GL_RGBA, GL_UNSIGNED_BYTE, pBits);  // (Modify This If You Want Mipmaps)



	DeleteObject(hbmpTemp);                        // Delete The Object

	DeleteDC(hdcTemp);                             // Delete The Device Context



	pPicture->Release();                           // Decrements IPicture Reference Count



	printf( "load %s!" , szPath );

    // Return True (All is okay)

	return TRUE;   

}



// 构造函数的功能是初始化tChunk数据

CLoad3DS::CLoad3DS()

{

	m_CurrentChunk = new tChunk;        // 初始化并为当前的块分配空间

	m_TempChunk = new tChunk;          // 初始化一个临时块并分配空间

}



// 打开一个3ds文件,读出其中的内容,并释放内存

bool CLoad3DS::Import3DS(t3DModel *pModel, char *strFileName)

{

	char strMessage[255] = {0};



	// 打开一个3ds文件

	m_FilePointer = fopen(strFileName,"rb");



	// 确保所获得的文件指针合法

	if(!m_FilePointer) 

	{

		sprintf(strMessage, "Unable to find the file: %s!", strFileName);

		MessageBox(NULL, (LPWSTR)strMessage, (LPWSTR)"Error", MB_OK);

		return false;

	}



	// 当文件打开之后,首先应该将文件最开始的数据块读出以判断是否是一个3ds文件

	// 如果是3ds文件的话,第一个块ID应该是PRIMARY



	// 将文件的第一块读出并判断是否是3ds文件

	ReadChunk(m_CurrentChunk);



	// 确保是3ds文件

	if (m_CurrentChunk->ID != PRIMARY)

	{

		sprintf(strMessage, "Unable to load PRIMARY chuck from file: %s!", strFileName);

		MessageBox(NULL, (LPWSTR)strMessage, (LPWSTR)"Error", MB_OK);

		return false;

	}



	// 现在开始读入数据,ProcessNextChunk()是一个递归函数

	// 通过调用下面的递归函数,将对象读出

	ProcessNextChunk(pModel, m_CurrentChunk);



	// 在读完整个3ds文件之后,计算顶点的法线

	ComputeNormals(pModel);



	// 释放内存空间

	CleanUp();



	return true;

}



// 下面的函数释放所有的内存空间,并关闭文件

void CLoad3DS::CleanUp()

{



	fclose(m_FilePointer);            // 关闭当前的文件指针

	delete m_CurrentChunk;            // 释放当前块

	delete m_TempChunk;              // 释放临时块

}



// 下面的函数读出3ds文件的主要部分

void CLoad3DS::ProcessNextChunk(t3DModel *pModel, tChunk *pPreviousChunk)

{

	t3DObject newObject = {0};          // 用来添加到对象链表

	tMaterialInfo newTexture = {0};     // 用来添加到材质链表

	unsigned int version = 0;           // 保存文件版本

	int buffer[50000] = {0};            // 用来跳过不需要的数据



	m_CurrentChunk = new tChunk;        // 为新的块分配空间    



	// 下面每读一个新块,都要判断一下块的ID,如果该块是需要的读入的,则继续进行

	// 如果是不需要读入的块,则略过



	// 继续读入子块,直到达到预定的长度

	while (pPreviousChunk->bytesRead < pPreviousChunk->length)

	{

		// 读入下一个块

		ReadChunk(m_CurrentChunk);



		// 判断块的ID号

		switch (m_CurrentChunk->ID)

		{

		case VERSION:              // 文件版本号



			// 在该块中有一个无符号短整型数保存了文件的版本



			// 读入文件的版本号,并将字节数添加到bytesRead变量中

			m_CurrentChunk->bytesRead += fread(&version, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);



			// 如果文件版本号大于3,给出一个警告信息

			if (version > 0x03)

				MessageBox(NULL, (LPWSTR)"This 3DS file is over version 3 so it may load incorrectly", (LPWSTR)"Warning", 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;



			// 进入下一个块

			ProcessNextChunk(pModel, m_CurrentChunk);

			break;



		case MATERIAL:              // 材质信息



			// 材质的数目递增

			pModel->numOfMaterials++;



			// 在纹理链表中添加一个空白纹理结构

			pModel->pMaterials.push_back(newTexture);



			// 进入材质装入函数

			ProcessNextMaterialChunk(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);



			// 进入其余的对象信息的读入

			ProcessNextObjectChunk(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;

		}



		// 增加从最后块读入的字节数

		pPreviousChunk->bytesRead += m_CurrentChunk->bytesRead;

	}



	// 释放当前块的内存空间

	delete m_CurrentChunk;

	m_CurrentChunk = pPreviousChunk;

}



// 下面的函数处理所有的文件中对象的信息

void CLoad3DS::ProcessNextObjectChunk(t3DModel *pModel, t3DObject *pObject, tChunk *pPreviousChunk)

{

	int buffer[50000] = {0};          // 用于读入不需要的数据



	// 对新的块分配存储空间

	m_CurrentChunk = new tChunk;



	// 继续读入块的内容直至本子块结束

	while (pPreviousChunk->bytesRead < pPreviousChunk->length)

	{

		// 读入下一个块

		ReadChunk(m_CurrentChunk);



		// 区别读入是哪种块

		switch (m_CurrentChunk->ID)

		{

		case OBJECT_MESH:          // 正读入的是一个新块



			// 使用递归函数调用,处理该新块

			ProcessNextObjectChunk(pModel, pObject, m_CurrentChunk);

			break;



		case OBJECT_VERTICES:        // 读入是对象顶点

			ReadVertices(pObject, m_CurrentChunk);

			break;



		case OBJECT_FACES:          // 读入的是对象的面

			ReadVertexIndices(pObject, m_CurrentChunk);

			break;



		case OBJECT_MATERIAL:        // 读入的是对象的材质名称



			// 该块保存了对象材质的名称,可能是一个颜色,也可能是一个纹理映射。同时在该块中也保存了

			// 纹理对象所赋予的面



			// 下面读入对象的材质名称

			ReadObjectMaterial(pModel, pObject, m_CurrentChunk);      

			break;



		case OBJECT_UV:            // 读入对象的UV纹理坐标



			// 读入对象的UV纹理坐标

			ReadUVCoordinates(pObject, m_CurrentChunk);

			break;



		default: 



			// 略过不需要读入的块

			m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);

			break;

		}



		// 添加从最后块中读入的字节数到前面的读入的字节中

		pPreviousChunk->bytesRead += m_CurrentChunk->bytesRead;

	}



	// 释放当前块的内存空间,并把当前块设置为前面块

	delete m_CurrentChunk;

	m_CurrentChunk = pPreviousChunk;

}



// 下面的函数处理所有的材质信息

void CLoad3DS::ProcessNextMaterialChunk(t3DModel *pModel, tChunk *pPreviousChunk)

{

	int buffer[50000] = {0};          // 用于读入不需要的数据



	// 给当前块分配存储空间

	m_CurrentChunk = new tChunk;



	// 继续读入这些块,知道该子块结束

	while (pPreviousChunk->bytesRead < pPreviousChunk->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颜色

			ReadColorChunk(&(pModel->pMaterials[pModel->numOfMaterials - 1]), m_CurrentChunk);

			break;



		case MATMAP:              // 纹理信息的头部



			// 进入下一个材质块信息

			ProcessNextMaterialChunk(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;

		}



		// 添加从最后块中读入的字节数

		pPreviousChunk->bytesRead += m_CurrentChunk->bytesRead;

	}



	// 删除当前块,并将当前块设置为前面的块

	delete m_CurrentChunk;

	m_CurrentChunk = pPreviousChunk;

}



// 下面函数读入块的ID号和它的字节长度

void CLoad3DS::ReadChunk(tChunk *pChunk)

{

	// 读入块的ID号,占用了2个字节。块的ID号象OBJECT或MATERIAL一样,说明了在块中所包含的内容

	pChunk->bytesRead = fread(&pChunk->ID, 1, 2, m_FilePointer);



	// 然后读入块占用的长度,包含了四个字节

	pChunk->bytesRead += fread(&pChunk->length, 1, 4, m_FilePointer);

}



// 下面的函数读入一个字符串

int CLoad3DS::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;

}



// 下面的函数读入RGB颜色

void CLoad3DS::ReadColorChunk(tMaterialInfo *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 CLoad3DS::ReadVertexIndices(t3DObject *pObject, tChunk *pPreviousChunk)

{

	unsigned short index = 0;  // 用于读入当前面的索引



	// 读入该对象中面的数目

	pPreviousChunk->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++)

		{

			// 读入当前面的第一个点 

			pPreviousChunk->bytesRead += fread(&index, 1, sizeof(index), m_FilePointer);



			if(j < 3)

			{

				// 将索引保存在面的结构中

				pObject->pFaces[i].vertIndex[j] = index;

			}

		}

	}

}



// 下面的函数读入对象的UV坐标

void CLoad3DS::ReadUVCoordinates(t3DObject *pObject, tChunk *pPreviousChunk)

{

	// 为了读入对象的UV坐标,首先需要读入UV坐标的数量,然后才读入具体的数据



	// 读入UV坐标的数量

	pPreviousChunk->bytesRead += fread(&pObject->numTexVertex, 1, 2, m_FilePointer);



	// 分配保存UV坐标的内存空间

	pObject->pTexVerts = new CVector2 [pObject->numTexVertex];



	// 读入纹理坐标

	pPreviousChunk->bytesRead += fread(pObject->pTexVerts, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer);

}



// 读入对象的顶点

void CLoad3DS::ReadVertices(t3DObject *pObject, tChunk *pPreviousChunk)

{

	// 在读入实际的顶点之前,首先必须确定需要读入多少个顶点。



	// 读入顶点的数目

	pPreviousChunk->bytesRead += fread(&(pObject->numOfVerts), 1, 2, m_FilePointer);



	// 分配顶点的存储空间,然后初始化结构体

	pObject->pVerts = new NBVector3 [pObject->numOfVerts];

	memset(pObject->pVerts, 0, sizeof(NBVector3) * pObject->numOfVerts);



	// 读入顶点序列

	pPreviousChunk->bytesRead += fread(pObject->pVerts, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer);



	// 现在已经读入了所有的顶点。

	// 因为3D Studio Max的模型的Z轴是指向上的,因此需要将y轴和z轴翻转过来。

	// 具体的做法是将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 CLoad3DS::ReadObjectMaterial(t3DModel *pModel, t3DObject *pObject, tChunk *pPreviousChunk)

{

	char strMaterial[255] = {0};      // 用来保存对象的材质名称

	int buffer[50000] = {0};        // 用来读入不需要的数据



	// 材质或者是颜色,或者是对象的纹理,也可能保存了象明亮度、发光度等信息。



	// 下面读入赋予当前对象的材质名称

	pPreviousChunk->bytesRead += GetString(strMaterial);



	// 遍历所有的纹理

	for(int i = 0; i < pModel->numOfMaterials; i++)

	{

		//如果读入的纹理与当前的纹理名称匹配

		if(strcmp(strMaterial, pModel->pMaterials[i].strName) == 0)

		{

			// 设置材质ID

			pObject->materialID = i;



			// 判断是否是纹理映射,如果strFile是一个长度大于1的字符串,则是纹理

			if(strlen(pModel->pMaterials[i].strFile) > 0) {

				

				//载入纹理

				BuildTexture(pModel->pMaterials[i].strFile, pModel->texture[pObject->materialID]);

				// 设置对象的纹理映射标志

				pObject->bHasTexture = true;



				char strMessage[100];

				sprintf(strMessage, "file name : %s!", pModel->pMaterials[i].strFile);

				printf( "%s\n" , strMessage );

//				MessageBox(NULL, strMessage, "Error", MB_OK);

			}  

			break;

		}

		else

		{

			// 如果该对象没有材质,则设置ID为-1

			pObject->materialID = -1;

		}

	}



	pPreviousChunk->bytesRead += fread(buffer, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer);

}      



// 下面的这些函数主要用来计算顶点的法向量,顶点的法向量主要用来计算光照

// 下面的函数用于计算对象的法向量

void CLoad3DS::ComputeNormals(t3DModel *pModel)

{

	NBVector3 vVector1, vVector2, vNormal, vPoly[3];



	// 如果模型中没有对象,则返回

	if(pModel->numOfObjects <= 0)

		return;



	// 遍历模型中所有的对象

	for(int index = 0; index < pModel->numOfObjects; index++)

	{

		// 获得当前的对象

		t3DObject *pObject = &(pModel->pObject[index]);



		// 分配需要的存储空间

		NBVector3 *pNormals    = new NBVector3 [pObject->numOfFaces];

		NBVector3 *pTempNormals  = new NBVector3 [pObject->numOfFaces];

		pObject->pNormals    = new NBVector3 [pObject->numOfVerts];

		int i=0;

		// 遍历对象的所有面

		for(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 = Vector(vPoly[0], vPoly[2]);    // 获得多边形的矢量

			vVector2 = Vector(vPoly[2], vPoly[1]);    // 获得多边形的第二个矢量



			vNormal = Cross(vVector1, vVector2);    // 获得两个矢量的叉积

			pTempNormals[i] = vNormal;          // 保存非规范化法向量

			vNormal = Normalize(vNormal);        // 规范化获得的叉积



			pNormals[i] = vNormal;            // 将法向量添加到法向量列表中

		}



		// 下面求顶点法向量

		NBVector3 vSum (0.0, 0.0, 0.0);

		NBVector3 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 = AddVector(vSum, pTempNormals[j]);

					shared++;                

				}

			} 



			pObject->pNormals[i] = DivideVectorByScaler(vSum, float(-shared));



			// 规范化最后的顶点法向

			pObject->pNormals[i] = Normalize(pObject->pNormals[i]);  



			vSum = vZero;                

			shared = 0;                    

		}



		// 释放存储空间,开始下一个对象

		delete [] pTempNormals;

		delete [] pNormals;

	}

}

// 对模型进行移动变换等操作

void changeObject(float trans[10])

{

	glTranslatef(trans[0],trans[1],trans[2]);

	glScalef(trans[3],trans[4],trans[5]);

	glRotatef(trans[6],trans[7],trans[8],trans[9]);

}

// 绘制模型

void drawModel(t3DModel Model,bool touming,bool outTex)

{	

	if( touming ){

		glEnable(GL_BLEND);

		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		glColor4f(1,1,1,0.5);

	}

	

	for(int i = 0; i < Model.numOfObjects; i++)

	{

		t3DObject *pObject = &Model.pObject[i];

		if(!outTex)

		{

			if(pObject->bHasTexture) 

			{

				glEnable(GL_TEXTURE_2D);

				

				glBindTexture(GL_TEXTURE_2D, Model.texture[pObject->materialID]);

			}

			else 

			{

				glDisable(GL_TEXTURE_2D);

				glColor3ub(255, 255, 255);

			}

		} 

		glBegin(GL_TRIANGLES);          

		for(int j = 0; j < pObject->numOfFaces; j++)

		{

			for(int whichVertex = 0; whichVertex < 3; whichVertex++)

			{

				int index = pObject->pFaces[j].vertIndex[whichVertex];

				glNormal3f(pObject->pNormals[ index ].x, pObject->pNormals[ index ].y, pObject->pNormals[ index ].z);

				if(pObject->bHasTexture) 

				{

					if(pObject->pTexVerts) 

					{ 

						glColor3f(1.0,1.0,1.0);

						glTexCoord2f(pObject->pTexVerts[ index ].x, pObject->pTexVerts[ index ].y);

					}

				}

				else

				{					

					if(Model.pMaterials.size() && pObject->materialID >= 0) 

					{

						BYTE *pColor = Model.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(); 

	}

	if( touming )

		glDisable(GL_BLEND);

}

nehewidget.h:

#ifndef NEHEWIDGET_H

#define NEHEWIDGET_H



#include <QGLWidget>

#include <QKeyEvent>

#include <QTimer>

#include <QImage>

#include <QMessageBox>

#include <QIcon>

#include <QDebug>

#include <GL/glu.h>

#include <CLoad3DS.h>



class NeHeWidget : public QGLWidget

{

    Q_OBJECT

public:

    explicit NeHeWidget(QWidget *parent = 0);

    ~NeHeWidget();



protected:

    void initializeGL();

    void paintGL();

    void resizeGL(int w, int h);

    void keyPressEvent(QKeyEvent *);



private:

    bool fullscreen;

    float xRot,yRot,zRot;

    GLuint texture[3];



    void initLight();

    void drawObject();

 void init3DMAX();



signals:



public slots:



};



#endif // NEHEWIDGET_H

nehewidget.cpp:

#include "nehewidget.h"



GLfloat lightAmbient[4] = { 0.5, 0.5, 0.5, 1.0 };

GLfloat lightDiffuse[4] = { 1.0, 1.0, 1.0, 1.0 };

GLfloat lightPosition[4] = { 0.0, 0.0, 2.0, 1.0 };



CLoad3DS *gothicLoader=new CLoad3DS; t3DModel gothicModel; 



NeHeWidget::NeHeWidget(QWidget *parent) :

    QGLWidget(parent)

{

	this->setWindowTitle(tr("OpenGL加载3DS模型"));

	this->setWindowIcon(QIcon(":/Textures/Resources/Desert.jpg"));

	resize(640,480); 

	xRot = 0.0;

	zRot = 0.0;

	yRot = 0.0;

}



NeHeWidget::~NeHeWidget()

{



}



//*************************************

// 作    者:  朱兴宇

// 时    间:  2015/4/16 15:08

// 权    限:  protected 

// 返    回:  void

// 方法说明:  OpenGL初始化

//*************************************

void NeHeWidget::initializeGL()

{

    //使用阴影平滑

	glShadeModel(GL_SMOOTH);

	//黑色清屏r,g,b,alpha

	glClearColor(0.0,0.0,0.0,0.0);

	//设置深度缓存

	glClearDepth(1.0);

	//启用深度缓存

	glEnable(GL_DEPTH_TEST);

	//启用深度测试的类型

	glDepthFunc(GL_LEQUAL);

	//使用透视

	glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);

	//初始化灯光

	initLight();

	//初始化3D模型 init3DMAX();

}



//*************************************

// 作    者:  朱兴宇

// 时    间:  2015/4/16 15:07

// 权    限:  protected 

// 返    回:  void

// 方法说明:  窗口大小变化

//*************************************

void NeHeWidget::resizeGL(int w, int h)

{

	if(h == 0)

	{

		h = 1;

	}                                   // Far

    //重置当前视口

    glViewport(0,0,(GLint)w,(GLint)h);

    //选择投影矩阵

    glMatrixMode(GL_PROJECTION);

    glLoadIdentity();

    gluPerspective(60.0, (GLfloat)w/(GLfloat)h, 1.0, 100.0);//建立透视投影矩阵

    glMatrixMode(GL_MODELVIEW);

    glLoadIdentity();

}



//*************************************

// 作    者:  朱兴宇

// 时    间:  2015/4/16 15:17

// 权    限:  protected 

// 返    回:  void

// 方法说明:  绘制

//*************************************

void NeHeWidget::paintGL()

{

    //清楚屏幕颜色和深度

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    drawObject();

}



//*************************************

// 作    者:  朱兴宇

// 时    间:  2015/4/16 15:06

// 权    限:  private 

// 返    回:  void

// 方法说明:  绘制物体

//*************************************

void NeHeWidget::drawObject()

{

	glLoadIdentity();

	glTranslatef(0.0,0.0,-30.0);

	glRotatef(xRot,1.0,0.0,0.0);

	glRotatef(yRot,0.0,1.0,0.0);

	glTranslatef(0.0,-3.0,0.0);

	glScaled(0.25,0.25,0.25); 

	drawModel(gothicModel,true,false);  



	glBlendFunc( GL_SRC_ALPHA, GL_ONE );

}



//*************************************

// 作    者:  朱兴宇

// 时    间:  2015/4/16 15:10

// 权    限:  protected 

// 返    回:  void

// 方法说明:  鼠标响应事件

//*************************************

void NeHeWidget::keyPressEvent(QKeyEvent * e)

{

    switch(e->key())

    {

    case Qt::Key_F2:

        fullscreen = !fullscreen;

        if(fullscreen)

        {

            showFullScreen();

        }

        else

        {

            showNormal();

            resize(640,480);

        }

        updateGL();

        break;

    case Qt::Key_Escape:

        close();

        break;

	case Qt::Key_Up:

		xRot+= 5.0;

		if (xRot>=360)

		{

			xRot=0;

		}

		updateGL();

		break;

    case Qt::Key_Down:

        xRot -= 5.0;

		if (xRot<=0)

		{

			xRot=360;

		}

        updateGL();

        break;

    case Qt::Key_Right:

        yRot += 5.0;

		if (yRot>=360)

		{

			yRot=0;

		}

        updateGL();

        break;

    case Qt::Key_Left:

        yRot -= 5.0;

		if (yRot<=0)

		{

			yRot=360;

		}

        updateGL();

        break;

	//启用灯光

    case Qt::Key_L:

    {

        static bool choose = false;

        if(choose)

        {

            glDisable(GL_LIGHTING);

            choose = false;

        }

        else

        {

            glEnable(GL_LIGHTING);

            choose = true;

        }

        updateGL();

    }

        break;

	//启用混合

    case Qt::Key_B:

    {

       static bool blend = !blend;

         if ( blend )

         {

            glEnable(GL_BLEND);

            glDisable(GL_DEPTH_TEST);

            blend = false;

         }

         else

         {

            glDisable(GL_BLEND);

            glEnable(GL_DEPTH_TEST);

            blend = true;

         }

         updateGL();

    }

        break;

    }

	

}



//*************************************

// 作    者:  朱兴宇

// 时    间:  2015/4/16 15:07

// 权    限:  private 

// 返    回:  void

// 方法说明:  初始化灯光

//*************************************

void NeHeWidget::initLight()

{

	glLightfv( GL_LIGHT1, GL_AMBIENT, lightAmbient );

	glLightfv( GL_LIGHT1, GL_DIFFUSE, lightDiffuse );

	glLightfv( GL_LIGHT1, GL_POSITION, lightPosition );

	glEnable( GL_LIGHT1 );

}



//*************************************

// 作    者:  朱兴宇

// 时    间:  2015/4/16 22:00

// 权    限:  private 

// 返    回:  void

// 方法说明:  初始化3DMAX导入模型

//*************************************

void NeHeWidget::init3DMAX() { //导入模型 模型的文件夹尽量这样设置 //然后模型贴图 装在Data/3DS里面,一定要跟前面截图的文件夹名字一样,想改得去CLoad3DS文件里面改 gothicLoader->Import3DS(&gothicModel, "Data/3DS/GUTEMB_L.3DS"); } 

main.cpp:

#include "nehewidget.h"

#include <QTextCodec>

#include <QtGui/QApplication>



int main(int argc, char *argv[])

{

	QApplication a(argc, argv);

	{

		QTextCodec *codec = QTextCodec::codecForName("utf8");

		QTextCodec::setCodecForLocale(codec);

		QTextCodec::setCodecForCStrings(codec);

		QTextCodec::setCodecForTr(codec);

	}

	NeHeWidget* w=new NeHeWidget;

	w->show();

	return a.exec();

}

dinlou.qrc:

<RCC>

    <qresource prefix="/Textures">

      <file>Resources/text.bmp</file>

      <file>Resources/Desert.jpg</file>

    </qresource>

</RCC>

 

你可能感兴趣的:(windows)