3DA File Format

1. Data format description.
2. Working environment to test 3DA.
3. 3DSMAX data export description.
4. Data preprocessing
5. Scheme of the 3DA file.
6. Data stuctures details.


1. Data format descripiton

File 3DA contains one model, which can be used in the game. That is one can load such a model into the game engine and than link it with a proper controller class. At the moment (java side) base class for the 3DA file is MeshObject (more on java classes in corresponding docs). File 3DA doesnt now about its usage in the scene, its position and orienttion depends on MeshObject it is linked to. That means that root for 3d transformations stored in 3DA it is controller's coordinate system. By the model in the context of 3DA we consider a hierarchy tree of nodes (enitites). A Node can be a geomerty node, or helper node (dummy object). Each node can be animated (in this case a node contains animation data in local space of that node) File 3DA doesnt contain information about cameras and lights. More on nodes later. File 3DA also contains material table accessed by the geometry nodes. For some 'historical' reasons material table contains except the material name some other informations (eg. ambient kolor) but the only information needed it is the material name. Material definition and properties are storred in a proper script files. That is if one find in the material table a name 'test.mat' a corresponding script file should be found in the file system. 3dsmax exporter plugin except the  file 3DA produces material scirpt files. One can create such script files by hand. Finally file 3DA contains collision map (AABB tree). In fact there are two collision maps. The first map is used for ray vs geometry test (all the geometry is storred in this map). Second map is used to restrict player movment (or AI movment) so called CollisionHull map (this map contains coarse collision geometry eg. it can be simplified origianal geometry).


2. Working environment to test 3DA. Configuring "Play3da.exe"

Play3da.exe uses the same library to render meshes as ChromeEngine does. That is Play3da renders models the same way as ChromeEngine (except to lights, play3da uses one light attached to the camera position). To start working with play3da make sure that configuration file "r_init.scr" is accessable. Put config file in the exe location or create the register entry that tells where to fing the confing. For example:

[HKEY_CURRENT_USER\Play3da_2]
@="D:\\project\\ChromeEngineII\\engine\\"

It is important to end the path with  \  character

File "r_init.scr" contains basic renderer configuration, for example:
-----------------
!BBoxModel( s )
!BSphereModel( s )

!ODEBoxModel( s )
!ODESphereModel( s )
!ODECylinderModel( s )
!ODEConeModel( s )

!DefaultModel( s )
!DefaultMaterial( s )
!MonitorTextures()          // declarations

DefaultMaterial( "default.mat" )  // name of the default material (it is used if desired materail can not be found or can not be loaded)

// should be initialized as first object in system
DefaultModel( "wykrzyknik.3da" ) // name of the default model (it is used if desired model can not be found or can not be loaded)

BBoxModel( "bbox.3da" )           // bounding box model
BSphereModel( "bsphere.3da" ) // bounding sphere model

---------------------
Important: it is necessary to have default material, defaul model and default bounding models. Without those renderer will not start.
play3da usage:

play3da.exe [ file.3da ]


3. 3DSMAX Data export description

This section contains detailed information on data exported/converted from 3DSMAX aplication. Basicly file 3DA format is similar to the interanal 3DSMAX node's data structure. 3ds  represent the scene as a node tree. So file 3DA contains corresponding structure/hierarchy. MAX node corresponding file 3DA structure is 'entity_s'. Node tree traversing scheme:

traverse ( node )
{
    save( node )

    for every node.child                   // in alphabetical order
          traverse( node.child )
}

Node's child list is iterated in alphabetical order. Example memory map for a scene:

idx   name                                        parent idx

0:     Node0,                                    parent: -1 
1:         Node0.Child0,                     parent:  0
2:            Node0.Child0.Child0,       parent:  1 (Node0.Child0)
3:            Node0.Child0.Child1,       parent:  1 (Node0.Child0)
4:         Node0.Child1,                     parent:  0 (Node0)
5:         Node0.Child2,                     parent:  0
6:     Node1,                                    parent: -1
7:        Node1.Child0,                      parent:  6 (Node1)

Node writing order is significant. It is because the way the parent - child link is stored. Each entity/node has idx number and parent idx number. If parent idx number is set to -1 it means no parent.

3DSMAX for a geometry node provides following data:

vertex table                                                   - mesh vertices (xyz, normals, uvs, ...) in node local space
index  table                                                    - mesh tri lists (indices to vertex table)
matid table                                                    - material idx for each triangle
smoothid table                                               - smothing group  table (for pervertex normal computation)

animation keys position, rotation, scale

Some naming convention:

Enitity, Node, MeshElement  -  smallest tree hierarchy element it has own trasnform matrix, it is made of Surfaces
    Surface - triangle set sharing the same material. Each entity may have several surfaces. Surface is made of streams and index table (to build triangles from stream data)
       Stream - data table describing vertex elements. Stream types: xyz, normal, color, tangent, uv coords, vertexblend                   
       Index table - 3 succeeding indices define a triangle. By indexing streams one can read: xyz, normals, uv cords etc..

    TrackPos - key table positions (vektor)
    TrackRot - key table rotation (qwaternion)
    TrackScl  - key table scale (vektor)

    Animation tracks description. For each entity there are 3 tracks: position, rotation and scale (PRS). For each entity PRS tracks must to be written with at least one key (One key in case when entity doesnt animate). Position track can  be : TCB, BEZIER, LINEAR. Rotation track can be : TCB, LINEAR, EULER. Scale track can be: TCB, BEZIER, LINEAR.
Each type of track contain a header with track id, key number, and offset from begining of the file to the key table and key table itself.
Tracks of type TCB, LINEAR, BEZIER they have 3 versions: 1D - float interpolation, 3D - vector (float3) interpolation, 4D - quaternion  interpolation (except BEZIER track: it has no 4D interpolator). EULER track it used to interpolate rotation. It is made of 3  1D tracks representing euler angles.


4. Data preprocessing.

    Part of the data before dumping it to the file is preprocessed with some tools. That is all mesh index tables go to the nVidia's TriStrip tool to optimize rendering considering GF3 vertex cache. Than collision map has to be precalculated. Collision map generator code in :  "lib3da_2/aabbtree.cpp, lib3da_2/aabbtree.hpp". Or external tool can generate collision map.


5. Scheme of the 3DA file.

Anim_c
{
    material block
    {
        0: material_s
        1: material_s
        ...
    }

    entity block: entity_s
    {
        0: entity_s
        {
            0: surface_s
            {
                geometry (xyz, normals, ... }
             }

            1: surface_s
            {
                geometry
            }
            ...

            position track: Track_c
            rotation track: Tack_c
            scale track: Track_c
        }

        ...

         1: entity_s
          {
             ...
          }

            ..  
    }

// one can ommit that block and pass file 3da without that block to external tool which will generate collison map.
    collision map block
    {
    }
}



6. Data structures details.

File 3DA id :
#define ANIM_FILE_ID                ( (int)'_' << 24 | 'A' << 16 | 'D' << 8 | '3' )

current version 3DA :
#define ANIM_FILE_VERSION           0x207

Node name used as a simplified geometry for collision map generator
#define COLLISION_HULL   "CollisionHull_0"

Node types
enum EntityID_e
{
  ANIM_UNKNOWN = 0, 
  ANIM_MESH_3DA = 1,                                  // mesh
  ANIM_MESH_SKINNED_3DA = 2,               // mesh skinned (vertex blending)
  ANIM_HELPER_3DA = 3,                               // dummy object
  ANIM_BONE_3DA = 4,                                   // bone
};

Collision map types, use only ANIM_COLMAP_AABBTREE
enum ColMapID_e
{
  ANIM_COLMAP_UNKNOWN       = 0x0000,
  ANIM_COLMAP_LOOSE_OCTREE  = 0x0001,   
  ANIM_COLMAP_AABBTREE      = 0x0002,
};

File 3DA header
class Anim_c {

        uint          m_iFileID;            // 4byte :  ANIM_FILE_ID               
        uint          m_iVersion;          // 4byte:  ANIM_FILE_VERSION          
        uint          m_iNumEntities;   // 4byte : number of nodes in the file
        uint          m_iFrames;          // 4byte:  number of animation frames

        uint          m_offsetMaterials;  // 4byte : offset from begining of file to the materials
        int           m_iMaterials;          // 4byte:  number of materials

        int           m_iNumFaces;        // 4byte: all faces count (information only)
        int           m_iNumVertices;    // 4byte: all vertices count (information only)

       uint          m_offsetFirstEntity;  // 4byte : offset from begining of file to the first node (enitty_s)

        EXTENTS       m_Bounds;    // 24byte : bounding volume for all nodes calculatet at first animation frame. (float minx, miny, minz, maxx, maxy, maxz)

        ColMapID_e    m_ColmapID; //  4byte :  ANIM_COLMAP_AABBTREE
        uint          m_offsetColmap;     // 4byte : offset from begining of file to the collision map
};

// ANIM_MAX_MATERIAL_NAMELEN = 32
// ANIM_MAP_CHANNELS_MAX = 4
struct material_s {

        char    name[ANIM_MAX_MATERIAL_NAMELEN]; 

        char    map_name[ANIM_MAP_CHANNELS_MAX][ANIM_MAX_MATERIAL_NAMELEN];   // not used
        uint    map_channel[ANIM_MAP_CHANNELS_MAX];                                                                   // not used
       
        int     num_maps;                   // 4byte : not used

        COLOR4F ambient;             // 16byte : not used
        COLOR4F diffuse;               // 16byte : not used
        COLOR4F specular;            // 16byte : not used
        COLOR4F emissive;            // 16byte : not used
       
        float   specular_pow;            // 4byte : not used

        int     flags                            // 4byte: not used
};


// ANIM_MAX_MESH_NAMELEN  = 32
Single node
struct entity_s {

        EntityID_e    type_id;                                                         // 4byte : node id. eg: ANIM_MESH_3DA
        char          name[ANIM_MAX_MESH_NAMELEN];      // node name

        EXTENTS       bounds;                                                     // 24byte : node bounding volume (local space)

        ushort        parent_idx;                                                       // 2byte : parend index (according to the order in file)
        ushort        num_children;                                                   // 2byte : number of child nodes

        uint          surfaces_offset;                                                  // 4byte : offset from begining of file to the first surface (surface_s)
        uint          num_surfaces;                                                    // 4byte : number of surfaces

        uint        pos_track_offset;                                                 // 4byte : offset from begining of file to the position track
        uint        rot_track_offset;                                                   // 4byte : offset from begining of file to the rotation track
        uint        scl_track_offset;                                                   // 4byte : offset from begining of file to the scale track

        CMatrix       bone_init_tm;                                                 // 64byte : float [4][4], matrix (set it to id)

        uint        next_offset;                                                          // 4byte :  offset from begining of file to the next entity
};

struct surface_s {
 
        int           material_idx;                                                     // 4byte : material index applied to the surface (according to material order in file)
 
        uint          tri_list_offset;                                                   // 4byte : offset from begining of file to the index table(ushort)  (triangle)
        uint          tri_list_size;                                                      // 4byte : number of indices

        uint          tri_strip_offset;                                                 // 4byte : offset from begining of file to the index table(ushort) (strips)
        uint          tri_strip_size;                                                    // 4byte : number of indices  (may be 0)

        uint        s_xyz_offset;                                                      // 4byte : offset from begining of file to the xyz stream: table float[3] 
        uint        s_tangent_offset;                                                 // 4byte : offset from begining of file to the tangent stream: table float[3]
        uint        s_color_offset;                                                    // 4byte : offset from begining of file to the color stream : table  RGBA_c ( usigned int )
        uint        s_normal_offset;                                                  // 4byte : offset from begining of file to the normal stream: table float[3]
        uint        s_texcoord_offset[ANIM_MAP_CHANNELS_MAX];  // 4byte * 4, offset from begining of file to the uvcoord stream: float[2]
        uint        s_lgtcoord_offset;                                                // 4byte : offset from begining of file to the lightmap uvcoords: float[2]
        uint        s_blending_offset;                                                // 4byte : offset from begining of file to the vertex blending stream : VBlend_s

        uint          s_size;                                                               // 4byte : stream length (each stream has the same length)

        uint        next_offset;                                                         // 4byte : offset from begining of file to the next surface
};

#define ANIM_VERTEX_WEIGHT_SCALE    32768
#define ANIM_FLOAT_2_WIEGHT(x) ((ushort)((x)*ANIM_VERTEX_WEIGHT_SCALE))
#define ANIM_WEIGHT_2_FLOAT(x) ((float)(x)*(1.0f/ANIM_VERTEX_WEIGHT_SCALE))

struct VBlend_s {

  uchar      bone_idx[4];           // bone index (according to the entities order in file)
  ushort     weight[4];               // blend wieght (weight[0] + ... + weight[3] = 1) , use ANIM_FLOAT_2_WIEGHT

};

enum TrackID_e {

        TRACK_TCB = 1,
        TRACK_BEZIER = 2,
        TRACK_LINEAR = 3,
        TRACK_EULER = 4,
};

class TCBTrack1D_c {

    void*            m_vptr;                                                                        // 4byte: placeholder for virtual jump table (engine will setup this with proper address)

    bool              m_copy;                                                                      // 1byte : internal flag : set it to:  0
     int                m_num_keys;                                                               //  4byte : number of keys in track
    TrackID_e    m_track_id                                                                   // 4byte : track id: TRACK_TCB
    int                  m_offset_keys;                                                            //  4byte : offset from begining of file to the key table TCBKey1D_c
    float               m_current_value;                                                         /// 4byte : current interpolated value
}

class TCBKey1D_c {

    uint              m_keyframe;                                                                  // 4byte : frame number

    float             m_fTension;                                                                  // 4byte :
    float             m_fContinuity;                                                               // 4byte :
    float             m_fBias;                                                                        // 4byte
    float             m_fEaseIn;                                                                    // 4byte
    float             m_fEaseOut;                                                                  // 4byte

    float            m_value;                                                                         // 4byte : key value
    float            m_in_tan;                                                                        // 4byte : tangent in
    float            m_out_tan;                                                                      // 4byte : tangent out
}

class TCBTrack3D_c {

    void*            m_vptr;                                                                        // 4byte: placeholder for virtual jump table (engine will setup this with proper address)

    bool              m_copy;                                                                      // 1byte : interanl flag, set it to: 0
     int                m_num_keys;                                                               //  4byte : numbe of keys
    TrackID_e    m_track_id                                                                   // 4byte : track id: TRACK_TCB
    int                  m_offset_keys;                                                            //  4byte : offset from begining of file to the key table TCBKey3D_c
    vec3              m_current_value;                                                         /// 12byte : current value
}

class TCBKey3D_c {

    uint              m_keyframe;                                                                  // 4byte : numer klatki kluczowej

    float             m_fTension;                                                                  // 4byte :
    float             m_fContinuity;                                                               // 4byte :
    float             m_fBias;                                                                        // 4byte
    float             m_fEaseIn;                                                                    // 4byte
    float             m_fEaseOut;                                                                  // 4byte

    vec3            m_value;                                                                         // 12byte : wartość w kluczu
    vec3            m_in_tan;                                                                        // 12byte : tangent wchodzący
    vec3            m_out_tan;                                                                      // 12byte : tangent wychodzący
}

class TCBTrack4D_c {

    void*            m_vptr;                                                                        // 4byte: miejsce na tablice skoków wirtualnych (poźniej engine wypełnia to miejsce odpowiednim adresem)

    bool              m_copy;                                                                      // 1byte : wewnetrzna flaga.. w 3DA zapisywana na : 0
     int                m_num_keys;                                                               //  4byte : liczba kluczy w tracku
    TrackID_e    m_track_id                                                                   // 4byte : id tracku TRACK_TCB
    int                  m_offset_keys;                                                            //  4byte : offset od początku pliku do tablicy kluczy TCBKey4D_c
    quat              m_current_value;                                                         /// 16byte : bierząca (przeinterpolowana) wartość
}

class TCBKey4D_c {

    uint              m_keyframe;                                                                  // 4byte : numer klatki kluczowej

    float             m_fTension;                                                                  // 4byte :
    float             m_fContinuity;                                                               // 4byte :
    float             m_fBias;                                                                        // 4byte
    float             m_fEaseIn;                                                                    // 4byte
    float             m_fEaseOut;                                                                  // 4byte
   
    quat            m_value;                                                                         // 16byte : wartość w kluczu
    quat            m_in_tan;                                                                        // 16byte : tangent wchodzący
    quat            m_out_tan;                                                                      // 16byte : tangent wychodzący
}

class LINEARTrack1D_c {

    void*            m_vptr;                                                                        // 4byte:

    bool              m_copy;                                                                      // 1byte :set it to: 0
     int                m_num_keys;                                                               //  4byte :
    TrackID_e    m_track_id                                                                   // 4byte : TRACK_LINEAR = 3,

    int                  m_offset_keys;                                                              // 4byte : offset from begining of file to the key table :  LINEARKey1D_c
    float               m_current_value;                                                            // 4byte
}

class LINEARKey1D_c  {
    int                 m_keyframe;                                                                  // 4byte :
    float               m_value;                                                                       // 4byte :
}

class LINEARTrack3D_c {

    void*            m_vptr;                                                                        // 4byte:

    bool              m_copy;                                                                      // 1byte : set it to: 0
     int                m_num_keys;                                                               //  4byte :
    TrackID_e    m_track_id                                                                   // 4byte : TRACK_LINEAR = 3,

    int                  m_offset_keys;                                                              // 4byte : offset from begining of file to the key table :  LINEARKey3D_c
    vec3              m_current_value;                                                            // 12byte :
}

class LINEARKey3D_c  {
    int                 m_keyframe;                                                                  // 4byte :
    vec3             m_value;                                                                        // 12byte :
}


class LINEARTrack4D_c {

    void*            m_vptr;                                                                        // 4byte:

    bool              m_copy;                                                                      // 1byte : set it to: 0
     int                m_num_keys;                                                               //  4byte :
    TrackID_e    m_track_id                                                                   // 4byte : iTRACK_LINEAR = 3,

    int                  m_offset_keys;                                                              // 4byte : offset from begining of file to the key table :  LINEARKey4D_c
    quat               m_current_value;                                                            // 16byte :
}

class LINEARKey4D_c  {
    int                 m_keyframe;                                                                  // 4byte :
    quat              m_value;                                                                        // 16byte :
}

calss BEZIERTrack_1D_c {

    void*            m_vptr;                                                                        // 4byte:

    bool              m_copy;                                                                      // 1byte : set it to: 0
     int                m_num_keys;                                                               //  4byte :
    TrackID_e    m_track_id                                                                   // 4byte : TRACK_BEZIER = 2,

    int                  m_offset_keys;                                                              // 4byte : offset from begining of file to the key table :  BEZIERKey1D_c
    float               m_current_value;                                                            // 4byte :
}

class BEZIERKey1D_c  {

         int         m_keyframe;                                                                  // 4byte :

        float        m_fInTan;                                                                     // 4byte : tangent in
        float        m_fOutTan;                                                                  // 4byte : tangent out
        float        m_fValue;                                                                     // 4byte :
}

calss BEZIERTrack_3D_c {

    void*            m_vptr;                                                                        // 4byte:

    bool              m_copy;                                                                      // 1byte : set it to: 0
     int                m_num_keys;                                                               //  4byte :
    TrackID_e    m_track_id                                                                   // 4byte : TRACK_BEZIER = 2,

    int                  m_offset_keys;                                                              // 4byte : offset from begining of file to the key table :  BEZIERKey3D_c
    vec3               m_current_value;                                                          // 12byte
}

class BEZIERKey3D_c  {

         int         m_keyframe;                                                                  // 4byte :

        vec3        m_fInTan;                                                                     // 4byte : tangent in
        vec3        m_fOutTan;                                                                  // 4byte : tangent out
        vec3        m_fValue;                                                                     // 4byte :
}

class EULERTrack_c {

    void*            m_vptr;                                                                        // 4byte:

    bool              m_copy;                                                                      // 1byte :set 0
     int                m_num_keys;                                                               //  4byte :
    TrackID_e    m_track_id                                                                   // 4byte : TRACK_EULER = 4,

    uint               m_offsetXTrack;                                                            // 4byte : offset from begining of file to the 1D track : can be TCB, LINEAR, BEZIER
    uint               m_offsetYTrack;                                                            // 4byte : offset from begining of file to the 1D track : can be TCB, LINEAR, BEZIER
    uint               m_offsetZTrack;                                                             // 4byte : offset from begining of file to the 1D track : can be TCB, LINEAR, BEZIER

    uint               m_aRotOrder[3];                                                      // 12byte : order of applying rotations (table can hold values 0, 1, 2)

    quat             m_cCurrentValue;                                                            // 16byte :
}

/----------------------------------  Using m_aRotOrder
        switch ( m_aRotOrder[0] )
        {
          case 0: rot[0].FromEulerYaw( -*(float*)m_pXTrack->GetValue() ); break;
          case 2: rot[0].FromEulerPitch( -*(float*)m_pXTrack->GetValue() ); break;
          case 1: rot[0].FromEulerRoll( *(float*)m_pXTrack->GetValue() ); break;
        }

        switch ( m_aRotOrder[1] )
        {
          case 0: rot[1].FromEulerYaw( -*(float*)m_pYTrack->GetValue() ); break;
          case 2: rot[1].FromEulerPitch( -*(float*)m_pYTrack->GetValue() ); break;
          case 1: rot[1].FromEulerRoll( *(float*)m_pYTrack->GetValue() ); break;
        }

        switch ( m_aRotOrder[2] )
        {
          case 0: rot[2].FromEulerYaw( -*(float*)m_pZTrack->GetValue() ); break;
          case 2: rot[2].FromEulerPitch( -*(float*)m_pZTrack->GetValue() ); break;
          case 1: rot[2].FromEulerRoll( *(float*)m_pZTrack->GetValue() ); break;
        }

        m_cCurrentValue = rot[0]*rot[1]*rot[2];
/----------------------------------