P3D File Format - ODOLV4x: Difference between revisions

From Bohemia Interactive Community
m (Text replacement - "\[\[Category:BIS( |_)File( |_)Formats\]\]" to "Category:Real Virtuality File Formats")
 
(62 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{unsupported-doc}}
{{TOC|side}}
{{Feature|UnsupportedDoc}}
<!--


Wiki Editors: DO NOT DOCUMENT ANYTHING AFTER ARMA 1
-->
== Introduction ==
== Introduction ==
===Acknowledgements===
 
=== 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.


===Versions===
=== General ===
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:
The general format of an ArmA ODOLV4x p3d model is similar to the ODOLV7 format. The '''major''' differences are that ArmA models have
•ModelInfo struct is now 179+4 bytes instead of 155 for V40


 
*an optional model.cfg, and
 
===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.
*Lods occur in the file from highest to lowest LodType value.


=== Legend ===
==== Legend ====
see [[Generic FileFormat Data Types]]
see [[Generic FileFormat Data Types]]


===File Paths===
==== Relative Coordinates ====
All coordinates are '''relative''' to [[P3D Model Info|ModelInfo]].CentreOfGravity


The PrefixRoot\ folder.
==== File Paths ====


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.
'''All''' file references are absolute to the Linux \ (root).


BI choose to use hard-wired Pbo-Prefix-addressing ONLY.  
As a convenience A drive letter is used (such as P:\) for sanity when modelling. The engine knows nothing about C:\my documents or even P:


All hardwired addressing is relative to a built-in-situ (ie virtual) PrefixRoot\ folder
The leading \ is optional.


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.
\my_project\data\some.paa AND
my_project\data\some.paa


The PrefixRoot\ folder contains the prefix names of all pbos encountered (almost) ANYWHERE.
point to the same file.


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


Thus all filename references in a p3d, *unconditionally* contain a prefixname\someFile\SomeWhere.
This Document covers ODOL versions:


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.
==== V40 ({{arma1}}) ====


Note also that there is some inconsistency in filename paths. Most do not have a leading \. Some, require it. Both are indeed \hardwired
* Original {{arma1}} binarised p3d


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 ==
== File Format ==


ODOLv4x
<syntaxhighlight lang="cpp">
{
ODOLv4x
  StandardP3DHeader Header;
{
  float            LodTypes[Header.NoOfLods];// alias resolutions
StandardP3DHeader Header;
  ModelInfo        ModelInfo;
    struct ModelInfo;
  Skeleton          Skeleton;
Animations Animations;
  UnknownStruct1    UnknownStruct;
ulong StartAddressOfLods[Header.NoOfLods]; // offset relative to start of file.
  Animations       Animations;
ulong EndAddressOfLods [Header.NoOfLods];
  ulong             StartAdressOfLods[Header.NoOfLods];// offset relative to start of file.
LODFaceDefaults LODFaceDefaults;
  ulong             EndAdressOfLods [Header.NoOfLods];
ODOLv40Lod ODOLv40Lods[Header.NoOfLods];
  tbool            LODFaceIndicator [Header.NoOfLods];
} // EndOfFile
  LodFace          LodFaces[NoOfFalseLODFaceIndicators];//there are only as many LodFaces as there are false LODFaceIndicators
</syntaxhighlight>
  ODOLv40Lod       ODOLv40Lods[Header.NoOfLods];
  }//EndOfFile




== Structures ==


===LodTypes===
=== StandardP3DHeader ===
LodTypes are resolutions. Most of them have humanly readable context such as the 'memory' lod. And are selected as such in Oxygen. The are consequently referred to here as 'LodTypes', since the floating point values, although relevant to the engine, are not as immediately apparent to a human.


*Lods in the file are highest to lowest. The lowest resolution (eg. Resolution 1.0) will be the last in the file.
Common header structure for all P3D file formats.


*Start and ending addresses are random (not sorted). The LodTypes array declares which Lod an address-pair is referring to.
<syntaxhighlight lang="cpp">
struct
{
char[4] Filetype; // "ODOL"
ulong Version;
ulong NoOfLods; // alias NoOfResolutions;
}
</syntaxhighlight>


Thus, if the first Lodtype entry(eg) specifies a resolution of 1.0e13 (a geometry lod), that lod is located via the 1st Starting address. It is NOT, necessarily, the 1st lod in the file.
[[P3D Model Info|ModelInfo]]


=== Animations ===


*The order, has significance to various lod offsets mentioned below.
<syntaxhighlight lang="cpp">
Animations
{
tbool AnimsExist;
if (AnimsExist)
{
ulong nAnimationClasses; // eg NoOfAnimSelections;
AnimationClass AnimationClasses[nAnimationClasses];


*For a list of 'lod types' see bottom of document.
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


== Structures ==
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;
}


=== 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
// corresponds to model.cfg
class CfgModels
{
// ...


===ModelInfo===
class whateverModel : Default
  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
<pre>
class cfgSkeletons
{
class SkeletonClassname: Default
{
{
skeletonBones[]=
// ...
class Animations
{
class RightDoor // AnimClassName
{
{
"RightDoor1","",
type = "translation"; // AnimTransformType
"RightDoor2","",
source = "rotor"; // AnimSource
"LeftDoor1","",
// etc
"LeftDoor2","RightDoor2",
etc"
};
};
};
};
</pre>
};
 
</syntaxhighlight>
===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===
==== Bones2Anims ====
<code><nowiki>
  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;
  }
</nowiki></code>
 
corresponds to model.cfg
class CfgModels
{
  ....
  class whateverModel: Default
  {
  ...
  class Animations
  {
    class RightDoor //AnimClassName
    {
      type = "translation";//AnimTransformType
      source = "rotor";    //AnimSource
      etc
 
 
 
 
====Bones2Anims====
  Bones2Anims
  Bones2Anims
  {
  {
Line 304: Line 171:
   Bone2AnimClassList  Bone2AnimClassLists[NoOfBones];
   Bone2AnimClassList  Bone2AnimClassLists[NoOfBones];
  }
  }
====Bone2AnimClassList====
==== Bone2AnimClassList ====
  Bone2AnimClassList
  Bone2AnimClassList
  {
  {
Line 311: Line 178:
  }
  }


====Anims2Bones====
==== Anims2Bones ====
  Anims2Bones
  Anims2Bones
  {
  {
Line 317: Line 184:
  }
  }


====AnimBones====
==== AnimBones ====


every lod contains an identical list of animation entries that declare the position and axis of the each animation classes
every lod contains an identical list of animation entries that declare the position and axis of the each animation classes
Line 332: Line 199:
   /*
   /*
   ** AnimationClass.AnimTransformType 8 (direct) and 9 (hide) never require axis information.  
   ** 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,  
   ** This because the "direct" (type 8) already has axis info in it is AnimationClass structure,  
   ** and "hidden" (type 9) clearly doesn't need it.
   ** and "hidden" (type 9) clearly doesn't need it.
   */
   */
Line 340: Line 207:
  }
  }


===LodFace===
=== LODFaceDefaults ===
//only when the LODFaceIndicator for that lod is false
 
  tbool            UseDefault[Header.NoOfLods];
LodFace   
  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];
  }[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.


===ODOLv40Lod===
   ODOLv4xLod
   ODOLv40Lod
   {
   {
     ulong                        nProxies;
     ulong                        nProxies;
     LodModelProxy                LodModelProxies[nProxies];
     LodProxy                      LodProxies[nProxies];             // see [[P3D Lod Proxies]]
     ulong                        nLodItems;
     ulong                        nLodItems;
     ulong                        LodItems[nLodItems];       // potentially compressed
     ulong                        LodItems[nLodItems];               // potentially compressed, except for v64 and later
     ulong                        nUsedBones;
     ulong                        nBoneLinks;
     LodUsedBone                   LodUsedBones[nUsedBones];
     LodBoneLink                   LodBoneLinks[nBoneLinks];
     LodPointProperties[...];                                 // Potentially compressed
     float                        UnknownFloat1;
    float                        UnknownFloat2;
     byte                          UnknownBytes[8];
    XYZTriplet                    MinPos;
     float                        UnknownFloats[10];
    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;
     ulong                        NoOfTextures;
     asciiz                        LodPaaTextureNames[NoOfTextures];  //"ca\characters\hhl\hhl_01_co.paa"
     asciiz                        LodPaaTextureNames[NoOfTextures];  //"ca\characters\hhl\hhl_01_co.paa"
     ulong                        NoOfMaterials;
     ulong                        NoOfMaterials;
     LodMaterial                  LodMaterials[NoOfMaterials];
     LodMaterial                  LodMaterials[NoOfMaterials];
     LodEdge                      LodEdge1;                   // potentially compressed
     LodEdges                      LodEdges;                         // compressed see [[P3D Lod Edges]]
     LodEdge                      LodEdge2;                   // potentially compressed
     ulong                        NoOfFaces;
     LodPolygons[...];
    ulong                        OffsetToSectionsStruct;           // see below
    ushort                        AlwaysZero;
     LodFace                      LodFace[NoOfFaces];               // see [[P3D Lod Faces]]
     ulong                        nSections;
     ulong                        nSections;
     LodSection                    LodSections[nSections];
     LodSection                    LodSections[nSections];           // see [[P3D Lod Sections]]
     ulong                        nNamedSelections;
     ulong                        nNamedSelections;
     LodNamedSelection            LodNamedSelections[nNamedSelections]; // potentially compressed
     LodNamedSelection            LodNamedSelections[nNamedSelections]; //See [[P3D Named Selections]] potentially compressed
     ulong                        nTokens;
     ulong                        nTokens;
     LodTokenPair                  LodTokenPairs[nTokens];
     NamedProperty                NamedProperties[nTokens];         //See [[Named Properties]]
     ulong                        nFrames;
     ulong                        nFrames;
     LodKeyFrame                  LodKeyFrames[nFrames];
     LodFrame                      LodFrames[nFrames];               //see [[P3D Lod Frames]]
    ulong                        IconColor;
     byte                          Unknown[17];
    ulong                        SelectedColor;
  if (V40 or V43)
    ulong                        special; // IsAlpha|IsTransparent|IsAnimated|OnSurface
     byte                          vertexBoneRefIsSimple;
    ulong                        sizeOfVertexTable;                 //(including these 4 bytes)
    if (v5x)
    LodPointFlags                LodPointFlags;                    // Potentially compressed
    endif
    VertexTable                  VertexTable;
  }


  {
==== VertexTable ====
     LodUV                         LodUV1;                     // Potentially compressed
all arrays are subject to compression
 
struct
{
     UvSet                         DefaultUVset;
     ulong                        nUVs;
     ulong                        nUVs;
     if (nUVs==2)
     UvSet                        UVSets[nUVs-1];
    LodUV                        LodUV2;                     // Potentially compressed
     ulong                        NoOfPoints;
     ulong                        NoOfVertices;
     XYZTriplet                    LodPoints[NoOfPoints];
     XYZTriplet                    LodXZY[NoOfVertices];       // Potentially compressed
     ulong                        nNormals;
     ulong                        nNormals;
     LodNormals                   LodNormals[nNormals];       // Potentially compressed
     (A2)LodNormals               LodNormals[nNormals];
     ulong                        nMinMax;
     ulong                        nMinMax;
     XYZTriplet                    LodMinMaxXYZ[nMinMax][2];   // Potentially compressed
     (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 [[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 ====


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


  {
struct
    A2LodUV                      A2LodUV1;                     // Potentially compressed
{
    ulong                         nUVs;
  ulong                        Count;
    if (nUVs==2)
  tbool                         DefaultFill;
    {
  if (DefaultFill)
    float                        A2Floats[4];
  type                        Array;         // default fill for all Counts
    A2LodUV                      A2LodUV2;                     // Potentially compressed
  else
    }
  type                         Array[Count];   // potentially compressed
    ulong                         NoOfVertices;
}
    XYZTriplet                    LodXZY[NoOfVertices];       // Potentially compressed, same as arma1
 
     ulong                         nNormals;
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.
    A2LodNormals                  A2LodNormals[nNormals];       // Potentially compressed
==== UVset ====
     ulong                        nMinMax;
if TrueARMA2
    float                        LodMinMaxXYZ[nMinMax][2];  // Potentially compressed
     float                         UVScale[4];
  }
endif
      // Note that nUV1's== nNormals == NoOfVertices
     (A2)LodUV                    LodUV;
   
 
    ulong                        Count;
==== LodUV ====
    LodUnknownStruct              UnknownLodStruct[Count];    // Potentially compressed
CompressedFill type = UVPair // eg float U,V;
    ulong                        nBytes;
==== A2LodUV ====
    byte                          UnknownBytes[nBytes][32];   // Potentially compressed
  CompressedFill type = float // eg float UV;
   }
==== LodNormals ====
   CompressedFill type = XYZTriplet
==== A2LodNormals ====
   CompressedFill type = CompressedXYZTriplet


====LodModelProxy====
===== CompressedXYZTriplet =====
  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.
contains 3x 10 bit fields in a 32bit 'integer'


====LodUsedBone====
code for converting back to a standard XYZTriplet is:


  LodUsedBone
  void DecodeXYZ(ulong CompressedXYZ, XYZTriplet *triplet)
  {
  {
  ulong nIDs;         //range 0..3
    double scaleFactor = -1.0 /511;
  ulong BoneID[nIDs];
    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);
  }
  }


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.
==== LodPointFlags ====
CompressedFill type = ulong bits


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


This table is the equivalent of Oxygen's points->properties dialog box
In ODOl7 it was part of the vertex table. In ArmA, it is separate.


it specifically stores the user values and other flags for that point
See [[P3D Point and Face Flags]]


See Points Flags below
==== 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
}


  LodPointProperties
==== VertexNeighborInfo ====
  CompressedArray
  {
  {
   ulong NoOfPts;
   ushort vertexIndex _posA;
   tbool  UseDefault;
   AnimationRTWeight _rtwA;
   if (UseDefault)
   ushort  vertexIndex _posB;
  ulong DefaultValue;
   AnimationRTWeight _rtwB;
   else // =0
  ulong PropertyValues[NoOfPts]; // potentially compressed
  }
  }


PropertyValues for NoOfPts are either all the same (UseDefault), or, they are individually declared.
==== LodBoneLink ====
 
LodBoneLink
{
  ulong NoOfLinks;        //range 0..3
  ulong Value[NoOfLinks];  //the 'Value' seems to reference the 'LodItems' structure, resulting in a circular-reference.
}


Similar to CompressedStructs of OdolV7, if the amount of data in the array exceeds 1023 bytes, that array is compressed.
==== LodMaterials ====
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


The use of a) potential compression and b) a default fill, is endemic to many ODOLV40 type packets.


====LodMaterial====
====LodMaterial====
    //Basically... A direct replication of the information in the given .rvmat file
   LodMaterial
   LodMaterial
   {
   {
     asciiz            RvMatName;    // "ca\characters\data\soldier_captive_hhl.rvmat"
     asciiz            RvMatName;    // "ca\characters\data\soldier_captive_hhl.rvmat"
     ulong            Type;          // 9 == Arma, 10==VBS2
     ulong            Type;          // 9 == ArmA
     D3DCOLORVALUE    Emissive;
     D3DCOLORVALUE    Emissive;
     D3DCOLORVALUE    Ambient;
     D3DCOLORVALUE    Ambient;
Line 478: Line 427:
     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          mainLight;     // 1 or zero
     ulong            AnIndex;       //0,1 or 2
     ulong            ul_FogMode;   /// 0..4
     asciiz           BiSurfaceName; // "ca\data\Penetration\plastic.bisurf"
     Asciiz           BiSurfaceName;   // "ca\data\Penetration\plastic.bisurf"
     ulong            Always0x01;
     LongBool          Arma1Mostly1;   //rarely zero
     ulong            aCount;       //Generally 0
     ulong            RenderFlags;     //Generally 0
     ulong            nTextures;
     ulong            nTextures;
     ulong            nTransforms;   // always same as nTextures
     ulong            nTransforms;     // always same as nTextures
     LodStageTexture  StageTextures  [nTextures];
     LodStageTexture  StageTextures  [nTextures];
     LodStageTransform StageTransforms[nTransforms];  
     LodStageTransform StageTransforms[nTransforms];
   }   
   }   


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


=====D3DCOLORVALUE=====
=== 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


=====LodStageTexture=====
===== LodStageTexture =====
  LodStageTexture
  LodStageTexture
  {
  {
Line 508: Line 467:
   asciiz PaaTexture;    // "ca\characters\data\civil_tvreport_body_as.paa
   asciiz PaaTexture;    // "ca\characters\data\civil_tvreport_body_as.paa
                         // alternatively "#(argb,8,8,3)color(0,0,0,1,CO)" (eg)
                         // alternatively "#(argb,8,8,3)color(0,0,0,1,CO)" (eg)
   ulong  StageID;      // zero based, see below
   ulong  TransformIndex;      // zero based, see below
  };
  };


:The StageID is iterative (linear sequential). 1st entry is 0, 2nd 1, 3rd 2, etc.
:The first stageTexture is a dummy entry. For N humanly readable stage classes, there are 1+N LodStageTextures


:TextureFilter maybe 1 of the following values.  
:TextureFilter maybe 1 of the following values.  
Line 519: Line 478:
:*3: Anisotropic (default)
:*3: Anisotropic (default)


=====LodStageTransform=====
===== LodStageTransform =====
   LodStageTransform
   LodStageTransform
   {
   {
Line 526: Line 485:
   };
   };


====LodEdge====
:UVSource corresponds to the 8 possible uvsets available
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.
:*0 "None"
*4 point vertices describe a rectangle.
:*1 "Tex" default
:*2: "Tex2"
:*........
:*8:"Tex8"


The indices must be transformed as follows
:*Tex1..8 cannot be taken literally as uvsource 1..8. They can mean anything, according to the template and are scarcely encountered
*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
==== NamedProperty ====
 
   struct
    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;
    Asciiz Property;// "noshadow" = "1" eg
    Asciiz Value;
   }
   }
  byte  Something6[2];
  ulong Something7;
  float Something8;
  float Something9;
}
====LodNamedSelection====


  LodNamedSelection
*See [[Named Properties]]
  {
    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====
== Decompression ==
  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)
see [[Compressed LZSS File Format]]


If a full array is declared (DefaultFill != 0) then that array is compressed if 2 * sizeof(float) * nVertices > 1023
see [[Compressed LZO File Format]]


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.


 
In ODOL v40 format files, some of the data structures present in the file are compressed by using LZSS compression.
====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.
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.
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>''Note:- Data structures that are identified as being compressible will only be compressed if the 'expectedSize' is >= 1024 bytes.''</b>
 
 
<b>''The code that follows is written in C# and may or may not be optimal or correct.''</b>
 
 
 
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.
 
 
<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>
 
 
===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
 
<pre><nowiki>
 
// 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;
{{Feature|informative|Data structures that are identified as being compressible will only be compressed if the 'expectedSize' is &ge; 1024 bytes.}}
            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
}
</nowiki></pre>
----


== 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 1,116: Line 526:
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 1,139: Line 549:
|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 1,147: Line 557:
|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 1,159: Line 569:
|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 1,191: Line 601:
|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
|}
|}


----


== Enums ==
== Enums ==


<code><nowiki>
<code style="display: block"><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>
  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>
----
==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 ==


[[User:Sy|Article Author - Sy (Synide)]] -- [[User:Sy|Sy]] 17:16, 11 August 2007 (CEST)
[[User:Sy|Article Author - Sy (Synide)]] -- [[User:Sy|Sy]] 17:16, 11 August 2007 (CEST)


[[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:Real Virtuality File Formats]]
[[Category:ArmA: File Formats]]
{{GameCategory|arma1|File Formats}}

Latest revision as of 13:27, 8 May 2025

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

All file references are absolute to the Linux \ (root).

As a convenience A drive letter is used (such as P:\) for sanity when modelling. The engine knows nothing about C:\my documents or even P:

The leading \ is optional.

\my_project\data\some.paa AND my_project\data\some.paa

point to the same file.

Versions

This Document covers ODOL versions:

V40 (Armed Assault)

  • Original Armed Assault binarised p3d


File Format

ODOLv4x
{
	StandardP3DHeader Header;
    struct ModelInfo;
	Animations		Animations;
	ulong			StartAddressOfLods[Header.NoOfLods]; // offset relative to start of file.
	ulong			EndAddressOfLods  [Header.NoOfLods];
	LODFaceDefaults	LODFaceDefaults;
	ODOLv40Lod		ODOLv40Lods[Header.NoOfLods];
} // EndOfFile


Structures

StandardP3DHeader

Common header structure for all P3D file formats.

struct
{
	char[4]	Filetype;		// "ODOL"
	ulong	Version;
	ulong	NoOfLods;		// alias NoOfResolutions;
}

ModelInfo

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];
 }[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.
 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];
   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;
 }

VertexTable

all arrays are subject to compression

struct
{
   UvSet                         DefaultUVset;
   ulong                         nUVs;
   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 3x 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.
}

LodMaterials

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

 LodMaterial
 {
   asciiz            RvMatName;     // "ca\characters\data\soldier_captive_hhl.rvmat"
   ulong             Type;          // 9 == ArmA
   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          mainLight;     // 1 or zero
   ulong             ul_FogMode;    /// 0..4
   Asciiz            BiSurfaceName;   // "ca\data\Penetration\plastic.bisurf"
   LongBool          Arma1Mostly1;    //rarely zero
   ulong             RenderFlags;     //Generally 0
   ulong             nTextures;
   ulong             nTransforms;     // always same as nTextures
   LodStageTexture   StageTextures  [nTextures];
   LodStageTransform StageTransforms[nTransforms];
 }  
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.

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
};
The first stageTexture is a dummy entry. For N humanly readable stage classes, there are 1+N LodStageTextures
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 format files, some of the data structures present in the file are compressed by using LZSS 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 is original format and size.

Data structures that are identified as being compressible will only be compressed if the 'expectedSize' is ≥ 1024 bytes.


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)