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