#include "main.h"
#include "Obj.h"
// 下面的函数的功能是将obj文件的信息读入指定的模型中
bool CLoadObj::ImportObj(t3DModel *pModel, char *strFileName)
{
char strMessage[255] = {0}; // 用于显示错误信息
// 判断是否是一个合法的模型和文件类型
if(!pModel || !strFileName) return false;
// 以只读方式打开文件,返回文件指针
m_FilePointer = fopen(strFileName, "r");
// 判断文件指针是否正确
if(!m_FilePointer) {
// 如果文件指针不正确,则显示错误信息
sprintf(strMessage, "Unable to find or open the file: %s", strFileName);
MessageBox(NULL, strMessage, "Error", MB_OK);
return false;
}
// 读入文件信息
ReadObjFile(pModel);
// 计算顶点的法向量,用于光照
ComputeNormals(pModel);
// 关闭打开的文件
fclose(m_FilePointer);
return true;
}
// 读入obj文件中的对象到模型中
void CLoadObj::ReadObjFile(t3DModel *pModel)
{
char strLine[255] = {0};
char ch = 0;
while(!feof(m_FilePointer))
{
float x = 0.0f, y = 0.0f, z = 0.0f;
// 获得obj文件中的当前行的第一个字符
ch = fgetc(m_FilePointer);
switch(ch)
{
case 'v': // 读入的是'v' (后续的数据可能是顶点/法向量/纹理坐标)
// 如果在前面读入的是面的行,那么现在进入的是另一个对象,因此在读入下一个对象之前,
// 需要将最后对象的数据写入到模型结构体中
if(m_bJustReadAFace)
{
// 将最后对象的信息保存到模型结构中
FillInObjectInfo(pModel);
}
// 读入点的信息,要区分顶点 ("v")、法向量 ("vn")、纹理坐标 ("vt")
ReadVertexInfo();
break;
case 'f': // 读入的是'f'
// 读入面的信息
ReadFaceInfo();
break;
default:
// 略过该行的内容
fgets(strLine, 100, m_FilePointer);
break;
}
}
// 保存最后读入的对象
FillInObjectInfo(pModel);
}
// 下面的函数读入顶点信息('v'是指顶点,'vt'指UV坐标)
void CLoadObj::ReadVertexInfo()
{
CVector3 vNewVertex = {0};
CVector2 vNewTexCoord = {0};
char strLine[255] = {0};
char ch = 0;
// 读入第一个字符,判断读入的是否顶点/法向量/UV坐标
ch = fgetc(m_FilePointer);
if(ch == ' ') // 如果是空格,则必是顶点("v")
{
// 读入顶点坐标,格式是"v x y z"
fscanf(m_FilePointer, "%f %f %f", &vNewVertex.x, &vNewVertex.y, &vNewVertex.z);
// 读入该行中余下的内容,则文件指针到下一行
fgets(strLine, 100, m_FilePointer);
// 添加一个新的顶点到顶点链表中
m_pVertices.push_back(vNewVertex);
}
else if(ch == 't') // 如果是't',则必定是纹理坐标("vt")
{
// 读入纹理坐标,格式是"vt u v"
fscanf(m_FilePointer, "%f %f", &vNewTexCoord.x, &vNewTexCoord.y);
// 读入该行余下的内容,则文件指针指向下一行
fgets(strLine, 100, m_FilePointer);
// 添加一个新的纹理坐标到链表中
m_pTextureCoords.push_back(vNewTexCoord);
// 设置对象具有纹理坐标为true
m_bObjectHasUV = true;
}
else // 否则可能是法向量("vn")
{
// 由于在最后计算各点的法向量,在这里略过
fgets(strLine, 100, m_FilePointer);
}
}
// 下面的函数读入面信息
void CLoadObj::ReadFaceInfo()
{
tFace newFace = {0};
char strLine[255] = {0};
// 判断对象是否具有纹理坐标
if(m_bObjectHasUV )
{
// 读入对象的顶点和纹理坐标索引,格式是"顶点1/纹理坐标1 顶点2/纹理坐标2 顶点3/纹理坐标3"
fscanf(m_FilePointer, "%d/%d %d/%d %d/%d", &newFace.vertIndex[0], &newFace.coordIndex[0],
&newFace.vertIndex[1], &newFace.coordIndex[1],
&newFace.vertIndex[2], &newFace.coordIndex[2]);
}
else // 对象无纹理坐标
{
// 读入对象的顶点索引,格式是"顶点1 顶点2 顶点3"
fscanf(m_FilePointer, "%d %d %d", &newFace.vertIndex[0],
&newFace.vertIndex[1],
&newFace.vertIndex[2]);
}
// 读入该行余下的内容,则文件指针指向下一行
fgets(strLine, 100, m_FilePointer);
// 添加一个新面到面链表中
m_pFaces.push_back(newFace);
// 设置刚才读入的是面
m_bJustReadAFace = true;
}
// 下面的函数将读入对象的信息写入模型结构体中
void CLoadObj::FillInObjectInfo(t3DModel *pModel)
{
t3DObject newObject = {0};
int textureOffset = 0, vertexOffset = 0;
int i = 0;
// 模型中对象计数器递增
pModel->numOfObjects++;
// 添加一个新对象到模型的对象链表中
pModel->pObject.push_back(newObject);
// 获得当前对象的指针
t3DObject *pObject = &(pModel->pObject[pModel->numOfObjects - 1]);
// 获得面的数量、顶点的数量和纹理坐标的数量
pObject->numOfFaces = m_pFaces.size();
pObject->numOfVerts = m_pVertices.size();
pObject->numTexVertex = m_pTextureCoords.size();
// 如果读入了面
if(pObject->numOfFaces)
{
// 分配保存面的存储空间
pObject->pFaces = new tFace [pObject->numOfFaces];
}
// 如果读入了点
if(pObject->numOfVerts) {
// 分配保存点的存储空间
pObject->pVerts = new CVector3 [pObject->numOfVerts];
}
// 如果读入了纹理坐标
if(pObject->numTexVertex) {
pObject->pTexVerts = new CVector2 [pObject->numTexVertex];
pObject->bHasTexture = true;
}
// 遍历所有的面
for(i = 0; i < pObject->numOfFaces; i++)
{
// 拷贝临时的面链表到模型链表中
pObject->pFaces[i] = m_pFaces[i];
// 判断是否是对象的第一个面
if(i == 0)
{
// 如果第一索引不是1,则必须略过第一个对象
if(pObject->pFaces[0].vertIndex[0] != 1) {
vertexOffset = pObject->pFaces[0].vertIndex[0] - 1;
// 对于纹理坐标,也进行同样的操作
if(pObject->numTexVertex > 0) {
// 当前的索引剪去1
textureOffset = pObject->pFaces[0].coordIndex[0] - 1;
}
}
}
for(int j = 0; j < 3; j++)
{
// 对于每一个索引,必须将其减去1
pObject->pFaces[i].vertIndex[j] -= 1 + vertexOffset;
pObject->pFaces[i].coordIndex[j] -= 1 + textureOffset;
}
}
// 遍历对象中的所有点
for(i = 0; i < pObject->numOfVerts; i++)
{
// 将当前的顶点从临时链表中拷贝到模型链表中
pObject->pVerts[i] = m_pVertices[i];
}
// 遍历对象中所有的纹理坐标
for(i = 0; i < pObject->numTexVertex; i++)
{
// 将当前的纹理坐标从临时链表中拷贝到模型链表中
pObject->pTexVerts[i] = m_pTextureCoords[i];
}
// 由于OBJ文件中没有材质,因此将materialID设置为-1,必须手动设置材质
pObject->materialID = -1;
// 清除所有的临时链表
m_pVertices.clear();
m_pFaces.clear();
m_pTextureCoords.clear();
// 设置所有的布尔值为false
m_bObjectHasUV = false;
m_bJustReadAFace = false;
}
// 下面的函数为对象序列中的对象赋予具体的材质
void CLoadObj::SetObjectMaterial(t3DModel *pModel, int whichObject, int materialID)
{
// 确保模型合法
if(!pModel) return;
// 确保对象合法
if(whichObject >= pModel->numOfObjects) return;
// 给对象赋予材质ID
pModel->pObject[whichObject].materialID = materialID;
}
// 下面的函数给模型手动添加材质
void CLoadObj::AddMaterial(t3DModel *pModel, char *strName, char *strFile, int r, int g, int b)
{
tMaterialInfo newMaterial = {0};
// 设置材质的RGB值[0 - RED 1 - GREEN 2 - BLUE]
newMaterial.color[0] = r; newMaterial.color[1] = g; newMaterial.color[2] = b;
// 如果具有文件名称,则将其拷贝到材质结构体中
if(strFile) {
strcpy(newMaterial.strFile, strFile);
}
// 如果具有材质名称,则将其拷贝到材质结构体中
if(strName) {
strcpy(newMaterial.strName, strName);
}
// 将材质加入到模型链表中
pModel->pMaterials.push_back(newMaterial);
// 材质数量递增
pModel->numOfMaterials++;
}
// 下面的这些函数主要用来计算顶点的法向量,顶点的法向量主要用来计算光照
// 下面的宏定义计算一个矢量的长度
#define Mag(Normal) (sqrt(Normal.x*Normal.x + Normal.y*Normal.y + Normal.z*Normal.z))
// 下面的函数求两点决定的矢量
CVector3 Vector(CVector3 vPoint1, CVector3 vPoint2)
{
CVector3 vVector;
vVector.x = vPoint1.x - vPoint2.x;
vVector.y = vPoint1.y - vPoint2.y;
vVector.z = vPoint1.z - vPoint2.z;
return vVector;
}
// 下面的函数两个矢量相加
CVector3 AddVector(CVector3 vVector1, CVector3 vVector2)
{
CVector3 vResult;
vResult.x = vVector2.x + vVector1.x;
vResult.y = vVector2.y + vVector1.y;
vResult.z = vVector2.z + vVector1.z;
return vResult;