NeHe Productions OpenGL Article #01


NeHe Productions: OpenGL Article #01

Article 01

Reading Simple Data From A Caligari TrueSpace File


By Dave Kerr


The Basics:

There are two types of trueSpace file, *.scn and *.cob. These are Caligari
Scenes, and Caligari Objects, respectively. The actual data in the files
is not too complicated, because each file is a series of ęchunksł. Each
chunk represents a different type of information. This allows us to scan
through every chunk, only reading in the ones that are relevant.

What We Need:

The official specification for trueSpace files is 82 pages long. The font
is 10pts. For anyone wanting to be able to import simple objects, this is
very daunting. Even looking through the file for a while wonłt get you
very far, there are masses of chunks, each more confusing than the last.

But donłt give up! It is actually very simple to read the objects, it is
done in a few steps:
Read the file header, make sure it is valid.
Iterate through the chunk headers, looking for the “PolH" (Polygon)
chunks.
Ignore all others, but read data from the PolH chunks, adding them to a
list of simple structures.
Make sure the chunk isnÅ‚t a “End “ chunk, if it is, close the file.
(Optional) Create a new file, go through your object structures, and
write them in your own way.
Getting Started:

Create a new *.h file, call it something like “CaligariObjects.h". The
first part is the inclusion guard. Then I typedef the BYTE symbol, which I
like (although feel free to use the actual type name. The same goes for
LPCTSTR).


#ifndef __CALIGARI_OBJECTS_H__
#define __CALIGARI_OBJECTS_H__

#include // We Need This Later

typedef unsigned char BYTE; // My Strange Ways
typedef const char* LPCTSTR; // Microsoft Made Me Strange

static const F_HOLE = 0x08; // (bit 3) Flags Used By Caligari Objects (Irrelevant)
static const F_BACKCULL = 0x10; // (bit 4) Flags Used By Caligari Objects (Irrelevant)

typedef struct tagCALIGARI_HEADER
{
char szIdentifier[9]; // Always "Caligari "
char szVersion[6]; // V00.01 Or Whatever Version The File Comes From
char chMode; // A: ASCII, B: BINARY. We Will Only Read Binary Files
char szBitMode[2]; // LH: Little Endian, HL: Big Endian (Irrelevant)
char szBlank[13]; // Blank Space
char chNewLine; // '\n' Char
} CALIGARI_HEADER;

Next you'll need a 'chunk header'. This comes at the top of every chunk in
the file.


typedef struct tagCHUNK_HEADER
{
char szChunkType[4]; // Identifies The Chunk Type
short sMajorVersion; // Version
short sMinorVersion; // Version
long lChunkID; // Identifier: Each Chunk Is Unique
long lParentID; // Parent, Some Chunks Own Each Other
long lDataBytes; // Number Of Bytes In Data
} CHUNK_HEADER;

A stream of data follows each chunk header. Sometimes this is ints, and
floats etc, but lots of chunks contain the same type of data, such as
position and axis data. Here is the structure that is used to give a chunk
it's name (not all chunks have names).


typedef struct tagCHUNK_NAME
{
short sNameDupecount; // Dupecount
short sStringLen; // Length Of String
char* szName; // Name
} CHUNK_NAME;

A lot of chunks that represent actual objects in the scenes can define
their own axies.


typedef struct tagCHUNK_AXES
{
float fCentre[3]; // x,y,z, Center
float fDirectionX[3]; // x,y,z, Coords Direction Of Local x
float fDirectionY[3]; // x,y,z, Coords Direction Of Local y
float fDirectionZ[3]; // x,y,z, Coords Direction Of Local z
} CHUNK_AXES;

A four by four matrix defines the position of chunks, but only the first
three lines are saved, the last line is always assumed to be [0, 0, 0, 1].



typedef struct tagCHUNK_POSITION
{
float fFirstRow[4];
float fSecondRow[4];
float fThirdRow[4];
} CHUNK_POSITION;

Before we actually create an object class, we need to define a few simple
data types. A face is basically a series of long ints, which represent
positions in the vertex and UV arrays, there is usually only 3 or four
pairs.


struct FACE
{
BYTE byFlags; // Flags
short sCount; // Number Of Vertices
short sMatIndex; // Material Index
long* pVertexUVIndex; // long Index To Vertex, long Index To UV
};

Some might say that representing a vertex in a struct is overkill, but it
doesn't incur any memory cost, and due to the added simplicity you can
actually get a significant speed increase. The same applies to the UV
coord structure. This approach means that if you make a huge change, like
adding the w coordinate to all vertices (for homogenous coordinates) you
can easily change the code.


struct VERTEX
{
float x, y, z;
};
struct UV
{
float u, v;
};

Now we create the actual TrueSpace object class. Later you could include
the name and position, but this serves well as a starting point.


class FC_CaligariObject
{
public:
FC_CaligariObject() {m_pFaces = NULL, m_pVertices = NULL, m_pUV = NULL;}
virtual ~FC_CaligariObject() {delete [] m_pFaces; delete [] m_pVertices; delete [] m_pUV;}
int m_nFaceCount;
FACE* m_pFaces;
int m_nVertexCount;
VERTEX* m_pVertices;
int m_nUVCount;
UV* m_pUV;

BYTE m_byDrawFlags[4];
BYTE m_byRadiositySetting[2];
};

We've included the vector template, now typedef it for readability, and
declare the big function we will use.


using namespace std;
typedef vector cob_vector;
bool ReadObjects(LPCTSTR lpszFileName, cob_vector* pDestination);

#endif

Now we create the loading function. Any file can contain a number of
objects, so the best way to manage this is to use some sort of container
for each object. Containers are a touchy subject, some people like the
standard libraries, some people like MFC ones, and others use their own.
In my opinion the standard library containers are that fastest and best.

Now create a new *.cpp file, call it something like "CaligariObjects.cpp",
and the following, including the header we've created, and starting the
implementation of the function we've declared.


#include // Or Whatever Your Header Is

bool ReadObjects(LPCTSTR lpszFileName, cob_vector* pDestination)
{

We first try to open the specified file.


FILE* pFile = fopen(lpszFileName, "rb");
if(!pFile)
return false;

Now we move the file pointer to the beginning of the chunk data.


// Get To The Beginning Of Real Data
fseek(pFile, sizeof(CALIGARI_HEADER), SEEK_SET);

Now we loop though every chunk, storing the data temporarily in 'ch'.


CHUNK_HEADER ch;
char chName[5];
do
{
// Read The Header
fread(&ch, sizeof(CHUNK_HEADER), 1, pFile);

We only want "PolH" chunks, so here we test to see if we have one.


if(strcmp(ch.szChunkType, "PolH") == 0) // Is It A Poly?

We will need a new caligari object, and we create it on the heap. It will
later be added to the vector.


FC_CaligariObject* pOb = new FC_CaligariObject;

This section reads the chunk name data into a struct, but doesn't use it.
Later on, you may want to name your objects so this has been included for
detail.


CHUNK_NAME cn;
// Read The Name Info
fread(&cn, sizeof(short) * 2, 1, pFile);
// Get Memory For The String
cn.szName = new char[cn.sStringLen + 1];

// Read The String
fread(cn.szName, sizeof(char), cn.sStringLen, pFile);
// Zero Terminate
cn.szName[cn.sStringLen] = '\0';

I cannot find a way to easily implement the local axis system (if anyone
can, please e-mail me), but we read the data anyway, just in case you want
to use it in your own implementation.


CHUNK_AXES ax; // Read The Local Axies
fread(&ax, sizeof(ax), 1, pFile);

Now we read the position. Try as I might, I am not good enough to
translate this into simple data (like a translate x, y and z factor, a
scale x, y, and z factor etc) so the objects I load are always at the
origin, not rotated or scaled. If you find a way to make this matrix into
simple values like those described, please e-mail me.


// Read The Position
CHUNK_POSITION ps;
fread(&ps, sizeof(ps), 1, pFile);

Don't be worried if this looks complex. First we read the number of
vertices, then we get space for them (using the new operator), and then we
get 'fread' to read them into our array, all at once.


// Read Number Of Verticies
fread(&pOb->m_nVertexCount, sizeof(int), 1, pFile);
// Get Space For Them
pOb->m_pVertices = new VERTEX[pOb->m_nVertexCount];
// This Reads All The Vertices
fread(&pOb->m_pVertices, sizeof(VERTEX), pOb->m_nVertexCount, pFile);

Exactly the same as before applies to our UVs.


// Read UV Count
fread(&pOb->m_nUVCount, sizeof(int),1,pFile);
// alloc Space For Them
pOb->m_pUV = new UV[pOb->m_nUVCount];
// Read Every UV
fread(pOb->m_pUV, sizeof(UV), pOb->m_nUVCount, pFile);

Here we get the number of faces and get memory for them, but they are
slightly more difficult to read in, so we have to use another loop.


// Read Faces
fread(&pOb->m_nFaceCount, sizeof(int),1,pFile);
// alloc Space
pOb->m_pFaces = new FACE[pOb->m_nFaceCount];

for(int i=0; im_nFaceCount; i++)
{

TrueSpace faces can be of different types. In the interest of simplicity,
we will ignore the special 'hole' faces (which are holes in the previous
face) however, when we do read these faces, we check their type, as some
have extra data.


// Read Face Type
FACE* pFace = &pOb->m_pFaces[i];
fread(&pFace->byFlags, sizeof(BYTE), 1, pFile);

// Read Vertex Count
fread(&pFace->sCount, sizeof(short), 1, pFile);

// Do We Read A Material Number?
if((pFace->byFlags&F_HOLE) == 0)
fread(&pFace->sMatIndex, sizeof(short), 1, pFile);

This is where we read the actual indices. Each one is actually a pair of
'longs' which are indices into the vertex and UV array, respectively.


// Vertex And UV
pFace->pVertexUVIndex = new long[pFace->sCount * 2];
fread(pFace->pVertexUVIndex, sizeof(long), pFace->sCount * 2, pFile);
}

We want to be able to read any version, so we must check the chunk version
ID, and depending on the version, we might read extra data.


// Any Extra Stuff?
if(ch.sMinorVersion > 4)
{
// We Have Flags To Read
fread(&pOb->m_byDrawFlags, sizeof(BYTE) * 4, 1 ,pFile);
if(ch.sMinorVersion > 5 && ch.sMinorVersion < 8)
fread(&pOb->m_byRadiositySetting, sizeof(BYTE) * 2, 1, pFile);
}
pDestination->push_back(pOb);
}
else
fseek(pFile, ch.lDataBytes, SEEK_CUR);

memcpy(chName, ch.szChunkType, 4);
chName[4] = '\0';
} while(strcmp(chName, "END ") != 0);

return true;
}

The easiest way to use this code is in an example. Create a new workspace,
call it 'test' or something, and then add the CaligariObjects.h and
CaligariObjects.cpp files to it.


// Working Example Of TrueSpace Loading Code
// Code Created By Dave Kerr

#include // If You Get An Error, #include Instead
#include "CaligariObjects.h" // The Structures Defined Earlier

using namespace std;

int main(int argc, char* argv[])
{
cout << "Caligari loading code tester.\nPlease enter file path: ";
char szFilePath[255];
cin >> szFilePath;

cout << "Attempting to load '"<
cob_vector cobs;

if(!ReadObjects(szFilePath, &cobs))
{
cout << "Failed to open the file.\n";
return 0;
}

cout << "Success! Showing object data . . .\n";
for(cob_vector::iterator i = cobs.begin(); i < cobs.end(); i++)
{
cout << "Object:\n"<< (*i)->m_nFaceCount << " faces.\n";
cout << (*i)->m_nVertexCount << " vertices.\n";
cout << (*i)->m_nUVCount << " UV coords.\n";
}

return 0;
}

Now you have the simple objects. They can very easily be drawn and
implemented, but that would add too much to the tutorial. If anyone can
find solutions to the problems concerning local axis and (the serious
problem) the position/translation/scale matrix, please e-mail me.

Dave Kerr - http://www.focus.esmartweb.com



Wyszukiwarka

Podobne podstrony:
NeHe Productions OpenGL Article #02
NeHe Productions OpenGL Article #04
NeHe Productions OpenGL Article #05
NeHe Productions OpenGL Article #03
article 01
01 opengl 3 2 wprowadzenie
01 opengl 4 2 wprowadzenie
products j01 01
t informatyk12[01] 02 101
r11 01
2570 01
introligators4[02] z2 01 n
Biuletyn 01 12 2014
beetelvoiceXL?? 01
01
2007 01 Web Building the Aptana Free Developer Environment for Ajax
Managing Producing Field

więcej podobnych podstron