====== OFP MLOD ======
[[https://www.pmctactical.org/forum/viewforum.php?f=43|OFP Forum]], [[:ofp|OFP Home]], [[ofp:file_formats|OFP File Formats]], [[ofp:tools|OFP Tools]], [[ofp:missions|OFP Missions]], [[ofp:modeling|OFP 3D Modeling]], [[ofp:terrain|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).