P3D File Format - ODOLV4x: Difference between revisions

From Bohemia Interactive Community
Jump to navigation Jump to search
m (Text replacement - " ( *class [a-zA-Z0-9_]+): *([a-zA-Z0-9_]+ *) " to " $1 : $2 ")
 
(165 intermediate revisions by 10 users not shown)
Line 1: Line 1:
{{unsupported-doc}}
{{TOC|side}}
{{Feature|UnsupportedDoc}}
== Introduction ==
 
=== Acknowledgements ===


== Introduction ==
===Acknowledgements===
This body of work is due to Synide's sweat and tears. To whom, all honour and glory.
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.
Ably assisted by T_D and Mikero that further detailed the data and gave this article a more general and correct structure.


===General===
=== General ===
The general file format of a ArmA ODOL v40 p3d model file is similar to the ODOL v7 format.
 
The major differences are that in ArmA models are
The general format of an ArmA ODOLV4x p3d model is similar to the ODOLV7 format. The '''major''' differences are that ArmA models have
 
*an optional model.cfg, and
*Lods occur in the file from highest to lowest LodType value.
 
==== Legend ====
see [[Generic FileFormat Data Types]]
 
==== Relative Coordinates ====
All coordinates are '''relative''' to [[P3D Model Info|ModelInfo]].CentreOfGravity
 
==== 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 is 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 is 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)
 
=== Versions ===
 
This Document covers ODOL versions:
 
==== V40 ({{arma1}}) ====
 
* Original {{arma1}} binarised p3d
 
==== VBS2 ====
• [[P3D Model Info|ModelInfo]] now has a 24 byte thermal profile appended
 
==== V43 (Arma2a) ====
 
* As per VBS2 plus
* An extra byte at end of Skeleton structure: always 0
* LZSS compression is still used at this level
 
====V47 (Arma2b LZO) ====
 
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
* LodFrame has 4 extra floats
* UVSet structure changed to:
<syntaxhighlight lang="cpp">
LodUV
{
float uvScaling[4];
ulong nVertices;
tbool DefaultFill;
if (DefaultFill)
float UV; // default fill for all nVertices
else
float UV[nVertices]; // potentially compressed
}
</syntaxhighlight>
 
==== V48 (Arma2c) ====
As per V47 plus:
* [[P3D Model Info|ModelInfo]] has a 4 byte appendix
 
Arma2c format is the mainstay of Arma2. Types 43 and 47 are rarely encountered being works-in-progress in the initial release.
 
==== V49 (Arrowhead) ====
As per V48. No known differences apart from version number


*an optional model.cfg
==== V50 (Arrowhead DLC) ====
*resolutions are in reverse numerical order.


The order of resolutions denoted in the header portion of the file is <b>not</b> necessarily the numerical order of the resolutions. (often the 11,000 resolution is the last in the header array)
Introduced in PMC and BAF DLC
The header resolutions need to be sorted in descending order. The resultant sorted array of resolutions is the order in which they appear in the file.
* additional count after usedbones
lodPointFlags swapped around


=== Legend ===
==== V52 (TOH) ====
intoduced in patch to BAF DLC, used mostly in TOH
*additional float after usedbones in each lod
* ModelInfo.PixelShaders additional 24 bytes
* LODFaceDefaults additional count and float
* LodStageTextures additional bool for type 11's


{| border="0"
==== V56 ({{arma3}}) ====
!width="100"|Type
same as V52 Plus:
!width="300" align="left"|Description
* thermal profile split into two chunks (same overall size as v52)
|-
* unknown byte indices increased from 12 to 14
|-
* animation class always four floats
|align="middle"|byte||align="left"| 8 bit (1 byte)
* physx data added at end of file
|-
|align="middle"|tbool||align="left"| byte: 0 = false.
|-
|align="middle"|short||align="left"| 16 bit signed integer (2 bytes)
|-
|align="middle"|ushort||align="left"| 16 bit unsigned integer (2 bytes)
|-
|align="middle"|int||align="left"| 32 bit signed integer (4 bytes)
|-
|align="middle"|ulong||align="left"| 32 bit unsigned integer (4 bytes)
|-
|align="middle"|float||align="left"| 32 bit signed single precision floating point value (4 bytes)
|-
|align="middle"|asciiz||align="left"| Null terminated (0x00) variable length ascii string
|-
|-
|}


Note that 'int' is not used in this documentation for the following reasons:
==== V58 ({{arma3}}) ====
:*an 'int' is machine and compiler and language dependent. It is an arbitrary size SIGNED value.
* prefix added to header
:*with exceptions, BI use floats when requiring negative values.
* default indicators added at end of headerinfo
:*almost all references to 'integers' in BI file formats are either positive-only offsets into memory, zero based indexes, and counts


----
==== V59 ({{arma3}}) ====
* no genuine changes simply an alteration of the type value from 32 to 64 bits


== File Format ==
==== V60 ({{arma3}}) ====
* alteration to the physx data at end of file


==== V64 ({{arma3}}) ====
* Compressed sections no longer follow the 1024 byte rule, they now have a byte field for compressed or not


The following is a mix of ''pseudo-code'' and structure references that could be used to describe the file format of ODOL v40.
==== V67 ({{arma3}}) ====
It may or may not be accurate but has do date been used to read ODOL v40 in some cases without manual intervention. As at the writing of this article in most cases though, manual intervention is required to complete navigation throughout the given p3d file as there is some unkonwn data that prevents continuous processing.
* LOD sections have an added 4 bytes and optional CollimatorInfo structure
* LOD struct has additional 4 bytes at the end and optional CollimatorInfo structure


==== V68 ({{arma3}}) ====
* LOD struct has an additional byte at the end


=== Simple ===


<code><nowiki>
== File Format ==
  ODOLv40
  {
    Header;
    Model.cfg; (optional)
    Resolutions; (reverse numerical order)
  }
</nowiki></code>


=== Detailed ===
See [[P3D Model Info]].
<code><nowiki>
<syntaxhighlight lang="cpp">
ODOLv40
ODOLv4x
{
{
  StandardP3DHeader Header;
StandardP3DHeader Header;
  float            HeaderResolutions[Header.NoOfLods];
ModelInfo P3DModelInfo;
  ModelInfo         ModelInfo;
Animations Animations;
  Skeleton          Skeleton;
ulong StartAddressOfLods[Header.NoOfLods]; // offset relative to start of file.
  UnknownStruct1    UnknownStruct;
ulong EndAddressOfLods [Header.NoOfLods];
  Animations       Animations;
LODFaceDefaults LODFaceDefaults;
  ulong             StartAdressOfLods[Header.NoOfLods];
ODOLv40Lod ODOLv40Lods[Header.NoOfLods];
  ulong             EndAdressOfLods [Header.NoOfLods];
if (any arma3 type)
  tbool            LODFaceIndicator [Header.NoOfLods];
{
  structLodFace    LodFaces[NoOfFalseLODFaceIndicators];
long Always0;
  structResolution  Resolutions      [Header.NoOfLods]; // The order in which lod's occur
A3_Physx A3_Physx[...]; // optional if not v60
                                                        // is descending numerical order.
}
                                                        // eg. Resolution 1.0 will be the last in the file.
} // EndOfFile
  }//EndOfFile
</syntaxhighlight>
</nowiki></code>


:*there are only as many LodFaces as there are false LODFaceIndicators
----


== Structures ==
== Structures ==


=== StandardP3DHeader ===
=== StandardP3DHeader ===
<code><nowiki>
StandardP3DHeader
{
  char[4]  Filetype; // "ODOL"
  ulong    Version;  // 40
  ulong    NoOfLods; // alias NoOfResolutions;
}
</nowiki></code>


common header structure for all P3D file formats
common header structure for all P3D file formats


===ModelInfo===
<syntaxhighlight lang="cpp">
  ModelInfo
struct
  {
{
  ulong Unknown;
char[4] Filetype; // "ODOL"
  float Sphere;
ulong Version; // 40
  byte  Unknown[36];  
if TYPE58 (arma3)
  float ViewDensity;
Asciiz P3dPrefix; // \a3\data_f\proxies\muzzle_flash\muzzle_flash_rifle_mk20
  byte  Unknown[24];  
endif
  float ModelVertexOffset[3];   //xyz
ulong NoOfLods; // alias NoOfResolutions;
  float Unknown[3];             //xyz
}
  float ModelCentreOfGravity[3]; //xyz
</syntaxhighlight>
  float ModelMassVectors[3][3];
 
  byte  AutoCenter,
An '''optional''' prefix declaring the actual location of the p3d was introduced for arma3 (2nd version). Idiotically, it means the p3d cannot be moved out of it is current pbo.
        lockAutoCenter,
 
        canOcclude,
The prefix is optional in the sense that it may be null. ("\0")
        canBeOccluded,
 
        allowAnimation;
=== Animations ===
  byte  Unknown[6];  
 
  }
<syntaxhighlight lang="cpp">
Animations
{
tbool AnimsExist;
if (AnimsExist)
{
ulong nAnimationClasses; // eg NoOfAnimSelections;
AnimationClass AnimationClasses[nAnimationClasses];
 
long NoOfResolutions; // is -1 if nAnimationClasses == 0
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
}
}
</syntaxhighlight>
 
=== AnimationClass ===
 
<syntaxhighlight lang="cpp">
AnimationClass
{
ulong AnimTransformType;
asciiz AnimClassName; // "RightDoor"
asciiz AnimSource; // "rotor"
float MinMaxValue[2];
float MinMaxPhase[2];
ulong junk; // used to be sourceAddress, no longer, always 953267991
IF ARMA3
ulong Always0; // no idea what this is used for
ulong sourceAddress; // this is the actual source address, 0 = clamp, 1 = mirror, 2 = loop
endif


=== structSkeleton ===
switch(AnimTransformType)
<code><nowiki>
case 0://rotaton
  structSkeleton
case 1://rotationX
  {
case 2://rotationY
    asciiz                    SkeletonName;
case 3://rotationZ
    if (SkeletonName != null)
float angle0;
    {
float angle1;
      tbool                  isInherited;
break;
      ulong                  NoOfBones;
case 4://translation
      structBone[NoOfBones]   Bones;
case 5://translationX
    }
case 6://translationY
  }
case 7://translationZ
</nowiki></code>
float offset0;
=== structBone ===
float offset1;
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;
}


  structBone
  {
    asciiz Bone;
    asciiz Parent;
  }
===UnknownStruct1===
UnknownStruct1
{
  byte  unknown1;
  tbool  Extra;
  if(Extra)
  {
  byte  ExtraByte;
  }
  bytes  unknown3[3];
  float ModelMass.
        ModelMassReciprocal,
        ModelMassModifier;
  bytes Unknown16[16];
  ulong  unknown4;
  byte  unknown5;
  asciiz ModelString1;
  asciiz ModelString2;
  bytes  byteArrayUnknown2[5];
}


===Animations===
// corresponds to model.cfg
Animations
class CfgModels
{
{
  tbool            AnimsExist;
// ...
  if (AnimsExist)
  {
  ulong            NoOfAnimSelections;
  structAnimation  Animations[NoOfAnimSelections];
  ulong            NoOfResolutions;// same value as Header.NoOfLods
  Bones2Anims      Bones2Anims[NoOfResolutions];
  Anims2Bones      Anims2Bones[NoOfResolutions][NoOfAnimSelections];
  //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
  }
}


=== structAnimation ===
class whateverModel : Default
<code><nowiki>
{
  structAnimation
// ...
  {
class Animations
    ulong      AnimTransformType;
{
    asciiz      AnimSelection;
class RightDoor // AnimClassName
    asciiz      AnimSource;
{
    switch(AnimTransformType)
type = "translation"; // AnimTransformType
      case 9: //"hide"
source = "rotor"; // AnimSource
      {
// etc
        float minValue;
};
        float maxValue;
};
        float minPhase;
};
        float maxPhase;
</syntaxhighlight>
        ulong sourceAddress;
        float hideValue;
      }
      case 8: //"direct"
      {
        float minValue;
        float maxValue;
        float minPhase;
        float maxPhase;
        ulong sourceAddress;
        float axisPos[3];
        float axisDir[3];
        float angle; //in radians whereas the model.cfg entry is in degrees
        float axisOffset;
      }
      default
      {
        float minValue;
        float maxValue;
        float minPhase;
        float maxPhase;
        ulong sourceAddress;
        float angle0/offset0; //depends on animType
        float angle1/offset1; //depends on animType
      }
  }
</nowiki></code>


====Bones2Anims====
==== Bones2Anims ====
  Bones2Anims
  Bones2Anims
  {
  {
   ulong        NoOfBones;
   ulong        NoOfBones;
   BoneAnims    BoneAnims[NoOfBones];
   Bone2AnimClassList  Bone2AnimClassLists[NoOfBones];
  }
  }
====BoneAnims====
==== Bone2AnimClassList ====
  BoneAnims
  Bone2AnimClassList
  {
  {
   ulong NoOfAnims;
   ulong NoOfAnimClasses;
   ulong Animation[NoOfAnims];
   ulong AnimationClassIndex[NoOfAnimClasses]; // a (sometimes repeating) list of zero based indexes into above animation classes
  }
  }
====Anims2Bones====
 
==== Anims2Bones ====
  Anims2Bones
  Anims2Bones
  {
  {
   AnimBones AnimBones[Animations.NoOfAnimSelections];
   AnimBones AnimBones[Animations.nAnimationClasses];
  }
  }
====AnimBones====
 
==== AnimBones ====
 
every lod contains an identical list of animation entries that declare the position and axis of the each animation classes
 
  AnimBones
  AnimBones
  {
  {
   long Bone;
   long SkeletonBoneNameIndex; // zero based index to the SkeletonBoneName name & parentname
  // equivalent to selection = "LeftDoor"; eg in the model.cfg
   /*
   /*
   ** structAnimation.TransformTypes 8 (direct) and 9 (hide) always have a bone associated with them
   ** SkeletonBoneNameIndex== -1 when no skeleton bone is for this Anim and (obviously?) no axis information follows.
  ** but never require axis information. This because the "direct" (type 8) already has axis info in
  ** it's Animation structure, and "hidden" (type 9) clearly doesn't need it.
  **
  ** All other types may, or may not have an associated bone.
  ** Bone == -1 when no bone is for this Anim and (obviously?) no axis information follows.
   */
   */
   if (Bone != -1) && (Animations[j].TransformType != 8 && Animations[j].TransformType != 9)
   if (SkeletonBoneNameIndex!= -1) && (AnimationClass.AnimTransformType != 8 || 9)
   {
   {
     float[3] axisPos; //describes the position of the axis used for this anim
  /*
     float[3] axisDir;
  ** AnimationClass.AnimTransformType 8 (direct) and 9 (hide) never require axis information.
  ** This because the "direct" (type 8) already has axis info in it is 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;
   }
   }
  }
  }
===structLodFace===
 
//only when the LODFaceIndicator for that lod is false
=== LODFaceDefaults ===
  tbool            UseDefault[Header.NoOfLods];
structLodFace   
  FaceData
{
  {
   ulong  HeaderFaceCount;
   ulong  HeaderFaceCount;
   bytes  Unknown[13];
  ulong  aDefaultLong;    //ffffffff or 6f 7a 80 fa eg
}
  byte    UnknownByte;      //generally zero
  byte    aFlag;            // zero or one
   bytes  Zeroes[7];
  ======if v52 =========
  ulong  AnotherCount;
  float  AnotherFloat;
  =======endif===========
  }[Number of false UseDefault's];
 
A face data struct only exists for those lods who's UseDefault is zero
 
=== ODOLv4xLod ===
*Lod layout corresponds to Arma1 (type40). The differences in a2 are in the nitty gritty of the structures themselves. Arrowhead(v50) has some changes.
*TrueArma2 == type 47 or greater (lzo compression)
*Type 43 was a preliminary p3d prior to lzo compression. rarely encountered


===structResolution===
   ODOLv4xLod
   structResolution
   {
   {
     ulong                        NoOfModelProxies;
     ulong                        nProxies;
     structProxy[NoOfModelProxies] ModelProxies;
     LodProxy                      LodProxies[nProxies];             // see [[P3D Lod Proxies]]
     ulong                        nLodItems;
     ulong                        nItems;
     ulong                         LodItems[nLodItems];               // potentially compressed, except for v64 and later
     ulong[nItems]                 Items;
    ulong                        nBoneLinks;
     LodBoneLink                  LodBoneLinks[nBoneLinks];
     StarterStructTwo[...];
  =========if v5x==========
     PointProperties[...];         // Potentially compressed
    ulong                        LodPointCount;
     TextureNames[...];
      if v52
     float                        UnknownV52Float;
    endif
    else
    LodPointFlags                LodPointFlags;                     // Potentially compressed
===========endif==========
    float                        UnknownFloat1;
    float                        UnknownFloat2;
    XYZTriplet                    MinPos;
     XYZTriplet                    MaxPos;
    XYZTriplet                    AutoCenterPos;
    float                        Sphere;                            // same as geo or mem values in modelinfo, if this lod is geo or memlod of course
    ulong                        NoOfTextures;
    asciiz                        LodPaaTextureNames[NoOfTextures]; //"ca\characters\hhl\hhl_01_co.paa"
     ulong                        NoOfMaterials;
     ulong                        NoOfMaterials;
     structMaterial                Materials[NoOfMaterials];
     LodMaterial                  LodMaterials[NoOfMaterials];
     structEdge                    Edge1;
     LodEdges                      LodEdges;                          // compressed see [[P3D Lod Edges]]
     structEdge                    Edge2;
    ulong                        NoOfFaces;
     PolygonStruct[...];
    ulong                        OffsetToSectionsStruct;           // see below
     ushort                        AlwaysZero;
     LodFace                      LodFace[NoOfFaces];               // see [[P3D Lod Faces]]
     ulong                        nSections;
     ulong                        nSections;
     structSection                Sections[nSections];
     LodSection                    LodSections[nSections];           // see [[P3D Lod Sections]]
 
     ulong                        nNamedSelections;
     ulong                        nComponents;
     LodNamedSelection            LodNamedSelections[nNamedSelections]; //See [[P3D Named Selections]] potentially compressed
     structComponent              Components[nComponents];
     ulong                        nTokens;
     NamedProperty                NamedProperties[nTokens];         //See [[Named Properties]]
     ulong                        nProperties;
     ulong                        nFrames;
     structProperties              Properties[nProperties];
     LodFrame                     LodFrames[nFrames];               //see [[P3D Lod Frames]]
    ulong                        IconColor;
     ushort                        nKeyFrames;
     ulong                        SelectedColor;
     KeyFrame                     KeyFrames[nKeyFrames];
     ulong                        special; // IsAlpha|IsTransparent|IsAnimated|OnSurface
    byte                          vertexBoneRefIsSimple;
     byte                          Unknown[17];
     ulong                        sizeOfVertexTable;                 //(including these 4 bytes)
     if (v5x)
     structUV                      UV1;                     // Potentially compressed
    LodPointFlags                LodPointFlags;                    // Potentially compressed
     ulong                        nUVs;
     endif
     if (nUVs==2)
    VertexTable                  VertexTable;
    structUV                    UV2;                    // Potentially compressed
=== V65 ===
      
    ulong  UnknownLongValue1
     ulong                        NoOfVertices;
    if UnknownLongValue1
     float                         XZY[NoOfVertices][3];   // Potentially compressed
    {
        CollimatorInfo  unknown //Vec3,Vec3,ulong,vec3,ulong
    }
===========
=== V68 ===
    byte unknown;
===========
  }
==== VertexTable ====
 
all arrays are subject to compression
 
struct
{
    UvSet                        DefaultUVset;
     ulong                        nUVs;                             //in error, V47 sometimes sets nUV's as 0 but means 1
     UvSet                         UVSets[nUVs-1];
    ulong                        NoOfPoints;
    XYZTriplet                    LodPoints[NoOfPoints];
     ulong                        nNormals;
     ulong                        nNormals;
     tbool                        DefaultFill;
     (A2)LodNormals                LodNormals[nNormals];
    if (DefaultFill)
    float                        XZY[3];
    else
    float                        XZY[nNormals][3];       // Potentially compressed
     ulong                        nMinMax;
     ulong                        nMinMax;
     float                        MinMaxXYZ[nMinMax][2][3];// Potentially compressed
     (A2)LodMinMax                MinMax[nMinMax];                   //optional
    ulong                        nProperties;
     // Note that NoOfVertices == nNormals == nMinMax
     VertProperty                  VertProperties[nProperties];      //optional related to skeleton
     ulong                        Count;
     ulong                        Count;
     UnknownResolutionStruct      UnknownResolutionStruct[Count]; // Potentially compressed
     VertexNeighborInfo            neighborBoneRef[Count];         //optional
    ulong                        nBytes;
 
    byte                          VerticesUnknown2[nBytes][32];  // Potentially compressed
 
  }
 
====KeyFrame====
  }
  KeyFrame
 
*All non zero counts counts are the same.
*Points,PointFlags, Normals and UV1 arrays are an integral group, they are either all there, or not specified (RacetK.p3d, a [[P3D Lod Frames|FrameTime]] lod has no counts at all)
*UV2,MinMax, VertProperties and Unknown are optional in the sense that their counts can individually be zero, else they are the same as the others
*In Odol7 PointFlags are part of this stucture, in Arma, they are a separated table.
 
==== CompressedFill Arrays ====
 
LodPointFlags, LodUV's and LodNormals arrays are not only subject to the standard 1024 rule compression, but also have a fill byte.
 
  struct
  {
  {
   ulong nFrames;
   ulong                         Count;
   Frame Frames[nFrames];
   tbool                        DefaultFill;
  if (DefaultFill)
  type                        Array;          // default fill for all Counts
  else
  type                        Array[Count];   // potentially compressed
  }
  }


=====Frame=====
The structure either contains a single set of type variables, or, an array of type variables. If a full array is declared (DefaultFill =false) then that array is subject to the 1024 rule as per normal.
  Frame
==== UVset ====
if TrueARMA2
    float                        UVScale[4];
endif
    (A2)LodUV                    LodUV;
 
==== LodUV ====
CompressedFill type = UVPair // eg float U,V;
==== A2LodUV ====
CompressedFill type = float // eg float UV;
==== LodNormals ====
  CompressedFill type = XYZTriplet
==== A2LodNormals ====
  CompressedFill type = CompressedXYZTriplet
 
===== CompressedXYZTriplet =====
 
contains 3 x 10 bit fields in a 32bit 'integer'
 
code for converting back to a standard XYZTriplet is:
 
  void DecodeXYZ(ulong CompressedXYZ, XYZTriplet *triplet)
  {
  {
   float FrameTime;
    double scaleFactor = -1.0 /511;
  ulong NoOfFramePoints;
    trp->X=trp->Y=trp->Z=0.0;
  float FramePoints[NoOfFramePoints][3]; // xyz
    int x=   CompressedXYZ      & 0x3FF;
    int y = (CompressedXYZ>> 10) & 0x3FF;
    int z = (CompressedXYZ>> 20) & 0x3FF;
    if (x > 511) x -= 1024;
    if (y > 511) y -= 1024;
    if (z > 511) z -= 1024;
    if (x) trp->X = (float)(x * scaleFactor);
    if (y) trp->Y = (float)(y * scaleFactor);
    if (x) trp->Z = (float)(z * scaleFactor);
  }
  }


====UnknownResolutionStruct====
==== LodPointFlags ====
  UnknownResolutionStruct
  CompressedFill type = ulong bits
 
This table is the equivalent of Oxygen's points->properties dialog box. It specifically stores the user values and other flags for that point.
 
In ODOl7 it was part of the vertex table. In Arma, it is separate.
 
See [[P3D Point and Face Flags]]
 
==== LodMinMax ====
CompressedArray
  {
  {
     ulong  Index;
  XYZTriplet     MinMax[Count][2]; // 2 == min vs max
    float  Unknown[2];// probably a vertices something
  }
  }


=== structProxy ===
==== A2LodMinMax ====
  structProxy
CompressedArray
  {
{
    asciiz      ProxyName;
  float         MinMax[Count][2]; // 2 == min vs max
    float       RotationMatrix[9];
}
    float      Translation[3];   //xyz
    ulong      Indexes[4];
  }


This structure is (almost) identical to ODOL7 except it has 4 indexes rather than 2.
==== VertProperty ====
CompressedArray
{
  ulong  index;// seen range 0..4
  ulong  a,b; // definite not floats. might be flags, or indices
}


====StarterStructTwo====
==== VertexNeighborInfo ====
  StarterTwo
  CompressedArray
  {
  {
   ulong Count;
   ushort  vertexIndex _posA;
   {
   AnimationRTWeight _rtwA;
  ulong NoOf3;
  ushort  vertexIndex _posB;
  ulong Items[NoOf3];
   AnimationRTWeight _rtwB;
   }[Count];
  }
  }


===PointProperties===
==== LodBoneLink ====
  PointProperties // into the ResolutionIndex
 
  LodBoneLink
  {
  {
  ushort NoOfPts;
   ulong NoOfLinks;         //range 0..3
  ushort Unknown;
  ulong Value[NoOfLinks]; //the 'Value' seems to reference the 'LodItems' structure, resulting in a circular-reference.
  tbool  UseDefault;
  if (UseDefault)
  {
   ulong DefaultValue;
  }
  else // =0
  {
    ulong PropertyValues[NoOfPts]; // if NoOfPoints*sizeof(ulong) > 1023 LZH compression.
  }
  byte  Unknown[8];
  float Unknown[10];
  }
  }


PropertyValues for NoOfPts are either all the same (UseDefault), or, they are individually declared.
==== LodMaterial ====
Basically... A direct replication of the information in the given .rvmat file
 
The stages in the p3d include a default stage and a TI stage that are not normally listed in the rvmat.
:The first stage (in the p3d) is unconditionally the default stage. It is defaulted to empty (RvMatName=""), unless, specified in the rvmat
:The last stage is the TI stage, and is also defaulted empty, unless specified in the rvmat.
::TI Stages were introduced for operation arrowhead. Lod Material Types 9 and 10 (Arma1 and Arma2) do not have a TI stage at all.
Neither of these two special, hidden, stage types use uvsets. The transform matrix for them is defaulted empty (so-called 'TexGen0').
 
When specified in the rvmat (class Stage0 and StageTI respectively), no  class uvTransform is declared for them. It is assumed default empty.
 
In an rvmat, uvTransforms are ordinarily declared within each stage body.
 
In a P3D, identical UVTransforms are declared once, and multiple 'stages' refer to them. There is, always, a default UVSet0 Transform as the 1st entry. (IE some stages dont require uvsets)


Similar to CompressedStructs of OdolV7, if the amount of data in the array exceeds 1023 bytes, that array is compressed.
This P3D style can, if preferred, be used in rvmat syntax as
class TexGenX
{
  .......
};


===TextureNames===
  class StageZ
  TextureNames
  {
  {
  ulong    NoOfTextures;
  .........
  asciiz   Textures[NoOfTextures];
   Texgen=X;
  }
  };
 
where X and Z are numbers
 
 


=== structMaterial ===
   LodMaterial
<code><nowiki>
    //Basically... A direct replication of the information in the given .rvmat file
   structMaterial
   {
   {
     asciiz         Material;
     asciiz           RvMatName;     // "ca\characters\data\soldier_captive_hhl.rvmat"
     ulong         Type;          // 9 == Arma, 10==VBS2
     ulong             Type;          // 9 == Arma, 10==VBS2,11==Arrowhead
     D3DCOLORVALUE Emissive;
     D3DCOLORVALUE     Emissive;
     D3DCOLORVALUE Ambient;
     D3DCOLORVALUE     Ambient;
     D3DCOLORVALUE Diffuse;
     D3DCOLORVALUE     Diffuse;
     D3DCOLORVALUE forcedDiffuse;
     D3DCOLORVALUE     forcedDiffuse;
     D3DCOLORVALUE Specular;
     D3DCOLORVALUE     Specular;
     D3DCOLORVALUE Unknown;      //Usually same as Specular
     D3DCOLORVALUE     Specular2;      //Usually same as Specular
     float         SpecularPower;
     float             SpecularPower;
     ulong         PixelShaderId; //See enumPixelShaderId
     ulong             PixelShaderId;   //See enumPixelShaderId
     ulong         VertexShaderId;//See enumVertexShaderId
     ulong             VertexShaderId; //See enumVertexShaderId
     ulong         BoolFlag;     //mostly 1 otherwise 0
     LongBool         Arma1UnKnownBool;//A2 deprecated, always 1
     ulong         AnIndex;       //0,1 or 2
                                      //A1 mostly 1 otherwise 0
     asciiz         SurfaceName;
     ulong             Arma1AnIndex;   //A2 deprecated, always 1
     ulong         Always0x01;
                                      //A1 0,1 or 2
     ulong         aCount;       //Generally 0
     asciiz           BiSurfaceName;   // "ca\data\Penetration\plastic.bisurf"
     ulong         nTextures;
     LongBool         Arma1Mostly0x01; //A2 deprecated, always 1
     ulong         nTransforms;   // always same as nTextures
                                      //A1 rarely zero
     StageTexture   StageTextures  [nTextures];
     ulong             RenderFlags;     //Generally 0
     StageTransform StageTransforms[nTransforms];  
     ulong             nTextures;
     ulong             nTransforms;     // always same as nTextures
     LodStageTexture   StageTextures  [nTextures];
     LodStageTransform StageTransforms[nTransforms];
    if type>=10//vbs2/arma2
    LodStageTexture  DummyStageTexture;// see special, additional byte for THIS stagetexture
    endif 
   }   
   }   
</nowiki></code>


:There is always one default Texture and Transform as the first entry.
:Each lodmaterial entry contains a default StageTexture and StageTransform as the first entry. It is not shown in the rvmat file and has no PaaTexture
:It is the only entry if a SurfaceName exists.
:It is the only entry if a SurfaceName exists.


====StageTexture====
=== A3_Physics Type1 (optional) ===
  StageTexture
 
  {
    A3_stanza A3_stanzas[...]; // optional
    float  aFloat;
  }
*The type1 structure may not be present at all, or, only contain the single ending float
 
==== A3 stanza ====
 
  {
  //header//////
  long  Xcount,Ycount,Zcount;
  float  floats[4];
  long  minCount; //Always2;
  long  maxCount; //Always4;
  //////////////
  float  frames[Xcount*Ycount*Zcount][5];
  }
 
A header with no frames is legal, it is XYZcounts (and floats[4]) are zero.
 
A 'stanza' consists of 5 float values. The amount of stanzas (if any) are determined by the 3 counts.
 
The range of each count value varies between 2,3 and 4.
 
 
=== A3 Physx type60 ===
Introduced for format 60
{
  long      signature; // 0x03020400
  long      nStanzas;
  stanza60  stanza60s[nStanzas];
  long      signature; // same as above
  long      nStanzas2;
  stanza60  stanza60s2[nStanzas2];
};
==== stanza60 ====
{
  {// 1st stanza
  long    signature;//0x03020400
  long    size;
  bytes  phsyx_data[size];
  }
  {// 2nd stanza
  long    signature;// as above
  long    size;
  bytes  phsyx_data2[size];
  }
}
* the signatures and sizes are NOT optional. the data is
===== phsyx_data =====
  {
  {
  ulong TextureFilter; // mostly 3, sometimes 0
  phsyx_header
  asciiz Texture;
  {
  ulong StageID;       // zero based
    {
      byte id[8]; // 'NXS.CVXM'
      long Always13;
      long Always0;
    }
    {
      byte id[8]; // 'ICE.CLHL'
      long Always6;
    }
    {
      byte id[8]; // always 'ICE.CVHL'
      long Always6;
    }
    long nTriplets;
    long count;
    long nFrames;
    long 2xcount; // always dbl 'count'
  }
  tripletXYZ triplets[nTriplets];
  short 00;
  frames
  {
    float floats[4];
    long  value;
  }frames[nFrames];
  byte index[variable length];
  //25x4 byte ending sequence
  float generally_zero;
  float f;
  long  value;
  float floats[7];
  long  value2;
  float floats[14];
  };
  };
:The StageID is iterative (linear sequential). 1st entry is 0, 2nd 1, 3rd 2, etc.
*the amount of index bytes can be determined by end of frame data to the start of the fixed 25x4 offset at end of lod
:TextureFilter maybe 1 of the following values. Point = 0,Linear = 1,TriLinear = 2,Anisotropic = 3. The default value is 3 for Anisotropic filtering.


====StageTransform====
=== D3DCOLORVALUE ===
  StageTransform
  {
    ulong UVSource;
    float Transform[4][3];//a DirectX texture space transform matrix
  };
====D3DCOLORVALUE====
  D3DCOLORVALUE
  D3DCOLORVALUE
  {
  {
   float r,g,b,a;
   float r,g,b,a;
  }
  }
===== RenderFlags =====
:*Bit0:AlwaysInShadow (A1 only)
:*Bit1:NoZWrite
:*Bit4:NoColorWrite
:*Bit5:NoAlphaWrite
:*Bit6:AddBlend
:*Bit7:AlphaTest (clutter)
:*Bit8:AlphaTest64 (clutter)
:*Bit19:Road      (a1only)
:*Bit11:NoTiWrite


===structEdge===
===== LodStageTexture =====
  structEdge
  LodStageTexture
  {
  {
   ulong  nEdges;
   ulong TextureFilter; // see below
   ushort Edges[nEdges]; // lzh compressed
   asciiz PaaTexture;   // "ca\characters\data\civil_tvreport_body_as.paa
                        // alternatively "#(argb,8,8,3)color(0,0,0,1,CO)" (eg)
   ulong  TransformIndex;      // zero based, see below
  if =======Type11 AND V52 OR Type11 AND last(dummy) stage Texture ===================
  byte  V52Type11bool;   // only for arrowhead/pmc . and only for material types 11
endif
  };
  };


:The first stageTexture is a dummy entry. For N humanly readable stage classes, there are 1+N LodStageTextures


:Later p3d formats (VBS2, Operation Arrowhead) append an additional classTI LodStageTexture. if not present or declared in the rvmat file, it is a dummy entry. The rvmat gui editor from BisTools is not able to display this.


===PolygonStruct===
:The TransformIndex is generally iterative (linear sequential). 1st entry is 0, 2nd 1, 3rd 2, etc. It refers to the nTH Transform Matrix
PolygonStruct
{
  ulong  NoOfPolygons;
  ulong  GrossFaceIndex;              // x28 eg
  ushort  Unknown;                      // 00 00 eg
  PoyygonVertices
  {
    byte  NoOfVertices;                // 3 or 4
    ushort VerticesIndex[NoOfVertices]; // 0-based index into Vertices Arrays
  }[NoOfPolygons];
}


Note that there are always 3, or 4, vertices.
:TextureFilter maybe 1 of the following values.
:*0: Point // sometimes
:*1: Linear  // rarely
:*2: TriLinear // not seen
:*3: Anisotropic (default)


3 point vertices describe a triangle.
===== LodStageTransform =====
4 point vertices describe a square.
  LodStageTransform
  {
    ulong UVSource;
    float Transform[4][3];//a DirectX texture space transform matrix
  };


The indices must be transformed as follows
:UVSource corresponds to the 8 possible uvsets available
3 point verts : 1st posn, 2nd posn, 0th posn.
4 point verts : 1st, 2nd, 3rd, 0th


===structSection===
:*0 "None"
struct structSection
:*1 "Tex" default
{
:*2: "Tex2"
  ulong FaceLowerIndex;
:*........
  ulong FaceUpperIndex; //NoOfFaces = (FaceUpperIndex - FaceLowerIndex) / 8
:*8:"Tex8"
  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;
}
=== structComponent ===


  structComponent
:*Tex1..8 cannot be taken literally as uvsource 1..8. They can mean anything, according to the template and are scarcely encountered
  {
    asciiz                    ComponentName;
    ulong                    NoOfSelectedFaces;
    ushort[NoOfSelectedFaces] SelectedFaceIndexes;      //NOTE: This array is Compressed if size > 1024.
    UnKnownComponentStruct    UnKnownComponentStruct
    ulong                    nSelectedVertices;
    ushort[nSelectedVertices] SelectedVerticesIndexes;  // NOTE: This array is Compressed if size > 1024.
    ulong                    nTextureWeights;
    byte[nTextureWeights]    SelectedVerticesWeights;  // NOTE: This array is Compressed if size > 1024.
  }
====UnKnownComponentStruct====
UnKnownComponentStruct
{
    ulong                    UnknownInt;
    tbool                    IsPresent;
    ulong                    NoOfUlongs;
    if (IsPresent)
    {
    ulong[NoOfUlongs]        UnknownArray;            // possibly subject to compression. none seen so far
    }
}


=== structProperties ===
==== NamedProperty ====
   structProperties
   struct
   {
   {
     asciiz Property;
     Asciiz Property;// "noshadow" = "1" eg
     asciiz Value;
     Asciiz Value;
   }
   }
=== structUV ===
structUV
{
  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)
*See [[Named Properties]]


If a full array is declared (DefaultFill != 0) then that array is compressed if 2 * sizeof(float) * nVertices > 1023
== Decompression ==


== Decompression ==
see [[Compressed LZSS File Format]]


In ODOL v40 format files some of the datastructures present in the file are compressed by using a form of LZ compression.
see [[Compressed LZO File Format]]
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.




<b>''Note:- Data structures that are identified as being compressible will only be compressed if the 'expectedSize' is >= 1024 bytes.''</b>
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. This is represented as


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 is original format and size.


<b>''The code that follows is written in C# and may or may not be optimal or correct.''</b>
<b>''Note:- Data structures that are identified as being compressible will only be compressed if the 'expectedSize' is >= 1024 bytes.''</b>


<b>V64+</b>
v64 implements a new flag for all compressed blobs. They no longer follow the 1024 byte rule. All possibly compressed arrays are now:


ulong elementCount
byte  compressed
    0 - not compressed
    2 - compressed
byte  data[elementCount]


As an example if one was expanding the array of vertices positions...
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.
* A vertex is described by it is 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.
* If we were processing 1968 vertices then our expected output size would be 1968 * (3 * 4) = 23,616 bytes.


Line 569: Line 766:




<code><nowiki>
  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);
  }
</nowiki></code>
----


== Reference Tables ==
== Reference Tables ==


Note: These are not part of the p3d model file but are reference tables used for processing.
=== Resolutions ===
<code><nowiki>
refResolutions
{
  float  Resolution;
  string ResolutionName;
}
</nowiki></code>
{| border="0"
!width="100" align="left"|Hex-Value
!width="50" align="left"|Value
!width="150" align="left"|Value
!width="300" align="left"|Description
|-
|-
|align="left"|0x447a0000||align="left"|1.0e3||align="left"|1,000||align="left"|View Gunner
|-
|align="left"|0x44898000||align="left"|1.1e3||align="left"|1,100||align="left"|View Pilot
|-
|align="left"|0x44960000||align="left"|1.2e3||align="left"|1,200||align="left"|View Cargo
|-
|align="left"|0x461c4000||align="left"|1.0e4||align="left"|10,000||align="left"|Stencil Shadow
|-
|align="left"|0x461c6800||align="left"|1.001e4||align="left"|10,010||align="left"|Stencil Shadow 2
|-
|align="left"|0x462be000||align="left"|1.1e4||align="left"|11000||align="left"|Shadow Volume
|-
|align="left"|0x462c0800||align="left"|1.101e4||align="left"|11010||align="left"|Shadow Volume 2
|-
|align="left"|0x551184e7||align="left"|1.0e13||align="left"|10,000,000,000,000||align="left"|Geometry
|-
|align="left"|0x58635fa9||align="left"|1.0e15||align="left"|1,000,000,000,000,000||align="left"|Memory
|-
|align="left"|0x58e35fa9||align="left"|2.0e15||align="left"|2,000,000,000,000,000||align="left"|Land Contact
|-
|align="left"|0x592a87bf||align="left"|3.0e15||align="left"|3,000,000,000,000,000||align="left"|Roadway
|-
|align="left"|0x59635fa9||align="left"|4.0e15||align="left"|4,000,000,000,000,000||align="left"|Paths
|-
|align="left"|0x598e1bca||align="left"|5.0e15||align="left"|5,000,000,000,000,000||align="left"|HitPoints
|-
|align="left"|0x59aa87bf||align="left"|6.0e15||align="left"|6,000,000,000,000,000||align="left"|View Geometry
|-
|align="left"|0x59c6f3b4||align="left"|7.0e15||align="left"|7,000,000,000,000,000||align="left"|Fire Geometry
|-
|align="left"|0x59e35fa9||align="left"|8.0e15||align="left"|8,000,000,000,000,000||align="left"|View Cargo Geometry
|-
|align="left"|0x59ffcb9e||align="left"|9.0e15||align="left"|9,000,000,000,000,000||align="left"|View Cargo Fire Geometry
|-
|align="left"|0x5a0e1bca||align="left"|1.0e16||align="left"|10,000,000,000,000,000||align="left"|View Commander
|-
|align="left"|0x5a1c51c4||align="left"|1.1e16||align="left"|11,000,000,000,000,000||align="left"|View Commander Geometry
|-
|align="left"|0x5a2a87bf||align="left"|1.2e16||align="left"|12,000,000,000,000,000||align="left"|View Commander Fire Geometry
|-
|align="left"|0x5a38bdb9||align="left"|1.3e16||align="left"|13,000,000,000,000,000||align="left"|View Pilot Geometry
|-
|align="left"|0x5a46f3b4||align="left"|1.4e16||align="left"|14,000,000,000,000,000||align="left"|View Pilot Fire Geometry
|-
|align="left"|0x5a5529af||align="left"|1.5e16||align="left"|15,000,000,000,000,000||align="left"|View Gunner Geometry
|-
|align="left"|0x5a635fa9||align="left"|1.6e16||align="left"|16,000,000,000,000,000||align="left"|View Gunner Fire Geometry
|-
|align="left"|0x5a7195a4||align="left"|1.7e16||align="left"|17,000,000,000,000,000||align="left"|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 ===
=== Material Stages ===
Line 757: Line 775:
A reference table is used when processing materials where depending on the shader specified the given number of stages should be processed.
A reference table is used when processing materials where depending on the shader specified the given number of stages should be processed.


<code><nowiki>
<code style="display: block"><nowiki>
  refShaderStages
  refShaderStages
  {
  {
Line 780: Line 798:
|align="left"|0x03, 3||align="left"|NormalMapThrough||align="left"|normal map shader - through lighting||align="left"|3
|align="left"|0x03, 3||align="left"|NormalMapThrough||align="left"|normal map shader - through lighting||align="left"|3
|-
|-
|align="left"|0x04, 4||align="left"|NormalMapSpecularDIMap||align="left"|?||align="left"|2
|align="left"|0x04, 4||align="left"|NormalMapSpecularDIMap||align="left"|VBS2 only||align="left"|2
|-
|-
|align="left"|0x05, 5||align="left"|NormalMapDiffuse||align="left"|?||align="left"|2
|align="left"|0x05, 5||align="left"|NormalMapDiffuse||align="left"|?||align="left"|2
Line 788: Line 806:
|align="left"|0x07, 7||align="left"|?||align="left"|?||align="left"|?
|align="left"|0x07, 7||align="left"|?||align="left"|?||align="left"|?
|-
|-
|align="left"|0x08, 8||align="left"|Water||align="left"|sea water||align="left"|2
|align="left"|0x08, 8||align="left"|Water||align="left"|A1 only sea water||align="left"|2
|-
|-
|align="left"|0x09, 9||align="left"|?||align="left"|?||align="left"|?
|align="left"|0x09, 9||align="left"|?||align="left"|vbs2||align="left"|?
|-
|-
|align="left"|0x0A, 10||align="left"|White||align="left"|?||align="left"|0
|align="left"|0x0A, 10||align="left"|White||align="left"|A1 only||align="left"|0
|-
|-
|align="left"|0x0B, 11||align="left"|?||align="left"|?||align="left"|?
|align="left"|0x0B, 11||align="left"|?||align="left"|vbs2||align="left"|?
|-
|-
|align="left"|0x0C, 12||align="left"|AlphaShadow||align="left"|shadow alpha write||align="left"|0
|align="left"|0x0C, 12||align="left"|AlphaShadow||align="left"|shadow alpha write||align="left"|0
Line 800: Line 818:
|align="left"|0x0D, 13||align="left"|AlphaNoShadow||align="left"|shadow alpha (no shadow) write||align="left"|0
|align="left"|0x0D, 13||align="left"|AlphaNoShadow||align="left"|shadow alpha (no shadow) write||align="left"|0
|-
|-
|align="left"|0x0E, 14||align="left"|?||align="left"|?||align="left"|?
|align="left"|0x0E, 14||align="left"|?||align="left"|vbs2||align="left"|?
|-
|-
|align="left"|0x0F, 15||align="left"|DetailMacroAS||align="left"|?||align="left"|3
|align="left"|0x0F, 15||align="left"|DetailMacroAS||align="left"|?||align="left"|3
|-
|-
|align="left"|0x10, 16||align="left"|?||align="left"|?||align="left"|?
|align="left"|0x10, 16||align="left"|?||align="left"|vbs2||align="left"|?
|-
|-
|align="left"|0x11, 17||align="left"|?||align="left"|?||align="left"|?
|align="left"|0x11, 17||align="left"|?||align="left"|vbs2||align="left"|?
|-
|-
|align="left"|0x12, 18||align="left"|NormalMapSpecularMap||align="left"|?||align="left"|2
|align="left"|0x12, 18||align="left"|NormalMapSpecularMap||align="left"|?||align="left"|2
Line 831: Line 849:
|-
|-
|align="left"|0x3C, 60||align="left"|NormalMapThroughSimple||align="left"|?||align="left"|0
|align="left"|0x3C, 60||align="left"|NormalMapThroughSimple||align="left"|?||align="left"|0
|-
|align="left"|0xxx, 102||align="left"|Super||align="left"|Arrowhead||align="left"|0
|-
|align="left"|0xxx, 103||align="left"|Multi||align="left"|Arrowhead||align="left"|0
|-
|align="left"|0xxx, 107||align="left"|Tree||align="left"|Arrowhead||align="left"|0
|-
|align="left"|0xxx, 110||align="left"|Skin||align="left"|Arrowhead||align="left"|0
|-
|align="left"|0x6F, 111||align="left"|CalmWater||align="left"|Arrowhead||align="left"|7
|-
|align="left"|0xxx, 114||align="left"|TreeAdv||align="left"|Arrowhead||align="left"|0
|-
|align="left"|0xxx, 116||align="left"|TreeAdvTrunk||align="left"|Arrowhead||align="left"|0
|-
|-
|-
|-
Line 839: Line 871:
== Enums ==
== Enums ==


<code><nowiki>
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
}
</nowiki></code>


<code><nowiki>
 
<code style="display: block"><nowiki>
  int enum VertexShaderId
  int enum VertexShaderId
  {
  {
  Basic = 0x00,
case 0: return "Basic";
  NormalMap = 0x01,
case 1: return "NormalMap";
  NormalMapAS = 0x0F
case 2: return "NormalMapDiffuse";
case 3: return "Grass";
case 8: return "Water";
case 11: return  "NormalMapThrough";
case 15: return "NormalMapAS";
case 14: return "BasicAS";
case 17: return "Glass";
case 18: return "NormalMapSpecularThrough";
case 19: return "NormalMapThroughNoFade";
case 20: return "NormalMapSpecularThroughNoFade";
case 23: return "Super";
case 24: return "Multi";
case 25: return "Tree";
case 30: return "CalmWater";
case 26: return "TreeNoFade";
case 29: return "Skin";
case 31: return "TreeAdv";
case 32: return "TreeAdvTrunk";
  }
  }
</nowiki></code>
</nowiki></code>
Line 873: Line 907:
[[P3D File Format - ODOLV40|Original ODOLv40 Article detailed by Bxbx (Biki'd by Mikero)]]
[[P3D File Format - ODOLV40|Original ODOLv40 Article detailed by Bxbx (Biki'd by Mikero)]]
[[Category:BIS_File_Formats]]
[[Category:BIS_File_Formats]]
[[Category:ArmA: File Formats]]
{{GameCategory|arma1|File Formats}}

Latest revision as of 11:58, 6 December 2023

bi symbol white.png
Disclaimer: This page describes internal undocumented structures of Bohemia Interactive software.

This page contains unofficial information.

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.
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.

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.

General

The general format of an ArmA ODOLV4x p3d model is similar to the ODOLV7 format. The major differences are that ArmA models have

  • an optional model.cfg, and
  • Lods occur in the file from highest to lowest LodType value.

Legend

see Generic FileFormat Data Types

Relative Coordinates

All coordinates are relative to ModelInfo.CentreOfGravity

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 is 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 is 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)

Versions

This Document covers ODOL versions:

V40 (Armed Assault)

  • Original Armed Assault binarised p3d

VBS2

ModelInfo now has a 24 byte thermal profile appended

V43 (Arma2a)

  • As per VBS2 plus
  • An extra byte at end of Skeleton structure: always 0
  • LZSS compression is still used at this level

V47 (Arma2b LZO)

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
  • LodFrame has 4 extra floats
  • 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
}

V48 (Arma2c)

As per V47 plus:

Arma2c format is the mainstay of Arma2. Types 43 and 47 are rarely encountered being works-in-progress in the initial release.

V49 (Arrowhead)

As per V48. No known differences apart from version number

V50 (Arrowhead DLC)

Introduced in PMC and BAF DLC

  • additional count after usedbones

lodPointFlags swapped around

V52 (TOH)

intoduced in patch to BAF DLC, used mostly in TOH

  • additional float after usedbones in each lod
  • ModelInfo.PixelShaders additional 24 bytes
  • LODFaceDefaults additional count and float
  • LodStageTextures additional bool for type 11's

V56 (Arma 3)

same as V52 Plus:

  • thermal profile split into two chunks (same overall size as v52)
  • unknown byte indices increased from 12 to 14
  • animation class always four floats
  • physx data added at end of file

V58 (Arma 3)

  • prefix added to header
  • default indicators added at end of headerinfo

V59 (Arma 3)

  • no genuine changes simply an alteration of the type value from 32 to 64 bits

V60 (Arma 3)

  • alteration to the physx data at end of file

V64 (Arma 3)

  • Compressed sections no longer follow the 1024 byte rule, they now have a byte field for compressed or not

V67 (Arma 3)

  • LOD sections have an added 4 bytes and optional CollimatorInfo structure
  • LOD struct has additional 4 bytes at the end and optional CollimatorInfo structure

V68 (Arma 3)

  • LOD struct has an additional byte at the end


File Format

See P3D Model Info.

ODOLv4x
{
	StandardP3DHeader Header;
	ModelInfo		P3DModelInfo;
	Animations		Animations;
	ulong			StartAddressOfLods[Header.NoOfLods]; // offset relative to start of file.
	ulong			EndAddressOfLods  [Header.NoOfLods];
	LODFaceDefaults	LODFaceDefaults;
	ODOLv40Lod		ODOLv40Lods[Header.NoOfLods];
	if (any arma3 type) 
	{
		long		Always0;
		A3_Physx	A3_Physx[...]; // optional if not v60
	}
} // EndOfFile


Structures

StandardP3DHeader

common header structure for all P3D file formats

struct
{
	char[4]	Filetype;		// "ODOL"
	ulong	Version;		// 40
	if TYPE58 (arma3)
		Asciiz	P3dPrefix;	// \a3\data_f\proxies\muzzle_flash\muzzle_flash_rifle_mk20
	endif
	ulong	NoOfLods;		// alias NoOfResolutions;
}

An optional prefix declaring the actual location of the p3d was introduced for arma3 (2nd version). Idiotically, it means the p3d cannot be moved out of it is current pbo.

The prefix is optional in the sense that it may be null. ("\0")

Animations

Animations
{
	tbool	AnimsExist;
	if (AnimsExist)
	{
		ulong			nAnimationClasses; // eg NoOfAnimSelections;
		AnimationClass	AnimationClasses[nAnimationClasses];

		long		NoOfResolutions; // is -1 if nAnimationClasses == 0
		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	junk; // used to be sourceAddress, no longer, always 953267991
		IF ARMA3
			ulong	Always0;		// no idea what this is used for
			ulong	sourceAddress;	// this is the actual source address, 0 = clamp, 1 = mirror, 2 = loop
		endif

		switch(AnimTransformType)
		case 0://rotaton
		case 1://rotationX
		case 2://rotationY
		case 3://rotationZ
			float angle0;
			float angle1;
			break;
		case 4://translation
		case 5://translationX
		case 6://translationY
		case 7://translationZ
			float offset0;
			float offset1;
			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 is 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;
 }
}

LODFaceDefaults

 tbool             UseDefault[Header.NoOfLods];
 FaceData
 {
  ulong   HeaderFaceCount;
  ulong   aDefaultLong;     //ffffffff or 6f 7a 80 fa eg
  byte    UnknownByte;      //generally zero
  byte    aFlag;            // zero or one
  bytes   Zeroes[7];
  ======if v52 =========
  ulong   AnotherCount;
  float   AnotherFloat;
  =======endif===========
 }[Number of false UseDefault's];

A face data struct only exists for those lods who's UseDefault is zero

ODOLv4xLod

  • Lod layout corresponds to Arma1 (type40). The differences in a2 are in the nitty gritty of the structures themselves. Arrowhead(v50) has some changes.
  • TrueArma2 == type 47 or greater (lzo compression)
  • Type 43 was a preliminary p3d prior to lzo compression. rarely encountered
 ODOLv4xLod
 {
   ulong                         nProxies;
   LodProxy                      LodProxies[nProxies];              // see P3D Lod Proxies
   ulong                         nLodItems;
   ulong                         LodItems[nLodItems];               // potentially compressed, except for v64 and later
   ulong                         nBoneLinks;
   LodBoneLink                   LodBoneLinks[nBoneLinks];
 =========if v5x==========
   ulong                         LodPointCount;
     if v52
   float                         UnknownV52Float;
    endif
   else
   LodPointFlags                 LodPointFlags;                     // Potentially compressed
===========endif==========
   float                         UnknownFloat1;
   float                         UnknownFloat2;
   XYZTriplet                    MinPos;
   XYZTriplet                    MaxPos;
   XYZTriplet                    AutoCenterPos;
   float                         Sphere;                            // same as geo or mem values in modelinfo, if this lod is geo or memlod of course
   ulong                         NoOfTextures;
   asciiz                        LodPaaTextureNames[NoOfTextures];  //"ca\characters\hhl\hhl_01_co.paa"
   ulong                         NoOfMaterials;
   LodMaterial                   LodMaterials[NoOfMaterials];
   LodEdges                      LodEdges;                          // compressed see P3D Lod Edges
   ulong                         NoOfFaces;
   ulong                         OffsetToSectionsStruct;            // see below
   ushort                        AlwaysZero;
   LodFace                       LodFace[NoOfFaces];                // see P3D Lod Faces
   ulong                         nSections;
   LodSection                    LodSections[nSections];            // see P3D Lod Sections
   ulong                         nNamedSelections;
   LodNamedSelection             LodNamedSelections[nNamedSelections]; //See P3D Named Selections potentially compressed
   ulong                         nTokens;
   NamedProperty                 NamedProperties[nTokens];          //See Named Properties
   ulong                         nFrames;
   LodFrame                      LodFrames[nFrames];                //see P3D Lod Frames
   ulong                         IconColor;
   ulong                         SelectedColor;
   ulong                         special; // IsAlpha|IsTransparent|IsAnimated|OnSurface
   byte                          vertexBoneRefIsSimple;
   ulong                         sizeOfVertexTable;                 //(including these 4 bytes)
   if (v5x)
   LodPointFlags                 LodPointFlags;                     // Potentially compressed
   endif
   VertexTable                   VertexTable;
=== V65 ===
    ulong   UnknownLongValue1
    if UnknownLongValue1
    {
        CollimatorInfo   unknown //Vec3,Vec3,ulong,vec3,ulong
    }
===========
=== V68 ===
   byte unknown;
===========
 }

VertexTable

all arrays are subject to compression

struct
{
   UvSet                         DefaultUVset;
   ulong                         nUVs;                              //in error, V47 sometimes sets nUV's as 0 but means 1
   UvSet                         UVSets[nUVs-1];
   ulong                         NoOfPoints;
   XYZTriplet                    LodPoints[NoOfPoints];
   ulong                         nNormals;
   (A2)LodNormals                LodNormals[nNormals];
   ulong                         nMinMax;
   (A2)LodMinMax                 MinMax[nMinMax];                   //optional
   ulong                         nProperties;
   VertProperty                  VertProperties[nProperties];       //optional related to skeleton
   ulong                         Count;
   VertexNeighborInfo            neighborBoneRef[Count];          //optional


  }
  • All non zero counts counts are the same.
  • Points,PointFlags, Normals and UV1 arrays are an integral group, they are either all there, or not specified (RacetK.p3d, a FrameTime lod has no counts at all)
  • UV2,MinMax, VertProperties and Unknown are optional in the sense that their counts can individually be zero, else they are the same as the others
  • In Odol7 PointFlags are part of this stucture, in Arma, they are a separated table.

CompressedFill Arrays

LodPointFlags, LodUV's and LodNormals arrays are not only subject to the standard 1024 rule compression, but also have a fill byte.

struct
{
 ulong                         Count;
 tbool                         DefaultFill;
 if (DefaultFill)
  type                         Array;          // default fill for all Counts
 else
  type                         Array[Count];   // potentially compressed
}

The structure either contains a single set of type variables, or, an array of type variables. If a full array is declared (DefaultFill =false) then that array is subject to the 1024 rule as per normal.

UVset

if TrueARMA2
   float                         UVScale[4];
endif
   (A2)LodUV                     LodUV;

LodUV

CompressedFill type = UVPair // eg float U,V;

A2LodUV

CompressedFill type = float // eg float UV;

LodNormals

 CompressedFill type = XYZTriplet

A2LodNormals

 CompressedFill type = CompressedXYZTriplet
CompressedXYZTriplet

contains 3 x 10 bit fields in a 32bit 'integer'

code for converting back to a standard XYZTriplet is:

void DecodeXYZ(ulong CompressedXYZ, XYZTriplet *triplet)
{
   double scaleFactor = -1.0 /511;
   trp->X=trp->Y=trp->Z=0.0;
   int x=   CompressedXYZ       & 0x3FF;
   int y = (CompressedXYZ>> 10) & 0x3FF;
   int z = (CompressedXYZ>> 20) & 0x3FF;
   if (x > 511) x -= 1024;
   if (y > 511) y -= 1024;
   if (z > 511) z -= 1024;
   if (x) trp->X = (float)(x * scaleFactor);
   if (y) trp->Y = (float)(y * scaleFactor);
   if (x) trp->Z = (float)(z * scaleFactor);
}

LodPointFlags

CompressedFill type = ulong bits

This table is the equivalent of Oxygen's points->properties dialog box. It specifically stores the user values and other flags for that point.

In ODOl7 it was part of the vertex table. In Arma, it is separate.

See P3D Point and Face Flags

LodMinMax

CompressedArray
{
 XYZTriplet     MinMax[Count][2]; // 2 == min vs max
}

A2LodMinMax

CompressedArray
{
 float         MinMax[Count][2]; // 2 == min vs max
}

VertProperty

CompressedArray
{
 ulong  index;// seen range 0..4
 ulong  a,b; // definite not floats. might be flags, or indices
}

VertexNeighborInfo

CompressedArray
{
 ushort  vertexIndex _posA;
 AnimationRTWeight _rtwA;
 ushort  vertexIndex _posB;
 AnimationRTWeight _rtwB;
}

LodBoneLink

LodBoneLink
{
  ulong NoOfLinks;         //range 0..3
  ulong Value[NoOfLinks];  //the 'Value' seems to reference the 'LodItems' structure, resulting in a circular-reference.
}

LodMaterial

Basically... A direct replication of the information in the given .rvmat file

The stages in the p3d include a default stage and a TI stage that are not normally listed in the rvmat.

The first stage (in the p3d) is unconditionally the default stage. It is defaulted to empty (RvMatName=""), unless, specified in the rvmat
The last stage is the TI stage, and is also defaulted empty, unless specified in the rvmat.
TI Stages were introduced for operation arrowhead. Lod Material Types 9 and 10 (Arma1 and Arma2) do not have a TI stage at all.

Neither of these two special, hidden, stage types use uvsets. The transform matrix for them is defaulted empty (so-called 'TexGen0').

When specified in the rvmat (class Stage0 and StageTI respectively), no class uvTransform is declared for them. It is assumed default empty.

In an rvmat, uvTransforms are ordinarily declared within each stage body.

In a P3D, identical UVTransforms are declared once, and multiple 'stages' refer to them. There is, always, a default UVSet0 Transform as the 1st entry. (IE some stages dont require uvsets)

This P3D style can, if preferred, be used in rvmat syntax as

class TexGenX
{
  .......
};
class StageZ
{
  .........
  Texgen=X;
};

where X and Z are numbers


 LodMaterial
 {
   asciiz            RvMatName;     // "ca\characters\data\soldier_captive_hhl.rvmat"
   ulong             Type;          // 9 == Arma, 10==VBS2,11==Arrowhead
   D3DCOLORVALUE     Emissive;
   D3DCOLORVALUE     Ambient;
   D3DCOLORVALUE     Diffuse;
   D3DCOLORVALUE     forcedDiffuse;
   D3DCOLORVALUE     Specular;
   D3DCOLORVALUE     Specular2;       //Usually same as Specular
   float             SpecularPower;
   ulong             PixelShaderId;   //See enumPixelShaderId
   ulong             VertexShaderId;  //See enumVertexShaderId
   LongBool          Arma1UnKnownBool;//A2 deprecated, always 1
                                      //A1 mostly 1 otherwise 0
   ulong             Arma1AnIndex;    //A2 deprecated, always 1
                                      //A1 0,1 or 2
   asciiz            BiSurfaceName;   // "ca\data\Penetration\plastic.bisurf"
   LongBool          Arma1Mostly0x01; //A2 deprecated, always 1
                                      //A1 rarely zero
   ulong             RenderFlags;     //Generally 0
   ulong             nTextures;
   ulong             nTransforms;     // always same as nTextures
   LodStageTexture   StageTextures  [nTextures];
   LodStageTransform StageTransforms[nTransforms];
   if type>=10//vbs2/arma2
    LodStageTexture   DummyStageTexture;// see special, additional byte for THIS stagetexture
   endif   
 }  
Each lodmaterial entry contains a default StageTexture and StageTransform as the first entry. It is not shown in the rvmat file and has no PaaTexture
It is the only entry if a SurfaceName exists.

A3_Physics Type1 (optional)

 {
   A3_stanza A3_stanzas[...]; // optional
   float  aFloat;
 }
  • The type1 structure may not be present at all, or, only contain the single ending float

A3 stanza

{
  //header//////
  long   Xcount,Ycount,Zcount;
  float  floats[4];
  long   minCount; //Always2;
  long   maxCount; //Always4;
  //////////////
  float  frames[Xcount*Ycount*Zcount][5];
 }

A header with no frames is legal, it is XYZcounts (and floats[4]) are zero.

A 'stanza' consists of 5 float values. The amount of stanzas (if any) are determined by the 3 counts.

The range of each count value varies between 2,3 and 4.


A3 Physx type60

Introduced for format 60

{
 long      signature; // 0x03020400
 long      nStanzas;
 stanza60  stanza60s[nStanzas];
 long      signature; // same as above
 long      nStanzas2;
 stanza60  stanza60s2[nStanzas2];
};

stanza60

{
  {// 1st stanza
  long    signature;//0x03020400
  long    size;
  bytes   phsyx_data[size];
  }
  {// 2nd stanza
  long    signature;// as above
  long    size;
  bytes   phsyx_data2[size];
  }
}
  • the signatures and sizes are NOT optional. the data is
phsyx_data
{
  phsyx_header
  {
    {
      byte id[8]; // 'NXS.CVXM'
      long Always13;
      long Always0;
    }
    {
      byte id[8]; // 'ICE.CLHL'
      long Always6;
    }
    {
      byte id[8]; // always 'ICE.CVHL'
      long Always6;
    }
    long nTriplets;
    long count;
    long nFrames;
    long 2xcount;  // always dbl 'count'
  }
  tripletXYZ triplets[nTriplets];
  short 00;
  frames
  {
    float floats[4];
    long  value;
  }frames[nFrames];
  byte  index[variable length];
  //25x4 byte ending sequence
  float generally_zero;
  float f;
  long  value;
  float floats[7];
  long  value2;
  float floats[14];
};
  • the amount of index bytes can be determined by end of frame data to the start of the fixed 25x4 offset at end of lod

D3DCOLORVALUE

D3DCOLORVALUE
{
  float r,g,b,a;
}
RenderFlags
  • Bit0:AlwaysInShadow (A1 only)
  • Bit1:NoZWrite
  • Bit4:NoColorWrite
  • Bit5:NoAlphaWrite
  • Bit6:AddBlend
  • Bit7:AlphaTest (clutter)
  • Bit8:AlphaTest64 (clutter)
  • Bit19:Road (a1only)
  • Bit11:NoTiWrite
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  TransformIndex;       // zero based, see below
if =======Type11 AND V52 OR Type11 AND last(dummy) stage Texture ===================
 byte   V52Type11bool;   // only for arrowhead/pmc . and only for material types 11
endif
};
The first stageTexture is a dummy entry. For N humanly readable stage classes, there are 1+N LodStageTextures
Later p3d formats (VBS2, Operation Arrowhead) append an additional classTI LodStageTexture. if not present or declared in the rvmat file, it is a dummy entry. The rvmat gui editor from BisTools is not able to display this.
The TransformIndex is generally iterative (linear sequential). 1st entry is 0, 2nd 1, 3rd 2, etc. It refers to the nTH Transform Matrix
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
  };
UVSource corresponds to the 8 possible uvsets available
  • 0 "None"
  • 1 "Tex" default
  • 2: "Tex2"
  • ........
  • 8:"Tex8"
  • Tex1..8 cannot be taken literally as uvsource 1..8. They can mean anything, according to the template and are scarcely encountered

NamedProperty

 struct
 {
    Asciiz Property;// "noshadow" = "1" eg
    Asciiz Value;
 }

Decompression

see Compressed LZSS File Format

see Compressed LZO File Format


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. This is represented as

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 is original format and size.

Note:- Data structures that are identified as being compressible will only be compressed if the 'expectedSize' is >= 1024 bytes.

V64+ v64 implements a new flag for all compressed blobs. They no longer follow the 1024 byte rule. All possibly compressed arrays are now:

ulong elementCount byte compressed

   0 - not compressed
   2 - compressed

byte data[elementCount]

As an example if one was expanding the array of vertices positions...

  • A vertex is described by it is 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.


Reference Tables

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 VBS2 only 2
0x05, 5 NormalMapDiffuse ? 2
0x06, 6 Detail ? 1
0x07, 7 ? ? ?
0x08, 8 Water A1 only sea water 2
0x09, 9 ? vbs2 ?
0x0A, 10 White A1 only 0
0x0B, 11 ? vbs2 ?
0x0C, 12 AlphaShadow shadow alpha write 0
0x0D, 13 AlphaNoShadow shadow alpha (no shadow) write 0
0x0E, 14 ? vbs2 ?
0x0F, 15 DetailMacroAS ? 3
0x10, 16 ? vbs2 ?
0x11, 17 ? vbs2 ?
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
0xxx, 102 Super Arrowhead 0
0xxx, 103 Multi Arrowhead 0
0xxx, 107 Tree Arrowhead 0
0xxx, 110 Skin Arrowhead 0
0x6F, 111 CalmWater Arrowhead 7
0xxx, 114 TreeAdv Arrowhead 0
0xxx, 116 TreeAdvTrunk Arrowhead 0

Enums

int enum VertexShaderId { case 0: return "Basic"; case 1: return "NormalMap"; case 2: return "NormalMapDiffuse"; case 3: return "Grass"; case 8: return "Water"; case 11: return "NormalMapThrough"; case 15: return "NormalMapAS"; case 14: return "BasicAS"; case 17: return "Glass"; case 18: return "NormalMapSpecularThrough"; case 19: return "NormalMapThroughNoFade"; case 20: return "NormalMapSpecularThroughNoFade"; case 23: return "Super"; case 24: return "Multi"; case 25: return "Tree"; case 30: return "CalmWater"; case 26: return "TreeNoFade"; case 29: return "Skin"; case 31: return "TreeAdv"; case 32: return "TreeAdvTrunk"; }


Links

Article Author - Sy (Synide) -- Sy 17:16, 11 August 2007 (CEST)

Original ODOLv40 Article detailed by Bxbx (Biki'd by Mikero)