ZenGin Animations¶
Quick Infos
Type: Asset Format
Format Name: Model Animation
File Extension: .MAN
Class Name: zCModelAni
Encoding: Binary, Chunked
ZenGin animation files contain skeletal animations for rigged meshes. Each file contains the data for one animation, including metadata and animation samples.
Animations are found within the Anims.vdf
file found in the Data/
directory in the Gothic installation.
Format Description¶
Model animations are stored in a chunked binary file which contains the following chunks. Also refer to the Datatype Reference for general information about often used datatypes.
The marker chunks marks the beginning of a model animation file.
struct zCModelAni_Source {
zDATE date; // Broken. See below.
string sourcePath;
string sourceScript;
};
This source chunks was originally written to the file using a function called
zCFileBIN::WriteBinSrcFileStats
. It has a bug which causes it to use an uninitialized
zDATE
and write it to the output file. This is why the values in the date don't make
any sense. Tread with care.
struct zCModelAni_Samples {
uint checksum;
uint nodeIndices[/* zCModelAni_Header.numNodes */];
struct zTMdl_AniSample {
ushort rotation[3];
ushort position[3];
} samples[/* zCModelAni_Header.numNodes * zCModelAni_Header.numFrames */];
};
The zCModelAni.checksum
field is used to match compatible the animation to a specific model. It is the same for
assets belonging to the same model. Linked assets are zCModelAni.checksum
, zCModelMeshLib.checksum
and
zCModelHierarchy.checksum
.
It is important to understand that the animation samples do not contain the actual values for the rotation and position. To save on memory and disk space consumption, the values are packed into shorts which are converted back to floats on demand. See Sample Positions and Sample Rotations for more information.
Sample Positions¶
The positions are represented as multiples of zCModelAni_Header.samplePositionScale
. The formula for packing a
given zVEC3
position into the stored format can be summarized like this:
where \(v_{min}\) is the smallest value across all components of all sample positions of the animation and \(v_{max}\)
is the maximum value. The output vector \(\begin{smallmatrix}x_{p} \\ y_{p} \\ z_{p} \end{smallmatrix}\) then contains a
scaled representation of the input vector \(\begin{smallmatrix}x \\ y \\ z\end{smallmatrix}\) as three 16-bit unsigned
integers. This operation is performed by zCModelAni::AddTrafoMatrix
and zTMdl_AniSample::PackTrans
.
Alongside these packed positions, both \(v_{min}\) and the constant \(s = (\frac{65535}{v_{max} - v_{min}})^{-1}\) are saved
as zCModelAni_Header.samplePositionMin
and zCModelAni_Header.samplePositionScale
respectively. Thus, to convert
the stored position back to its floating point representation, the following calculation may be applied:
where \(x_{p}\) , \(y_{p}\) and \(z_{p}\) are zTMdl_AniSample.position[0]
, zTMdl_AniSample.position[1]
and
zTMdl_AniSample.position[2]
respectively. This operation is performed by zTMdl_AniSample::UnpackTrans
.
Sample Rotations¶
The rotations are represented as a packed quaternion which is calculated in the following way:
where
and
This operation packs a quaternion of the form \(r = \begin{smallmatrix}x \\ y \\ z \\ w\end{smallmatrix}\) into a set of
three 16-bit unsigned integers \(x_{p}\) , \(y_{p}\) and \(z_{p}\) which are stored into the sample. The input quaternion
\(r\) is required to be a unit vector. This operation was performed by zCModelAni::AddTrafoMatrix
and
zTMdl_AniSample::PackQuat
.
They can be unpacked using some quaternion wizardry as described below.
where
and
The values \(x_{p}\) , \(y_{p}\) and \(z_{p}\) for this computation are stored in zTMdl_AniSample.rotation[0]
,
zTMdl_AniSample.rotation[1]
and zTMdl_AniSample.rotation[2]
respectively. This is possible since the quaternion
\(r\) used to calculate the packed rotation is guaranteed to be a unit-quaternion, thus, if we have three components
(like we do), it is possible to calculate the third component. This operation is performed by
zTMdl_AniSample::UnpackQuat
.
(todo) How animated models are loaded¶
Animated models are loaded by using their associated model script file. While parsing the model script, the parser
will encounter a meshAndTree
directive (in MSB files a chunk of type 0xf300
) which points to a model hierarchy.
This model hierarchy is then loaded and acts as the ground truth of the model. If a hierarchy can't be loaded for any reason, the engine tries to load a model mesh instead.
The model script parser continues and eventually hits the aniEnum
section. While loading it, it will come across
ani
sections. Those sections contain a model and animation name which are then loaded as animations and model meshes
respectively.
Linking these parts together is a checksum calculates from the list of hierarchy nodes. It is just the CRC32 checksum
of all node names appended after each other in the order they are saved in. I.e. a model hierarchy with two nodes
"BIP01"
and "BIP01 LEVER STICK"
(in that order) will have a checksum of ea809bf7
(which
is crc32("BIP01BIP01 LEVER STICK")
.