(Slightly OT) Working on an OpenGL model format

Here’s what I’ve been able to come up with thus far, let me know what you think.

typedef struct
{
unsigned long NumVertex; // Vertex count (also the count for all variant data)
float *Vertex; // Vertex array

unsigned char HasTangent; // Does it have UV tangent basis?
float *Tangent; // U Tangent array (tangents)
float *Binormal; // V Tangent array (binormals)

unsigned char HasNormal; // Has normals?
float *Normal; // Normal array

unsigned char NumTexCoordSet; // Number of texture coord sets (how many tex coord arrays, max of 4 at this point)

unsigned long Tex0ID; // Texture ID for TMU0
float *TexCoord0; // Tex coord array 0

unsigned long Tex1ID; // Texture ID for TMU1
float *TexCoord1; // Tex coord array 1

unsigned long Tex2ID; // Texture ID for TMU2
float *TexCoord2; // Tex coord array 2

unsigned long Tex3ID; // Texture ID for TMU3
float *TexCoord3; // Tex coord array 3

unsigned char HasWeights; // Does it have vertex weights?
unsigned char NumWeights; // Number of weights per vertex (depends on extension used, probably a max of 4)
float *Weights; // Weight array

unsigned long NumFace; // Number of faces
unsigned long *Face; // Face array (should this be a ushort?)

unsigned long UserDataMagic; // Magic key for user data ID (kind of like 3D Studio’s chunk)
unsigned long NumUserData; // User data count, NumUserData really isn’t the right name, so maybe change it
void *UserData; // User defined data (for expandibility)
} Surface_t;

typedef struct
{
unsigned long Magic; // Model magic ID (to be determined, probably end up being OGLM)

unsigned short NumSurface; // Number of surfaces in this model
Surface_t *Surface; // Surface structure array

// Maybe add material support here?
} Model_t;

Obvoiusly there are a few things missing. I’m wondering if a variant color array should be added, but I’m thinking no since that isn’t really used any more, and if it’s really needed you could just use user data.

Also is there a big enough of a demand for material properties? I think so, but how would you like it implimented? I think an index per surface into a material structure would be best (which would also contain the texture name and OpenGL texture object ID), but like I said, this is a work in progress.

why not all has* flags in the beginning? first of all the stucture packs better and second, you have better control befpre loading the data.

Originally posted by Mazy:
why not all has* flags in the beginning? first of all the stucture packs better and second, you have better control befpre loading the data.

Yeah, I had just relized that, and I’ve move it to the top as a header. Though the header size would then need to be fixed (that isn’t that big of a problem though).

Are you assuming that all texcoords are 2d? If so, why?

Not sure about embedding texture IDs at this level. Lots of possible scenarios in which you might want to use multiple instances of the same model with different textures.

Personally I’d definitely include colours, but obviously it depends on your usage.

No, they are all 3D coords, it’s just if W isn’t needed it’s set to 0.

here’s an update:

// All arrays are linear, however they could be loaded into vector structs if the developer wants to.

typedef struct
{
unsigned long NumVertex; // Vertex count (also the count for all variant data)
unsigned long NumFace; // Number of faces
unsigned char HasTangent; // Does it have UV tangent basis?
unsigned char HasNormal; // Does it have normals?
unsigned char HasTexCoord; // Does it have texture coords? If so, how many?
unsigned char HasColor; // Does it have color?
unsigned char HasWeights; // Does it have vertex weights?
unsigned char NumWeights; // Number of weights per vertex (depends on extension used, probably a max of 4)
unsigned long UserDataMagic; // Magic key for user data ID (if 0 then there isn’t any user data)
unsigned long UserDataSize; // User data size (in bytes)

unsigned short MaterialID; // Material index

float *Vertex; // Vertex array (XYZ)
float *Tangent; // U Tangent array (XYZ)
float *Binormal; // V Tangent array (XYZ)
float *Normal; // Normal array (XYZ)

float *TexCoord0; // Tex coord array 0 (UVW, if there isn’t a need for W then it’s just set to 0)
float *TexCoord1; // Tex coord array 1 (UVW, “”)
float *TexCoord2; // Tex coord array 2 (UVW, “”)
float *TexCoord3; // Tex coord array 3 (UVW, “”)

float *Color; // Color array (4 elements, RGBA; if the surface doesn’t have any colors, then the render function should set the color to white as a global)

float *Weight; // Weight array (XYZW, depends on NumWeights)

unsigned long *Face; // Face array (should this be a ushort?)

void *UserData; // User defined data (for expandibility)
} Surface_t;

typedef struct
{
char MaterialName[255]; // Material name

char Tex0Name[255]; // Texture 0 file name
unsigned long Tex0ID; // Texture 0 texture object ID

char Tex1Name[255]; // Texture 1 file name
unsigned long Tex1ID; // Texture 1 texture object ID

char Tex2Name[255]; // Texture 2 file name
unsigned long Tex2ID; // Texture 2 texture object ID

char Tex3Name[255]; // Texture 3 file name
unsigned long Tex3ID; // Texture 3 texture object ID

float Diffuse[4]; // Material’s diffuse color (alpha can be used for object transparency)
float Specular[3]; // Material’s specular color
float SpecularExponent; // Material’s specular exponent (need a different name?)
float Ambient[3]; // Material’s ambient color
} Material_t;

typedef struct
{
unsigned long Magic; // Model magic ID (to be determined, probably end up being OGLM)
unsigned short NumSurface; // Number of surfaces in this model
unsigned short NumMaterial; // Number of materials in this model

Surface_t *Surface; // Surface structure array
Material_t *Material; // Materials
} Model_t;

[This message has been edited by NitroGL (edited 09-13-2002).]

Also, I should note that the geometry is all triangle. Triangle strips could be stored in user data though.

Well, I have a couple of suggestions, feel free to shoot them down, I won’t feel insulted…

1 - Use the GL types instead (like GLubyte instead of unsigned char, to keep it in line
with the rest of the GL specification (I know it really doesn’t matter much, atleast
I don’t think it does)

2 - What’s to say that we have a ‘float’ type struct and not a ‘double’ and possibly
an ‘int’ or a ‘uint’ type as well (mostly for specifying the tex coords and normals
and stuff)? (I know we’re just trying to get the structure straightened out first, but
something we should keep in mind)

3 - Make the tex coordinates one continuous array instead of multiple (keep them from
getting fragmented), and make it’s length proportional with the number of texture
units supported, and the number of components consistent for all (ones that
only need 2 have 3 because others have 3 [constant stride], or just make them all 4
no matter what)

4 - Condense most of those ‘Has’ attributes that will only be true (1?) or false (0?)
into 1 ‘Has’ attribute to save on storage (use individual bits). You could even
compact the number of texture coords into 5 bits because the most supported by the ARB
is supposed to be 32 (2^5==32). And the vertex weights can get condensed into just 1
attribute, if ‘NumWeights’ is 0, then it ‘Has’ no weights, right?

5 - You could make all the ‘data’ (meaning vertex and tangent and …) into one array
to advoid some memory fragmentation. The disadvantage to this would be that something
could obviously take up way more data than needed. But the good thing is that you would
always know the ‘stride’ and the start of where each piece of data is located. Then
would also need to keep track of interleaving of the data.

Dan

[This message has been edited by Dan82181 (edited 09-13-2002).]

1 - Use the GL types instead (like GLubyte instead of unsigned char, to keep it in line
with the rest of the GL specification (I know it really doesn’t matter much, atleast
I don’t think it does)

I thought about doing that, but for right now, I’ll just leave it as is and worry about portability later.

3 - Make the tex coordinates one continuous array instead of multiple (keep them from
getting fragmented), and make it’s length proportional with the number of texture
units supported, and the number of components consistent for all (ones that
only need 2 have 3 because others have 3 [constant stride], or just make them all 4
no matter what)

That’s a pretty good idea. I’ll leave it the way it is for now, until I get a few other things worked out.

4 - Condense most of those ‘Has’ attributes that will only be true (1?) or false (0?)
into 1 ‘Has’ attribute to save on storage (use individual bits). You could even
compact the number of texture coords into 5 bits because the most supported by the ARB
is supposed to be 32 (2^5==32). And the vertex weights can get condensed into just 1
attribute, if ‘NumWeights’ is 0, then it ‘Has’ no weights, right?

I thought of using a bit field for that, but I figured it might get too complex (though I suppose I could just use a union, huh…).
I just changed the weight so it’s just one (thanks).

seems like youre mixing things up

sorry but this is very complicated but ill give a short simplified description

model is a collection of meshes
shader is a collection of materials

each mesh has a shader

make a struct that contains both meshes with their shaders

btw everything uses pointers!
all the real data is kept in your modellibrary,meshlibrary,shaderlibrary,materiallibrary
my code for all this is about 5000 lines of code, so its no small undertaking
have fun

edit - i meant global id’s not pointers

[This message has been edited by zed (edited 09-13-2002).]

Each mesh has an index into the material array, so it doesn’t waste memory if there are dup materials.

Everything is a pointer because it needs to be allocated first (which is done apon model load).

unsigned long Tex3ID;// Texture ID for TMU3
float *TexCoord3;// Tex coord array 3

these 2 dont belong in the same structure

Many kinds of coordinate data use shorts instead of floats, or, for color, ubytes isntead of floats.

Many kinds of data use a different number of components than the default; i e 4 for position, or 1 for texture.

There are kinds of data that you want to shuffle into GL, or keep around with your mesh at least, that aren’t in this description. For example, the bone index for matrix palette skinning (which can be done using vertex programs on the card).

What I do is treat a “piece of geometry” as a container for multiple arrays. Each array has a component count and a component type. The geometry itself has a vertex count, and additionally has one or more lists of indices with associated draw modes. Each array also has a usage indicator (position, normal, color etc) so that I know how to bind it when sending it to GL.

This system seems very flexible, and works well for me. The set-up code will ask for each kind of array in turn, and set it up if present, else disable that array type. This scales very easily to support arbitrary shader needs that weren’t foreseen when writing the engine. If I need another geometry channel, I just add that to my art tools, and use them in the shader, and the set-up code will happily pass it from one place to the other without throwing a hissy fit.

Originally posted by NitroGL:
I thought of using a bit field for that, but I figured it might get too complex (though I suppose I could just use a union, huh…).
I just changed the weight so it’s just one (thanks).

you could always use C’s bitfield:

typedef struct header {
unsigned hasTangent : 1;
unsigned hasNormal: 1;
unsigned numWeights: 2;
/* etc */
} header ;

header h ;
h.numWeights = 3 ;
if (h.hasTangent) {…}
/* etc */

compact and easy to use

/jonas