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

效果:

源代码:

解释:

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下Qt开发环境:OpenGL导入3DMax模型(.3DS)的更多相关文章

  1. QT + OpenCV + MinGW 在windows下配置开发环境

           由于研究项目需要,最近开始接触C++界面设计,关于“QT + OpenCV + MinGW在windows下配置开发环境”着实让人头疼,单次配置时间相当长,也十分不容易,本人第一次配置成 ...

  2. 关于 Windows 下 Qt 开发,这个问题必须要搞清楚!

    小伙伴们,大家好,小北师兄又来喂饭啦,从上次写完<一个例子让你秒懂 Qt Creator 编译原理>后,师兄对于 Qt 的一些环境配置有了更深的理解,这对师兄进行 Qt 的后续学习起到了很 ...

  3. 【Objective-C】Windows下Objective-C开发环境配置

    [Objective-C]Windows下Objective-C开发环境配置 ftp://ftpmain.gnustep.org/pub/gnustep/binaries/windows/   最近打 ...

  4. windows下STM32开发环境的搭建

    一.概述 1.说明 笔者已经写了一篇Linux下STM32开发环境的搭建 ,这两篇文章的最区别在于开发环境所处的系统平台不一样,而其实这个区别对于开发环境的搭建其实影响不大,制作局部上的操作上发生了改 ...

  5. windows下spark开发环境配置

    http://www.cnblogs.com/davidwang456/p/5032766.html windows下spark开发环境配置 --本篇随笔由同事葛同学提供. windows下spark ...

  6. Metabase在Windows下的开发环境配置

    Metabase在Windows下的开发环境配置 */--> pre.src {background-color: #292b2e; color: #b2b2b2;} Metabase在Wind ...

  7. 【Qt开发】Linux下Qt开发环境的安装与集成

    近期工作需要在Linux下用Qt进行C++开发,所以就在linux下尝试装QT开发环境.本人用的linux是CentOS 6.5.现在对安装过程做出总结.有两种安装方式,下面分别详述: 1 图形化安装 ...

  8. Windows 下 Ionic 开发环境搭建

    Ionic 介绍 首先,Ionic 是什么. Ionic 是一款基于 Cordova 及 Angular 开发 Hybrid/Web APP 的前端框架,类似的其他框架有:Intel XDK等. 简单 ...

  9. NDK在windows下的开发环境搭建及开发过程

    在Android应用的开发工程中,不管是游戏还是普通应用,都时常会用到.so即动态链接库,关于.so是什么玩意儿,有什么好处,这个大家可以在网上查一下,本人不做过多解释..so本是linux下的文件类 ...

随机推荐

  1. 10 harsh truths that will help you grow

    10 harsh truths that will help you grow帮你成长的10个残酷事实In the game of life, if it often seems like you’r ...

  2. WCF揭秘(一)——简单的WCF开发实例

    一.WCF是什么 WCF是微软为了实现各个开发平台之间的无疑缝连接而开发一种崭新工具,它是为分布式处理而开发.WCF将DCOM.Remoting.Web Service.WSE.MSMQ.AJAX服务 ...

  3. jQuery插件开发(转)

    jQuery插件开发 - 其实很简单 [前言]jQuery已经被广泛使用,凭借其简洁的API,对DOM强大的操控性,易扩展性越来越受到web开发人员的喜爱,我在社区也发布了很多的jQuery插件,经常 ...

  4. 关于java -version版本问题

    因为安装了Oracle,而Oracel会自带JDK,安装完成后,会自动把自己的JDK设置在最前面(path变量里). 这就是为什么结果与事实不相同的原因. 解决方法: 进入系统环境变量,找到path变 ...

  5. SqlServer 如何以脚本形式导出数据

    你是否遇到这样的情况,在公司导出一个数据库,回到家里导入自己的电脑里,然后发现数据库版本不匹配,这真是一个悲剧. 那么以下这个方法就可以避免这个悲剧,将数据以脚本的形式导出,这样灵活性更好. 1.选择 ...

  6. mongodb管理工具rockmongo

    mongodb的图像管理工具非常之多,我用的是rockmongo. RockMongo 是一个PHP5写的MongoDB管理工具. 主要特征: 使用宽松的New BSD License协议 速度快,安 ...

  7. c# FastReport开发报表

    本文介绍c#应用FastReport开发报表,因此首先附该工具下载地址:http://download.csdn.net/detail/hws1058648831a/6378499 下载解压后可以直接 ...

  8. url、href、src 详解

    发现自己居然没把url.href.src关系及使用搞清楚,今天就理一下.主要包括:url.src.href定义以及使用区别.顺便试下在segmentfault来一发. URL(Uniform Reso ...

  9. UVa 10817 (状压DP + 记忆化搜索) Headmaster's Headache

    题意: 一共有s(s ≤ 8)门课程,有m个在职教师,n个求职教师. 每个教师有各自的工资要求,还有他能教授的课程,可以是一门或者多门. 要求在职教师不能辞退,问如何录用应聘者,才能使得每门课只少有两 ...

  10. ubuntu下安装mysql及外网访问设置

    这么多年一直是mssql或者Oracle,mysql基本没用过,借着.net即将跨平台之际,也mysql一把.windows安装基本没啥难度,然后就是试了把linux下...结果坑不少,由于linux ...