A3D (Tanki Online)

Custom 3D model format used by Tanki Online HTML5 for HD maps and all tank models (including legacy) to replace the old 3ds format (non HD maps still use 3ds).

There are three versions of the A3D file format which are used for different types of objects in the game: version 1 files are no longer used, version 2 files are used for map geometry (props and terrain meshes etc...), version 3 files are used for tank models (turrets and hulls).

The format is able to store: basic material data, mesh data (including coordinate, UV, normals and colour vertex buffers), submesh/surface data (with material indices), transform data and hierarchy data.

Format

Padding

Padding is only used by version 3 models, it is calculated using:

paddingSize = (((dataLength + 3) // 4) * 4) - dataLength

Strings

Version 2 uses null terminated strings, e.g. the strings have no length and characters are read from the stream until a null is reached (\0). Version 3 uses an int32 length field which comes before the characters with no null terminator, this is followed by padding bytes (padding calculated from string length).

Data blocks

The A3D file format is based around data blocks, data blocks are sections of the file which encode different data in the model such as meshes and materials.

struct DataBlock
{
    int dataBlockSignature; // Identifies which datablock this is
    int dataBlockLength; // This field is used to compute padding in version 3 files, other versions don't use it
    // Data block specifc data goes here
};

Version 1

TODO

Version 2

struct {
    char signature[4]; // "A3D\0"
    int version; // 2
    RootDataBlock root;
};

Root block

This contains all model data blocks.

struct RootDataBlock
{
    int rootBlockSignature;
    int rootBlockLength;
    MaterialBlock materialBlock;
    MeshBlock meshBlock;
    TransformBlock transformBlock;
    ObjectBlock objectBlock;
};

Material block

struct MaterialBlock
{
    int materialBlockSignature;
    int materialBlockLength;
    int materialCount;
    struct A3DMaterial
    {
        string name;
        float colorR;
        float colorG;
        float colorB;
        string diffuseMap;
    } materials[materialCount];
};

Mesh block

struct MeshBlock
{
    int meshBlockSignature;
    int meshBlockLength;
    int meshCount;
    struct Mesh
    {
        int vertexCount;
        int vertexBufferCount;
        struct VertexBuffer
        {
            int bufferType;
            float vertices[]; // vertex size * vertex count
        } vertexBuffers[vertexBufferCount];
        int submeshCount;
        struct Submesh
        {
            int faceCount;
            short indices[faceCount*3]; // array of 16 bit indices
            int smoothingGroups[faceCount];
            short materialID;
        } submeshes[submeshCount];
    } mesh[meshCount];
};

Transform block

struct TransformBlock
{
    int transformBlockSignature;
    int transformBlockLength;
    int transformCount;
    struct Transform {
        float positionX;
        float positionY;
        float positionZ;
        float rotationX;
        float rotationY;
        float rotationZ;
        float rotationW;
        float scaleX;
        float scaleY;
        float scaleZ;
    } transforms[transformCount];
    int transformIDs[transformCount]; // Each ID corresponds to a transform component at the same index, this ID is referenced by external data blocks
};

Object block

struct ObjectBlock
{
    int objectBlockSignature;
    int objectBlockLength;
    int objectCount;
    struct Object
    {
        string name;
        int meshID;
        int transformID;
    } objects[objectCount];
};

Version 3

struct {
    char signature[4]; // "A3D\0"
    int version; // 3
    RootDataBlock root;
};

Root block

This contains all model data blocks.

struct RootDataBlock
{
    int rootBlockSignature;
    int rootBlockLength;
    MaterialBlock materialBlock;
    MeshBlock meshBlock;
    TransformBlock transformBlock;
    ObjectBlock objectBlock;
};

Material block

struct A3DMaterialBlock
{
    int materialBlockSignature;
    int materialBlockLength;
    int materialCount;
    struct A3DMaterial
    {
        string name;
        float colorR;
        float colorG;
        float colorB;
        string diffuseMap;
    } materials[materialCount];
    byte padding[]; // depends on materialBlockLength
};

Mesh block

struct MeshBlock
{
    int meshBlockSignature;
    int meshBlockLength;
    int meshCount;
    struct Mesh
    {
        string name;
        float bboxMaxX;
        float bboxMaxY;
        float bboxMaxZ;
        float bboxMinX;
        float bboxMinY;
        float bboxMinZ;
        float unknown;
        int vertexCount;
        int vertexBufferCount;
        struct VertexBuffer
        {
            int bufferType;
            float vertices[]; // vertex size * vertex count
        } vertexBuffers[vertexBufferCount];
        int submeshCount;
        struct Submesh
        {
            int indexCount;
            short indices[indexCount]; // array of 16 bit indices
            byte padding[]; // dependent on indexCount*2
        } submeshes[submeshCount];
    } mesh[meshCount];
    byte padding[]; // depends on meshBlockLength
};

Transform block

struct TransformBlock
{
    int transformBlockSignature;
    int transformBlockLength;
    int transformCount;
    struct Transform {
        string name;
        float positionX;
        float positionY;
        float positionZ;
        float rotationX;
        float rotationY;
        float rotationZ;
        float rotationW;
        float scaleX;
        float scaleY;
        float scaleZ;
    } transforms[transformCount];
    int transformIDs[transformCount]; // Each ID corresponds to a transform component at the same index, this ID is referenced by external data blocks
    byte padding[]; // depends on transformBlockLength
};

Object block

struct ObjectBlock
{
    int objectBlockSignature;
    int objectBlockLength;
    int objectCount;
    struct Object
    {
        int meshID;
        int transformID;
        int materialCount;
        int materialIDs[materialCount];
    } objects[objectCount];
    byte padding[]; // depends on objectBlockLength
};