Users browsing this thread: 4 Guest(s)
ozz animation, concrete file format
#1
3D Modelling 
I'm having trouble decoding the ozz animation skeleton file format. It should be easy because it's open source (link), but I'm stumped by how the transformations of the bones are stored.

This is the structure of the start of the file:
The file starts with 0x01
The Null-terminated string ozz-skeleton
u32 version field (value 2)
u32 num_joints, which tells the number of joints in the file
u32 chars_count, which tells the length of the array of null-terminated bone names, in bytes. The number of names should match num_joints.
s16 array joint_parents , length 2 * num_joints bytes which tells the parents of each joint, 0xFFFF (-1) meaning "no parent"

This is where I'm stumped:
According to the source code, the rest of the file is the bind pose of the joints, expressed as transforms, I can't figure out the order of the fields and the correct length of the data.

What it should be is float x,y,z translation, float x,y,z,w quaternion rotation and float x,y,z scale.

Below is a sample file, both as a link and as a hex dump, that I get from adding a single bone in blender, then exporting that as an fbx, then converting the fbx to ozz skeleton. The bone in blender is named Bone, its head is at (x,y,z) 0,0,0 and its tail is at 0,0,1. The fbx export process adds a second bone named Bone_end, but I don't know its properties. When you import the file back into blender, it adds a second bone that is identical to the previous bone, so it might be a 0-length bone.

When I inspect the file, I find a float value for "0.01", which I assume is a Z coordinate, because there are 3 float values for "100" (0x0000C842), so I assume that the bone is exported at length 0.01 (0x0AD7233C) and scaled uniformly by 100. 

There are also 13 float "1"s (0x00008037) in the bind pose section, but I can't figure out what they should be. 
2 of them should be the W component of the "no rotation" quaternion for both bones.
Another 3 might be uniform 1 1 1 scale of the second bone, whose length is 0?

There is some C++ magic happening in the source code I linked and I can't figure out what exactly is happening when the file gets written or read. There is something about SoA (structs of arrays) for simd processing and something about data being processed depth first, but I don't understand what.

"default bone" ozz file:
link: https://file.io/Ukavuc3ZvtX1
Hex dump: (the FFFF0000 is the joint_parents part (-1, 0), the rest of the file after that is the bind pose part)

Code:
016F7A7A 2D736B65 6C65746F 6E000200 00000200 00000E00 0000426F 6E650042
6F6E655F 656E6400 FFFF0000 00000000 00000000 00000000 00000000 00000000
0AD7233C 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 0000803F 0000803F 0000803F 0000803F 0000C842
0000803F 0000803F 0000803F 0000C842 0000803F 0000803F 0000803F 0000C842
0000803F 0000803F 0000803F
Reply
Thanked by:
#2
I managed to fit the data for the first time by reading num_joints + 2 pieces of information, in the following order. Here, num_joints is 2, so the total number of data read is 4. 

First 4 floats are the x translation, then 4 y translation, then 4 z translation, values: [0,0,0,0]; [0,0.01,0,0]; [0,0,0,0]; I guess ozz-animation is Y-up and not Z-up, like blender.

Then come rotations: 4 floats for x, 4*y, 4*z and 4*w rotations [0,0,0,0]; [0,0,0,0]; [0,0,0,0]; [1,1,1,1]; Everything as expected for a "no rotation" quaternion

Then come the scales: 4*x, 4*y, 4*z, with the values [100,1,1,1]; [100,1,1,1]; [100,1,1,1];

So this is my guess: the first value is the value of the armature itself: Location 0,0,0; rotation 0,0,0,1; scale 100,100,100;
Second value is the base of the first bone (in its parent's coordinates): Location (or length? displacement?) 0,0.01,0; rotation 0,0,0,1; scale 1,1,1; Where the location 0.01 in its parent's coordinates get converted to 1 due to the parent's scaling.
Third value is the base of the second bone, its rotation and scale. It's a 0-length bone so all its coordinates are 0.
The 4th value might be ozz animation's own "leaf bone marker" or something, another 0-length bone attached to the 0-length bone that blender added.

This is definitely not the finalized file format spec, but it's the first time I've managed to read the correct amount of data and make it fit the values they should be
Reply
Thanked by:
#3
Nevermind, I guess I finally understood the source code and the data packing after reading this page again: http://guillaumeblanc.github.io/ozz-anim.../advanced/

Each of the transform elements is actually 4 elements in one. 

So a single translation struct contains data like so:
Code:
{
    float xs[4];
    float ys[4];
    float zs[4];
    // the below row is only present for rotations/quaternions
    float w[4];
}
The transformations are stored in an array whose size is (numJoints + 3) / 4. (this is an integer division, so the result truncates to an integer result) (source)

So for joint count 1..4, you have 1 element in the transformations array. Then for joint count 5...8, you have 2 elements and so on.

The data in the file is as follows (the prefix t means transformation, r means rotation, s means scale. The elements are all floats, so 32 bits or 4 bytes)
tx0,tx1,tx2,tx3; ty0,ty1,ty2,ty3; tz0,tz1,tz2,tz3;
rx0,rx1,rx2,rx3; ry0,ry1,ry2,ry3; rz0,rz1,rz2,rz3; rw0,rw1,rw2,rw3;
sx0,sx1,sx2,sx3; sy0,sy1,sy2,sy3; sz0,sz1,sz2,sz3;

The minimum size of the transform matrices, even if you have a single joint, is 160 bytes. Then whenever you go over a number divisable by 4, you get 160 bytes more.
Reply
Thanked by:


Forum Jump: