I wrote a COB Loader for my previous projects that were non-Irrlicht. Now, having a need for COB loading again, I've put my COB Loader into my version of Irrlicht (which includes my irr::core::string patch and a function to drop a mesh to free memory). It is one BIG hack. I didn't feel like going through my code and cleaning it up and making it fit Irrlicht code, It's 10 pm already So, the code is ugly, doesn't fit Irrlicht style at all, etc. Also IReadFile doesn't have a way to fscanf, which forced me to open the model file myself to get a usable FILE * so I could use fscanf. At one point I tried making IReadFile::File public, but for some odd reason that didn't work..the pointer was always bad. Not sure why.
So yeah, a huge hack but whatcha going to do? It works, as far as I can tell. I've used the loader before, as I said, in older projects and I've loaded a custom-built labratory computer model, which is semi-complex, and besides messy UV corrds it loaded fine (probably TrueSpace's fault). So this loader, despite being hackish and missing some COB features, should work pretty well.
And for those who don't know, COB is the TrueSpace native format.
Oh, and my loader only supports ASCII, for now. a binary loader shouldn't be too hard to make, but for debugging purposes I left it as ASCII.
So here it is for anyone who is desperate to use it. If you aren't desperate and you're not an Irrlicht developer, don't use it, it's ugly and hackish If you're an irrlicht developer and you're up to the job you could clean it up (A LOT) and put it in the official Irrlicht. That'd be pretty cool.
COB.cpp
Code: Select all
#include "CCOBMeshFileLoader.h"
#include <string.h>
#include "os.h"
#include "SMeshBuffer.h"
#include "SAnimatedMesh.h"
#include <map>
using namespace std;
namespace irr
{
namespace scene
{
#define charsToUIntD(a, b, c, d) ((a << 24) | (b << 16) | (c << 8) | d)
inline unsigned int charsToUInt(const char *str)
{
return (str[0] << 24) | (str[1] << 16) | (str[2] << 8) | str[3];
}
CVector3 applyMatrix(CVector3 vec, float matrix[4][4])
{
CVector3 ret;
ret.x = (vec.x * matrix[0][0]) + (vec.y * matrix[1][0]) + (vec.z * matrix[2][0]) + matrix[3][0];
ret.y = (vec.x * matrix[0][1]) + (vec.y * matrix[1][1]) + (vec.z * matrix[2][1]) + matrix[3][1];
ret.z = (vec.x * matrix[0][2]) + (vec.y * matrix[1][2]) + (vec.z * matrix[2][2]) + matrix[3][2];
return ret;
}
//! Constructor
CCOBMeshFileLoader::CCOBMeshFileLoader(io::IFileSystem* fs, video::IVideoDriver* driver)
: FileSystem(fs), Driver(driver), Mesh(0), File(0), mFilePointer(0), numOfObjects(0)
{
if (FileSystem)
FileSystem->grab();
if (Driver)
Driver->grab();
}
//! destructor
CCOBMeshFileLoader::~CCOBMeshFileLoader()
{
if (FileSystem)
FileSystem->drop();
if (Driver)
Driver->drop();
if (Mesh)
Mesh->drop();
}
//! returns true if the file maybe is able to be loaded by this class
//! based on the file extension (e.g. ".bsp")
bool CCOBMeshFileLoader::isALoadableFileExtension(const c8* filename)
{
return strstr(filename, ".cob")!=0;
}
//! creates/loads an animated mesh from the file.
//! \return Pointer to the created mesh. Returns 0 if loading failed.
//! If you no longer need the mesh, you should call IAnimatedMesh::drop().
//! See IUnknown::drop() for more information.
IAnimatedMesh* CCOBMeshFileLoader::createMesh(io::IReadFile* file)
{
os::Printer::log("Attempting to load COB...");
s32 i;
file->seek(0);
File = file;
mFilePointer = fopen(File->getFileName(), "rb");
numOfObjects = 0;
numOfMaterials = 0;
i = readFileHeader();
if(i == -1)
return false;
if(i != 0) //if(Not Ascii)
return false; //We don't support binary yet
if (Mesh)
Mesh->drop();
Mesh = new SMesh();
if(readHeaders())
return false;
if(writeToMesh())
return false;
SAnimatedMesh* am = new SAnimatedMesh();
am->Type = EAMT_X;
for (s32 j=0; j<Mesh->getMeshBufferCount(); ++j)
((SMeshBuffer*)Mesh->getMeshBuffer(j))->recalculateBoundingBox();
Mesh->recalculateBoundingBox();
am->addMesh(Mesh);
am->recalculateBoundingBox();
Mesh->drop();
Mesh = 0;
return am;
}
s32 CCOBMeshFileLoader::readHeaders()
{
int lastPos;
int size, major, minor, id, parent;
unsigned int uiType;
char type[5];
while(true)
{
if(feof(mFilePointer))
break;
size = 0; //I guess this is suppose to be done...
fscanf( mFilePointer, "%4c V%d.%d Id %d Parent %d Size %d",
type, &major, &minor, &id, &parent, &size); //Read Chunk Header
lastPos = ftell(mFilePointer); //Read !after! chunk header
//Convert 4-char string to 4-byte integer
//Makes it possible to do a switch statement
uiType = charsToUInt(type);
if(readChunk(uiType, id, parent) == 1) //read the chunk
break; //if(1) then we reached an end chunk
if(
fseek(mFilePointer, lastPos + size, SEEK_SET) //Go to next chunk
)
break;
}
return 0;
}
s32 CCOBMeshFileLoader::readChunk(unsigned int type, int id, int parent)
{
int ret = 0;
switch(type)
{
case charsToUIntD('E','N','D',' '):
ret = 1;
break;
case charsToUIntD('P','o','l','H'):
readPolH();
break;
case charsToUIntD('U','n','i','t'):
break; //I added this case, because I've seen it used often, but I just
//break because it isn't necissary info....yet?
case charsToUIntD('M','a','t','1'):
readMat();
break;
default:
break;
}
return ret;
}
s32 CCOBMeshFileLoader::readMat()
{
int matId, iTemp;
char shader[1024], facet[1024];
float r, g, b, a, ka, ks, eXP, ior, f;
tMaterialInfo tempMat;
char textureName[256], sTemp[1024], *cpTemp;
textureName[0] = 0;
tempMat.strFile[0] = 0;
tempMat.uOffset = tempMat.uTile = tempMat.vOffset = tempMat.vTile = 0.0;
//This is all of the basic material info(non-optional)
fscanf(mFilePointer, "\nmat# %d", &matId);
fscanf(mFilePointer, "\nshader: %s facet: %s", shader, facet);
fscanf(mFilePointer, "\nrgb %g,%g,%g", &r, &g, &b);
fscanf(mFilePointer, "\nalpha %g ka %g ks %g exp %g ior %g", &a, &ka, &ks, &eXP, &ior);
//Read all maps, we ignore all of them except the texture
if(fscanf(mFilePointer, "\nenvironment: %d", &iTemp))
{
fread(sTemp, 1, iTemp, mFilePointer);
fscanf(mFilePointer, "\nflags %d", &iTemp);
}
if(fscanf(mFilePointer, "\ntexture: %d", &iTemp))
{
fread(tempMat.strFile, 1, iTemp, mFilePointer);
tempMat.strFile[iTemp] = 0;
cpTemp = strrchr( tempMat.strFile, '\\' );
if(cpTemp != NULL)
{
cpTemp++;
strcpy(sTemp, cpTemp);
strcpy(tempMat.strFile, sTemp);
}
fscanf(mFilePointer, "\noffset %g,%g repeats %g,%g flags %d", &(tempMat.uOffset), &(tempMat.vOffset), &(tempMat.uTile), &(tempMat.vTile), &iTemp);
}
if(fscanf(mFilePointer, "\nbump: %d", &iTemp))
{
fread(sTemp, 1, iTemp, mFilePointer);
fscanf(mFilePointer, "\noffset %g,%g repeats %g,%g amp %g flags %d", &f, &ka, &ks, &eXP, &ior, &iTemp);
}
tempMat.color[0] = (char)(r * 255);
tempMat.color[1] = (char)(g * 255);
tempMat.color[2] = (char)(b * 255);
tempMat.strName[0] = 0; //No material names
tempMat.texureId = 0; //This is set later
if(mMaterials.size() < (matId + 1))
mMaterials.resize(matId + 1);
mMaterials[matId] = tempMat;
if(tempMat.strFile[0] != 0) //There is a texture, enable object bHasTextures
{
for(vector< t3DObject >::iterator iter = mCurObjects.begin(); iter != mCurObjects.end(); ++iter)
{
if(iter->materialID == matId)
{
iter->bHasTexture = true;
//Modify the uv's, thus that the offset/tile data is applied
for(int w = 0; w < iter->numTexVertex; w++)
{
iter->pTexVerts[w].x = (iter->pTexVerts[w].x * tempMat.uTile) + tempMat.uOffset;
iter->pTexVerts[w].y = (iter->pTexVerts[w].y * tempMat.vTile) + tempMat.vOffset;
}
}
}
}
return 0;
}
s32 CCOBMeshFileLoader::readPolH()
{
CVector3 triVectorTemp;
CVector2 biVectorTemp;
tFace tempFace;
float fMatrix[4][4];
float fTempA, fTempB, fTempC;
char sTemp[1024];
int vCount, tCount, fCount, i, j, a, b, c, d;
map < int, vector < tFace > > polys;
vector< CVector3 > verts;
vector< CVector2 > texVerts;
fscanf(mFilePointer, "\nName %s", sTemp); //Read name and skip it! We ain't using it.
//We will only obey the transformation matrix
//The axes can be useful, but to make things simple I ignore them
//So I still read them to get to the right file position and to
//leave the code open in case I ever want the axes.
fscanf(mFilePointer, "\ncenter %g %g %g", &fTempA, &fTempB, &fTempC);
fscanf(mFilePointer, "\nx axis %g %g %g", &fTempA, &fTempB, &fTempC);
fscanf(mFilePointer, "\ny axis %g %g %g", &fTempA, &fTempB, &fTempC);
fscanf(mFilePointer, "\nz axis %g %g %g", &fTempA, &fTempB, &fTempC);
//Read Transformation Matrix
fscanf(mFilePointer, "\nTransform\n%g %g %g %g", &(fMatrix[0][0]), &(fMatrix[1][0]), &(fMatrix[2][0]), &(fMatrix[3][0]));
fscanf(mFilePointer, "\n%g %g %g %g", &(fMatrix[0][1]), &(fMatrix[1][1]), &(fMatrix[2][1]), &(fMatrix[3][1]));
fscanf(mFilePointer, "\n%g %g %g %g", &(fMatrix[0][2]), &(fMatrix[1][2]), &(fMatrix[2][2]), &(fMatrix[3][2]));
fscanf(mFilePointer, "\n%g %g %g %g", &(fMatrix[0][3]), &(fMatrix[1][3]), &(fMatrix[2][3]), &(fMatrix[3][3]));
fscanf(mFilePointer, "\nWorld Vertices %ld", &vCount);
for(i = 0; i < vCount; i++)
{
fscanf(mFilePointer, "\n%f %f %f", &(triVectorTemp.x), &(triVectorTemp.y), &(triVectorTemp.z)); //Read 1 Point
//Now we apply the matrix
triVectorTemp = applyMatrix(triVectorTemp, fMatrix);
verts.push_back(triVectorTemp); //Add vertex to list
}
fscanf(mFilePointer, "\nTexture Vertices %ld", &tCount);
for(i = 0; i < tCount; i++)
{
fscanf(mFilePointer, "\n%f %f", &(biVectorTemp.x), &(biVectorTemp.y)); //Read 1 Point
texVerts.push_back(biVectorTemp); //Add texture coord to list
}
fscanf(mFilePointer, "\nFaces %ld", &fCount);
for(i = 0; i < fCount; i++)
{
tempFace.coordIndex.clear();
tempFace.vertIndex.clear();
fscanf(mFilePointer, "\nFace verts %d flags %d mat %d\n", &a, &j, &b);
//We will dump j(flags) because the flags seem pointless.
//"a" is the number of verts, "b" is the material #
for(j = 0; j < a; j++)
{
fscanf(mFilePointer, "<%d,%d> ", &c, &d); //Read in index and texIndex
tempFace.vertIndex.push_back(c);
tempFace.coordIndex.push_back(d);
}
for(j = a - 1; j >= 0; j--) //Reverse the order
{
tempFace.coordIndex.push_back(tempFace.coordIndex[j]);
tempFace.vertIndex.push_back(tempFace.vertIndex[j]);
}
tempFace.vertIndex.erase(tempFace.vertIndex.begin(), tempFace.vertIndex.begin() + a);
tempFace.coordIndex.erase(tempFace.coordIndex.begin(), tempFace.coordIndex.begin() + a);
polys[b].push_back(tempFace);
}
t3DObject tempObject;
for(map < int, vector < tFace > >::iterator iter = polys.begin(); iter != polys.end(); ++iter)
{
tempObject.bHasTexture = false; //This may be changed later in the code, depending on what materials are loaded
tempObject.materialID = iter->first;
tempObject.numOfFaces = fCount;
tempObject.numOfVerts = vCount;
tempObject.numTexVertex = tCount;
tempObject.pNormals = NULL;
tempObject.pFaces = new tFace[fCount];
for(i = 0; i < iter->second.size(); i++)
tempObject.pFaces[i] = (iter->second)[i];
tempObject.pTexVerts = new CVector2[tCount];
for(i = 0; i < tCount; i++)
tempObject.pTexVerts[i] = texVerts[i];
tempObject.pVerts = new CVector3[vCount];
for(i = 0; i < vCount; i++)
tempObject.pVerts[i] = verts[i];
tempObject.strName[0] = 0; //No name
mCurObjects.push_back(tempObject);
}
return 0;
}
s32 CCOBMeshFileLoader::writeToMesh()
{
vector<float> verts;
numOfObjects = mCurObjects.size();
numOfMaterials = mMaterials.size();
for(int i = 0; i < numOfObjects; i++)
{
video::SMaterial mat;
tMaterialInfo mat_info = mMaterials[mCurObjects[i].materialID];
mat.AmbientColor = video::SColor(255, mat_info.color[0], mat_info.color[1], mat_info.color[2]);
if(mat_info.strFile[0] != 0)
mat.Texture1 = Driver->getTexture(mat_info.strFile);
SMeshBuffer* mb = new scene::SMeshBuffer();
Mesh->addMeshBuffer(mb);
mb->Material = mat;
mb->drop();
for(int j = 0; j < mCurObjects[i].numOfFaces; j++)
{
int vertSize = mCurObjects[i].pFaces[j].vertIndex.size();
for(int whichVertex = 0; whichVertex < vertSize; whichVertex++)
{
int index = mCurObjects[i].pFaces[j].vertIndex[whichVertex];
int tIndex = mCurObjects[i].pFaces[j].coordIndex[whichVertex];
//Odd, but first we build a vector of vertex array.
//Later we will allocate the real array.
verts.push_back(mCurObjects[i].pTexVerts[ tIndex ].x);
verts.push_back(mCurObjects[i].pTexVerts[ tIndex ].y);
verts.push_back(mCurObjects[i].pVerts[ index ].x);
verts.push_back(mCurObjects[i].pVerts[ index ].y);
verts.push_back(mCurObjects[i].pVerts[ index ].z);
if(whichVertex >= 2)
{
video::S3DVertex vtx;
vtx.Color.set(255,255,255,255);
vtx.TCoords = core::vector2df(verts[0], verts[1]);
vtx.Pos = core::vector3df(verts[2], verts[3], verts[4]);
mb->Vertices.push_back(vtx);
mb->Indices.push_back(mb->Vertices.size() - 1);
vtx.TCoords = core::vector2df(verts[5], verts[6]);
vtx.Pos = core::vector3df(verts[7], verts[8], verts[9]);
mb->Vertices.push_back(vtx);
mb->Indices.push_back(mb->Vertices.size() - 1);
vtx.TCoords = core::vector2df(verts[10], verts[11]);
vtx.Pos = core::vector3df(verts[12], verts[13], verts[14]);
mb->Vertices.push_back(vtx);
mb->Indices.push_back(mb->Vertices.size() - 1);
//Remove 2nd vertex
verts.erase(verts.begin() + 5, verts.begin() + 10);
}
} //for(verts)
verts.clear(); //Duh, gotta clear it
} //for(faces)
}//for(objects)
/*for(int i = 0; i < numOfObjects; i++)
{
SMaterial mat;
tMaterialInfo mat_info = mMaterials[mCurObjects[i].materialID];
mat.AmbientColor = SColor(mat_info.color[0], mat_info.color[1], mat_info.color[2]);
if(mat_info.strFile[0] != 0)
mat.Texture1 = Driver->getTexture(mat_info.strFile);
SMeshBuffer* mb = new scene::SMeshBuffer();
Mesh->addMeshBuffer(mb);
mb->Material = mat;
mb->drop();
video::S3DVertex vtx;
vtx.Color.set(255,255,255,255);
vtx.Normal.set(0,0,0);
mb->
mCurObjects[i].
mb->Vertices
}
pModel->numOfObjects = mCurObjects.size();
pModel->numOfMaterials = mMaterials.size();
pModel->pMaterials = mMaterials;
pModel->pObject = mCurObjects;*/
return 0;
}
s32 CCOBMeshFileLoader::readFileHeader()
{
char sTemp[1024];
fread(sTemp, 1, 9, mFilePointer);
sTemp[9] = 0;
if(strcmp(sTemp, "Caligari "))
return -1;
fread(sTemp, 1, 6, mFilePointer); //skip the version, cause I don't check
fread(sTemp, 1, 1, mFilePointer);
fseek(mFilePointer, 32, SEEK_SET); //skip everything else!
if(sTemp[0] == 'A')
return 0;
if(sTemp[0] == 'B')
return 1;
return -1;
}
} // end namespace scene
} // end namespace irr
Code: Select all
#ifndef __C_COB_MESH_FILE_LOADER_H_INCLUDED__
#define __C_COB_MESH_FILE_LOADER_H_INCLUDED__
#include "IMeshLoader.h"
#include "IFileSystem.h"
#include "IVideoDriver.h"
#include "irrString.h"
#include "SMesh.h"
#include <vector>
#ifndef BYTE
#define BYTE unsigned char
#endif
namespace irr
{
namespace scene
{
class CVector3
{
public:
float x, y, z;
};
class CVector2
{
public:
float x, y;
};
struct tFace
{
std::vector< int > vertIndex; // indicies for the verts that make up this triangle
std::vector< int > coordIndex; // indicies for the tex coords to texture this face
};
struct tMaterialInfo
{
char strName[255]; // The texture name
char strFile[255]; // The texture file name (If this is set it's a texture map)
BYTE color[3]; // The color of the object (R, G, B)
int texureId; // the texture ID
float uTile; // u tiling of texture (Currently not used)
float vTile; // v tiling of texture (Currently not used)
float uOffset; // u offset of texture (Currently not used)
float vOffset; // v offset of texture (Currently not used)
} ;
struct t3DObject
{
int numOfVerts; // The number of verts in the model
int numOfFaces; // The number of faces in the model
int numTexVertex; // The number of texture coordinates
int materialID; // The texture ID to use, which is the index into our texture array
bool bHasTexture; // This is TRUE if there is a texture map for this object
char strName[255]; // The name of the object
CVector3 *pVerts; // The object's vertices
CVector3 *pNormals; // The object's normals
CVector2 *pTexVerts; // The texture's UV coordinates
tFace *pFaces; // The faces information of the object
};
//! Meshloader capable of loading COB meshes.
class CCOBMeshFileLoader : public IMeshLoader
{
public:
//! Constructor
CCOBMeshFileLoader(io::IFileSystem* fs, video::IVideoDriver* driver);
//! destructor
virtual ~CCOBMeshFileLoader();
//! returns true if the file maybe is able to be loaded by this class
//! based on the file extension (e.g. ".cob")
virtual bool isALoadableFileExtension(const c8* fileName);
//! creates/loads an animated mesh from the file.
//! \return Pointer to the created mesh. Returns 0 if loading failed.
//! If you no longer need the mesh, you should call IAnimatedMesh::drop().
//! See IUnknown::drop() for more information.
virtual IAnimatedMesh* createMesh(irr::io::IReadFile* file);
private:
int numOfObjects;
int numOfMaterials;
std::vector< t3DObject > mCurObjects;
std::vector< tMaterialInfo > mMaterials;
s32 readFileHeader();
s32 readHeaders();
s32 readChunk(unsigned int type, int id, int parent);
s32 readPolH();
s32 readMat();
s32 writeToMesh();
io::IFileSystem* FileSystem;
video::IVideoDriver* Driver;
io::IReadFile* File;
FILE *mFilePointer;
SMesh* Mesh;
};
} // end namespace scene
} // end namespace irr
#endif