P3D File Format - ODOLV4x: Difference between revisions
| m (→ModelInfo) | m (point flags) | ||
| Line 376: | Line 376: | ||
|      byte                          Unknown[17]; |      byte                          Unknown[17]; | ||
|    if (V40 or V43) |    if (V40 or V43) | ||
|     { |     { | ||
|      LodUV                         LodUV1;                     // Potentially compressed |      LodUV                         LodUV1;                     // Potentially compressed | ||
| Line 381: | Line 382: | ||
|      if (nUVs==2) |      if (nUVs==2) | ||
|       LodUV                        LodUV2;                     // Potentially compressed |       LodUV                        LodUV2;                     // Potentially compressed | ||
|      ulong                         NoOfVertices; |      ulong                         NoOfVertices; | ||
|      XYZTriplet                    LodXZY[NoOfVertices];       // Potentially compressed |      XYZTriplet                    LodXZY[NoOfVertices];       // Potentially compressed | ||
| Line 390: | Line 390: | ||
|     }else // V47 & V48 |     }else // V47 & V48 | ||
|     { |     { | ||
|      A2LodUV                       A2LodUV1;                     // Potentially compressed |      A2LodUV                       A2LodUV1;                     // Potentially compressed | ||
| Line 406: | Line 407: | ||
|     } |     } | ||
|         // Note that nUV1's== nNormals == NoOfVertices |         // Note that nUV1's== nNormals == NoOfVertices | ||
|      ulong                         Count; |      ulong                         Count; | ||
| Line 438: | Line 438: | ||
| ====LodPointProperties==== | ====LodPointProperties==== | ||
| This table is the equivalent of Oxygen's points->properties dialog box | |||
| it specifically stores the user values and other flags for that point | |||
| See Points Flags below | |||
|   LodPointProperties |   LodPointProperties | ||
|   { |   { | ||
| Line 1,212: | Line 1,220: | ||
| ---- | ---- | ||
| ==Point Flags== | |||
| Point Flags are as per Oxygen's Points->Properties as follows: | |||
| <code><nowiki> | |||
| 0NhUUFdLS  | |||
|         Surface | |||
|          xxxxxxx0 normal | |||
|          xxxxxxx1 on surface  | |||
|          xxxxxxx2 above  | |||
|          xxxxxxx4 under  | |||
|          xxxxxxx8 keep height(fence) | |||
|        Lighting face | |||
|          xxxxxx0x normal | |||
|          xxxxxx1x shine | |||
|          xxxxxx2x always in shadow | |||
|          xxxxxx4x full light | |||
|          xxxxxx8x half light | |||
|       decal | |||
|          xxxxx1xx decal  | |||
|      Fog | |||
|          xxxx0xxx none | |||
|          xxxx1xxx sky | |||
|          xxxx2xxx normal | |||
|    UU    xxUUxxxx user 0..255 | |||
|   H      x1xxxxxx hidden vertex | |||
|  Normals | |||
|          x0xxxxxx by face | |||
|          x2xxxxxx fixed (normal) | |||
|          x4xxxxxx by impedence | |||
| </code></nowiki> | |||
| == Links == | == Links == | ||
Revision as of 17:37, 25 April 2010
Introduction
Acknowledgements
This body of work is due to Synide's sweat and tears. To whom, all honour and glory. Ably assisted by T_D and Mikero that further detailed the data and gave this article a more general and correct structure.
Versions
This Document covers ODOL versions:
- V40 Original Arma1 binarised p3d
- V43 Arma2
- V47 Arma2
- V48 Arma2
The differences, where they exist, are documented in the relevent coded sections. They are summarised here
V43
•ModelInfo struct is now same as vbs2 155+24 bytes instead of 155 for V40 •The 'extra byte' flag in the UnknownStruct1 is always set, in arma1 (v40) it was always 0
- LZSS compression is still used at this level
V47
As per V43 plus: •all compressed blocks are LZO compressed •CompressedMinMax block is now nMinMax*8 in size •CompressedNormals block is now nNormals*4 in size •UVSet structure changed to:
LodUV
{
 float  uvScaling[4];
 ulong  nVertices;
 tbool  DefaultFill;
 if (DefaultFill)
  float                        UV;              // default fill for all nVertices
 else
  float                        UV[nVertices];   // potentially compressed
}
LodKeyFrame has 4 extra floats
V48
As per V47 plus: •ModelInfo struct is now 179+4 bytes instead of 155 for V40
General
The general file format of a ArmA ODOL v4x p3d model file is similar to the ODOL v7 format. The major differences are that in ArmA models are
- an optional model.cfg
- Lods occur in the file from highest to lowest LodType value.
Legend
see Generic FileFormat Data Types
File Paths
The PrefixRoot\ folder.
Life for modellers would be far less tedious if filenames could also be relative to the p3d they are encountered in. Altering or moving or renaming the pbo (and specifically it's prefix) would not alter the relative location of the paa's it contains.
BI choose to use hard-wired Pbo-Prefix-addressing ONLY.
All hardwired addressing is relative to a built-in-situ (ie virtual) PrefixRoot\ folder
Each and every pbo in Arma contains a unique identity name, a prefix. Irrespective of the name of the pbo, the prefixname is THE name of the pbo from the perspective of the engine. In most cases, the prefixname is, conveniently, the filename. One huge advantage of this mechanism, sorely sorely missed in OFP. is that self-documenting increasing revisions of an addonV123.pbo can be supplied to Arma, with no changes to the mission sqms and other pbos that refer to it.
The PrefixRoot\ folder contains the prefix names of all pbos encountered (almost) ANYWHERE.
Thus the pbos in the Official Addons folder, the Oem Mods\Addons folder(s), the Dta core and bin pbo's, are all examined for their unique prefix names. These prefix names become the dictionary index of where the pbo really is, AND, what filename it actually is.
Thus all filename references in a p3d, *unconditionally* contain a prefixname\someFile\SomeWhere.
In most cases they refer to the very same pbo as the containing p3d and a great pity that the extraneous information could not have been removed by (optional) relative addressing as it requires a great deal of fiddling about when modifying models.
Note also that there is some inconsistency in filename paths. Most do not have a leading \. Some, require it. Both are indeed \hardwired
An Example:
P3dProxyName ="\ca\a10\agm65";
The immediate (and unfortunate) impression is that there is an A10 folder inside the official CA.pbo addon. In fact, the prefix of the A10.pbo = "ca\A10". Thus this reference is to the A10.pbo within which, is a agm65.p3d in it's root folder. (and again, this reference is in fact an extraneous reference to itself since the referring p3d (A10.p3d) is in the same pbo)
File Format
ODOLv4x
{
 StandardP3DHeader Header;
 float             LodTypes[Header.NoOfLods];// alias resolutions
 ModelInfo         ModelInfo;
 Skeleton          Skeleton;
 UnknownStruct1    UnknownStruct;
 Animations        Animations;
 ulong             StartAdressOfLods[Header.NoOfLods];
 ulong             EndAdressOfLods  [Header.NoOfLods];
 tbool             LODFaceIndicator [Header.NoOfLods];
 LodFace           LodFaces[NoOfFalseLODFaceIndicators];//there are only as many LodFaces as there are false LODFaceIndicators
 ODOLv40Lod        ODOLv40Lods[Header.NoOfLods];  
 }//EndOfFile
LodTypes
The order in which lod's occur is descending resolutions. The lowest resolution (eg. Resolution 1.0) will be the last in the file.
- LodTypes is an unsorted list of all these resolutions. To make it usable (eg to specifically access the 'memory lod') the list must be sorted from highest to lowest value.
This then gives the order of occurrence of lods.
- The order, has significance to various lod offsets mentioned below.
- For a list of 'lod types' see bottom of document.
LodTypes are resolutions. Most of them have humanly readable context such as the 'memory' lod. And are selected as such in Oxygen. The are consequenlty referred to here as LodTypes, since the floating point values, although relevant to the engine, are not as immediately apparent to a human.
Structures
StandardP3DHeader
 StandardP3DHeader 
 {
   char[4]  Filetype; // "ODOL"
   ulong    Version;  // 40
   ulong    NoOfLods; // alias NoOfResolutions;
 }
common header structure for all P3D file formats
ModelInfo
 ModelInfo
 {
  ulong      Unknown;
  float      Sphere;
  float      Unknown;
  XYZTriplet UnknownTriplet0;// always zero ?
  XYZTriplet UnknownTriplet1;//mostly same as UnknownTriplet2
  bytes      Unknown[8]; // perhaps flags
  float      ViewDensity;
  XYZTriplet LowerBoundingPoint;   //2nd triplet the complement of 1st
  XYZTriplet UpperBoundingPoint;   // Resolution/GeometryBounds in pew is UpperBoundingPoint-LowerBoundingPoint for X and Z
  XYZTriplet ModelVertexOffset; //Resolution/GeometryAutoCenterPos in pew
  XYZTriplet UnknownTriplet2; // mosten same as UnknownTriplet1, often same as ModelCentreOfGravity
  XYZTriplet ModelCentreOfGravity;
  XYZTriplet ModelMassVectors[3];
  byte  AutoCenter,
        lockAutoCenter,
        canOcclude,
        canBeOccluded,
        allowAnimation;
  byte  Unknown[6]; 
  if (Version != 40)
  {
   byte ThermalProfile[24];// eg 43,47,48
   if (Version==48)
   {
    ulong UnknownLong;
  }
}
structSkeleton
 structSkeleton
 {
   asciiz                    SkeletonName;  //"A10Skeleton"
   if (SkeletonName != null)
   {
     tbool                   isInherited;
     ulong                   NoOfBoneNames;
     SkeletonBoneName        SkeletonBoneNames[NoOfBoneNames];
   }
 }
SkeletonBoneName
 SkeletonBoneName
 {
   asciiz BoneName;            //"3dhud" or "Gearlocks" or "Fuel" or ...
   asciiz ParentBoneName;      // "Aeileron_1"
 }
corresponds to model.cfg
 class cfgSkeletons
 {
	class SkeletonClassname: Default
	{
		skeletonBones[]=
		{
			"RightDoor1","",
			"RightDoor2","",
			"LeftDoor1","",
			"LeftDoor2","RightDoor2",
			etc"
		};
	};
UnknownStruct1
UnknownStruct1
{
 byte   UnknownByte;
 tbool  Extra;     // generally always 0 for v40, always 1 for v43
 if(Extra)
 {
  byte  ExtraByte;
 }
 byte   UnknownBytes[3];
 float	 ModelMass.
        ModelMassReciprocal,
        ModelMassModifier;
 byte	 UnknownBytes[16];
 ulong  UnknownLong;
 byte   UnknownByte;
 asciiz ClassType;           //class="" or "House" or...
 asciiz DestructType;        //damage="" or "Tent" or "Building",,,,
 byte   UnknownBytes[5];
}
Animations
Animations
{
 tbool             AnimsExist;
 if (AnimsExist)
 {
  ulong            nAnimationClasses; // eg NoOfAnimSelections;
  AnimationClass   AnimationClasses[nAnimationClasses];
  ulong            NoOfResolutions;// same value as Header.NoOfLods
  Bones2Anims      Bones2Anims[NoOfResolutions];
  Anims2Bones      Anims2Bones[NoOfResolutions];
  //For every bone there is a list of Animations for each resolution
  //And, a reversed table of every Animation gets a Bone.
  //The reversed table optionally appends axis info dependent on the AnimTransformType
 }
}
AnimationClass
  AnimationClass
  {
    ulong       AnimTransformType;
    asciiz      AnimClassName;  // "RightDoor"
    asciiz      AnimSource;     // "rotor"
    float MinMaxValue[2];
    float MinMaxPhase[2];
    ulong sourceAddress;
    switch(AnimTransformType)
    case 0://rotaton
    case 1://rotationX
    case 2://rotationY
    case 3://rotationZ
      float angle[2];
      break;
    case 4://translation
    case 5://translationX
    case 6://translationY
    case 7://translationZ
      float offset[2];
      break;
    case 8: //"direct"
      float axisPos[3];
      float axisDir[3];
      float angle; //in radians whereas the model.cfg entry is in degrees
      float axisOffset;
      break;
    case 9: //"hide"
      float hideValue;
      break;
   }
corresponds to model.cfg
class CfgModels
{
 ....
 class whateverModel: Default
 {
  ...
  class Animations
  {
   class RightDoor //AnimClassName
   {
     type = "translation";//AnimTransformType
     source = "rotor";    //AnimSource
     etc
Bones2Anims
Bones2Anims
{
 ulong        NoOfBones;
 Bone2AnimClassList   Bone2AnimClassLists[NoOfBones];
}
Bone2AnimClassList
Bone2AnimClassList
{
 ulong NoOfAnimClasses;
 ulong AnimationClassIndex[NoOfAnimClasses]; // a (sometimes repeating) list of zero based indexes into above animation classes
}
Anims2Bones
Anims2Bones
{
 AnimBones AnimBones[Animations.nAnimationClasses];
}
AnimBones
every lod contains an identical list of animation entries that declare the position and axis of the each animation classes
AnimBones
{
 long SkeletonBoneNameIndex; // zero based index to the SkeletonBoneName name & parentname
 // equivalent to selection = "LeftDoor"; eg in the model.cfg
 /*
 ** SkeletonBoneNameIndex== -1 when no skeleton bone is for this Anim and (obviously?) no axis information follows.
 */
 if (SkeletonBoneNameIndex!= -1) && (AnimationClass.AnimTransformType != 8 || 9)
 {
 /*
 ** AnimationClass.AnimTransformType 8 (direct) and 9 (hide) never require axis information. 
 ** This because the "direct" (type 8) already has axis info in it's AnimationClass structure, 
 ** and "hidden" (type 9) clearly doesn't need it.
 */
    XYZTriplet axisPos; //describes the position of the axis used for this anim
    XYZTriplet axisDir;
 }
}
LodFace
//only when the LODFaceIndicator for that lod is false
LodFace     
{
  ulong   HeaderFaceCount;
  bytes   Unknown[13];
}
ODOLv40Lod
 ODOLv40Lod
 {
   ulong                         nProxies;
   LodModelProxy                 LodModelProxies[nProxies];
   ulong                         nLodItems;
   ulong                         LodItems[nLodItems];        // potentially compressed
   ulong                         nUsedBones;
   LodUsedBone                   LodUsedBones[nUsedBones];
   LodPointProperties[...];                                  // Potentially compressed
   byte                          UnknownBytes[8];
   float                         UnknownFloats[10];
 
   ulong                         NoOfTextures;
   asciiz                        LodPaaTextureNames[NoOfTextures];  //"ca\characters\hhl\hhl_01_co.paa"
   ulong                         NoOfMaterials;
   LodMaterial                   LodMaterials[NoOfMaterials];
   LodEdge                       LodEdge1;                   // potentially compressed
   LodEdge                       LodEdge2;                   // potentially compressed
   LodPolygons[...];
   ulong                         nSections;
   LodSection                    LodSections[nSections];
   ulong                         nNamedSelections;
   LodNamedSelection             LodNamedSelections[nNamedSelections]; // potentially compressed
   ulong                         nTokens;
   LodTokenPair                  LodTokenPairs[nTokens];
   ulong                         nFrames;
   LodKeyFrame                   LodKeyFrames[nFrames];
   byte                          Unknown[17];
 if (V40 or V43)
  {
   LodUV                         LodUV1;                     // Potentially compressed
   ulong                         nUVs;
   if (nUVs==2)
    LodUV                        LodUV2;                     // Potentially compressed
   ulong                         NoOfVertices;
   XYZTriplet                    LodXZY[NoOfVertices];       // Potentially compressed
   ulong                         nNormals;
   LodNormals                    LodNormals[nNormals];       // Potentially compressed
   ulong                         nMinMax;
   XYZTriplet                    LodMinMaxXYZ[nMinMax][2];   // Potentially compressed
}else // V47 & V48
  {
   A2LodUV                       A2LodUV1;                     // Potentially compressed
   ulong                         nUVs;
   if (nUVs==2)
   {
    float                        A2Floats[4];
    A2LodUV                      A2LodUV2;                     // Potentially compressed
   }
   ulong                         NoOfVertices;
   XYZTriplet                    LodXZY[NoOfVertices];       // Potentially compressed, same as arma1
   ulong                         nNormals;
   A2LodNormals                  A2LodNormals[nNormals];       // Potentially compressed
   ulong                         nMinMax;
   float                         LodMinMaxXYZ[nMinMax][2];   // Potentially compressed
  }
      // Note that nUV1's== nNormals == NoOfVertices
   ulong                         Count;
   LodUnknownStruct              UnknownLodStruct[Count];    // Potentially compressed
   ulong                         nBytes;
   byte                          UnknownBytes[nBytes][32];   // Potentially compressed
 }
LodModelProxy
 LodModelProxy
 {
   asciiz      P3dProxyName;        //"\ca\a10\agm65" (.p3d is implied) <<note the leading filename backslash
   XYZTriplet  RotationMatrix[3];
   XYZTriplet  Translation;
   ulong       FaceIndex;
   ulong       NamedSelectionIndex;
   ulong       Unknown[2];
 }
This structure is (almost) identical to ODOL7 except it has 4 indices rather than 2.
LodUsedBone
LodUsedBone
{
  ulong nIDs;         //range 0..3
  ulong BoneID[nIDs];
}
This struct seems to assign unique iterative IDs (starting from zero) to some bones or their anims. Quite weird way of doing that, so it is probably not the whole truth.
LodPointProperties
This table is the equivalent of Oxygen's points->properties dialog box
it specifically stores the user values and other flags for that point
See Points Flags below
LodPointProperties
{
 ulong  NoOfPts;
 tbool  UseDefault;
 if (UseDefault)
  ulong DefaultValue;
 else // =0
  ulong PropertyValues[NoOfPts]; // potentially compressed
}
PropertyValues for NoOfPts are either all the same (UseDefault), or, they are individually declared.
Similar to CompressedStructs of OdolV7, if the amount of data in the array exceeds 1023 bytes, that array is compressed.
The use of a) potential compression and b) a default fill, is endemic to many ODOLV40 type packets.
LodMaterial
   //Basically... A direct replication of the information in the given .rvmat file
 LodMaterial
 {
   asciiz            RvMatName;     // "ca\characters\data\soldier_captive_hhl.rvmat"
   ulong             Type;          // 9 == Arma, 10==VBS2
   D3DCOLORVALUE     Emissive;
   D3DCOLORVALUE     Ambient;
   D3DCOLORVALUE     Diffuse;
   D3DCOLORVALUE     forcedDiffuse;
   D3DCOLORVALUE     Specular;
   D3DCOLORVALUE     Unknown;       //Usually same as Specular
   float             SpecularPower;
   ulong             PixelShaderId; //See enumPixelShaderId
   ulong             VertexShaderId;//See enumVertexShaderId
   ulong             BoolFlag;      //mostly 1 otherwise 0
   ulong             AnIndex;       //0,1 or 2
   asciiz            BiSurfaceName; // "ca\data\Penetration\plastic.bisurf"
   ulong             Always0x01;
   ulong             aCount;        //Generally 0
   ulong             nTextures;
   ulong             nTransforms;   // always same as nTextures
   LodStageTexture   StageTextures  [nTextures];
   LodStageTransform StageTransforms[nTransforms]; 
 }  
- There is always one default Texture and Transform as the first entry.
- It is the only entry if a SurfaceName exists.
D3DCOLORVALUE
D3DCOLORVALUE
{
  float r,g,b,a;
}
LodStageTexture
LodStageTexture
{
 ulong  TextureFilter; // see below
 asciiz PaaTexture;    // "ca\characters\data\civil_tvreport_body_as.paa
                       // alternatively "#(argb,8,8,3)color(0,0,0,1,CO)" (eg)
 ulong  StageID;       // zero based, see below
};
- The StageID is iterative (linear sequential). 1st entry is 0, 2nd 1, 3rd 2, etc.
- TextureFilter maybe 1 of the following values.
- 0: Point // sometimes
- 1: Linear // rarely
- 2: TriLinear // not seen
- 3: Anisotropic (default)
 
LodStageTransform
  LodStageTransform
  {
   ulong UVSource;
   float Transform[4][3];//a DirectX texture space transform matrix
  };
LodEdge
LodEdge
{
 ulong   nEdges;
 ushort  Edges[nEdges]; // potentially compressed
};
LodPolygons
LodPolygons
{
 ulong   NoOfPolygons;
 ulong   OffsetToSectionsStruct;       // see below
 ushort  AlwaysZero;
 PolygonVertice
 {
   byte   NoOfVertices;                // 3 or 4
   ushort VerticesIndex[NoOfVertices]; // 0-based index into Vertices Arrays
 }PolygonVertices[NoOfPolygons];
}
Note that there are always 3, or 4, vertices.
- 3 point vertices describe a triangle.
- 4 point vertices describe a rectangle.
The indices must be transformed as follows
- triangles : 1st posn, 2nd posn, 0th posn.
- quadrangles : 1st, 2nd, 3rd, 0th
- Because of the variable amount of vertices in this struct (3 or 4), OffsetToSectionsStruct is used to skip the block. It's value is relative to the first PolygonVertice and is computed as follows
OffsetToSectionsStruct= NoOfPolygons * (SizeofEach (PolygonVertice));
Each PolygonVertice is
nOfVertices *sizeof(ushort) + sizeof(ushort); // always 8 or 10
This, is in fact, an ERROR because the type size of the NoOfVertices is byte, NOT ushort. Hence the REAL offset is
RealOffsetToSectionsStruct = OffsetToSectionsStruct - NoOfPolygons *(sizeof(ushort)- sizeof(byte) );
or, to put it more simply
RealOffsetToSectionsStruct = OffsetToSectionsStruct - NoOfPolygons;
NOTE: See discussions for actual nature of this value
LodSection
LodSection
{
 ulong FaceLowerIndex;
 ulong FaceUpperIndex; //NoOfFaces = (FaceUpperIndex - FaceLowerIndex) / 8
 ulong Something1;
 ulong Something2;
 ulong UserValue;
 short TextureIndex;
 short Something4;
 byte  ZBias;
 byte  Something5;
 short MaterialIndex;
 if MaterialIndex ==-1
 {
   byte ExtraByte;
 }
 byte  Something6[2];
 ulong Something7;
 float Something8;
 float Something9;
}
LodNamedSelection
 LodNamedSelection
 {
   asciiz                    NamedSelectionName;                               // "rightleg" or "neck" eg
   ulong                     NoOfSelectedFaces;
   ushort                    SelectedFaceIndexes[NoOfSelectedFaces];      //NOTE: This array is Compressed if size > 1024.
   ulong                     Always0;
   tbool                     IsSectional;                                 //Appears in the sections[]= list of a model.cfg
   ulong                     NoOfUlongs;
   ulong                     UnknownArray[NoOfUlongs];                    // possibly subject to compression. none seen so far
   ulong                     nSelectedVertices;
   ushort                    SelectedVerticesIndexes[nSelectedVertices];  // NOTE: This array is Compressed if size > 1024.
   ulong                     nTextureWeights;
   byte                      SelectedVerticesWeights[nTextureWeights];    // NOTE: This array is Compressed if size > 1024.
 }
LodTokenPair
 LodTokenPair
 {
    asciiz Property;// "noshadow" = "1" eg
    asciiz Value;
 }
LodKeyFrame
LodKeyFrame
{
 float      FrameTime;
 ulong      NoOfFramePoints;
 XYZTriplet LodFramePoints[NoOfFramePoints];
 if V47 or V48
 {
float Arma2[4];
} }
LodUV
For V40 & V3
LodUV
{
 ulong                         nVertices;
 tbool                         DefaultFill;
 if (DefaultFill)
  float                        UV[2];              // default fill for all nVertices
 else
  float                        UV[nVertices][2];   // potentially compressed
}
The structure either contains a single UV pair of floats. Or, pairs of UV floats for all positions (nVertices)
If a full array is declared (DefaultFill != 0) then that array is compressed if 2 * sizeof(float) * nVertices > 1023
For V47 & 8
A2LodUV
{
 ulong                         nVertices;
 tbool                         DefaultFill;
 if (DefaultFill)
  float                        UV;              // default fill for all nVertices
 else
  float                        UV[nVertices];   // potentially compressed
}
As above, except only a single float value per vertex is used.
LodNormals
A2LodNormals
LodNormals
{
   tbool                         DefaultFill;
   if (DefaultFill)
    type XZY;
   else
    XYZTriplet                   XZY[nNormals];        // Potentially compressed
}
V40 and V43 type == XYZTriplet V47 and V48 type == float
LodUnknownStruct
LodUnknownStruct    // potentially compressed
{
   ulong  Index;
   float  Unknown[2];         // probably a vertices something
}
Decompression
LZSS
In ODOL v40 and v43 format files, some of the data structures present in the file are compressed by using LZSS compression. ODOL v47 and v48 use LZO compression.
Unlike pbo compression, in ArmA model files, one only knows the number of items to decompress, the expected output size (in bytes) and the expected checksum. With this information and the size of a given data item one has the necessary information to expand the data to it's original format and size.
Note:- Data structures that are identified as being compressible will only be compressed if the 'expectedSize' is >= 1024 bytes.
The code that follows is written in C# and may or may not be optimal or correct.
As an example if one was expanding the array of vertices positions...
- A vertex is described by it's x,y,z coordinates which are floats. A float is a 32bit (4 byte) number.
- If we were processing 1968 vertices then our expected output size would be 1968 * (3 * 4) = 23,616 bytes.
This 'expectedSize' is the only necessary information one would need to pass to a processing sub-routine or function.
  public bool Expand(int ExpectedSize)
  {
      byte PacketFlagsByte; //packet flags
      byte WIPByte;
      BitVector32 BV;
      msLZ = new MemoryStream(ExpectedSize);
      BinaryWriter bwLZ = new BinaryWriter(msLZ);
      byte[] Buffer = new byte[ExpectedSize + 15];
      bool[] BitFlags = new bool[8];
      int i = 0, PointerRef = 0, ndx = 0, CalculatedCRC = 0, ReadCRC = 0, rPos, rLen, CurrentPointerRef = 0, Count = 0;
      int Bit0 = BitVector32.CreateMask();
      int Bit1 = BitVector32.CreateMask(Bit0);
      int Bit2 = BitVector32.CreateMask(Bit1);
      int Bit3 = BitVector32.CreateMask(Bit2);
      int Bit4 = BitVector32.CreateMask(Bit3);
      int Bit5 = BitVector32.CreateMask(Bit4);
      int Bit6 = BitVector32.CreateMask(Bit5);
      int Bit7 = BitVector32.CreateMask(Bit6);
      PacketFlagsByte = br.ReadByte();
      do
      {
          BV = new BitVector32(PacketFlagsByte);
          BitFlags[0] = BV[Bit0];
          BitFlags[1] = BV[Bit1];
          BitFlags[2] = BV[Bit2];
          BitFlags[3] = BV[Bit3];
          BitFlags[4] = BV[Bit4];
          BitFlags[5] = BV[Bit5];
          BitFlags[6] = BV[Bit6];
          BitFlags[7] = BV[Bit7];
          i = 0;
          do
          {
              if ((int)bwLZ.BaseStream.Position >= ExpectedSize) { break; }
              if (BitFlags[i++]) //Direct Output
              {
                  WIPByte = br.ReadByte();
                  bwLZ.Write(WIPByte);
                  Buffer[PointerRef++] = WIPByte;
                  CalculatedCRC += WIPByte;
              }
              else //Get from previous 4k
              {
                  rPos = (int)(br.ReadByte());
                  rLen = (int)(br.ReadByte());
                  rPos |= (rLen & 0xF0) << 4;
                  rLen = (rLen & 0x0F) + 2;
                  CurrentPointerRef = PointerRef;
                  if ((CurrentPointerRef - (rPos + rLen)) > 0)
                  {
                      //Case of wholly within the buffer, partially within the end of the buffer or wholly outside the end of the buffer
                      for (Count = 0; Count <= rLen; Count++)
                      {
                          ndx = (CurrentPointerRef - rPos) + Count;
                              if (ndx < 0)
                              {
                                  //Beyond the start of the buffer
                                  WIPByte = 0x20;
                              }
                              else
                              {
                                  //Within the buffer
                                  WIPByte = Buffer[ndx];
                              }
                          //}
                          bwLZ.Write(WIPByte);
                          Buffer[PointerRef++] = WIPByte;
                          CalculatedCRC += WIPByte;
                      }
                  }
                  else
                  {
                      //Case of wholly or partially beyond the start of the buffer.
                      for (Count = 0; Count <= rLen; Count++)
                      {
                          ndx = (CurrentPointerRef - rPos) + Count;
                          if (ndx < 0)
                          {
                              //Beyond the start of the buffer
                              WIPByte = 0x20;
                          }
                          else
                          {
                              //Within the buffer
                              WIPByte = Buffer[ndx];
                          }
                          bwLZ.Write(WIPByte);
                          Buffer[PointerRef++] = WIPByte;
                          CalculatedCRC += WIPByte;
                      }
                  }
              }
          }
          while ((i < 8) & (bwLZ.BaseStream.Position < ExpectedSize));
          if (bwLZ.BaseStream.Position < ExpectedSize) { PacketFlagsByte = br.ReadByte(); }
      }
      while (bwLZ.BaseStream.Position < ExpectedSize);
      ReadCRC = br.ReadInt32();
      return (ReadCRC == CalculatedCRC);
  }
LZO
This is LZO as defined by oberhumer
http://www.oberhumer.com/opensource/lzo/
there are 3 sources to the lzo code
minilzo (on above site) lzo version 2 (ditto) lzo version 1 http://sourceforge.net/project/showfiles.php?group_id=102072&package_id=110218&release_id=221836
// some of the includes are for compression only (not listed here)
int lzo::lzo1x_decompress_safe ( const byte* in , byte* out, unsigned OutLen)
// returns length of consumed input bytes ,or, negative status
// Outlen is the desired output 'block size' this function will return neg status if the output buffer is not completely filled
// example call
byte Array=(byte)malloc(Outlen); // no zero clearing is necessary
int status_or_len=lzo->lzo1x_decompress_safe (input, Array,OutLen);#include "minilzo.h" 
#include <limits.h>
#include <stddef.h>
#include <limits.h>
#include <stddef.h>
#include <string.h>
#include <malloc.h>
#define assert(val) if (!(val)) return LZO_E_ERROR
#define M2_MAX_OFFSET   0x0800
#define NEED_OP(x)  if ((unsigned)(op_end - op) < (unsigned)(x)) return LZO_E_OUTPUT_OVERRUN;
#define TEST_LB()   if (m_pos < out || m_pos >= op) return LZO_E_LOOKBEHIND_OVERRUN;
#define COPY4(dst,src)    * (unsigned *)(dst) = * (const unsigned *)(src)
int lzo::lzo1x_decompress_safe ( const byte* in , byte* out, unsigned OutLen)
{
register byte* op;
register const byte* ip;
register size_t t;
register const byte* m_pos;
byte* const op_end = out + OutLen;
    OutLen = 0;
    op = out;
    ip = in;
    if (*ip > 17)
    {
        t = *ip++ - 17;
        if (t < 4) goto match_next;
        assert(t > 0);// return LZO_E_ERROR;
        NEED_OP(t); 
        do *op++ = *ip++; while (--t > 0);
        goto first_literal_run;
    }
    while (1 )
    {
        t = *ip++;
        if (t >= 16)           goto match;
        if (t == 0)
        {
            while (*ip == 0)
            {
                t += 255;
                ip++;
            }
            t += 15 + *ip++;
        }
        assert(t > 0); NEED_OP(t+3); 
        COPY4(op,ip);
        op += 4; ip += 4;
        if (--t > 0)
        {
            if (t >= 4)
            {
                do {
                    COPY4(op,ip);
                    op += 4; ip += 4; t -= 4;
                } while (t >= 4);
                if (t > 0) do *op++ = *ip++; while (--t > 0);
            }
            else
                do *op++ = *ip++; while (--t > 0);
        }
first_literal_run:
        t = *ip++;
        if (t >= 16)  goto match;
        m_pos = op - (1 + M2_MAX_OFFSET);
        m_pos -= t >> 2;
        m_pos -= *ip++ << 2;
        TEST_LB(); 
        NEED_OP(3);
        *op++ = *m_pos++; *op++ = *m_pos++; *op++ = *m_pos;
        goto match_done;
        do {
match:
            if (t >= 64)
            {
                m_pos = op - 1;
                m_pos -= (t >> 2) & 7;
                m_pos -= *ip++ << 3;
                t = (t >> 5) - 1;
                TEST_LB();     assert(t > 0); NEED_OP(t+3-1);
                goto copy_match;
            }
            else if (t >= 32)
            {
                t &= 31;
                if (t == 0)
                {
                    while (*ip == 0)
                    {
                        t += 255;
                        ip++;
                    }
                    t += 31 + *ip++;
                }
                m_pos = op - 1;
                m_pos -= (ip[0] >> 2) + (ip[1] << 6);
                ip += 2;
            }
            else if (t >= 16)
            {
                m_pos = op;
                m_pos -= (t & 8) << 11;
                t &= 7;
                if (t == 0)
                {
                    while (*ip == 0)
                    {
                        t += 255;
                        ip++;
                    }
                    t += 7 + *ip++;
                }
                m_pos -= (ip[0] >> 2) + (ip[1] << 6);
                ip += 2;
                ////// done
                if (m_pos == op)
                {
                    assert(t==1);
                    if (m_pos!=op_end) 
                        return LZO_E_LOOKBEHIND_UNDERRUN;
                    return ip-in;
                }
                m_pos -= 0x4000;
            }
            else
            {
                m_pos = op - 1;
                m_pos -= t >> 2;
                m_pos -= *ip++ << 2;
                TEST_LB(); 
                NEED_OP(2);
                *op++ = *m_pos++; *op++ = *m_pos;
                goto match_done;
            }
            TEST_LB(); 
            assert(t > 0);
            NEED_OP(t+3-1);
            if (t >= 2 * 4 - (3 - 1) && (op - m_pos) >= 4)
            {
                COPY4(op,m_pos);
                op += 4; m_pos += 4; t -= 4 - (3 - 1);
                do {
                    COPY4(op,m_pos);
                    op += 4; m_pos += 4; t -= 4;
                } while (t >= 4);
                if (t > 0) do *op++ = *m_pos++; while (--t > 0);
            }
            else
            {
copy_match:
               *op++ = *m_pos++; *op++ = *m_pos++;
                do *op++ = *m_pos++; while (--t > 0);
             }
match_done:
            t = ip[-2] & 3;
            if (t == 0)   break;
match_next:
            assert(t > 0); assert(t < 4); NEED_OP(t); 
            *op++ = *ip++;
            if (t > 1) { *op++ = *ip++; if (t > 2) { *op++ = *ip++; } }
            t = *ip++;
        } while (1 );
    }
//    return LZO_E_EOF_NOT_FOUND;/never gets here
 }
Reference Tables
Note: These are not part of the p3d model file but are reference tables used for processing.
Resolutions
 refResolutions
 {
   float  Resolution;
   string ResolutionName;
 }
| Hex-Value | Value | Value | Description | 
|---|---|---|---|
| 0x447a0000 | 1.0e3 | 1,000 | View Gunner | 
| 0x44898000 | 1.1e3 | 1,100 | View Pilot | 
| 0x44960000 | 1.2e3 | 1,200 | View Cargo | 
| 0x461c4000 | 1.0e4 | 10,000 | Stencil Shadow | 
| 0x461c6800 | 1.001e4 | 10,010 | Stencil Shadow 2 | 
| 0x462be000 | 1.1e4 | 11000 | Shadow Volume | 
| 0x462c0800 | 1.101e4 | 11010 | Shadow Volume 2 | 
| 0x551184e7 | 1.0e13 | 10,000,000,000,000 | Geometry | 
| 0x58635fa9 | 1.0e15 | 1,000,000,000,000,000 | Memory | 
| 0x58e35fa9 | 2.0e15 | 2,000,000,000,000,000 | Land Contact | 
| 0x592a87bf | 3.0e15 | 3,000,000,000,000,000 | Roadway | 
| 0x59635fa9 | 4.0e15 | 4,000,000,000,000,000 | Paths | 
| 0x598e1bca | 5.0e15 | 5,000,000,000,000,000 | HitPoints | 
| 0x59aa87bf | 6.0e15 | 6,000,000,000,000,000 | View Geometry | 
| 0x59c6f3b4 | 7.0e15 | 7,000,000,000,000,000 | Fire Geometry | 
| 0x59e35fa9 | 8.0e15 | 8,000,000,000,000,000 | View Cargo Geometry | 
| 0x59ffcb9e | 9.0e15 | 9,000,000,000,000,000 | View Cargo Fire Geometry | 
| 0x5a0e1bca | 1.0e16 | 10,000,000,000,000,000 | View Commander | 
| 0x5a1c51c4 | 1.1e16 | 11,000,000,000,000,000 | View Commander Geometry | 
| 0x5a2a87bf | 1.2e16 | 12,000,000,000,000,000 | View Commander Fire Geometry | 
| 0x5a38bdb9 | 1.3e16 | 13,000,000,000,000,000 | View Pilot Geometry | 
| 0x5a46f3b4 | 1.4e16 | 14,000,000,000,000,000 | View Pilot Fire Geometry | 
| 0x5a5529af | 1.5e16 | 15,000,000,000,000,000 | View Gunner Geometry | 
| 0x5a635fa9 | 1.6e16 | 16,000,000,000,000,000 | View Gunner Fire Geometry | 
| 0x5a7195a4 | 1.7e16 | 17,000,000,000,000,000 | Sub Parts | 
Note: Hex-Values are provided for convenience, as you can use those in different programming languages 'switch'-statement as opposed to floating point values.
Material Stages
The number of material stages is dependant on the type of Shader that is used to process the material by the ArmA game engine. A reference table is used when processing materials where depending on the shader specified the given number of stages should be processed.
 refShaderStages
 {
   int PixelShaderId;
   int NoOfStages;
 };
| ID (Hex/Decimal) | Name | Description | NoOfStages | 
|---|---|---|---|
| 0x00, 0 | Normal | diffuse color modulate, alpha replicate | 0 | 
| 0x01, 1 | NormalDXTA | diffuse color modulate, alpha replicate, DXT alpha correction | 0 | 
| 0x02, 2 | NormalMap | normal map shader | 3 | 
| 0x03, 3 | NormalMapThrough | normal map shader - through lighting | 3 | 
| 0x04, 4 | NormalMapSpecularDIMap | ? | 2 | 
| 0x05, 5 | NormalMapDiffuse | ? | 2 | 
| 0x06, 6 | Detail | ? | 1 | 
| 0x07, 7 | ? | ? | ? | 
| 0x08, 8 | Water | sea water | 2 | 
| 0x09, 9 | ? | ? | ? | 
| 0x0A, 10 | White | ? | 0 | 
| 0x0B, 11 | ? | ? | ? | 
| 0x0C, 12 | AlphaShadow | shadow alpha write | 0 | 
| 0x0D, 13 | AlphaNoShadow | shadow alpha (no shadow) write | 0 | 
| 0x0E, 14 | ? | ? | ? | 
| 0x0F, 15 | DetailMacroAS | ? | 3 | 
| 0x10, 16 | ? | ? | ? | 
| 0x11, 17 | ? | ? | ? | 
| 0x12, 18 | NormalMapSpecularMap | ? | 2 | 
| 0x13, 19 | NormalMapDetailSpecularMap | Similar to NormalMapDiffuse | 3 | 
| 0x14, 20 | NormalMapMacroASSpecularMap | ? | 4 | 
| 0x15, 21 | NormalMapDetailMacroASSpecularMap | ? | 5 | 
| 0x16, 22 | NormalMapSpecularDIMap | Same as NormalMapSpecularMap, but uses _SMDI texture | 2 | 
| 0x17, 23 | NormalMapDetailSpecularDIMap | ? | 3 | 
| 0x18, 24 | NormalMapMacroASSpecularDIMap | ? | 4 | 
| 0x19, 25 | NormalMapDetailMacroASSpecularDIMap | ? | 5 | 
| 0x38, 56 | Glass | ? | 2 | 
| 0x3A, 58 | NormalMapSpecularThrough | ? | 3 | 
| 0x3B, 59 | Grass | Special shader to allow volumetric shadows to be cast on grass clutter | 0 | 
| 0x3C, 60 | NormalMapThroughSimple | ? | 0 | 
Enums
 int enum PixelShaderId
 {
   Normal = 0x00,
   NormalMap = 0x02,
   NormalMapDiffuse = 0x05,
   NormalMapMacroASSpecularMap = 0x14,
   NormalMapSpecularDIMap = 0x16,
   NormalMapMacroASSpecularDIMap = 0x18,
   AlphaShadow = 0x0C,
   AlphaNoShadow = 0x0D,
   Glass = 0x38,
   Detail = 0x06,
   NormalMapSpecularMap = 0x12
 }
 int enum VertexShaderId
 {
   Basic = 0x00,
   NormalMap = 0x01,
   NormalMapAS = 0x0F
 }
Point Flags
Point Flags are as per Oxygen's Points->Properties as follows:
0NhUUFdLS 
        Surface
         xxxxxxx0 normal
         xxxxxxx1 on surface 
         xxxxxxx2 above 
         xxxxxxx4 under 
         xxxxxxx8 keep height(fence)
       Lighting face
         xxxxxx0x normal
         xxxxxx1x shine
         xxxxxx2x always in shadow
         xxxxxx4x full light
         xxxxxx8x half light
      decal
         xxxxx1xx decal 
     Fog
         xxxx0xxx none
         xxxx1xxx sky
         xxxx2xxx normal
   UU    xxUUxxxx user 0..255
  H      x1xxxxxx hidden vertex
 Normals
         x0xxxxxx by face
         x2xxxxxx fixed (normal)
         x4xxxxxx by impedence
</code>
Links
Article Author - Sy (Synide) -- Sy 17:16, 11 August 2007 (CEST)
Original ODOLv40 Article detailed by Bxbx (Biki'd by Mikero)
