ALOD Mesh format (ALM)
----------------------
The ALM format is a binary format with the following features:
- progressive level of detail
- skeletal animation
- texture coordinates
- materials
- embedded texture maps
- embedded tangent maps
The file consists of a hi-res mesh, plus a number of edge collapse
records. This is backwards from the conventional approach to PMs, which
store a lo-res mesh plus vertex splits. Our justification is that
a) this is not intended as a streaming or compression format
b) in cases where only the high-res info is required, it is a lot
easier to get at
c) edge collapses are easier to encode than vertex splits.
The following structures are used. They are packed and in big endian
ordering. The 'float' type is IEEE 32-bit floating point (but versions 1
and 2 used 64-bit). The layout is conceptual; it won't compile as C/C++
code.
struct AlmFile
{
char magic[4]; // "ALM\0"
uint32 version; // 10
uint32 num_comments;
uint32 num_bones;
uint32 num_positions; // total over all LODs
uint32 num_vertices; // total over all LODs
uint32 num_textures; // total over all materials
uint32 num_tangents; // (8) total over all materials
uint32 num_materials;
uint32 num_triangles; // total over hi-res mesh
uint32 num_collapses;
uint32 num_frames; // (4)
float frame_rate; // (6)
uint32 override_P; // (6) non-zero if user-specified P is used
AlmComment comments[num_comments];
AlmBone bones[num_bones];
P[4 * num_bones][4 * num_bones]; // (6) only present if override_P.
AlmPosition positions[num_positions];
AlmVertex vertices[num_vertices];
AlmTexture textures[num_textures];
AlmTangentMap tangents[num_tangents]; // (8)
AlmMaterial materials[num_materials];
AlmTriangle triangles[num_triangles];
AlmCollapse collapses[num_collapses];
AlmFrame frames[num_frame]; // (4)
};
struct AlmComment
{
uint32 length;
char comment[length];
};
Comments are not NULL-terminated.
struct AlmVectorComponent
{
uint32 bone_index;
float coords[4];
};
struct AlmVector
{
uint32 num_weights;
AlmVectorComponent components[num_weights];
};
See AlmPosition for details.
struct AlmBone
{
int32 parent; // index; -1 for a root bone
float transform[4][4]; // 4x4 row-major pre-multiplication matrix for rest position
uint32 flags; // (6)
float tail[3]; // (10)
// bounding sphere information
float max_singular; // (6) largest singular value for the linear part
float translate_centre[3]; // (6) centre of bounding sphere for translation
float translate_radius; // (6) radius of this bounding sphere
// expectations
float expect_L[4][4]; // (6)
float expect_GTG[4][4]; // (6)
float expect_LTGTGL; // (6)
};
To transform bone-local coordinates to object coordinates, pre-multiply
by the transformation matrix, then reinterpret in the parent space (or
object space, if parent == -1).
The flags are:
BONE_FLAG_FIXED_TRANSLATE = 0x01:
The translation part of transform will always be the same.
BONE_FLAG_FIXED_ROTATE = 0x02:
The rotation part of transform will always be the same (specifically,
the rest-to-pose relative transform will be symmetric).
BONE_FLAG_FIXED_SCALE = 0x04:
This flag is only valid with either FIXED_ROTATE or UNIFORM_SCALE. In
the first case it means that the linear part is completely fixed. In the
second, the linear part will always be a rotation times a fixed scale
factor.
BONE_FLAG_UNIFORM_SCALE = 0x08:
The linear part is a scalar times a rotation matrix (no reflection).
The combination of the first three indicates a constant transformation matrix.
BONE_FLAG_EXPECT_L_LINEAR = 0x10000:
Use the linear part of expect_L.
BONE_FLAG_EXPECT_L_TRANSLATE = 0x20000:
Use the translate part of expect_L.
BONE_FLAG_EXPECT_GTG_LINEAR = 0x40000:
Use the linear part of expect_GTG.
BONE_FLAG_EXPECT_GTG_TRANSLATE = 0x80000
Use the translate part of expect_GTG (3 elements from last column).
BONE_FLAG_EXPECT_GTG_WEIGHT = 0x100000:
Use the last entry of expect_GTG.
BONE_FLAG_EXPECT_LTGTGL = 0x200000:
Use the value of expect_LTGTGL. This is the expected value of
translate(L)^T linear(G)^T linear(G) translate(L), and is used to
compute the last entry of GTG if BONE_FLAG_EXPECT_GTG_WEIGHT is not
set. If neither is set, it is assumed that translate(L) is fixed.
Missing L entries will be taken from the rest matrix. Missing GTG
entries will be estimated using assumptions about independence.
struct AlmPosition
{
AlmVector position;
};
The positions contain geometric information, while vertices contain
other information like texture coordinates. Vertices may share
positions but have different texture coordinates.
Each bone i defines a transformation matrix M_i from local space to
world space (in homogeneous coordinates). The final skinned position is
sum{i in bones[]} M_i.coords[i].
struct AlmPlane
{
AlmVector span[2];
};
A plane passing through the origin, span[0] and span[1] in ccw order.
struct AlmVertex
{
uint32 position; // index into positions
AlmVector tangents[2]; // (7) tangent plane
float tex_coords[2]; // s and t coordinates
};
struct AlmTexture
{
uint32 width; // should be power of 2
uint32 height; // should be power of 2
uint32 channels; // 3 (RGB) or 4 (RGBA)
uint32 signed; // boolean; true for vectors, false for colours
uint8 values[width*height*channels]; // packed, row major
}l
struct AlmTangentMapEntry
{
int32 patch; // matches face patches
AlmVector tangents[2]; // tangent plane
};
struct AlmTangentMap
{
uint32 width; // should be power of 2
uint32 height; // should be power of 2
AlmTangentMapEntry[width*height]; // packed, row major
};
Note: whether the texture is top-to-bottom or bottom-to-top only really
affects import and export of textures. See doc/texcoords.txt.
struct AlmMaterial
{
int32 texture_id; // -1 for no diffuse map
int32 tangent_id; // -1 for no tangent map
};
/* In versions prior to 8, AlmMaterial was
*
* struct AlmMaterial
* {
* int32 texture_id; // -1 for no diffuse texture
* uint32 num_weights; // 0 for no tangent maps
* uint32 bones[num_weights];
* uint32 tangent_ids[num_weights]; // was a pair in earlier versions, but this was never used in code
* };
*
* The tangent information was turned into 4-channel texture images stored
* as AlmTextures. The extra information in the material fields provided
* the interpretation. This conversion to 4-channel texture images is now
* done as part of tools/meshrender, so as to provide greater flexibility
* in the encoding.
*/
The texture_id is a diffuse map (also called decal). The usual normal
map is replaced by a set of tangent maps, interpreted like AlmPlane.
Each map is actually a pair of textures, side by side. The left half is
the U tangent, the right half the V tangent. There is a separate
texture per involved bone, which need to be combined to produce a n-D
tangent vector.
struct AlmTriangle
{
uint32 vertices[3]; // indices, in ccw order
uint32 material; // index
}
struct AlmEdgeCollapse
{
uint32 p1, p2; // endpoints of edge to collapse
uint32 vl, vr; // new vertices to collapse to
float error; // error metric
}
vl and vr are the vertex information for the new position. vl is for
the patch on the left of p1->p2 (with p1 below p2), vr is for the patch
on the right. vl and vr must reference the same position, which may be
the same as p1 or p2 (and must be the same, if more than 2 patches meet
at p1 or p2).
struct AlmInfluenceCollapse
{
uint32 magic = 0xffffffff; // marker to distinguish from edge collapse
uint32 count; // number of vertices on the position
uint32 vertices[count][2]; // pairs of old, new vertex ids
float error; // error metric
}
union AlmCollapse
{
AlmEdgeCollapse;
AlmInfluenceCollapse;
}
(Prior to version 9, AlmCollapse could only be AlmEdgeCollapse)
struct AlmFrame
{
float transform[num_bones][4][4];
}
Each frame is simply a collection of matrices replacing those
in the bone structures.
Version history
---------------
10. Bone tails
9. Influence simplifications
8. Tangent maps
7. Vertex tangent planes.
6. Lots of changes to the bone structure, to support statistical and
bounding sphere information.
5. Changed 3x3 matrix plus offset to 4x4 matrix, and made animation full
4x4. The spec also removed some restrictions on root bones, although
it makes no difference to the file format.
4. Added animation.
3. Changed from float64 to float32.
2. First documented version.