I'm starting to get the hang of this format. I've been able to figure
out a little more about it, and to my suprise, it actually does
more than I thought. Not only are the bones and their corresponding
animations in this file, but so is the mesh data, normals, and UV maps!
First there's a 12 byte header.
Header |
dword | Unknown Size |
dword | Skeleton Size |
dword | DataTable Size |
Unknown Size is always 0. I don't know what it contains, since I've never
found a file that had it present. Skeleton Size is the size of the skeleton
block. DataTable Size is the size of the datatable, which is referenced
from the skeleton block.
First let's detail some of the conventions used in this file format. All
offsets are relative to the start of the block. For example, if you're
referencing something in the skeleton block, you'll need to add the offset
of the skeleton block in order to locate it's exact location in the file.
Quick example: You find an offset that references the skeleton block,
the offset is 0x3044. Since Unknown block never exists, the start of
the skeleton block is 0xc (right after the header). So the exact file
location of the offset is 0x3044+0xc or 0x3050.
Another convention is the heavy use of strange TAGs.
A typical tag looks like this: 0x40bbc0, or 0x40bbd0. I have no idea what these tags represent.
The final convention is the fact that all strings in this file are fixed
width. These strings aren't zeroed out before writing, so there might
be junk after the null character.
Now let's look at the skeleton block.
The skeleton starts off with a header that looks like this:
Skeleton Header |
dword | TAG (0x40bbc0) |
dword | TAG (0x40bbd0) |
char[64] | Skeleton Name |
dword | Body Offset |
dword[11] | Body Args |
dword | Animation Offset |
dword[11] | Animation Args |
char[64] | Skeleton Desc |
Ok. Body Offset points to the main Body in the SkeletonTable.
Body Args are unknown.
Animation Offset points to the animations that are applied
to the Body. The first dword in the Animation Args is the
number of Animations present. Let's look at the Body first.
It all starts with a single bone, which references all
the other bones in the object.
Bone |
dword[6] | TAGS |
dword | 0 |
dword | Bone Number |
char[40] | Body Name |
dword | ChildrenOffsets |
dword | NumChildren |
dword | NumChildren |
dword | PosFlagsOffset |
dword | NumPosTypes |
dword | NumPosTypes |
dword | posDataOffset |
dword | posDataSize |
dword | posDataSize |
dword | Flags |
dword[2] | TAGS |
dword | PolyTexOffset |
dword | numPolyTex |
dword | numPolyTex |
dword[20] | floats |
dword[5] | Reserved |
char[40] | Texture Name |
dword[61] | floats |
dword | ObjCountOffset |
dword | NumObjects |
dword | NumObjects |
dword | PolyGroupOffset |
dword | NumPGroups |
dword | NumPGroups |
dword[4] | Reserved |
dword | MeshOffset |
dword | NumPoints |
dword | TextureOffset |
dword[3] | Reserved |
dword | NormalsOffset |
dword | negOneOffset |
dword[6] | Reserved |
dword[3] | floats |
Whew! Anyways, I'm not sure why there are duplicate
counts for all of those.. there just are. They're always
the same too.
ChildrenOffset points to a list of child bone offsets.
PosFlagsOffset points to a list of PositionFlags (see below)
PosDataOffset points to Positional Data. This data
is defined by the PositionFlags entries.
If Flags&0x20, then the second half of this block
is present. Otherwise this block ends right after the
Flags.
PolyTexOffset points to a polygon texture object, I think.
I'm not sure what this data represents yet, but there
are 4 floats and 8 words in each "PolyTex".
ObjCountOffset points to a list of Polygon Counts. Each one
represents the number of Polygons in a Group.
PolyGroupOffset points to a list of PolygonGroup Offsets
(which point to a bunch of polygon definitions... actually
triangles. 3 words per polygon, each represents a vertex).
MeshOffset points to a list of vertices. XYZ baby.
NumPoints&0xffff is the number of vertices at MeshOffset.
if NumPoints&0xf0000, then a texturemap is present.
If there is a texturemap present, then textureOffset points
to the UV points, otherwise textureOffset is -1.
They claim you can have more than 1 texture per object,
so I bet NumPoints&0xffff0000 is actually the # of
textures. I've only ever seen "1", so I cannot say for
sure
NormalsOffset points to a list of vertex normals.
NegOneOffset points to a bunch of -1's... I believe it's
real use is to determine the lenght of the Normals data
(although there should be one normal per vertex)
PositionFlags |
word | Positon Type (0x8 = translation, 0x14 = rotation) |
word | 0 |
word | Num Changes |
word[2] | Unknown |
word | Unknown |
There we go. Now that we know this, we can look at the
PositionData. This data is structured after these flags.
The first part of the data is the timeline. There are
NumChanges floats that represent the timeline, from 0 to 1.
Following this is the actual positioning data. If the
type is 0x8, then each block is 3 floats, if the type is 0x14
then the block is 4 floats. There are NumChanges blocks,
corresponding to each time on the timeline.
For example, if there are 2 PositionFlags, the first
being type 0x8, and NumChanges=2, and the second being
type 0x14, and NumChanges=4, then the Position Data
would look like this:
2 floats for the translation timeline,
2 sets of 3 floats (6 floats total) for the XYZ data,
4 floats for the rotational timeline,
4 sets of 4 floats (16 floats total) for the xyzw quaterion
data.
Now, lets take a look at the animation data (noo!!)
First things first. The animation table starts out with
a table of offsets. There is an offset for each animation.
Each offset points to a full animation tree. Let's look at
that structure now.
Animation |
dword[2] | TAGS |
char[64] | Animation Name |
dword | FirstBoneOffset |
dword | 1a |
dword[10] | floats |
char[40] | Root Bone Name |
dword[9] | floats |
Not much to say here.
The FirstBoneOffset points to a Bone object just like
the one mentioned above. Only this time the data inside
the Bone is different, and no mesh data is present (at least
not in any of the files I've seen).
That's really all there is to it.
Using what I've found so far, I've written a quick little
utility that'll output a skeleton hierarchy.
Get it here. You'll need to
extract the file from the BIF to use it.
It also demonstrates how to read in each section of data.
once I figure out a few more things, I'll write a 3DS/LWO
converter.
|