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.