P3D File Format - ODOLV4x: Difference between revisions
m (→structProxy: moved) |
m (→LodMaterial) |
||
(244 intermediate revisions by 10 users not shown) | |||
Line 1: | Line 1: | ||
{{ | {{TOC|side}} | ||
{{Feature|UnsupportedDoc}} | |||
== 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, | 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 [[P3D Model Info|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 ({{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 | |||
==== V50 (Arrowhead DLC) ==== | |||
Introduced in PMC and BAF DLC | |||
* additional count after usedbones | |||
*lodPointFlags swapped around | |||
=== | ==== V53 (Dayz) ==== | ||
*edges set to zero | |||
*added Specular and Specular2 to rvmats | |||
*added SpecularPower and SpecularPower2 stage info type 15 to rvmats | |||
* added htMin,htMaz etc thermal info to rvmat stage infos (same as vbs) | |||
*added disableCover | |||
*added optional; bisurf data | |||
==== V54 (Dayz) ==== | |||
bool added to [[P3D Model Info|ModelInfo]] | |||
==== 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 ({{arma3}}) ==== | |||
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 ({{arma3}}) ==== | |||
* prefix added to header | |||
* default indicators added at end of headerinfo | |||
==== V59 ({{arma3}}) ==== | |||
* no genuine changes simply an alteration of the type value from 32 to 64 bits | |||
==== 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 | |||
==== V67 ({{arma3}}) ==== | |||
* 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 | |||
==== V69 ({{arma3}}) ==== | |||
edges changed from shorts to longs | |||
face structure changed from shorts to longs | |||
face indices ditto | |||
point_indices changed from shorts to longs | |||
==== V70 ({{arma3}}) ==== | |||
float ModelInfo.lodDensityCoef added | |||
==== V71 ({{arma3}}) ==== | |||
float ModelInfo.drawImportance added | |||
==== V72 ({{arma3}}) ==== | |||
float ModelInfo.explosionshielding added | |||
==== V72 ({{arma3}}) ==== | |||
bool ModelInfo.disableCover added | |||
==== V75 ({{arma3}}) ==== | |||
optional encrypted p3d signature | |||
== | == File Format == | ||
<syntaxhighlight lang="cpp"> | |||
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]; | |||
if (any arma3 type) | |||
{ | |||
long Always0; | |||
A3_Physx A3_Physx[...]; // optional if not v60 | |||
} | |||
} // EndOfFile | |||
</syntaxhighlight> | |||
== Structures == | == Structures == | ||
=== StandardP3DHeader === | === StandardP3DHeader === | ||
common header structure for all P3D file formats | common header structure for all P3D file formats | ||
=== | <syntaxhighlight lang="cpp"> | ||
struct | |||
{ | |||
char[4] Filetype; // "ODOL" | |||
ulong Version; | |||
if Version>=75 (arma3) | |||
</ | ulong enc1; | ||
ulong enc2; | |||
if enc1 or enc2 | |||
return encrypted | |||
if Version>=58 (arma3) | |||
ulong appid; | |||
if Version==58 (arma3) | |||
Asciiz P3dPrefix; // \a3\data_f\proxies\muzzle_flash\muzzle_flash_rifle_mk20 | |||
ulong NoOfLods; // alias NoOfResolutions; | |||
} | |||
</syntaxhighlight> | |||
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") | |||
[[P3D Model Info|ModelInfo]] | |||
=== Animations === | |||
= | <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 | |||
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 | |||
}; | |||
}; | |||
}; | |||
</syntaxhighlight> | |||
==== 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 [[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 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. | |||
} | } | ||
=== | |||
==== 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, 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 mainLight; // 1 or zero | |||
ulong ul_FogMode; /// 0..4 | |||
Asciiz BiSurfaceName; // "ca\data\Penetration\plastic.bisurf" | |||
LongBool Arma1Mostly1; //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; | |||
float | |||
} | } | ||
*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; | |||
} | |||
*See [[Named Properties]] | |||
== Decompression == | |||
see [[Compressed LZSS File Format]] | |||
see [[Compressed LZO File Format]] | |||
This | 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>''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... | |||
= | * 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 === | === Material Stages === | ||
Line 873: | Line 796: | ||
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 896: | Line 819: | ||
|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"| | |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 904: | Line 827: | ||
|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"|0x09, 9||align="left"|?||align="left"|vbs2||align="left"|? | ||
|- | |- | ||
|align="left"|0x0A, 10||align="left"|White||align="left"| | |align="left"|0x0A, 10||align="left"|White||align="left"|A1 only||align="left"|0 | ||
|- | |- | ||
|align="left"|0x0B, 11||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 916: | Line 839: | ||
|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"|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"|0x10, 16||align="left"|?||align="left"|vbs2||align="left"|? | ||
|- | |- | ||
|align="left"|0x11, 17||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 947: | Line 870: | ||
|- | |- | ||
|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 955: | Line 892: | ||
== Enums == | == Enums == | ||
<code><nowiki> | |||
<code style="display: block"><nowiki> | |||
int enum VertexShaderId | 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"; | |||
} | } | ||
</nowiki></code> | </nowiki></code> | ||
Line 989: | Line 928: | ||
[[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]] | ||
{{GameCategory|arma1|File Formats}} |
Latest revision as of 07:24, 8 November 2024
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
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:
- 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
V50 (Arrowhead DLC)
Introduced in PMC and BAF DLC
- additional count after usedbones
- lodPointFlags swapped around
V53 (Dayz)
- edges set to zero
- added Specular and Specular2 to rvmats
- added SpecularPower and SpecularPower2 stage info type 15 to rvmats
- added htMin,htMaz etc thermal info to rvmat stage infos (same as vbs)
- added disableCover
- added optional; bisurf data
V54 (Dayz)
bool added to ModelInfo
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
V69 (Arma 3)
edges changed from shorts to longs face structure changed from shorts to longs face indices ditto point_indices changed from shorts to longs
V70 (Arma 3)
float ModelInfo.lodDensityCoef added
V71 (Arma 3)
float ModelInfo.drawImportance added
V72 (Arma 3)
float ModelInfo.explosionshielding added
V72 (Arma 3)
bool ModelInfo.disableCover added
V75 (Arma 3)
optional encrypted p3d signature
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];
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;
if Version>=75 (arma3)
ulong enc1;
ulong enc2;
if enc1 or enc2
return encrypted
if Version>=58 (arma3)
ulong appid;
if Version==58 (arma3)
Asciiz P3dPrefix; // \a3\data_f\proxies\muzzle_flash\muzzle_flash_rifle_mk20
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.
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, 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 mainLight; // 1 or zero ulong ul_FogMode; /// 0..4 Asciiz BiSurfaceName; // "ca\data\Penetration\plastic.bisurf" LongBool Arma1Mostly1; //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; }
- See Named Properties
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)