{"name":"P3D File Format - MLOD","title":"P3D File Format - MLOD","type":"topic","url":"https://community.bistudio.com/wiki/P3D_File_Format_-_MLOD","revid":373432,"updated":"2025-05-08T11:27:12Z","aliases":[],"markdown":"> **Disclaimer: This page describes internal undocumented structures of Bohemia Interactive software.**\n>\n> This page contains unofficial information.\n>\n> Some usage of this information may constitute a violation of the rights of Bohemia Interactive and is in no way endorsed or recommended by Bohemia Interactive.\n> 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.\n\n## Acknowledgments\n\nThis information comes largely from the long defunct 'ofpinternals', who in turn acknowledge\n\n```\nThanks to FlipeR (filipus@hotmail.com) for helping in research\n```\n\nThis is archive material is held at http:\\\\www.ofpec.com\n\n## Legend\n\nsee Generic FileFormat Data Types\n\n## Introduction\n\nAny given P3D file is THE 3 dimensional model reference for a specific model. Tank, Man, House, sausage. The P3d contains references to pac/paa files which are individual 'surfaces' for *this* model. Some / Most / All of the pac/paa files *might* also be used in other models, but this p3d is THE T80 tank, THE civilian man, or THE tangerine sausage with pink spots.\n\nArma P3D files also contain references to materials (rvmats).\n\nThere are three basic P3D types\n\n- DEMO: P3d's supplied for the 1977 demo of operation flashpoint.\n- MLOD: An editable version as used by Oxygen2/3 eg.\n- ODOL: A binarised and compressed version as used by various engines. (OFP, ARMA eg)\n\nThis document describes MLOD and DEMO P3D's\n\nThe FILE header (if present) defines which type of p3d this is.\n\n- DEMO P3D's have no header, and a single lod.\n  - File headers were introduced at the launch of Operation Flashpoint to distinguish between binarised an unbinarised content. Prior to the launch, the p3d was, in effect, a single sp3 lod.\n\nEvery LOD contains a LOD Header or Signature which defines the type of lod as being\n\n- SP3D: Only used in Operation Flashpoint demo.\n- SP3X: used in OFP and demo: Uses O2Light for editing\n- P3DM used in ARMA:Uses O2PE for editing\n\nArma can read SP3X and P3DM.\n\nBecause each of the contiguous lods declares what 'type' it is (SP3X, or P3DM), architecturally, you could have mixtures of both types. Materials however, can only be defined by P3DM lods.\n\nAlthough the overall structure of a P3DM mlod model file is similar to SP3X mlod model files there are some notable differences. Namely, Materials, Multiple UVSets and Animations.\n\n---\n\n## File Format\n\n```\nif MLOD_P3D\n{\n  P3DHeader P3DHeader;\n  MLOD_LOD  MLOD_LODs[Header.NoOfLods];\n  char      SP3X_DefaultPath[32]; // optional OFP only\n}\n//else a single sp3lod\n```\n\n- There is no header for demo p3d's instead they supply a single sp3x or sp3d lod in the standard manner.\n\n- SP3X Lods contain a rarely encountered, optional, Default File path (probably for the editor)\n\n### P3DHeader\n\n```\nP3DHeader\n{\n char   Signature[4];        //\"MLOD\"\n ulong  Version;             // 0x101 see note\n ulong  NoOfLods;            // at least one\n};\n```\n\n#### OdolExplorer\n\nOdolExplorer sets the version to a float value of 1.1 with no ill-effect\n\n### MLOD_LOD\n\n```\nStruct\n{\n  char[4]                     Signature;           //\"SP3X\" or \"P3DM\" or \"SP3D\" (demo)\n  ============if NOT SP3D==========================\n  ulong                       MajorVersion;        //28 (x1C)\n  ulong                       MinorVersion;        //0x99 (SP3X) or 0x100 (P3DM)\n  ===============endif=============================\n  ulong                       NoOfPoints;\n  ulong                       NoOfFaceNormals;         //(perpendicular)\n  ulong                       NoOfFaces;\n  ============if NOT SP3D==========================\n  ulong                       UnknownFlagBits;     //Probably 'Model Flags' - Unused.\n  ===============endif=============================\n  Point                       Points[NoOfPoints];\n  XYZTriplet                  FaceNormals[NoOfFaceNormals];\n  LodFace                     LodFaces[NoOfFaces]; //see P3D Lod Faces\n  ============if NOT DEMO==========================\n  char                        TagSig;              //Always 'TAGG'\n  Tagg[]                      Taggs;               //Always, minimum of #EndOfFile# tag exists.\n  float                       Resolution;          //See P3D_Model_Info\n  ===============endif=============================\n  ============if DEMO==========================\n            ****optional****\n  char                        SS3DSig;              //Always 'SS3D'\n  ulong                       nPoints\n  ulong                       nFaces\n  ulong                       nNormals              //identical counts to above\n  ulong                       nBytes\n  Bytes                       TinyBools[nPoints+nFaces+nNormals] // 0, or 1\n  ulong                       Indexes[nBytes/4]\n  NamedSelections             NamedSelections[until eof] // optional\n  ===============endif=============================\n}\n```\n\n- The end of each lod *always* contains an #EndOfFile# TAGG followed by the resolution (of that lod)\n- Demo Lods contain an optional SS3D structure\n- Demo Lod named selections (optional)\n\n```\n{\n  char Name[32]; // \"velka_vitrule\"\n  Bytes    Undecoded[...];\n}.....\n```\n\nthe above structure is repeated for an indeterminate number of 'names'\n\n#### Points\n\n```\nstruct\n{\n   XYZTriplet    Position\n   if NOT SP3D\n   ulong         PointFlags;// see P3D Point and Face Flags\n   endif\n}\n```\n\n#### FaceNormals\n\nEach LodFace contains index values into the FaceNormals Table. There are generally as many Normals Triplets, as there are index entries in LodFaces.\n\nBecause of the varying number of faces in the face table (eg 3 or 4)\n\n```\nNoOfFaceNormals (= nNormalsTriplets) = 3*LodFaces.NoOfTriangles + 4*LodFaces.NoOfQuads\n```\n\nGenerally.\n\n- FaceNormals must be inverted (-X, -Y, -Z) for clockwise vertex order (default for DirectX), and not changed for counterclockwise order.\n\n### Taggs\n\n- Taggs do not exist for DEMO p3ds, neither SP3X nor SP3D\n\n```\n struct\n {\n   //////// P3DM ONLY ///////////\n   TinyBool Active;            // always 1\n   Asciiz   TaggName;          // \"#EndOfFile#\\0\" eg\n   ///////  SP3X ONLY ///////////\n   Asciiz   TaggName[64];      // \"#EndOfFile#\\0\" eg\n   //////////////////////////////\n   ulong    NoOfBytes;         // offset to next tagg\n   byte     TaggData[NoOfBytes];// arbitrary data\n };\n```\n\n#### TagNames\n\nEvery Lod contains one or more Tagname entries. The #EndOfFile# tag is a mandatory entry in every Lod to indicate no more tags! The contiguous tags that make up the tag section are always in the above format. The actual structure of the 'ArbitraryData' is dependant on the TagName.\n\nTaggNames consist of a mixure of pre-defined Taggs indicated with #.....# marks, and 'Named Selections'\n\nNamed Selection tagg names can contain Proxy names beginning with 'proxy:' + ProxyName + '.' + ProxyNumber ('01' ...\n\n- Every Lod has an #EndOfFile# Tagg\n- Every Arma Lod has at least one #UVSet# Tagg. The first of which is a duplicate of that found in LodFaces. (ofp has no #UVset# because there is only ever one)\n- The #Mass# tag is mandatory for Geometry LODs and only present in Geometry LODs.\n\nTagg names 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.\n\n##### #EndOfFile\\#\n\n```\nulong   NoOfBytes; //always 0\n```\n\nMandatory for every Lod. This is the last tag of **current** LOD. It contains no data.\n\n##### #SharpEdges\\#\n\n```\nulong          NoOfBytes;\nulong          PointsIndex[NoOfIndexes][2]; // NoOfIndexes= NoOfBytes /  2 * sizeof(ulong)..// 1st and 2nd indexes into the VertexTable\n```\n\nSharp edge means that these vertices normals are not calculated as average (normalized) between polygons.\n\n##### #Property\\#\n\n```\n ulong                      NoOfBytes;            //Always 128\n Asciiz                     TokenKey[64];         //\"lodnoshadow\" (=) \"1\"\n Asciiz                     TokenValue[64];       //\n```\n\nAdditional Properties (if any) for the lod are contained in a series of one or more property tags. Each one contains one, and one only Token-Pair. The Asciiz strings each have a fixed length of 64 characters. Regardless, they are null terminated.\n\nExamples:\n\n```\ndamage=tree;\nCanOcclude=0;\n```\n\n##### #Mass# (only for Geometry LOD)\n\n```\nulong          NoOfBytes;\nfloat          PointsMass[NoOfPoints];  // == NoOfBytes / sizeof(float).. same as Lod.NoOfPoints\n```\n\n**Must** be present for Geometry LOD, and **only** for Geometry lod. It refers to the mass of each Point entry.\n\n##### #Animation\\#\n\nsee P3D Lod Frames\n\n##### #UVSet# ARMA\n\n```\nulong       NoOfBytes;                   //see below\nulong       ID;\nUVPair      FaceUV[NoOfFaces][FaceType]; //see P3D Lod Faces\n```\n\nIntroduced for Arma to supply up to 8 distinct #UVset#s per lod.\n\n- There is at least one (and generally only one) #UVSet# for every lod.\n\nThis #UVSet# (ID=0) is a redundant duplication of that found in Lodfaces.\n\nThere is a slight re-arrangement of the UV array in the UVset vs that found in LodFaces. The amount of data however, is identical. (And in the case of uvset0, the data is identical).\n\nnote that 'identical' floating point values are rare because the IEEE represention of any given value is a range of precisions. The value 0.02 eg cannot be represented exactly, as a float (or double for that matter).\n\nThe following code compares, in a general sense, two floats for 'identicalness'\n\n```\nbool AlmostEqual(float A, float B)\n{\n   if (A == B)  return true; // gets over neg and positive zero\n   return abs(*(int*)&A - *(int*)&B)==0; // gets around nans' qnans\n}\n```\n\nFor a very, very good article on this subject http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm\n\nA UVpair matches each vertex in every LodFace\n\nLodFaces themselves can have either 3, or 4, UVPairs per index. Thus, the amount of data in this structure varies.\n\nThe number of bytes therefore used by *this* #UVset and every other UVset in this lod is calculated as\n\n```\nsizeof(ulong)+ sizeof(UVPair)* (3*LodFace.NoOfTriangles+4*LodFace.NoOfQuads)\n```\n\n##### #Lock# (only used in O2)\n\n##### #Selected# (only used in O2)\n\n##### #Hide# (only used in O2)\n\n```\nulong      NoOfBytes;                  // ==Lod.NoOfPoints + lod.NoOfFaces\nTinyBool   PointsIndex[NoOfPoints];    // nonzero = true\nTinyBool   FaceIndex[NoOfFaces];\n```\n\nThese 3 are used so that O2 can save your last selections in the edited file. They serve no other purpose outside of editing the model in O2.\n\nThese table correspond to each entry of Points and Faces respectively and indicate whether that Point (or Face) at that index is 'selected/locked/hidden' or not and is a a persisted selection within the P3DM at this time.\n\nThe values are supposed to be 0 and 1. This is mostly the case, however, some 'bytes' contain values such as 2 or 6, but also mean, 'true'\n\n##### #MaterialIndex# (only in O2)\n\nUsed only in O2. Possibly Operation Flashpoint only.\n\nmaterial properties\n\n```\n{\n RGBA diffuse; //default  51, 75, 55, 0\n RGBA ambient  // Default 0\n RGBA specular // Default -1\n RGBA emissive // Default -1\n}nBytes/4;\n```\n\nonly seen these in clusters of 4x4\nThey appear to be set for all lods if present at all\nEach lod appears to have identical info","sections":[{"heading":null,"level":1,"markdown":"> **Disclaimer: This page describes internal undocumented structures of Bohemia Interactive software.**\n>\n> This page contains unofficial information.\n>\n> Some usage of this information may constitute a violation of the rights of Bohemia Interactive and is in no way endorsed or recommended by Bohemia Interactive.\n> 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."},{"heading":"Acknowledgments","level":2,"markdown":"This information comes largely from the long defunct 'ofpinternals', who in turn acknowledge\n\n```\nThanks to FlipeR (filipus@hotmail.com) for helping in research\n```\n\nThis is archive material is held at http:\\\\www.ofpec.com"},{"heading":"Legend","level":2,"markdown":"see Generic FileFormat Data Types"},{"heading":"Introduction","level":2,"markdown":"Any given P3D file is THE 3 dimensional model reference for a specific model. Tank, Man, House, sausage. The P3d contains references to pac/paa files which are individual 'surfaces' for *this* model. Some / Most / All of the pac/paa files *might* also be used in other models, but this p3d is THE T80 tank, THE civilian man, or THE tangerine sausage with pink spots.\n\nArma P3D files also contain references to materials (rvmats).\n\nThere are three basic P3D types\n\n- DEMO: P3d's supplied for the 1977 demo of operation flashpoint.\n- MLOD: An editable version as used by Oxygen2/3 eg.\n- ODOL: A binarised and compressed version as used by various engines. (OFP, ARMA eg)\n\nThis document describes MLOD and DEMO P3D's\n\nThe FILE header (if present) defines which type of p3d this is.\n\n- DEMO P3D's have no header, and a single lod.\n  - File headers were introduced at the launch of Operation Flashpoint to distinguish between binarised an unbinarised content. Prior to the launch, the p3d was, in effect, a single sp3 lod.\n\nEvery LOD contains a LOD Header or Signature which defines the type of lod as being\n\n- SP3D: Only used in Operation Flashpoint demo.\n- SP3X: used in OFP and demo: Uses O2Light for editing\n- P3DM used in ARMA:Uses O2PE for editing\n\nArma can read SP3X and P3DM.\n\nBecause each of the contiguous lods declares what 'type' it is (SP3X, or P3DM), architecturally, you could have mixtures of both types. Materials however, can only be defined by P3DM lods.\n\nAlthough the overall structure of a P3DM mlod model file is similar to SP3X mlod model files there are some notable differences. Namely, Materials, Multiple UVSets and Animations.\n\n---"},{"heading":"File Format","level":2,"markdown":"```\nif MLOD_P3D\n{\n  P3DHeader P3DHeader;\n  MLOD_LOD  MLOD_LODs[Header.NoOfLods];\n  char      SP3X_DefaultPath[32]; // optional OFP only\n}\n//else a single sp3lod\n```\n\n- There is no header for demo p3d's instead they supply a single sp3x or sp3d lod in the standard manner.\n\n- SP3X Lods contain a rarely encountered, optional, Default File path (probably for the editor)"},{"heading":"P3DHeader","level":3,"markdown":"```\nP3DHeader\n{\n char   Signature[4];        //\"MLOD\"\n ulong  Version;             // 0x101 see note\n ulong  NoOfLods;            // at least one\n};\n```"},{"heading":"OdolExplorer","level":4,"markdown":"OdolExplorer sets the version to a float value of 1.1 with no ill-effect"},{"heading":"MLOD_LOD","level":3,"markdown":"```\nStruct\n{\n  char[4]                     Signature;           //\"SP3X\" or \"P3DM\" or \"SP3D\" (demo)\n  ============if NOT SP3D==========================\n  ulong                       MajorVersion;        //28 (x1C)\n  ulong                       MinorVersion;        //0x99 (SP3X) or 0x100 (P3DM)\n  ===============endif=============================\n  ulong                       NoOfPoints;\n  ulong                       NoOfFaceNormals;         //(perpendicular)\n  ulong                       NoOfFaces;\n  ============if NOT SP3D==========================\n  ulong                       UnknownFlagBits;     //Probably 'Model Flags' - Unused.\n  ===============endif=============================\n  Point                       Points[NoOfPoints];\n  XYZTriplet                  FaceNormals[NoOfFaceNormals];\n  LodFace                     LodFaces[NoOfFaces]; //see P3D Lod Faces\n  ============if NOT DEMO==========================\n  char                        TagSig;              //Always 'TAGG'\n  Tagg[]                      Taggs;               //Always, minimum of #EndOfFile# tag exists.\n  float                       Resolution;          //See P3D_Model_Info\n  ===============endif=============================\n  ============if DEMO==========================\n            ****optional****\n  char                        SS3DSig;              //Always 'SS3D'\n  ulong                       nPoints\n  ulong                       nFaces\n  ulong                       nNormals              //identical counts to above\n  ulong                       nBytes\n  Bytes                       TinyBools[nPoints+nFaces+nNormals] // 0, or 1\n  ulong                       Indexes[nBytes/4]\n  NamedSelections             NamedSelections[until eof] // optional\n  ===============endif=============================\n}\n```\n\n- The end of each lod *always* contains an #EndOfFile# TAGG followed by the resolution (of that lod)\n- Demo Lods contain an optional SS3D structure\n- Demo Lod named selections (optional)\n\n```\n{\n  char Name[32]; // \"velka_vitrule\"\n  Bytes    Undecoded[...];\n}.....\n```\n\nthe above structure is repeated for an indeterminate number of 'names'"},{"heading":"Points","level":4,"markdown":"```\nstruct\n{\n   XYZTriplet    Position\n   if NOT SP3D\n   ulong         PointFlags;// see P3D Point and Face Flags\n   endif\n}\n```"},{"heading":"FaceNormals","level":4,"markdown":"Each LodFace contains index values into the FaceNormals Table. There are generally as many Normals Triplets, as there are index entries in LodFaces.\n\nBecause of the varying number of faces in the face table (eg 3 or 4)\n\n```\nNoOfFaceNormals (= nNormalsTriplets) = 3*LodFaces.NoOfTriangles + 4*LodFaces.NoOfQuads\n```\n\nGenerally.\n\n- FaceNormals must be inverted (-X, -Y, -Z) for clockwise vertex order (default for DirectX), and not changed for counterclockwise order."},{"heading":"Taggs","level":3,"markdown":"- Taggs do not exist for DEMO p3ds, neither SP3X nor SP3D\n\n```\n struct\n {\n   //////// P3DM ONLY ///////////\n   TinyBool Active;            // always 1\n   Asciiz   TaggName;          // \"#EndOfFile#\\0\" eg\n   ///////  SP3X ONLY ///////////\n   Asciiz   TaggName[64];      // \"#EndOfFile#\\0\" eg\n   //////////////////////////////\n   ulong    NoOfBytes;         // offset to next tagg\n   byte     TaggData[NoOfBytes];// arbitrary data\n };\n```"},{"heading":"TagNames","level":4,"markdown":"Every Lod contains one or more Tagname entries. The #EndOfFile# tag is a mandatory entry in every Lod to indicate no more tags! The contiguous tags that make up the tag section are always in the above format. The actual structure of the 'ArbitraryData' is dependant on the TagName.\n\nTaggNames consist of a mixure of pre-defined Taggs indicated with #.....# marks, and 'Named Selections'\n\nNamed Selection tagg names can contain Proxy names beginning with 'proxy:' + ProxyName + '.' + ProxyNumber ('01' ...\n\n- Every Lod has an #EndOfFile# Tagg\n- Every Arma Lod has at least one #UVSet# Tagg. The first of which is a duplicate of that found in LodFaces. (ofp has no #UVset# because there is only ever one)\n- The #Mass# tag is mandatory for Geometry LODs and only present in Geometry LODs.\n\nTagg names 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."},{"heading":"#EndOfFile\\#","level":5,"markdown":"```\nulong   NoOfBytes; //always 0\n```\n\nMandatory for every Lod. This is the last tag of **current** LOD. It contains no data."},{"heading":"#SharpEdges\\#","level":5,"markdown":"```\nulong          NoOfBytes;\nulong          PointsIndex[NoOfIndexes][2]; // NoOfIndexes= NoOfBytes /  2 * sizeof(ulong)..// 1st and 2nd indexes into the VertexTable\n```\n\nSharp edge means that these vertices normals are not calculated as average (normalized) between polygons."},{"heading":"#Property\\#","level":5,"markdown":"```\n ulong                      NoOfBytes;            //Always 128\n Asciiz                     TokenKey[64];         //\"lodnoshadow\" (=) \"1\"\n Asciiz                     TokenValue[64];       //\n```\n\nAdditional Properties (if any) for the lod are contained in a series of one or more property tags. Each one contains one, and one only Token-Pair. The Asciiz strings each have a fixed length of 64 characters. Regardless, they are null terminated.\n\nExamples:\n\n```\ndamage=tree;\nCanOcclude=0;\n```"},{"heading":"#Mass# (only for Geometry LOD)","level":5,"markdown":"```\nulong          NoOfBytes;\nfloat          PointsMass[NoOfPoints];  // == NoOfBytes / sizeof(float).. same as Lod.NoOfPoints\n```\n\n**Must** be present for Geometry LOD, and **only** for Geometry lod. It refers to the mass of each Point entry."},{"heading":"#Animation\\#","level":5,"markdown":"see P3D Lod Frames"},{"heading":"#UVSet# ARMA","level":5,"markdown":"```\nulong       NoOfBytes;                   //see below\nulong       ID;\nUVPair      FaceUV[NoOfFaces][FaceType]; //see P3D Lod Faces\n```\n\nIntroduced for Arma to supply up to 8 distinct #UVset#s per lod.\n\n- There is at least one (and generally only one) #UVSet# for every lod.\n\nThis #UVSet# (ID=0) is a redundant duplication of that found in Lodfaces.\n\nThere is a slight re-arrangement of the UV array in the UVset vs that found in LodFaces. The amount of data however, is identical. (And in the case of uvset0, the data is identical).\n\nnote that 'identical' floating point values are rare because the IEEE represention of any given value is a range of precisions. The value 0.02 eg cannot be represented exactly, as a float (or double for that matter).\n\nThe following code compares, in a general sense, two floats for 'identicalness'\n\n```\nbool AlmostEqual(float A, float B)\n{\n   if (A == B)  return true; // gets over neg and positive zero\n   return abs(*(int*)&A - *(int*)&B)==0; // gets around nans' qnans\n}\n```\n\nFor a very, very good article on this subject http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm\n\nA UVpair matches each vertex in every LodFace\n\nLodFaces themselves can have either 3, or 4, UVPairs per index. Thus, the amount of data in this structure varies.\n\nThe number of bytes therefore used by *this* #UVset and every other UVset in this lod is calculated as\n\n```\nsizeof(ulong)+ sizeof(UVPair)* (3*LodFace.NoOfTriangles+4*LodFace.NoOfQuads)\n```"},{"heading":"#Lock# (only used in O2)","level":5,"markdown":""},{"heading":"#Selected# (only used in O2)","level":5,"markdown":""},{"heading":"#Hide# (only used in O2)","level":5,"markdown":"```\nulong      NoOfBytes;                  // ==Lod.NoOfPoints + lod.NoOfFaces\nTinyBool   PointsIndex[NoOfPoints];    // nonzero = true\nTinyBool   FaceIndex[NoOfFaces];\n```\n\nThese 3 are used so that O2 can save your last selections in the edited file. They serve no other purpose outside of editing the model in O2.\n\nThese table correspond to each entry of Points and Faces respectively and indicate whether that Point (or Face) at that index is 'selected/locked/hidden' or not and is a a persisted selection within the P3DM at this time.\n\nThe values are supposed to be 0 and 1. This is mostly the case, however, some 'bytes' contain values such as 2 or 6, but also mean, 'true'"},{"heading":"#MaterialIndex# (only in O2)","level":5,"markdown":"Used only in O2. Possibly Operation Flashpoint only.\n\nmaterial properties\n\n```\n{\n RGBA diffuse; //default  51, 75, 55, 0\n RGBA ambient  // Default 0\n RGBA specular // Default -1\n RGBA emissive // Default -1\n}nBytes/4;\n```\n\nonly seen these in clusters of 4x4\nThey appear to be set for all lods if present at all\nEach lod appears to have identical info"}],"categories":["ArmA: Armed Assault: File Formats","Real Virtuality File Formats"],"groups":["Tools and File Formats"]}