A brief note on cross platform compilation: Plib comes with both Makefiles (for Unix and for the CygWin-system under Windows) and workspace/project-files for Micro$oft Visual C++. If you have both, please update, test and commit both. Otherwise, when committing, please tell the people that something is missing (for example: you didn't update the workspace files) and that some kind soul should do so.
Both loaders and writers convert between ssg's internal geometry representation and that of the file format. One key difference is that when loading, you should support all possibile geometry-representations of the file format. This is to ensure that plib can handle all the possible variations of a file format that may be generated by the tools that export to it. On the other hand, when writing a format, you can pretty safely only write to one geometry format (unless you need features peculiar to more than one geometry representation), because you have the final say on how the data is to be written.
Regarding plib's geometry-representations: there are only two on the highest
ssgVtxArray. Actually, there is also
ssgVTable, but that is deprecated.
ssgVtxArray is newer, is derived
from ssgVtxTable and uses a index-list. Apart from that they are quite
similar, and both have an interface
getNumTriangles () ; and
getTriangle ( i, ...);
For this reasons, it is easier to write a writer than a loader.
For both ssgVtxTable and ssgVtxArray you need to choose a
GL-type. Currently ssgLoaderWriterMesh (more on this in the
next section) uses
void ssgLoaderWriterMesh::addFaceFromIntegerArray( int numVertices, int *vertices );With several calls, you can add several n-sided polys to one mesh ("one node"). When you are done constructing the mesh from the file, you call
void ssgLoaderWriterMesh::addToSSG( class ssgSimpleState *currentState, class ssgLoaderOptions* current_options, class ssgBranch *curr_branch_)and the class adds the information into the scene graph. It handles ssgs' restrictions. For example, if the polygons of the mesh use 5 textures then at least 5 nodes will be added to the scene graph.
Unfortunately, this class isn't completely finished. As of this writing, Wolfram Kuss (firstname.lastname@example.org) has implemented those parts that were needed for the loaders he has finished thus far. Hopefully people will contribute more features as time goes on.
If you are writing a new loader for a file format that doesn't hold to all restrictions of ssg (and virtually none do), you are urged to use this class. Your loader will be more consistent and easier to maintain and read.
Further, there are many optimizations that can be done (For example: "If the state is different, but not the texture, do we need several nodes?" or "When we have multitexturing, can we use that?" or "Is there an optimal strip length?" or "How do I subdivide polys into triangles so that the stripifier will work well?"-- the list goes on forever). Once we have good answers to these questions (and the will to implement them), it will be easier to do them once in the ssgLoaderWriterMesh than in all the loaders seperately. It is noteable that most loaders written before ssgLoaderWriterMesh have had some sort of intermediatory mesh structure.
In the future, ssgLoaderWriterMesh should also be used for writers, doing the opposite job: It takes the information from ssg with the restrictions and then looks whether it can optimize (for example merging nodes) by relaxing the restrictions.
ssgLoaderWriterMeshor do a
reInit(). To insert the data into the
ssgLoaderWriterMesh, you have to add vertices, faces, materials, materialindexes (saying what face uses what material) and, if applicable texture coordinates. For all of these, you can say in advance how many you have. If you know that you have 3712 vertices, call
createVertices(3712)and everything is allocated at once and
addVertexwill be very fast. If you don't know in advance how many you have, you still have to call
createVertices(), as this also allocates the vertices. In this case, a certain amount of vertices will be reserved and the list will dynamically grow as more are added.
Vertices are simply sgVec3s. Faces are simply lists/arrays of vertex indexes.
For adding faces, use
addFace if you already have a
ssgIndexArray or use
addFaceFromIntegerArray if you have the vertex indexes in a C(++) array.
You need to add at least one material (
ssgSimpleState). For each face, you tell
ssg which material to use via
addMaterialIndex. Here is code from ssgLoadOFF, which tells
ssg to use the
ssgSimpleState ss for all faces:
theMesh.createMaterials( 1 ); theMesh.addMaterial( &ss ); theMesh.createMaterialIndices( _ssgNoFacesToRead ) ; for(i=0;i<_ssgNoFacesToRead ;i++) theMesh.addMaterialIndex ( 0 ) ;
void createPerFaceAndVertexTextureCoordinates2( int numReservedTextureCoordinate2Lists = 3 ); void addPerFaceAndVertexTextureCoordinate2( ssgTexCoordArray **textureCoordinateArray );or
void createPerVertexTextureCoordinates2( int numReservedTextureCoordinates2 = 3 ); void addPerVertexTextureCoordinate2( sgVec2 textureCoordinate );
"Whenever a branch node is created. The deal is that most file formats are missing important features at the Branch level - but many support comment fields - or long ASCII name strings or something. The idea was to allow the artists to attach an ARBITARY comment string in their modeller - and to have the loader trap these strings and pass them on to the application.So much for the quote from Steve.
Hence, if the hook function is defined then when a branch node needs to be created, we call the application's callback with the ASCII string that was embedded in the file and let the application construct the ssgBranch node. Hence, you could put the string "~LOD: RANGE=100 meters" into the comment field in (say) the AC3D modeller. (AC3D calls this a "Data" field)...the application could then say to itself: "Any comment that starts with a tilde ('~') is a command to the loader" and parse such 'comments' as commands. In this case, it would construct an ssgRangeSelector and set the transition range to 100m and return the application back to the loader.
Check the Tux Kart sources to see this in action."
As of this writing, the ssgLoaderOptions code has been copied from another loader into ssgLoaderWriterMesh.
The formats which currently use the parser are .X (which uses the line-independant API), .ase, .scenery and .off (which use the line-by-line-API).
Some functions are used by both APIs. For example:
void openFile( const char* fname, const _ssgParserSpec* spec = 0 );
In ssgParserSpec, you give the parser the specification of the format. You say which characters start a comment, which characters are skipable, which characters are used for braces (which are used to determine the parser's level-- useful for parser's which work recursively). Most important are the delimiters. These determine where one token ends and the next one begins. For example, the first token of the line
","is a delimiter and
1234,567otherwise. The parser differentiates between skipable delimiters that are "swallowed" by the parser and non-skipable ones that are passed to the loader. So, regarding the example-line there are three possibilities:
","is not a delimiter => The line contains one token, namely
","is a skipable delimiter => The line contains two tokens, namely
","is a non-skipable delimiter => The line contains three tokens, namely
ssgSaveTRI. The function
int ssgSaveXYZ ( const char *filename, ssgEntity *ent )normally calls a function
static void save_entities ( ssgEntity *e )which just recursively walks the scene graph. You should be able to use this function and just write a
static void save_vtx_table ( ssgVtxTable *vt )which writes a
|<= previous =||Return to SSG Index|
|Steve J. Baker. <email@example.com>|