Skip to main content
gamedev.net
🔒 Locked

Format Specification

Started by swiftcoder Feb 24, 2007 at 7:34 AM 5 replies 1.9k views
Original Post
swiftcoder
swiftcoder
Not sure if this is the right forum, so admins feel free to move if it would be better elsewhere. For a project with a couple of friends, I have been asked to draw up some documents. The first is a file format specification for 3d models. I have roughed out the specification, and just wanted some feedback on whether the specification document is readable, and easily understandable. Comments on the format itself are of course also welcome. I would mainly like to know if someone with a fairly solid C/C++ background could make sense of this, and be able to implement loading/saving from this document. You can read the specification here: http://www.darkcoda.net/software-development/dc3d-file-format.html Thanks in advance for your feedback, Tristam
Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]
ApochPiQ
ApochPiQ
My drive-by thoughts:

  • You mention storing two sets of texture coordinates. Two is, however, an impossible number; your format spec should obey the zero one infinity rule. This seems to apply also to the number of weighted bones per vertex limitation, although frankly I've never done any skeletal animation code, so I don't know if there's a sensible reason for that particular limit.

  • I don't really see any particular need to mention other formats in your spec - hence, the snipe at MD2 is probably not really necessary. A spec is a technical document, not a marketing spiel [wink]

  • I don't understand why the "Prerequisites" section is so titled. None of those seem like requirements for using the spec - they're important implementation details, but the term "prerequisites" doesn't seem applicable.

  • I initially misread the beginning of the "Specification" section as talking about the file format and not a description of how the document itself is laid out. That, however, may just be because I've been up all night.

  • My only point of confusion with the format itself: how do I denote which of the alternate options are used? For example, the "Static mesh specific" vs. "Keyframe animated mesh specific" data blocks are very different, but I'm not sure from reading the document how the file records which block is actually present. Is there some kind of flag byte that says "the next block is option (1) as opposed to options (2) or (3)" ?

  • Aside from that one little glitch, I could probably bang out a loader/writer for this format pretty easily given this spec; not bad overall.
Excors
Excors
The specification seems understandable to me [smile]

Some random comments:

"Both arrays and strings are prefixed by a 32-bit integer length (strings are not null terminated)" - but actually the bone array is an unsigned short instead.

"Arrays are indicated with square brackets, and alternates with angle brackets" - they seem to use curly brackets ({}) instead of angle brackets (<>).

You can't quickly jump to a single mesh in the file, without reading all the ones before it. (That could be helped by storing a mesh_size (size of entire mesh structure in bytes) at the beginning of each mesh so that they can be skipped over quickly, or by storing a table of file offsets at the beginning of the file.)

Why store multiple meshes per file, rather than just one? (Presumably all the data files will be packed into some archive file anyway, so there won't be the overhead of opening lots of individual files.)

"[2] parent_index" - doesn't specify the type.

(Is it necessary to store the whole skeleton hierarchy? I guess that depends on the engine - it probably cares about the hierarchy if it's trying to do nice blends between different poses, but basic deform-this-mesh-to-this-pose stuff can simply have a flat list of bones.)

I would have thought rotation would be better represented with quaternions instead of radian angles.

Could you get rid of the static mesh case, and make it just a keyframe animated mesh with frame_count = 1?

If a vertex has fewer than 4 bone_weights, what should it put in the empty array slots? (Presumably it'd have weight=0 but it'd be nice to specify what the bone index would be.)

Skeletal meshes and their animations are tied together, which means you can't benefit from sharing mesh data with multiple animations or from sharing common animations between multiple meshes - it would probably be nicer to store them separately, then mix them together at run-time from multiple files.

You can have an unsigned int number of vertices in the mesh, but the mesh indices are unsigned shorts, so vertex_count should probably be an unsigned short. And you're not really going to have more than 2^16 meshes, or materials with 64KB names, so perhaps every array/string count could just be an unsigned short.

Are the strings ASCII, or ISO-8859-1, or UTF-8, etc?

"Usage of the works is permitted provided that this instrument is retained with the works, so that any entity that uses the works is notified of this instrument." - I can't understand what that means - not sure what 'instrument' is. And it sounds like the file format itself is copyrighted with that licence, which doesn't seem sensible - it should only be the text of the specification which is copyrighted and has restrictions, whereas anyone can create importers/exporters/files with that format (or with a modified version of it) without having to follow any licence.

Quote:
your format spec should obey the zero one infinity rule

I would tend to disagree with that, since this is meant to be an efficient format for a specific game engine, not a generic file format for whatever data you want. (If you do want a generic one, try COLLADA, but don't try using it as a run-time format). The engine is probably limited to 2 texture coordinates and 4 bone weights, because flexibility is too expensive for low-level code like that (particularly given that it's interacting with the graphics card and so inherits those limits), so it is best for its file format to reflect those features.
ApochPiQ
ApochPiQ
Quaternions are not some kind of rotational panacea - why, precisely, would they be any better than just storing Euler angles?


As for 0/1/∞ - it's not about being "generic for whatever data you want." Format specs with very specific and (essentially) arbitrary limits like "2" and "4" are brittle. If the spec itself enforces this limit, there is a very good chance that particular implementations will assume that limit as well. This means that, should the underlying engine ever become capable of doing, say, three layers of multitexturing (or 6 bone weights), the format spec itself is now broken and the format loader/writer implementations.

Data specifications follow the same principles as code. To achieve maximum efficiency, maintainability, and reusability, some rules should be observed:
  • Minimize assumptions - this format may not be used with the same 3D graphics code forever

  • Maximize flexibility where appropriate - zero, one, infinity. Why not just allow an arbitary number of texture layers from day one? It doesn't add any significant overhead to implementing the spec, and it eliminates any future possibility of having the 2/4 magic numbers go out of date

  • Prevent abstraction leakage/maintain loose couple - the 2 and 4 numbers almost certainly originate, as you note, with the 3D engine. However, these implementation details of the 3D engine should not be affecting such disparate elements as the file format specification


The optimal solution is to allow arbitrary texture layers, etc. in the format, and have the engine barf if it tries to load a model with too many layers. This enforces the 2-layer limit while avoiding a senseless coupling between the engine and the data format; this in turn allows the format to remain relevant far outside the scope of this particular 3D engine/project's scope. If you can easily get extra mileage out of a piece of work, why not do it?

Besides, if the implementation is clever, this can actually make the format more space-efficient. Specifically, we observe that if a mesh has N texture layers, all vertices will have N sets of texture coordinates. Therefore, we can add one data field - as little as one byte if we want to be really frugal - to the model header, which specifies the number of texture layers. Each vertex then only records as many coordinate sets as needed. For meshes with only 1 texture layer, the savings are (2 * size of texture coordinate * vertex count) bytes per mesh.



One other important note: the spec mentions types like "unsigned short" and so on. Note that the size of an unsigned short is defined by a particular compiler on a particular platform; it is not a universal constant. I'd highly recommend switching from "short" and "int" and so on to "16 bit" and "32 bit" explicitly, which will help make the format a lot more portable and prevent awkwardness when it comes time to move to 64 bit platforms.
Excors
Excors
Quote:
Quaternions are not some kind of rotational panacea - why, precisely, would they be any better than just storing Euler angles?

My understanding is: Interpolation works better between quaternions than between Euler angles, because you can easily interpolate along the shortest path, and the motion is dependent only on relative orientations and not on absolute orientations. So the game engine is likely to be interpolating quaternions (then converting that into a matrix to calculate all the skinning), so it's logical to store quaternions in the file format instead of having to convert between that and Euler angles. And I assume quaternions give a more uniform precision, whereas Euler angles are far more precise near the poles than the equator, though I'm not sure how to visualise the precision of quaternions so maybe I'm wrong about that (and it doesn't matter too much when you're using floats anyway).

Quote:
The optimal solution is to allow arbitrary texture layers, etc. in the format, and have the engine barf if it tries to load a model with too many layers.

But then you need an extra specification on top of the file format specification, saying "this game engine only supports DC3D files with up to 2 texture coordinates and up to 4 bone weights per vertex", and the exporter tools will have to follow that new specification too (e.g. by discarding the less significant bone weights if there are too many).

If you're primarily going to use that file format in that engine, why bother splitting up the specification like that and adding the extra complexity? When you want to extend the engine, you just modify the file format and increment the version number and write a quick converter for all your old models (or add some trivial backward-compatibility into the importer library).

It's likely that most extensions will be adding new unforeseen features (like per-vertex colours, or physics properties, or whatever), so simply allowing flexibility in the numbers of texcoords/weights won't help for many future extensions, and allowing more extreme flexibility will mean you end up with COLLADA again and then you'll need to invent a new efficient representation that's optimised for your game engine and this whole process will start again [smile]

I agree with what you're saying about data formats in general, but in the case of 3D models I think I prefer the approach of using an existing generic (relatively inefficient) format like COLLADA for exporting from modelling programs and for processing and for exchanging data between different tools and engines; and then converting to a simple engine-specific format so you can load and distribute the data efficiently. In that case, there's no need for the engine-specific format to ever be relevant outside the scope of this project - the existing higher-level formats handle that already, and everyone can make their own low-level format which is best suited to their own engine's inflexible limits.
swiftcoder
swiftcoder
Thanks for all the comments guys! I have updated the spec, still at http://www.darkcoda.net/software-development/dc3d-file-format.html.

I agree with most of your points, and I have removed the ambiguous references to shorts and ints, and made the alternate syntax clearer (it now shows the value to test). Also shortened all array lengths to 16-bit integers.

This is intended to be an efficient runtime format, I believe we will be using COLLADA for the content pipeline. It is intended to be fairly flexible (i.e. all types of supported mesh in one format), fast to load, and to match well with the internal display format. It isn't meant to be the be-all-and-end-all of formats, and it sticks to the hard limits of the engine.

Two sets of texcoords are all that we require, at least as far as the current engine spec, and 4 seems a good balance for bone weights (OGRE for instance sticks to this limit).

I am glad that it seems clear, and easily implemented, and thanks again for all the comments,

Tristam
Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]
swiftcoder
swiftcoder
It is also worth mentioning, seeing the discussion on flexible formats, that this format is intended for one purpose and one purpose only: efficient delivery of geometry data to the renderer.

We are building a separate materials framework to handle the appearance of rendering, which uses its own data formats. We don't expect to add any other data to the format unless it is directly needed by the renderer. All physics properties, etc. will be related to entities, and the models loaded here will be attached to entities.
Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.