The Ultimate 3D model file format (v. 2.0)

The Ultimate 3D model file format (*.u3d) is used to save model objects of Ultimate 3D with all properties, which are not specific to a scene. This means everything except for the object to world space transformation, the lighting and other scene specific data can be saved to a *.u3d file. The form in which the data is saved is quite close to the form in which Ultimate 3D needs it, so importing and exporting *.u3d files can work very efficiently. It is a binary file format with a chunk based structure. This document describes version 2.0 of the file format. Version 1.0 was used by Ultimate 3D 2.1, version 2.0 is used by Ultimate 3D 3.0. Ultimate 3D 3.0 can load all versions of the file format, Ultimate 3D 2.1 can load exceptionally version 1.0. In version 2.0 the file format is more extensible cleaned up and easier to read (but a bit harder to write). Thanks to the more flexible structure of version 2.0 a well-written loader can also load files using a newer version of the model file format (more about this below).

This document is the official and binding definition of Ultimate 3D model files of version 2.0. A file which does not match the description given here is not a Ultimate 3D model file of version 2.0. This means that you can rely on the things written in this document when you write a loader and that you have to be careful to avoid violating the rules when you write an exporter. If every loader and every exporter is designed to match this definition precisely this means that all applications using the file format can exchange models without any problems and that should be worth it.


Target group

Before reading this document you should know how to work with binary files and you should be familiar with the basics of 3D graphics (having worked with OpenGL or Direct3D for a while is recommended). If you have this background knowledge this document may contain a solution to an old problem of 3D graphics. It tends to be hard to find a model file format, which meets all needs and if you have found one you often fail to find software that can be used to export animated models from well-known 3D modeling software properly. A good model file format for games should meet the following requirements:

The good thing about *.u3d is that it fulfills all of these requirements. The first requirement is fulfilled through this document, the second one is fulfilled since the format is designed to fulfill it, the third and the fifth one are fulfilled through the chunk-based architecture, the fourth one is fulfilled through the wide set of features and the sixth one is fulfilled through the open-source exporter for Blender. So if you are interested in using the Ultimate 3D model file format in your software go ahead. This documentation and the information given by it is released under the Creative Commons Attribution 3.0 unported license. Do not be worried about being bound to Ultimate 3D when using the file format. There is not much that is specific to Ultimate 3D and the things which are specific can be ignored without getting any problems.


The export plugin for Blender

The Ultimate 3D model file format can be exported from the well-known open source modeling software Blender in a very easy and convenient way. There is a plugin which takes care of everything and over time it turned out that it works very reliable. Unlike other export plugins it is not a matter of fortune, whether you get normals, materials and animations exported correctly. Feel free to use any technique you like. If there is a reasonable way to get the result to triangle meshes, materials and animated bones, the plugin will find it.


Some definitions

To make this document as clear as possible I want to define some terms here, especially the use of data structures and the used coordinate system:
- bool: A 8-bit value, giving a boolean information (true or false). For false all bits are zero, for true at least the first bit is one.
- char: A 8-bit signed integer value in the range from -128 to 127. Just like all other integer types it uses a least-significant bit first order and two's-complement for the sign. It is usually used to describe ASCII characters.
- BYTE: A 8-bit unsigned integer value in the range from 0 to 255.
- short: A 16-bit signed integer value in the range from -32768 to 32767.
- WORD: A 16-bit unsigned integer value in the range from 0 to 65535.
- DWORD: A 32-bit unsigned integer value in the range from 0 to 4294967295.
- float: A 32-bit signed floating point value as defined in IEEE 754-1985.
- String: A null-terminated string of ASCII characters. It is one char after the other and the data ends after the first char that equals zero.
- Vector: Three float values giving a position or direction vector. The first one is the x-component, the second one is the y-component, the third one is the z-component. Ultimate 3D uses a left-handed coordinate system. The x-axis points to the right, the y-axis points upwards and the z-axis points into the depth.
- Quaternion: Four float values describing a rotation by giving a quaternion as described by Ken Shoemake. Search for his name in combination with "quaternions" and you will find a description of how to get from a quaternion to a 3x3 rotation matrix. Here is exemplary code showing how to create a 3x3 rotation matrix from a quaternion (multiply a line vector from the left to apply the rotation):

SMatrix3x3 SQuaternion::CreateMatrix() const{
return SMatrix3x3(
1.0f-2.0f*(y*y+z*z),2.0f*(x*y+z*w),2.0f*(x*z-y*w),
2.0f*(x*y-z*w),1.0f-2.0f*(x*x+z*z),2.0f*(y*z+x*w),
2.0f*(x*z+y*w),2.0f*(y*z-x*w),1.0f-2.0f*(x*x+y*y));
}


- Color: Four float values in the range from 0 to 1 giving a color in the order r, g, b, a.
- Triangle16: Three WORD values giving three indices of vertices, which result in a triangle. Ultimate 3D uses counter-clockwise backface culling. If the vertices are in counter-clockwise order on the screen the triangle will not be rendered. This structure can only be used, if there are 65536 vertices or less.
- Triangle32: Same as Triangle16, but using three DWORD values rather than three WORD values. It allows for up to 2^32=4,294,967,296 vertices.
- Matrix: 16 float values describing a 4x4 transformation matrix. The first four values contain the entries of the first line and so on. Vectors get interpreted as column vectors and when they get transformed using a matrix the matrix gets multiplied from the left side, so the first component of the new vector is the dot product of the old vector with the first column. The fourth component of a 3D vector is always assumed to be 1.0.
- CompressedNormal: This is a very special data structure, which is used to save normal vectors efficiently. Since normal vectors are always normalized they can be described by a latitude and a longitude. Sufficient precision for these values can be reached with 16-bit integers (short). For this reason this structure is made up of two short values, the first one describing the latitude, the second describing the longitude. For the latitude -32767 refers to -Pi/2 and 32767 refers to Pi/2. For the longitude -32767 refers to -Pi and 32767 refers to Pi. Through this method the memory needed to describe a normal gets reduced from 12 bytes to 4 bytes without any significant precision loss (just imagine how dense the grid of a UV sphere with 65563 segments and 65564 rings would be). Here is some exemplary C++ code on how to convert between struct SVector{float x,y,z;}; and struct SCompressedNormal{short Latitude,Longitude;};.

SCompressedNormal(SVector Vector){
Vector.Normalize();
float LatitudeF=-asinf(Vector.y);
float LongitudeF=atan2(Vector.x,Vector.z);
Latitude=static_cast<short>(LatitudeF*20860.12008);
Longitude=static_cast<short>(LongitudeF*10430.06004);
}
operator SVector(){
SVector Result;
float LatitudeF=static_cast<float>(Latitude*0.0000479383625);
float LongitudeF=static_cast<float>(Longitude*0.000095876725);
float CosLatitude=cos(LatitudeF);
Result.x=CosLatitude*sin(Longitude);
Result.y=-sin(Latitude);
Result.z=CosLatitude*cos(Longitude);
return Result;
}

Comment: In an earlier version of this specification CompressedNormal had only 16 bits instead of 32 bits. This has been changed lateron. See this forum topic for more information.


Finally here are some notations, which will be used below. A construct in the form of <DataType1;DataType2;...;DataTypeN> implies a new data type, that is a structure made up of one occurrence of DataType1 at its beginning, one occurrence of DataType2 after that and so on. Square brackets in the form [n] behind a variable imply that this is an array made up of n values of the given type. If a variable identifier begins with "n", this implies that the variable gives the count of something. If the name of a data set begins with a "p", this implies that it is an array, if it begins with an "i", this implies that it is an index.

If there is a * behind the name of a data set, this implies that there are some cases in which this data set does not exist. Read the description of the other data sets to find out about these cases. A variant of this notation is *(from X.X.X to X.X.X). The X.X.X are version numbers. This means that the chunk exists only in a particular range of file format versions. The file format version is given in the $U3D_FILE_HEADER chunk. The first X.X.X gives the earliest version in which the data set is contained, the second X.X.X gives the latest version in which the data set is contained. Alternatively to *(from X.X.X to X.X.X) you may find *(from X.X.X) or *(to X.X.X). The first expression implies that the data set is available in version X.X.X and all newer versions, the second expression implies that the data set is part of X.X.X and all older versions.


The chunk structure

The chunk structure of the file format is quite simple, but still powerful and flexible. A chunk is a block of data that describes a particular object. Every chunk starts with a chunk header. This header starts with a String giving the type identifier of the chunk. The type identifier is a string specifying what is described by the chunk. Native chunk identifiers of the file format start with "$U3D_" and after that terms like "MESH", "MATERIAL" or "BONE" follow. Custom chunks should use the prefix "$U3DC_". The chunk identifier is followed by a DWORD giving the size of the chunk, not including the size of the chunk header. So basically every chunk looks like this:

String ChunkIdentifier
The type identifier of the chunk, e.g. "$U3D_MESH".

DWORD ChunkSize
The size of the chunk in bytes, not including the header.

BYTE pChunkData[ChunkSize]
ChunkSize bytes of data giving all information of the chunk. How it needs to be interpreted depends on the type of the chunk.

An Ultimate 3D model file is just a list of chunks. It is chunk header, chunk data, chunk header, chunk data, ..., chunk header, chunk data, end of file. On the top-level there is no data between the chunks. If your application is unable to read a particular chunk it can simply skip it, using the ChunkSize.

Note that the specification of chunks makes it possible to have chunks in the chunk data of other chunks. This is used in the file format for several purposes. For example a material chunk contains texture chunks.

Usually the chunk size is never needed to read a chunk, because the size of the chunk can be derived from its data, but it is important to make it possible to skip chunks easily and it is important for upwards compatibility. This requires some further explanation. There are three types of possible changes in future versions of the Ultimate 3D model file format, but only one of them does break upwards compatibility:

Looking at the first two possible ways of extending the file format you should see how flexible it is. You can add a lot of data without getting incompatible. Anyway you should remember that it does not make sense to add data, which is specific to particular occurrences of an object. Data which may be different for two different occurrences of the model in a scene must not be added to the file format, because this would be illogical. Also keep in mind that you only get this degree of extensibility, if you follow the two rules mentioned above. Always use the ChunkSize to skip unknown data, otherwise your loader will interpret added data as the beginning of a new chunk and that can not lead to anything good. If a chunk occurs within the data of another chunk <ChunkIdentifier> is used as data type name (e.g. <$U3D_MESH>).

Now this was enough theory. It is time for the chunk descriptions.


The file header ($U3D_FILE_HEADER)

Every Ultimate 3D model file starts with a header chunk. Its identifier is "$U3D_FILE_HEADER". From now on I will not mention the identifier explicitly, just look at the heading. The file header gives the version of the file and it says whether it is encrypted or compressed. This chunk will be placed at the beginning of every Ultimate 3D model file of a newer version and the data currently contained in it will always be contained. This is warranted, even for file format versions with increased major. Here is a description of the chunk data:

DWORD Major
The major of the file format version. If this value is higher than the version your loader is designed for you should stop loading here, because it will probably fail.

DWORD Minor
The minor of the file format version.

DWORD SubMinor
The sub-minor of the file format version. A complete file format version would be written Major.Minor.SubMinor.

DWORD EncryptionVersion
The version of the encryption, which has been used for this file. If it is 0 the file is unencrypted. If it is 1 the file has been encrypted with the technique of version 1.0 of the file format (undocumented). If it is 2 the file is encrypted through AES with a key size of 128 bits. If it is higher the decryption mechanism for encryption version 2 or 1 will not work. If the file is encrypted all data behind the file header is encrypted and a password is needed to decrypt it. For AES the binary data of the password (a String) is used as key. If the password has a size smaller than 128-bit (15 characters) the rest of the key is filled with zeroes. Since the size of data encrypted with AES has to be a multiple of the key size (128-bit) the encrypted data is bigger than the original data. The last byte in the decrypted data (interpreted as BYTE) tells how many bytes have to be removed at the end of the decrypted data to get back to the size of the original data. For more information about AES please refer to the Wikipedia article about AES and to the sources cited by it.

DWORD CompressionVersion
The version of the compression, which has been used for this file. If it is 0 the file is uncompressed. Currently there is no compression, so it is always 0. Compression is applied before the encryption, so decryption has to be done before decompression. The compression applies to all data behind the file header.

All this information is not specific to models. Other file formats may use the same structure and the same file header (and probably I will design some file formats, which do so). The file becomes model specific through the next chunk.


The model header ($U3D_MODEL_HEADER)

The model header gives general information about the model object. It has to occur after the file header and before any texture, material, bone or mesh chunk and it must not be used more than once in a model file. It tells you which chunks will be contained in the file, how the model looks basically and which animation technique is used by it. Here is a description of its chunk data:

DWORD nMesh
The total number of meshes for this model object. This is the number of mesh chunks you will find in this file. This is the number of meshes per frame multiplied by the number of LoDs and if no skeletal animation is used multiplied by the number of frames (nMeshesPerFrame*nLOD*((nBone==0)?nFrame:1)). If skinning is used this value has to equal one. This means that there has to be exactly one mesh if vertex skinning is used.

DWORD nMeshPerFrame
The number of different meshes that are needed to render the model in every frame. For example for a model with a skeleton that has ten meshes appended to it, but only five different meshes, this value would be five.

DWORD nFrame
The number of frames the animation of this model is made up of. This has to be at least 1, because every model has at least one frame. If the model does not use a skeleton there is one set of meshes for every frame (frame based animation). Otherwise this value just gives the length of the animation.

DWORD nLOD
The number of different levels of detail (LoD) for this model. Just like nFrame this value has to be at least 1.

DWORD nMaterial
The number of different materials for this model, which equals the number of material chunks. This has to be at least 1.

DWORD nBone
The number of bones in this model, which equals the number of bone chunks. For models without skeletal animation this is 0.

bool VertexTweening
Whether vertex tweening is to be used to smooth the animation of this model. Setting this to true is only valid, if the model does not use skeletal animation (nBone==0) and has the same number of vertices and triangles for every frame of the frame based animation.

float pLODMaxCameraDistance[nLOD]
For every LoD this value gives the maximal world space distance to the camera at which this LoD is still visible. The array begins with information about the highest level of detail and continues with lower LoDs. The last value specifies a distance at which the model will be invisible. Use FLT_MAX=3.402823466e+38F, if you want that the model is always visible.

DWORD pTexCoordDimension[8]
Every mesh can have up to eight different texture coordinate sets. Ultimate 3D enforces that the vertex format is identical for all meshes, so this is why this information is part of the model header. Every texture coordinate set can have at least one and at most four dimensions (meaning that it is made up of one, two, three or four float values per vertex). If the dimension is 0 this implies that there is no texture coordinate set in this index. This array gives the dimension for every index. The common case is that the first value is two and all other values are zero.

DWORD nSkinWeight
The number of skin weights per vertex. Ultimate 3D enforces that the vertex format is identical for all meshes, so this is why this information is part of the header. It can be zero, if no skinning is used or an integer up to 3. If skinning is used there must not be more than one mesh (nMesh==1).

bool ShaderPackTemplateExists
Whether the next data set exists.

<$U3D_SHADER_PACK> ShaderPackTemplate *
A shader pack describing the parts of a shader, which are identical for all materials of the model. This is e.g. the vertex format and the transformation technique.

That is all for the header. What follows are chunks describing all objects of the model. No particular order is enforced for them. Disregarding some exceptions, which will be mentioned below, every order of the chunks is valid. The chunks are listed in the order in which the official exporters of Ultimate 3D export them, but loaders should never rely on this order.


The shader pack chunk ($U3D_SHADER_PACK)

Ultimate 3D uses shader packs for rendering. Shader packs contain descriptions of shaders, which can be put together from a set of code fragments. Since the whole system of shader packs is very complex and very specific to Ultimate 3D it is not publicly documented. Please skip this chunk. The most important information for rendering materials is found in the material chunk and if you want to save your own effects to *.u3d files you can extend the file format with your own effects chunk.


The mesh chunk ($U3D_MESH)

In your file you will find ModelHeader.nMesh mesh chunks. They contain all data that is related to the geometry of the model. Here is a list of all the data it can contain:

DWORD iMeshPerFrame
The mesh index within the frame disregarding LoDs. This is a value smaller than ModelHeader.nMeshPerFrame.

DWORD iLOD
The index of the LoD this mesh belongs to. This is a value smaller than ModelHeader.nLOD. Again 0 refers to the highest LoD.

DWORD iFrame
The frame this mesh belongs to. This is a value smaller than ModelHeader.nFrame. If bones exist it is always 0.

String Name
The name of this mesh. It can be used for debug output and similar purposes, but it is not needed to load or display the model.

float NormalScalar
A factor the normals will be multiplied by after loading or recalculating. If some bone uses a negative scaling, which mirrors the mesh, this value should be -1.0 to get correct normals. Usually it is 1.0.

bool ValidMeshToTangentSpaceMatrices
If this boolean is true the last two texture coordinate sets of this mesh are three-dimensional. Combined with the normals these texture coordinate sets give a 3x3 matrix for every vertex. These matrices are mesh to tangent space matrices. Those are needed for most effects, which use a normal or a height map, e.g. normal mapped lighting or parallax mapping. The second last texture coordinate set holds the first column, the last one holds the second column and the normals hold the third column. All column vectors always have a length of one, so the matrix does not change lengths strongly. Besides the matrices are guaranteed to be orthogonal. Usually the mesh to tangent space matrices are computed for the first texture coordinate set, but $U3D_MODEL_HEADER.ShaderTemplate may define something different. Optionally the first additional texture coordinate may be four-dimensional. In this case the w-component saves the length of the bitangent divided by the length of the tangent. This is a useful additional information for parallax mapping.

DWORD nVertex
The number of vertices in this mesh. This value must not be zero.

Vector pPosition[nVertex]
The position of every vertex in mesh space.

CompressedNormal pNormal[nVertex]
The normal of every vertex in mesh space.

float ppTextureCoordinateSet0[nVertex][ModelHeader.pTexCoordDimension[0]]
A 2-dimensional array holding all texture coordinates for the first texture coordinate set. After pTexCoordDimension[0] float you reach the next vertex. The texture coordinates are interpreted as usual in Direct3D. For 2D textures (0,0) refers to the left top.

float ppTextureCoordinateSeti[nVertex][ModelHeader.pTexCoordDimensions[i]]
It works in the same way for the other seven possible texture coordinate sets.

float ppSkinWeight[nVertex][ModelHeader.nSkinWeight]
A 2-dimensional array holding the skin weights for every vertex. After nSkinWeight float you reach the data of the next vertex. The given skin weights imply an additional skin weight. It is computed by subtracting all skin weights from 1.0. This way the sum of all skin weights is always 1.0.

BYTE pSkinWeightBoneIndex[nVertex][(ModelHeader.nSkinWeight==0)?0:4]
If skinning is used it is always indexed matrix palette skinning. This means that there is one matrix for every bone in a matrix array (the array indices equal the bone indices). For every skin weight (there are at most three skin weights given, so there are at most four weights in sum) this array gives a bone index for the transformation. For every skin weight the corresponding transformation is used to transform the vertex and this way nSkinWeight+1 vertex positions or normals are created. Then these results are multiplied by the weights and added together.

DWORD nTriangle
The number of triangles this mesh is made up of. This value must not be zero.

bool TrianglesOwned
If this value is false this mesh does not have its own index data and the next three data sets do not exist. In this case this mesh uses the index data of the mesh with identical iLOD and iMeshPerFrame but with iFrame=0. This mesh must have an index buffer and its chunk must have occured before the chunk for this mesh. Usually it is true. In this case the next three data sets hold all the index data of this mesh and it is does not need data of any other meshes. This setting is used to save memory for models with frame based animation.

Triangle16 pTriangle16[nTriangle] *
The vertex indices for all triangles. This field exists only, if TrianglesOwned is true and if nVertex is 65536 or less.

Triangle32 pTriangle32[nTriangle] *
The vertex indices for all triangles. This field exists only, if TrianglesOwned is true and if nVertex greater than 65536.

WORD piTriangleMaterial[nTriangle] *
The index of the used material for every triangle.

bool ContainsShadowOptimizedGeometry
If this boolean is true the next data set exists, otherwise it does not. Shadow optimized geometry is used for rendering shadow volumes through a vertex shader. It does not have any holes and triangles are connected through other triangles at the edges. If you are interested in using it let me know and I will describe it more detailed.

<$U3D_SHADOW_GEOMETRY> ShadowOptimizedGeometry *
The shadow optimized geometry of the model. See the description of ContainsShadowOptimizedGeometry.


The shadow optimized geometry chunk ($U3D_SHADOW_GEOMETRY)

For the purpose of this chunk see the description of Mesh.ContainsShadowOptimizedGeometry. This chunk is always used within a mesh chunk. Here is a description of its data:

DWORD nVertex
The number of vertices for the shadow optimized geometry.

DWORD nTriangle
The number of triangles for the shadow optimized geometry.

float pShadowVolumeVBuffer[nShadowVolumeVertex][6+ModelHeader.nSkinWeight+((ModelHeader.nSkinWeight!=0)?1:0)]
The vertex data of the shadow optimized geometry. For every vertex it begins with the position (3 float), continues with its skin weights (ModelHeader.nSkinWeight float) and - if there are skin weights - the skin weight indices (4*BYTE has the same size as one float) and after that the normals (3 float) follow. You can copy this data directly into a vertex buffer, if you use the default vertex formats of Direct3D.

Triangle pShadowVolumeIBuffer[nShadowVolumeTriangle]
The index buffer of the shadow optimized geometry.

Triangle16 pShadowVolumeIBuffer16[nTriangle] *
The index buffer of the shadow optimized geometry. This field exists only, if nVertex is 65536 or less.

Triangle32 pShadowVolumeIBuffer32[nTriangle] *
The index buffer of the shadow optimized geometry. This field exists only, if nVertex greater than 65536.


The material chunk ($U3D_MATERIAL)

In the mesh chunk there has been the piTriangleMaterial array. This array associates every triangle with a material, which effects how it will be rendered. Diffuse colors, specular data, textures and special effects (shaders) are held by materials. Here is a description of the data sets in a material chunk:

DWORD iMaterial
The index of this material. It is a value smaller than ModelHeader.nMaterial and it is unique within this model file. Since there are exactly nMaterial material chunks this means that every value in the range from 0 to ModelHeader.nMaterial-1 is used exactly once as material index.

Name
The name of this material. It can be used to find materials within a model or for debug output, but it is not needed for loading or rendering.

Color Ambient
The ambient lighting color of this material. This value is multiplied by the ambient lighting color and the result is added to the color, which is applied in the first texture stage.

Color Diffuse
The diffuse color of this material. It is multiplied by the color, which has been computed for diffuse lighting through light sources and added to the color, which is applied in the first texture stage.

Color Specular
The specular color of this material. It is multiplied by the color, which has been computed for specular lighting through light sources and added to the final pixel color (before fogging).

Color Emissive
The emissive color of this material. It is added to the color, which is applied in the first texture stage.

float SpecularPower
The exponent which is used to make the specular lighting factor harder or softer.

float MaterialDepth
The depth of this material in mesh space. This value is used for parallax mapping. Parallax mapping creates the illusion that the material has a depth using a height map. Note that it is not enough to assign a non-zero value to this data set to make Ultimate 3D use parallax mapping. It depends on the shader pack in the data set below.

float ParallaxQuality
For parallax occlusion mapping this value gives a factor specifying the quality of the effect. 1.0 is default.

DWORD pColorOp[8]
An array giving a color operation for every texture stage. It is a value from the D3DTEXTUREOP enumerated type of Direct3D. The first argument used in the color operation is always the value from the texture. In the first stage the second argument is the color, which has been computed through the values above. For all other stages it is the color that resulted from the previous stages. It is recommended to set 1 for all stages, which do not contain a texture, 5 for the primary stage (if it contains a texture) and 4 for other stages with texture.

DWORD piTexCoordSet[8]
An array giving the index of the used texture coordinate set for each stage. A common choice would be i for stage i or 0 for all stages. This value may also be set to a member of the Direct3D D3DTSS_TCI enumerated type (see the MSDN). This specifies that a particular method is used to compute texture coordinates on the fly. It is commonly used for environment mapping.

<$U3D_TEXTURE> pTextureData[8]
An array specifying the textures for every stage.

bool ShaderPackExists
Whether the next data set exists.

<$U3D_SHADER_PACK> ShaderPack *
The shader pack of this material. It specifies all shaders, which are used for rendering.


The texture chunk ($U3D_TEXTURE)

The texture chunk can give a texture (but it can also specify that no texture is used). Ultimate 3D supports 2D textures and cube textures. Here is the chunk data:

bool HoldsTexture
Whether this chunk contains a texture. If it is true the following data sets exist and they describe a texture. If it is false the chunk ends after this member and does not describe a texture. In this case a stage with this chunk does not use a texture.

DWORD Width *
The width the texture should have when it is loaded. If it is 0 the texture has to be loaded at its original size.

DWORD Height *
The height the texture should have when it is loaded. If it is 0 the texture has to be loaded at its original size.

bool IsCubeTexture *
If this boolean is true this texture is not a usual 2D texture, but a cube texture (made up of six quadratic 2D textures). The width of the cube is given by Width, Height is ignored. For more information about cube textures see Cubic Environment Mapping on the MSDN.

bool IsNormalMap *
If this boolean is true this texture is a greyscale height map and should be converted to a normal map after loading. If the texture is a cube texture it has to be false.

float HeightScalar *
The height factor, which should be used for creating the normal map (irrelevant, if IsNormalMap=false). If your texture is a quad of size 1.0*1.0 you have to expand it to a height of HeightScalar to compute the normal map. The normal maps are created as usual in Direct3D so a flat normal map would be R=G=128 and B=255. When processing the normal map the height map has to be copied to the alpha channel of the texture.

String TextureFile[IsCubeTexture?6:1] *
An array giving either six file names with path (for cube textures) or a single file name with path (for 2D textures), from which the texture should be loaded. For cube textures the order of the textures is right, left, top, bottom, back, front (as in the MSDN link above). The file may be a *.bmp, *.jpg, *.tga, *.png, *.dds, *.ppm or *.dib file. The string may start with a "*". In this case a default path known by the program should be inserted. The default path should end with a (back-)slash, so the file string should not start with one. The default path used by Ultimate 3D is "gfx/", but it can be changed by the client.


The bone chunk ($U3D_BONE)

If you put them together the bone chunks describe the complete skeleton of a model including the animation and lists of appended meshes for every bone. Here is a description of their data:

DWORD iBone
The index of this bone. It is unique within a model file and is smaller than ModelHeader.nBone.

String Name
The name of this bone. It can be used to find bones and for debug output, but it is not needed for loading or rendering.

DWORD iParent
The index of the parent of this bone. If this bone does not have a parent it is called a root bone and this value has to be 4294967295 (=2^32-1, the maximal value, which can be handled by an unsigned 32-bit integer). A model may have multiple root bones.

float BoneFrame
If this value is positive the frame value given by it will be used for this bone instead of the frame value of the parent bone. If it is negative the frame value of the parent will be used. Since that is the usual behavior you should set up -1.0 for this.

bool PassOnBoneFrame
Whether the frame value used by this bone should be passed to the child bone.

DWORD nMesh
The number of meshes that are appended to this bone. If skinning is used this has to be one for all bones.

DWORD piMesh[nMesh]
The indices of the meshes that are appended to this bone. It is a value smaller than ModelHeader.nMesh. The chunks of the referred meshes have to occur before the bone chunks.

Matrix pMeshToBoneSpace[nMesh]
The mesh to bone space transformation matrix for every mesh that is appended to this bone. To get the final transformation of a mesh this matrix gets multiplied by the bone to world space transformation from the right side (see below).

DWORD nScalingKey
The number of scaling keys. It may be zero. In this case a default key with no scaling has to be used.

<DWORD;Vector> pScalingKey[nScalingKey]
The scaling keys for this bone. The DWORD gives the frame the key is at and the Vector gives the scaling among each of the three axes in bone space.

DWORD nTranslationKey
The number of translation keys. It may be zero. In this case a default key with no translation has to be used.

<DWORD;Vector> pTranslationKey[nTranslationKey]
The translation keys for this bone. The DWORD gives the frame the key is at and the Vector gives the translation in parent bone space.

DWORD nRotationKey
The number of rotation keys. It may be zero. In this case a default key with no rotation has to be used.

<DWORD;Quaternion> pRotationKey[nRotationKey]
The rotation keys for this bone. The first DWORD gives the frame the key is at and the Quaternion describes the rotation in parent bone space.

This is all information that is needed to load the bones. Since loading animations is always a quite complicated task I want to give some additional details on the relations of the given transformation data at this point. Sorry for the pseudo code formulas, but it should be quite understandable this way. From the transformation keys you can compute a rotation, a translation and a scaling for every frame value (any real value). For positions and scalings linear interpolation should be used for rotations a spherical linear interpolation is recommended (this is what Ultimate 3D uses, but you may also use a normalized linear interpolation). The bone to parent bone space matrix is defined as BoneToParentBoneSpace=Scaling*Rotation*Translation. Assuming that the parent bone already has a parent bone to world space matrix the bone to world space matrix is defined as BoneToWorldSpace=BoneToParentBoneSpace*ParentBoneToWorldSpace. It is defined recursive. You start with the root bone(s) and for every bone you continue with its siblings and its children. With the mesh to bone space matrices given in the bone chunk you can easily get the mesh to world space matrices through pMeshToWorldSpace[i]=pMeshToBoneSpace[i]*BoneToWorldSpace then.


The action range chunk ($U3D_ACTION_RANGE)

In Ultimate 3D model files there is only one long animation with ModelHeader.nFrame frames. In many cases it can be useful to have multiple smaller animations in a single model file. Smaller animations like this are referred to as actions here. The usual way to have multiple actions in a *.u3d file is to have one action after the other, each taking up a particular range of the available frames. This way it becomes necessary to save the frames associated with particular actions. To provide a uniform way to do this there is the action range chunk. It associates ranges of frames with names for actions. This chunk can occur either zero times or once in an Ultimate 3D model file and it can be placed anywhere after the model header. Here is a description of its data:

DWORD nAction
The number of actions in this Ultimate 3D model file. It can be any positive integer or zero.

<String;DWORD;DWORD> pActionData[nAction]
An array describing all actions in this file. The String gives the name of the action. The first DWORD gives the value for the first frame, which is part of the animation. The second DWORD gives the last frame of the animation. The animation will end at the beginning of this frame, so at the end of the animation the state of the model will be described by the keys at this frame. In other words the frame values which need to be played are the closed interval from the first DWORD to the second DWORD.


Conclusion

I hope this documentation is clear enough. I have gone through the frustration of being forced to guess around how a particular part of a model file format is to be interpreted due to an ambiguous documentation often enough. For this reason anybody who has problems to understand a part of this documentation or thinks that there is a mistake in it should feel free to contact me at any time. You can contact me by email, on the forum of Ultimate 3D or via the contact form on Ultimate3D.org.



© Christoph Peters. Some rights reserved.

Creative Commons License XHTML 1.0 Transitional