Table of Contents
OFP MLOD
OFP Forum, OFP Home, OFP File Formats, OFP Tools, OFP Missions, OFP 3D Modeling, OFP Terrain
Operation Flashpoint (OFP) aka ArmA: Cold War Assault (CWA)
OFP MLOD file format. This information comes largely from the long defunct 'ofpinternals'.
Acknowledgement from OFPInternals. Thanks to FlipeR (filipus@hotmail.com) for helping in research.
Type Explanation
byte - 8-bit unsigned integer integer - 32-bit signed integer float32 - 32-bit float number (single precision) zstring32 - zero-terminated string, data length is fixed = 32 bytes (so max. string length is 31) zstring64 - zero-terminated string, data length is fixed = 64 bytes (so max. string length is 63)
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
Overall
struct P3D { char Signature[4]; //"MLOD" float Version; //1.1 ulong LodCount; //at least one struct Lod1 { char Signature[4]; //"SP3X" ulong VersionMajor; //0x1C ulong VersionMinor; //0x99 ulong VerticesCount; ulong NormalsCount; //(perpendicular) ulong PolygonCount; ulong Flags; // unknown values struct VertexTables[VerticesCount]; struct NormalsTables[NormalsCount]; struct PolyTables[PolyCount]; char TagSig[4]; //"TAGG" struct TagSets[........]; float LodResolution; // see Appendix A }; ..... Struct LodN { .... }; Asciiz DefaultPath[32]; //Optional. Default Path to Textures - (see O2L 'Options') };
VertexTable
struct VertexTable { float XZY[3]; long 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 DirextX), and not changed for counterclockwise order.
PolygonTable
struct PolygonTable { asciiz TextureName[32]; ulong 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) ulong PolygonFlags; };
Important Notes: Order of vertices
Vertices must be reordered for clockwise vertex order (default for DirextX), 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 { ulong Vertex_Index; // (zero based) into respective tables ulong Normals_Index; float UV_values[2]; // for texture mapping };
TagSet
struct TagSet { asciiz Name[64]; ulong Size; byte Data[Size]; };
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
ulong 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 { Asciiz PropertyName1[64]; Asciiz PropertyValue[64]; }; struct PropertyPair1{...}; ..... struct PropertyPairN{...}; // N == size of this tag
#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 writes down these 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.
ulong VerticesCount; // Must be the same as Vertices Count in the LOD header. float VertexMassArray[VerticesCount];
#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.
byte VertexSaved; // Non-zero value if vertex selected/hidden/locked. byte 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).
#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
Other values (< 1000.0) are extact LOD's resolution.
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 P3DEdit).