参考: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. Java实现二维码QRCode的编码和解码

    涉及到的一些主要类库,方便大家下载: 编码lib:Qrcode_swetake.jar   (官网介绍-- http://www.swetake.com/qr/index-e.html) 解码lib: ...

  2. lintcode :Remove Duplicates from Sorted Array II 删除排序数组中的重复数字 II

    题目: 删除排序数组中的重复数字 II 跟进“删除重复数字”: 如果可以允许出现两次重复将如何处理? 样例 给出数组A =[1,1,1,2,2,3],你的函数应该返回长度5,此时A=[1,1,2,2, ...

  3. [itint5]最短路径遍历点

    http://www.itint5.com/oj/#50 此题有点难,参考了这篇文章,是个两条路的DP: http://blog.csdn.net/a83610312/article/details/ ...

  4. jvm调优具体参数配置

    3.JVM参数 在JVM启动参数中,可以设置跟内存.垃圾回收相关的一些参数设置,默认情况不做任何设置JVM会工作的很好,但对一些配置很好的Server和具体的应用必须仔细调优才能获得最佳性能.通过设置 ...

  5. Sina App Engine(SAE)入门教程(6)- memcache使用

    Memcache是一个高性能的分布式的内存对象缓存系统,通过在内存里维护一个统一的巨大的hash表,它能够用来存储各种格式的数据,包括图像.视频.文件以及数据库检索的结果等.简单的说就是将数据调用到内 ...

  6. ios开发--清理缓存

    ios文章原文 一段清理缓存的代码如下: dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) , ...

  7. Entity Freamwork 6连接PostgreSql数据库

    原文 Entity Freamwork 6连接PostgreSql数据库 开发环境 VS 2015  Update 1   Postgre Sql 9.4 使用过程 1.使用Nuget在项目中添加对E ...

  8. Android scrollview嵌套listview运行后最先显示出来的位置不在顶部而是中间问题

    scrollview里面嵌套了一个listview ,通过设置一个方法设置了listview的高度 现在的情况就是进到这个界面的时候看到的不是最上面 而是中间 ,该问题的解决办法为: mScrollV ...

  9. ios跳转

    目标应用程序:打开info.plist,添加一项URL types展开URL types,再展开Item1,将Item1下的URL identifier修改为URL Scheme展开URL Schem ...

  10. C# 写入XML文档三种方法详细介绍

      三个类将同样的xml内容写入文档,介绍了如何使用XmlDocument类对XML进行操作,以及如何使用LINQ to XML对XML进行操作. 它们分别使用了XmlDocument类和XDocum ...