P3D File Format - MLOD

From Bohemia Interactive Community

Jump to: navigation, search
Disclaimer: This page describes an internal undocumented structures of Bohemia Interactive software.

This page contains unofficial information.

Some usage of this information may be violating rights of Bohemia Interactive and is in no way endorsed or recommended by Bohemia Interactive and Bohemia Interactive is not willing to tolerate use of such tools if it contravenes any general licenses granted to end users of this community wiki or BI products.

Contents

Intro

This information comes largely from the long defunct 'ofpinternals'.

This is archive material held at www.ofpec.cpom, and being re-massaged here to make some sense of it.

Acknowledgement from OFPInternals

 Thanks to FlipeR (filipus@hotmail.com) for helping in research

Basics: Polygon Vertex Order

For a visible 3-vertex polygon:

AB // clockwise order
C 
.
AC // CounterClock
B 

The same for 4-vertex polygon:

AB // clockwise
DC 
.
AD // CounterClock
BC 

Your 3D device will cull invisible polygons. An invisible polygon is a polygon that has the other direction order. For example, DirectX default setting is 'cull counterclockwise polygons', so only clockwise is visible.

Structure

Data Types

 uint8 unsigned 8-bit integer (unsigned char in C)
 uint32 unsigned 32-bit integer (unsigned int in C)
 int8 signed 8-bit integer (signed char in C)
 float single precision IEEE-floating point (float in C)
 char[4]  signature string (not zero-terminated)
 char[32] zero-terminated string of maximum length 31
 char[64] zero-terminated string of maximum length 63
 char[]  zero-terminated string, arbitrary length

Overall

struct P3D 
 {
 char Signature[4]; //"MLOD"
 uint32 Version;     // 0x101
 uint32 LodCount;    //at least one
 struct SP3X_Lod
 {
  char Signature[4]; //"SP3X"
  uint32 VersionMajor; //0x1C
  uint32 VersionMinor; //0x99
  uint32 VerticesCount;
  uint32 NormalsCount; //(perpendicular)
  uint32 PolygonCount;
  uint32 Flags;        // unknown values
  struct VertexTables[VerticesCount];
  struct NormalsTables[NormalsCount];
  struct PolyTables[PolygonCount];
  char TagSig[4]; //"TAGG"
  struct TagSets[........];
  float LodResolution; // see Appendix A
 };
 
 struct P3DM_Lod
 {
   char Signature[4]; //"P3DM"
   uint32 VersionMajor; // 0x1c
   uint32 VersionMinor; // 0x100
   uint32 VerticesCount;
   uint32 NormalsCount;
   uint32 PolygonCount;
   uint32 Flags;  // unknown
   struct VertexTables[VerticesCount];
   struct NormalsTables[NormalsCount];
   struct P3DM_PolyTables[PolygonCount];
   char TaggSignature[4]; //"TAGG"
   struct P3DM_TagSets[];
   float LodResolution; // see Appendix A
 };
 union
 {
   struct SP3X_Lod lods[LodCount]; // O2Light uses SP3X
   struct P3DM_Lod lods[LodCount]; // O2PE uses P3DM
 };
 char DefaultPath[32]; //Optional. Default Path to Textures - (see O2L 'Options') 
};

VertexTable

 struct VertexTable
 {
   float XZY[3];
   uint32 VertexFlags;
 };

Vertex Flags

See also O2L 'Point Properties'.

Surface: (0x0000000f mask)

0x00000000 - normal 
0x00000001 - on surface 
0x00000002 - above surface 
0x00000004 - undersurface 
0x00000008 - keep height (fence) 

Decal (0x00000300 mask)

0x00000000 - normal 
0x00000100 - decal 
0x00000200 - radio12 

Fog (0x00003000 mask)

0x00000000 - normal 
0x00001000 - none 
0x00002000 - sky 

Lightning (0x000000f0 mask)

0x00000000 - normal 
0x00000010 - shining 
0x00000020 - always in shadow 
0x00000040 - fully lighted 
0x00000080 - half lighted 

User (0x00ff0000 mask)

0x00ff0000 - user additional mark value (0..255)

Hidden (0x01000000 mask)

0x00000000 - Visible
0x01000000 - Hidden

NormalsTable

 struct NormalsTable
 {
   float XZY[3];
 };

Important Note: normals must be inverted (-X, -Y, -Z) for clockwise vertex order (default for DirectX), and not changed for counterclockwise order.

PolygonTable

struct PolygonTable
{
  char  TextureName[32];
  uint32 VerticesCount;    // (valid values: 3 or 4) 
  PolyVertex PVertex[4]; // vertex descriptors, 3, or 4
   // 4th always present in file, even if polygon has only 3 vertex (0 filled) 
  uint32 PolygonFlags;
};

Important Notes: Order of vertices

Vertices must be reordered for clockwise vertex order (default for DirectX), and not changed for counterclockwise order:

for 3-vertices polygon: 1. 1st vertice descriptor 2. 3rd vertice descriptor 3. 2nd vertice descriptor 4. (not used, zero filled)

for 4-vertices polygon: 1. 1st vertice descriptor 2. 4th vertice descriptor 3. 3rd vertice descriptor 4. 2nd (not used, zero filled)

PolygonFlags

See also O2L 'face properties'.

Enable shadow

0x00000010 - off (disable shadow) 

Enable texture merging

0x01000000 - off (disable texture merging) 

ZBias (0x00000300 mask)

0x00000000 - none 
0x00000100 - low 
0x00000200 - middle 
0x00000300 - high 

Lightning (0x003000a0 mask)

0x00000020 - both sides 
0x00000080 - position 
0x00200000 - flat 
0x00100000 - reversed (transpared) 

User (0xfe000000 mask)

0xfe000000 - user additional mark value (0..127)

PolyVertex

struct PolyVertex
{
   uint32 VertexIndex; // (zero based) into respective tables
   uint32 NormalsIndex;
   float UV[2]; // for texture mapping 
};

P3DM_PolygonTable

 struct P3DM_PolygonTable
 {
   uint32 VerticesCount; // (valid: 3 or 4)
   PolyVertex PVertex[4]; // there are always 4, even if VerticesCount == 3
   char Texture[]; // unbounded-length zero-terminated string
   char Material[]; // material name
 };

TagSet

struct TagSet
{
   char    Name[64];
   uint32  Size;
   uint8   Data[Size];
};

P3DM_TagSet

 struct P3DM_TagSet
 {
   uint8  unknownValue; // seems to be always 1
   char   TaggName[]; // unbounded-length zero-terminated string
   uint32 TaggSize; // size of data; unknown Taggs can be easily skipped
   uint8  data[TaggSize]; // arbitrary data
 };

TagNames

Every LodSet contains one or more Tagname entries. The #EndOfFile# tag is a mandatory entry in every LodSet if only to indicate no more tags!

The #Mass# tag is mandatory for Geometry LODs.

Tagnames are Asciiz and can be up to 63 bytes long. 'Official' bis tags are surround by #.....# marks.

Tagnames not listed here (one's with no ## marks) are component names (named selections).

Component names can contain space characters. Proxy names contain 'proxy:' + ProxyName + '.' + ProxyNumber ('01' .. )

'Official' tagnames listed below are a mish mash of obsolete, and still used, commands. This because, the p3d was a still in development at time of CWC release.

#SharpEdges#

The Data field for his tag is

 uint32 Vertex[VertexCount][2] // 1st and 2nd vertex index of each edge for 'VertexCount' edges
 

Sharp edge means that these vertices normals are not calculated as average (normalized) between polygons.

#Property#

 struct PropertyPair
 {
   char key[64]; 
   char value[64];
 };

It seems that properties must be placed in separate #Property#-Taggs (=> Taggsize == 128), as putting them into a single one is not recognized by O2 (O2 stops loading).

#MaterialIndex# (only in O2L)

Unknown tag. Used only in O2L. This tag contains a 16-byte structure:

Seems, this tag contains material properties:

4 (RGBA or BGRA) diffuse 
4 (RGBA or BGRA) ambient 
4 (RGBA or BGRA) specular 
4 (RGBA or BGRA) emissive 

By default, O2L write down this values:

51, 75, 55, 0 
0, 0, 0, 0 
255, 255, 255, 255 
255, 255, 255, 255

#Animation# (obsolete)

Obsolete tag. MLOD animation is per-vertex animation and not used now.

float KeyFrameTime; // Valid Values: 0.0 (start) - 1.0 (stop).
                    // Used to change duration of animation playing. 
ulong VerticesCount; // Must be the same as Vertices Count in the LOD header. 
byte  VertexArray[VerticesCount];// This data replaces main LOD Vertex Table.

#Mass# (only for Geometry LOD)

Must be present only for Geometry LOD.

 float VertexMassArray[VerticesCount];

Where VerticesCount must be the same as for the Geometry-LOD and VerticesCount == TaggSize/4.

#Selected# (only in O2L)

#Hide# (only in O2L)

#Lock# (only in O2L)

These tags all have the same structure and are used only while editing in O2L.

They are used so that O2L can save your last selections in the edited file.

int8 VertexSaved; // Non-zero value if vertex selected/hidden/locked. 
int8 PolygonsSaved;// Non-zero value if polygon selected/hidden/locked. 

Data Size for any of these tags is

VertexCount + PolygonsCount from current LOD header. 

Example: if VertexCount=5, PolygonsCount=3 then a #selected# tag would contain:

2, 2, 0, 2, 0, 0, 1, 1 

then Vertex 0, 1, 3 is selected (indices of Vertex Table entries) and

Polygon 1, 2 selected (indices of Polygon Table entries).

Common values for vertices and polygons are the 2 and 1 respectively. But I recommend to compare it only with zero value (for example I saw early this value was 6 instead of 2 or 1).

#UVSet#

This Tagg occurs within P3Ds made with O2PE. It is used with multiple UV sets.

This needs further documentation.

Named Selections

Every Tagg-name that doesn't have the form #Name# is a named selection. The name of the selection is simply the Tagg-name.

 struct NamedSelection
 {
   int8 VertexWeight[VerticesCount]; // this are coded values, see below
   int8 FaceWeight[PolygonCount]; // this is are coded values
 };
 struct P3DM_NamedSelection
 {
   int8 VertexWeight[VerticesCount]; // this are coded values, see below
   int8 FaceWeight[PolygonCount]; // this is are coded values
 };

Taggsize must be the same as NumberOfVertices + NumberOfPolygons for this LOD. The weights are 'compressed' floating point values; the following functions may be used to encode/decode those values. The conversion functions were found by experimenting and are not guaranteed to yield the correct results.

 float decodeWeight( uint8 b )
 {
   if( b == 0 ) return  0.0f;
   else if( b == 2 ) return 1.0f;
   else if( b > 2 ) return 1.0f - round( (b-2) / 2.55555f )*0.01f;
   else if( b < 0 ) return -round( b / 2.55555f ) * 0.01f;
   else assert(false); // b == 1 does not occur?
 }


 uint8 encodeWeight( float f )
 {
   // assume clamped value from 0.0 to 1.0
   if( f < 0.01 ) return 0;
   else if( f > 0.99 ) return 2;
   else if( f > 0.5 ) return 0xff & (int)floor((1.0f - f) * 255.55f + 2);
   else if( f > 0.49 ) return -127;
   else return 0xff & (int)floor(f*2.5555f);
 }

#EndOfFile#

Mandatory for every LodSet. This is the last tag of current LOD Data structure. It contains no data, so 'Tag Size' field is 0.

LOD Resolution

Valid Values for LOD Resolution are:

1.0e3 = 1'000.0 = View - Gunner 
1.1e3 = 1'100.0 = View - Pilot 
1.2e3 = 1'200.0 = View - Cargo 
1.0e13 = 10'000'000'000'000.0 = Geometry 
1.0e15 = 1'000'000'000'000'000.0 = Memory 
2.0e15 = 2'000'000'000'000'000.0 = LandContact 
3.0e15 = 3'000'000'000'000'000.0 = Roadway 
4.0e15 = 4'000'000'000'000'000.0 = Paths 
5.0e15 = 5'000'000'000'000'000.0 = Hitpoints 
6.0e15 = 6'000'000'000'000'000.0 = View Geometry 
7.0e15 = 7'000'000'000'000'000.0 = Fire Geometry 
8.0e15 = 8'000'000'000'000'000.0 = View - Cargo - Geometry 
9.0e15 = 9'000'000'000'000'000.0 = View - Cargo - Fire Geometry 
1.0e16 = 10'000'000'000'000'000.0 = View - Commander 
1.1e16 = 11'000'000'000'000'000.0 = View - Commander - Geometry 
1.2e16 = 12'000'000'000'000'000.0 = View - Commander - Fire Geometry 
1.3e16 = 13'000'000'000'000'000.0 = View - Pilot - Geometry 
1.4e16 = 14'000'000'000'000'000.0 = View - Pilot - Fire Geometry 
1.5e16 = 15'000'000'000'000'000.0 = View - Gunner - Geometry 
1.6e16 = 16'000'000'000'000'000.0 = View - Gunner - Fire Geometry 

If the value is below 1000.0, then the LOD is supposed to be a graphical LOD. If the value equals or is above 10000.0 and is not one of the functional LODs, it is a shadow LOD.

In case you have problems with the resolution LOD value for functional LODs: Those floating point constants have a specific binary representation which is correct for C-compilers, but may be different in other languages (Python for example).


LOD selected for displaying if

DistanceToObject * LODCoef * M <= LODResolution

Where: - LODCoef is value from OFP preferences (I have LODCoef = 0.019). - M is some value that changed by OFP developers (I use M=1 in WRPEdit and M=2 in P3DEdot).

Personal tools
Buy ArmA now!