# 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];
/----------------------------------