COB Loader: Version 0.1

Announce new projects or updates of Irrlicht Engine related tools, games, and applications.
Also check the Wiki
Post Reply
Morrog

COB Loader: Version 0.1

Post by Morrog »

I have a horrible time with X, MS3D, OBJ, you name it. None of them seem to fit my needs. OBJ textures don't work, I could never make X files load right, MS3D I can't create. The only thing I can load that actually works are BSP files from GMax, heh.

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
COB.h

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
Guest

Post by Guest »

Nice. Why dont you go to http://www.irrlichtnx.mmdevel.de/phpBB2/ where you can add this loader as patch?
Post Reply