Bayonetta/Bayonetta 2/Nier Automata/TD/TMNT: MM/MGRR/Astral Chain/TW101 Motion Files

Skeletons, animations, shaders, texturing, converting, fixing and anything else related to read game models
Kerilk
Posts: 18
Joined: Sun Aug 27, 2017 1:21 pm

Bayonetta/Bayonetta 2/Nier Automata/TD/TMNT: MM/MGRR/Astral Chain/TW101 Motion Files

Post by Kerilk »

Okay, after implementing Bayonetta 2 motions in the plugin I though I would make a recapitulation of the findings contained in the previous posts. I will be using Binary Templates from https://www.sweetscape.com/010editor/templates.html to describe the file structure. Thanks again Alquazar for the help.

pghalf stands for half floats with 1 bit sign 6 bit exponent 9 bit significand and a bias of 47.

Edit: added Nier Automata, TRANSFORMERS: Devastation PC, Teenage Mutant Ninja Turtles: Mutants in Manhattan, Astral Chain motion format. Identical to Bayonetta 2 except that for motion type 8 one index is stored in big endian format (WTF...)

Thanks to the administrator that gave me edit rights on my posts.

Bayonetta 1 (PC version):

Code: Select all

LittleEndian();

typedef uint16 pghalf;

struct {
   char      id[4]; // "mot\0"
   uint16    flag; //sometimes seem to indicate that out of bound bone index are used no idea why.
   int16     frameCount;
   uint32    recordOffset;
   uint32    recordNumber;
} header;

FSeek( header.recordOffset );

struct {
   int16    boneIndex;
   char     index;
   char     flag;
   int16    elemNumber;
   int16    unknown; //always -1
   union {
      float    flt;
      uint32   offset;
   } value;
} records[header.recordNumber];

local int i;
for ( i = 0; i < header.recordNumber; i++) {

   // 0: constant value for each frame. The value is in records[i].value.flt. -1: only found on terminator (bone index 0x7fff).
   if( records[i].flag == 0 || records[i].flag == -1)
      continue;

   FSeek( records[i].value.offset );

   switch ( records[i].flag ) {

   // usually one value per frame, if some are missing the last is to be repeated
   case 1:
        struct {
          float values[records[i].elemNumber];
        } interpol1;
      break;

   // quantized hermit spline coeffs values at key point index :
   // value: p + dp * cp
   // incoming derivative: m0 + dm0 * cm0
   // outcoming derivative: m1 + dm1 * cm1
   // if some ranges are missing at start or after last index, first value or last value should be repeated for missing values.
   case 4:
        struct {
          struct {
             float p;
             float dp;
             float m0;
             float dm0;
             float m1;
             float dm1;
          } values;
          struct {
             uint16 index; // absolute frame index
             uint16 cp;
             uint16 cm0;
             uint16 cm1;
          } keys[records[i].elemNumber];
        } interpol4;
      break;

   // same as 4 with reduced precision and relative frame index encoding
   case 6:
        struct {
          struct {
             pghalf p;
             pghalf dp;
             pghalf m0;
             pghalf dm0;
             pghalf m1;
             pghalf dm1;
          } values;
          struct {
             ubyte index; // frame index relative to previous key
             ubyte cp;
             ubyte cm0;
             ubyte cm1;
          } keys[records[i].elemNumber];
        } interpol6;
      break;

   // same as 6 but with absolute frame index (at least one relative frame index would have been > 255)
   case 7:
        struct {
          struct {
         pghalf p;
             pghalf dp;
             pghalf m0;
             pghalf dm0;
             pghalf m1;
             pghalf dm1;
          } values;
          struct {
             uint16 index; // absolute frame index
             ubyte dummy;
             ubyte cp;
             ubyte cm0;
             ubyte cm1;
          } keys[records[i].elemNumber];
        } interpol7;
      break;

   // unkown flag, I have yet to find another for Bayonetta
   default:
      break;
   }
}


Bayonetta 2 (WiiU version):

Code: Select all

BigEndian();

typedef uint16 pghalf;

struct {
   char      id[4]; // "mot\0"
   uint32    hash;
   uint16    flag;
   int16     frameCount;
   uint32    recordOffset;
   uint32    recordNumber;
   uint32    unknown; // usually 0 or 0x003c0000, maybe two uint16
   string    animName; // found at most 12 bytes with terminating 0
} header;


FSeek( header.recordOffset );

struct RECORD{
   int16    boneIndex;
   char     index;
   char     flag;
   int16    elemNumber;
   int16    unknown; //always -1
   union {
      float    flt;
      uint32   offset;
   } value;
} records[header.recordNumber];

local int i;
for ( i = 0; i < header.recordNumber; i++) {

   // 0: constant value for each frame. The value is in records[i].value.flt. -1: only found on terminator (bone index 0x7fff).
   if( records[i].flag == 0 || records[i].flag == -1)
      continue;

   FSeek( header.recordOffset + i * sizeof(RECORD) + records[i].value.offset );

   switch ( records[i].flag ) {

   // usually one value per frame, if some are missing the last is to be repeated
   case 1:
        struct {
          float values[records[i].elemNumber];
        } interpol1;
      break;

   // same as 1 but with quantized data
   // value: p + dp * cp;
   case 2:
        struct {
          struct {
             float p;
             float dp;
          } values;
          uint16 cp[records[i].elemNumber];
        } interpol2;
      break;

   // same as 2 but with reduced precision
   case 3:
        struct {
          struct {
             pghalf p;
             pghalf dp;
          } values;
          ubyte cp[records[i].elemNumber];
        } interpol3;
      break;

   // spline coeffs values at key point index :
   // value: p
   // incoming derivative: m0
   // outcoming derivative: m1
   // if some ranges are missing at start or after last index, first value or last value should be repeated for missing values.
   case 4:
        struct {
          struct {
             uint16 index; // absolute frame index
             uint16 dummy;
             float p;
             float m0;
             float m1;
          } keys[records[i].elemNumber];
        } interpol4;
      break;

   // same as 4 but with quantized values:
   // value: p + dp * cp
   // incoming derivative: m0 + dm0 * cm0
   // outcoming derivative: m1 + dm1 * cm1
   // if some ranges are missing at start or after last index, first value or last value should be repeated for missing values.
   case 5:
        struct {
          struct {
             float p;
             float dp;
             float m0;
             float dm0;
             float m1;
             float dm1;
          } values;
          struct {
             uint16 index; // absolute frame index
             uint16 cp;
             uint16 cm0;
             uint16 cm1;
          } keys[records[i].elemNumber];
        } interpol5;
      break;

   // same as 5 with reduced precision
   case 6:
        struct {
          struct {
             pghalf p;
             pghalf dp;
             pghalf m0;
             pghalf dm0;
             pghalf m1;
             pghalf dm1;
          } values;
          struct {
             ubyte index; // absolute frame index
             ubyte cp;
             ubyte cm0;
             ubyte cm1;
          } keys[records[i].elemNumber];
        } interpol6;
      break;

   // same as 6 with relative frame index encoding
   case 7:
        struct {
          struct {
             pghalf p;
             pghalf dp;
             pghalf m0;
             pghalf dm0;
             pghalf m1;
             pghalf dm1;
          } values;
          struct {
             ubyte index; // frame index relative to previous key
             ubyte cp;
             ubyte cm0;
             ubyte cm1;
          } keys[records[i].elemNumber];
        } interpol7;
      break;


   // same as 7 but with absolute frame index (at least one relative frame index would have been > 255)
   case 8:
        struct {
          struct {
             pghalf p;
             pghalf dp;
             pghalf m0;
             pghalf dm0;
             pghalf m1;
             pghalf dm1;
          } values;
          struct {
             uint16 index; // absolute frame index
             ubyte cp;
             ubyte cm0;
             ubyte cm1;
          } keys[records[i].elemNumber];
        } interpol8;
      break;
   // unkown flag, I have yet to find another for Bayonetta 2
   default:
      break;
   }
}


Nier Automata/TRANSFORMERS: Devastation (PC version)/Bayonetta 2 Swotch/Metal Gear Rising: Revengeance:

Code: Select all

LittleEndian();

typedef uint16 pghalf;

struct {
    char      id[4]; // "mot\0"
    uint32    hash;
    uint16    flag;
    int16     frameCount;
    uint32    recordOffset;
    uint32    recordNumber;
    uint32    unknown; // usually 0 or 0x003c0000, maybe two uint16
    string    animName; // found at most 12 bytes with terminating 0
} header;

FSeek( header.recordOffset );

struct RECORD{
    int16    boneIndex;
    char     index;
    char     flag;
    int16    elemNumber;
    int16    unknown; //always -1
    union {
        float    flt;
        uint32   offset;
    } value;
} records[header.recordNumber];

local int i;
for ( i = 0; i < header.recordNumber; i++) {

    // 0: constant value for each frame. The value is in records[i].value.flt. -1: only found on terminator (bone index 0x7fff).
    if( records[i].flag == 0 || records[i].flag == -1)
        continue;

    FSeek( header.recordOffset + i * sizeof(RECORD) + records[i].value.offset );

    switch ( records[i].flag ) {

    // usually one value per frame, if some are missing the last is to be repeated
    case 1:
        struct {
            float values[records[i].elemNumber];
        } interpol1;
        break;

    // same as 1 but with quantized data
    // value: p + dp * cp;
    case 2:
        struct {
            struct {
                float p;
                float dp;
            } values;
            uint16 cp[records[i].elemNumber];
        } interpol2;
        break;

    // same as 2 but with reduced precision
    case 3:
        struct {
            struct {
                pghalf p;
                pghalf dp;
            } values;
            ubyte cp[records[i].elemNumber];
        } interpol3;
        break;

    // spline coeffs values at key point index :
    // value: p
    // incoming derivative: m0
    // outcoming derivative: m1
    // if some ranges are missing at start or after last index, first value or last value should be repeated for missing values.
    case 4:
        struct {
            struct {
                uint16 index; // absolute frame index
                uint16 dummy;
                float p;
                float m0;
                float m1;
            } keys[records[i].elemNumber];
        } interpol4;
        break;

    // same as 4 but with quantized values:
    // value: p + dp * cp
    // incoming derivative: m0 + dm0 * cm0
    // outcoming derivative: m1 + dm1 * cm1
    // if some ranges are missing at start or after last index, first value or last value should be repeated for missing values.
    case 5:
        struct {
            struct {
                float p;
                float dp;
                float m0;
                float dm0;
                float m1;
                float dm1;
            } values;
            struct {
                uint16 index; // absolute frame index
                uint16 cp;
                uint16 cm0;
                uint16 cm1;
            } keys[records[i].elemNumber];
        } interpol5;
        break;


    // same as 5 with reduced precision
    case 6:
        struct {
            struct {
                pghalf p;
                pghalf dp;
                pghalf m0;
                pghalf dm0;
                pghalf m1;
                pghalf dm1;
            } values;
            struct {
                ubyte index; // absolute frame index
                ubyte cp;
                ubyte cm0;
                ubyte cm1;
            } keys[records[i].elemNumber];
        } interpol6;
        break;

    // same as 6 with relative frame index encoding
    case 7:
        struct {
            struct {
                pghalf p;
                pghalf dp;
                pghalf m0;
                pghalf dm0;
                pghalf m1;
                pghalf dm1;
            } values;
            struct {
                ubyte index; // frame index relative to previous key
                ubyte cp;
                ubyte cm0;
                ubyte cm1;
            } keys[records[i].elemNumber];
        } interpol7;
        break;

    // same as 7 but with absolute frame index (at least one relative frame index would have been > 255)
    case 8:
        struct {
            struct {
                pghalf p;
                pghalf dp;
                pghalf m0;
                pghalf dm0;
                pghalf m1;
                pghalf dm1;
            } values;
            struct {
                BigEndian();
                uint16 index; // absolute frame index (in big endian order!!!!)
                LittleEndian();
                ubyte cp;
                ubyte cm0;
                ubyte cm1;
            } keys[records[i].elemNumber];
        } interpol8;
        break;

    default:
        break;
    }
}


Using this code in the scripts displays the pgfloats correctly.

Code: Select all

// 1 sign bit, 6 exponent bit, 9 bit significand, 47 bias
typedef uint16 pghalf<read=pghalfRead>;

string pghalfRead( pghalf value )
{
    double f = 0.0;
    uint32 ui = value;
    uint32 sign = ui & 0x8000;
    ui = ui ^ sign;

    uint32 exponent = ui & 0x7E00;
    uint32 significand = ui ^ exponent;
    int i;
    int bit = 0x1 << 8;
    for ( i = 1; i <= 9; i++ ) {
        if ( bit & significand ) {
            f += Pow(2.0, -i);
        }
        bit >>= 1;
    }
    string s;

    int32 sexponent;
    if ( exponent == 0x7E00 ) {
        if (significand) {
            SPrintf( s, "NaN" );
        } else if (sign) {
            SPrintf( s, "-Infinity" );
        } else {
            SPrintf( s, "+Infinity" );
        }
   } else if ( exponent != 0 ) {
        exponent >>= 9;
        sexponent = exponent;
        sexponent -= 47;
        f += 1.0;
        f *= Pow(2.0, sexponent);
        if (sign) {
            f *= -1.0;
        }
        SPrintf( s, "%e", f );
   } else {
        if (significand) { //denorm
            if( sign ) {
                f *= -1;
            }
            f *= Pow(2.0, -46.0);
            SPrintf( s, "%e", f );
        } else if (sign) {
            SPrintf( s, "-0.0" );
        } else {
            SPrintf( s, "0.0" );
        }
    }
    return s;
}


Values Mapping:

for Bayonetta triplets are always fully given, seems to be relaxed in Bayonetta 2

index 0: translation along axis 0
index 1: translation along axis 1
index 2: translation along axis 2

index 3: rotation around axis 0
index 4: rotation around axis 1
index 5: rotation around axis 2

index 7: scaling along axis 0
index 8: scaling along axis 1
index 9: scaling along axis 2

missing tripplets (or values for Bayonetta 2) have default values:
translations have bone relative offset as default values
rotations have 0.0 as default values
scalings have 1.0 as default values

in noesis rotations are applied in this order:
-rotate[2]
+rotate[1]
-rotate[0]

Interpolation Mechanism:

interpolation values are given as:
between keys i and i+1 and thus index between keys[i].index and keys[i+1].index

Code: Select all

float p0 = values.p + values.dp * keys[i].cp
float m0 = values.m1 + values.dm1 * keys[i].cm1
float p1 = values.p + values.dp * keys[i+1].cp
float m1 = values.m0 + values.dm0 * keys[i+1].cm0
float t = (float)(index - keys[i].index)/(keys[i+1].index - keys[i].index)
float val = (2*t^3 - 3*t^2 + 1)*p0 + (t^3 - 2*t^2 + t)*m0 + (-2*t^3 + 3*t^2)*p1 + (t^3 - t^2)*m1;


Bone Index Translation Table:

at offset 0x40 of the wmb model file is a 3 level bone lookup table:
here is the c function I use for conversion (0x0fff is used as forbidden bone value):

Code: Select all

static inline short int Model_Bayo_DecodeMotionIndex(const short int *table, const short int boneIndex) {
   short int index = table[(boneIndex >> 8) & 0xf];
   if ( index != -1 ) {
      index = table[((boneIndex >> 4) & 0xf) + index];
      if ( index != -1 ) {
         index = table[(boneIndex & 0xf) + index];
         return index;
      }
   }
   return 0x0fff;
}
Last edited by Kerilk on Mon Jun 22, 2020 2:28 am, edited 7 times in total.
Kerilk
Posts: 18
Joined: Sun Aug 27, 2017 1:21 pm

Re: Bayonetta Motion Files

Post by Kerilk »

Hello,

I am currently porting Noesis' Bayonetta plugin for the PC version of the game (with Rich permission). You can find the work in progress here:
https://github.com/Kerilk/noesis_bayonetta_pc

Models load correctly and most of them are more or less textured correctly. Lightmaps are missing, and some texture have transformation that are not applied.

As improving material support would require knowledge I don't have for now (and that must be buried in the binary) I am trying to add animation support.

Animations are stored in .mot files. I have more or less decoded the format but there are still things I don't understand and though that maybe some of you would have some insight about it.

The layout of the file is the following:
- header
- entries
- supplementary data

The header looks like this

Code: Select all

typedef struct bayoMOTHdr_s
{
   BYTE      id[4];
   short int   unknownA;
   short int   frameCount;
   int      ofsMotion;
   int      numEntries;
} bayoMOTHdr_t;


id is "mot\0". As far as i know unknownA is always 0 or 1. ofsMotion is always 0x10 and is the start of the entries data.
frameCount is self explanatory.

numEntries follow:

Code: Select all

typedef union bayoMotField_u
{
   float flt;
   int offset;
} bayoMotField_t;
typedef struct bayoMotItem_s
{
   short int         boneIndex;
   char            index;
   BYTE            flag;
   short int         elem_number;
   short int         unknown;
   bayoMotField_t      value;
} bayoMotItem_t;


the value is an union and can be either a float or an offset in the file. This is decided based on the value of the flag. if the flag is 0x00 then value is a float (and elem_number seems to be ignored). Here is an example (generated with this version of the plugin https://github.com/Kerilk/noesis_bayone ... d66e8efe7a):

this model has 4 bones
see: em020b_002.mot.log

value of index 0,1,2 could be position offset and value 3,4,5 could be Euler angles. Or something else I don't really know.
Sometimes you can find index 7, 8 and 9. (coud be scaling values ?)
The last record is always (as far as I can tell): 32767 -1 0xff 0 0 +0.000000



If the flag is 6 then the union is an offset into the file where you will find a 12 byte header (6 half float?) and numEntrie records. those records contain 4 bytes: one index and 3 coefficients. The index is a duration between key frames for this value (I think), thus their sum is usually frameCount - 1. I have no idea what kind of interpolation method is used here that would require 6 fixed values and 3 variable coefficient per frame.

Here is an example from the same model (as I am not entirely sure about the half floats I put the corresponding hex string below):
see: em020b_000.mot.log

If the flag is 4 then the union is an offset into the file where you will find a 24 byte header (6 float?) and numEntrie records. those records contain 8 bytes: one index and 3 coefficients. The index is the key frame index for this value. Seems like a more precise version of the method above with twice as much data.

Here is an example from another model with 38 bones:
see: em020a_045.mot.log

Flag 7 can also be found, in that case the header is 12 bytes (6 half floats) and 6 bytes per record. 2 bytes are a short int for frame index (as in flag 4) and it leaves 4 bytes for coefficient. As it is seldom found and in huge models I will not put an example here, the post is already very long.

Any Idea what those coefficients can be?

After doing some reading on motion compression techniques for skeletal animation I think the floats and integral parameters are used for quantization.

what leads me to believe that is:
- odd floats are always positive, while even floats can be any sign => odd floats are ranges, even floats a reference values
- integer parameters always explore the complete range between 0xff and 0x00 or 0xffff and 0x0000 => normalized values

so a possible formula would be:

float0 + float1 * integer0 / integer_max
float2 + float3 * integer1 / integer_max
float4 + float5 * integer2 / integer_max

but it could also be something like that:

float0 + float1 * (integer0 - integer_max/2) / (integer_max/2)
float2 + float3 * (integer1 - integer_max/2) / (integer_max/2)
float4 + float5 * (integer2 - integer_max/2) / (integer_max/2)

Summing the obtained values doesn't really make sense as the cost for storing the 3 integers is 0.75 of 1.5times the cost of a float. At this rate storing a float would have been easier for similar storage cost and good enough accuracy.
So the question that remains is why is there 3 values per key-frame. For interpolation purposes?

If anyone has an idea, I'd gladly hear it.

PS: I added the resulting log (see: bayo.log) when applying the first formula for the example with flag 6, if it can help someone make sense of the obtained values.
Last edited by Kerilk on Tue Oct 24, 2017 3:43 pm, edited 1 time in total.
Kerilk
Posts: 18
Joined: Sun Aug 27, 2017 1:21 pm

Re: Bayonetta Motion Files

Post by Kerilk »

In order to determine which component corresponds to what I programmed a motion file editor in order to modify the values of the coefficients.

here is what I found:

coefficient 0: translation along x axix (lateral respective to actor)
coefficient 1: translation along y axix (altitude respective to actor)
coefficient 2: translation along z axis (front back of actor)
coefficient 3: rotation around x axis
coefficient 4: rotation around y axis
coefficient 5: rotation around z axis

I studied more precisely rotations in type 6 interpolation.

the 6 values for the coefficient and 3 quantization values per keyframe.
half_0, half_1, half_2, half_3, half_4, half_5,
delta_0: a_00 a_01 a_02
delta_1: a_10 a_11 a_12
.....

3 values can be computed:
keyvalue_i = half0 + half1 * ai0 / 16
derivative_i0 = half2 + half3 * ai1 / 16
derivative_i1 = half4 + half5 * ai2 / 16

From what I could observe the values correspond to Bezier segments control points with 1 derivative for the incoming segment and one for the outgoing segment.

The angle is also non linear. the best approximation I could come up with, provided you want a rotation of angle alpha, is:
sqrt(2*alpha)*256
this improves precision for small angles

nonetheless it is slightly off so they must have used a different function (or I am missing something).
Alquazar
Posts: 2
Joined: Wed Sep 20, 2017 4:47 am

Re: Bayonetta Motion Files

Post by Alquazar »

The 16-bit floats follow the same format as IEEE 16-bit floats, but have a 9-bit fraction, 6-bit exponent, and exponent bias of 47.

The frame value is calculated as:
frame_i = half_0 + half_1 * a_i0

I've also encountered keyframe types 1 and 4. Type 1 is a float value for each frame. Type 4 is the same structure as 6 but uses floats and uint16s.
Kerilk
Posts: 18
Joined: Sun Aug 27, 2017 1:21 pm

Re: Bayonetta Motion Files

Post by Kerilk »

Thanks a lot for this information.

It will help me a lot. I was starting to look at crazy stuff like piece-wise linear quantizers:
http://oldweb.mit.bme.hu/books/quantiza ... -point.pdf

I will implement this format in my project and then report the results I obtain. Hopefully I should be able to add animation support for the noesis Bayonetta plugin with this.
Kerilk
Posts: 18
Joined: Sun Aug 27, 2017 1:21 pm

Re: Bayonetta Motion Files

Post by Kerilk »

Animations start to display correctly.
Many animations are not stored in the model file so I will need a way to search for them.
You can look at em0600.dat for instance (Jubileus) which contains a lot of animations. Each is stored in a different model for now, click on the skeleton at the bottom of the preview screen to switch between animations.

https://github.com/Kerilk/noesis_bayone ... tag/v0.9.2


The most tricky bit once the float format and interpolation format (cubic hermit spline is my best guess and seems to work well) are determined is that the bone index needs to be translated.

So, at offset 0x40 in the wmb file inside the dat file is an offset to a translation table.
the layout is the following:

Code: Select all

short int mask_mask[16];
short int mask[16*byteCount];
short int translationData[16*sixteensCount];


byteCount is computed the following way:

Code: Select all

int byteCount = 0;
for( int i = 0; i < 16; i++ ) {
  if( mask_mask[i] != -1 ) byteCount++;
}


and sixteensCount this way:

Code: Select all

int sixteensCount = 0;
for( int i = 0; i < 16*byteCount; i++ ) {
  if( mask[i] != -1 ) sixteensCount++;
}


Then in the best of worlds, the index in the motion can be translated to the bone index by using:

Code: Select all

int boneIndex = translationData[motionBoneIndex];


But there is a trick here. Some input index ranges are skipped and further input indexes need to be reduced accordingly:
The skipped ranges are specified in the mask data. if the mask data at index i is -1 it means the ith sixteen is to be skipped:

Code: Select all

unsigned char reductionTable[16*byteCount]; //assume index index < 0xfff, seems safe as index >= 0xf60 seem to be reserved when the unknown flag at the beginning of the motion file is 2
for( int i = 0; i < 16*byteCount; i++ ) {
  if( mask[i] != -1 ) {
    reductionTable[i] = sixteensCount;
    sixteensCount++;
  } else {
    reductionTable[i] = 0xff;
  }
}


and then an index can translated this way (if motionBoneIndex > -1 ( world transformation) and motionBoneIndex <= 0xf60 (special case for flag 2, no idea yet)) :

Code: Select all

short int boneIndex = motionBoneIndex;
short int lowerBits = boneIndex & 0xf;
boneIndex >>= 4;
boneIndex = reductionTable[boneIndex];
if( boneIndex == 0xff ) {
  //something fell in a forbidden range, couldn't find any really (which were meaningfull)
}
boneIndex <<= 4;
boneIndex |= lowerBoneIndex;
boneIndex = translationData[motionBoneIndex];


This allows them to remove bones between the source model and the ingame model, but it seems overly complicated. Anyway it works.

I still have some problems on some shoulder rotation around the vertical axis but I don't think they come from the way I read the data.
There is also the bone transformation values at index 7,8,9 which are almost always around 1 so maybe scaling data.
Alquazar
Posts: 2
Joined: Wed Sep 20, 2017 4:47 am

Re: Bayonetta Motion Files

Post by Alquazar »

I think that table is a 3-level lookup, indexed by 0x0f00, 0x00f0, 0x000f bits in the mot bone id in that order. The first two levels would be starting offsets for the next lookup in the table.

Something like this:

Code: Select all

uint16_t *bone_translation_table = ...;
uint16_t index = bone_id;
index = bone_translation_table[(bone_id >> 8) & 0xf];
if (index != 0xffff) {
   index = bone_translation_table[((bone_id >> 4) & 0xf) + index];
   if (index != 0xffff) {
      index = bone_translation_table[((bone_id >> 0) & 0xf) + index];
      if (index != 0x0fff) {
         // bone_id is index in wmb bone list
      }
   }
}
Kerilk
Posts: 18
Joined: Sun Aug 27, 2017 1:21 pm

Re: Bayonetta Motion Files

Post by Kerilk »

You are indeed correct. This is a much nicer way to look at it, much simpler than my convoluted solution. Thanks for the insight.
Kerilk
Posts: 18
Joined: Sun Aug 27, 2017 1:21 pm

Re: Bayonetta Motion Files

Post by Kerilk »

Animations in the plugin are functional. They are not always located inside the file so I provide a mechanism to load other files that would contains animations for the loaded model. Scaling transformations are not yet supported as I don't know how to put them into Noesis when not using NoeKeyframedAnims.
Kerilk
Posts: 18
Joined: Sun Aug 27, 2017 1:21 pm

Re: Bayonetta Motion Files

Post by Kerilk »

Okay, I found out how to apply those scaling values. It looks mostly good.
Kerilk
Posts: 18
Joined: Sun Aug 27, 2017 1:21 pm

Re: Bayonetta Motion Files

Post by Kerilk »

Okay, after implementing Bayonetta 2 motions in the plugin I though I would make a recapitulation of the findings contained in the previous posts. I will be using Binary Templates from https://www.sweetscape.com/010editor/templates.html to describe the file structure. Thanks again Alquazar for the help.

pghalf stands for half floats with 1 bit sign 6 bit exponent 9 bit significand and a bias of 47.

I can't seem to be able to edit my posts, so if an administrator could put this in front of the thread, or give me edit rights on the first post, it would be great.

Bayonetta 1 (PC version):

Code: Select all

LittleEndian();

typedef uint16 pghalf;

struct {
   char      id[4]; // "mot\0"
   uint16    flag; //sometimes seem to indicate that out of bound bone index are used no idea why.
   int16     frameCount;
   uint32    recordOffset;
   uint32    recordNumber;
} header;

FSeek( header.recordOffset );

struct {
   int16    boneIndex;
   char     index;
   char     flag;
   int16    elemNumber;
   int16    unknown; //always -1
   union {
      float    flt;
      uint32   offset;
   } value;
} records[header.recordNumber];

local int i;
for ( i = 0; i < header.recordNumber; i++) {

   // 0: constant value for each frame. The value is in records[i].value.flt. -1: only found on terminator (bone index 0x7fff).
   if( records[i].flag == 0 || records[i].flag == -1)
      continue;

   FSeek( records[i].value.offset );

   switch ( records[i].flag ) {

   // usually one value per frame, if some are missing the last is to be repeated
   case 1:
        struct {
          float values[records[i].elemNumber];
        } interpol1;
      break;

   // quantized hermit spline coeffs values at key point index :
   // value: p + dp * cp
   // incoming derivative: m0 + dm0 * cm0
   // outcoming derivative: m1 + dm1 * cm1
   // if some ranges are missing at start or after last index, first value or last value should be repeated for missing values.
   case 4:
        struct {
          struct {
             float p;
             float dp;
             float m0;
             float dm0;
             float m1;
             float dm1;
          } values;
          struct {
             uint16 index; // absolute frame index
             uint16 cp;
             uint16 cm0;
             uint16 cm1;
          } keys[records[i].elemNumber];
        } interpol4;
      break;

   // same as 4 with reduced precision and relative frame index encoding
   case 6:
        struct {
          struct {
             pghalf p;
             pghalf dp;
             pghalf m0;
             pghalf dm0;
             pghalf m1;
             pghalf dm1;
          } values;
          struct {
             ubyte index; // frame index relative to previous key
             ubyte cp;
             ubyte cm0;
             ubyte cm1;
          } keys[records[i].elemNumber];
        } interpol6;
      break;

   // same as 6 but with absolute frame index (at least one relative frame index would have been > 255)
   case 7:
        struct {
          struct {
             pghalf p;
             pghalf dp;
             pghalf m0;
             pghalf dm0;
             pghalf m1;
             pghalf dm1;
          } values;
          struct {
             uint16 index; // absolute frame index
             ubyte dummy;
             ubyte cp;
             ubyte cm0;
             ubyte cm1;
          } keys[records[i].elemNumber];
        } interpol7;
      break;

   // unkown flag, I have yet to find another for Bayonetta
   default:
      break;
   }
}


Bayonetta 2 (WiiU version):

Code: Select all

BigEndian();

typedef uint16 pghalf;

struct {
   char      id[4]; // "mot\0"
   uint32    hash;
   uint16    flag;
   int16     frameCount;
   uint32    recordOffset;
   uint32    recordNumber;
   uint32    unknown; // usually 0 or 0x003c0000, maybe two uint16
   string    animName; // found at most 12 bytes with terminating 0
} header;


FSeek( header.recordOffset );

struct RECORD{
   int16    boneIndex;
   char     index;
   char     flag;
   int16    elemNumber;
   int16    unknown; //always -1
   union {
      float    flt;
      uint32   offset;
   } value;
} records[header.recordNumber];

local int i;
for ( i = 0; i < header.recordNumber; i++) {

   // 0: constant value for each frame. The value is in records[i].value.flt. -1: only found on terminator (bone index 0x7fff).
   if( records[i].flag == 0 || records[i].flag == -1)
      continue;

   FSeek( header.recordOffset + i * sizeof(RECORD) + records[i].value.offset );

   switch ( records[i].flag ) {

   // usually one value per frame, if some are missing the last is to be repeated
   case 1:
        struct {
          float values[records[i].elemNumber];
        } interpol1;
      break;

   // same as 1 but with quantized data
   // value: p + dp * cp;
   case 2:
        struct {
          struct {
             float p;
             float dp;
          } values;
          uint16 cp[records[i].elemNumber];
        } interpol2;
      break;

   // same as 2 but with reduced precision
   case 3:
        struct {
          struct {
             pghalf p;
             pghalf dp;
          } values;
          ubyte cp[records[i].elemNumber];
        } interpol3;
      break;

   // spline coeffs values at key point index :
   // value: p
   // incoming derivative: m0
   // outcoming derivative: m1
   // if some ranges are missing at start or after last index, first value or last value should be repeated for missing values.
   case 4:
        struct {
          struct {
             uint16 index; // absolute frame index
             uint16 dummy;
             float p;
             float m0;
             float m1;
          } keys[records[i].elemNumber];
        } interpol4;
      break;

   // same as 4 but with quantized values:
   // value: p + dp * cp
   // incoming derivative: m0 + dm0 * cm0
   // outcoming derivative: m1 + dm1 * cm1
   // if some ranges are missing at start or after last index, first value or last value should be repeated for missing values.
   case 5:
        struct {
          struct {
             float p;
             float dp;
             float m0;
             float dm0;
             float m1;
             float dm1;
          } values;
          struct {
             uint16 index; // absolute frame index
             uint16 cp;
             uint16 cm0;
             uint16 cm1;
          } keys[records[i].elemNumber];
        } interpol5;
      break;

   // same as 5 with reduced precision
   case 6:
        struct {
          struct {
             pghalf p;
             pghalf dp;
             pghalf m0;
             pghalf dm0;
             pghalf m1;
             pghalf dm1;
          } values;
          struct {
             ubyte index; // absolute frame index
             ubyte cp;
             ubyte cm0;
             ubyte cm1;
          } keys[records[i].elemNumber];
        } interpol6;
      break;

   // same as 6 with relative frame index encoding
   case 7:
        struct {
          struct {
             pghalf p;
             pghalf dp;
             pghalf m0;
             pghalf dm0;
             pghalf m1;
             pghalf dm1;
          } values;
          struct {
             ubyte index; // frame index relative to previous key
             ubyte cp;
             ubyte cm0;
             ubyte cm1;
          } keys[records[i].elemNumber];
        } interpol7;
      break;


   // same as 7 but with absolute frame index (at least one relative frame index would have been > 255)
   case 8:
        struct {
          struct {
             pghalf p;
             pghalf dp;
             pghalf m0;
             pghalf dm0;
             pghalf m1;
             pghalf dm1;
          } values;
          struct {
             uint16 index; // absolute frame index
             ubyte cp;
             ubyte cm0;
             ubyte cm1;
          } keys[records[i].elemNumber];
        } interpol8;
      break;
   // unkown flag, I have yet to find another for Bayonetta 2
   default:
      break;
   }
}



Using this code in the scripts displays the pgfloats correctly.

Code: Select all

// 1 sign bit, 6 exponent bit, 9 bit significand, 47 bias
typedef uint16 pghalf<read=pghalfRead>;

string pghalfRead( pghalf value )
{
    double f = 0.0;
    uint32 ui = value;
    uint32 sign = ui & 0x8000;
    ui = ui ^ sign;

    uint32 exponent = ui & 0x7E00;
    uint32 significand = ui ^ exponent;
    int i;
    int bit = 0x1 << 8;
    for ( i = 1; i <= 9; i++ ) {
        if ( bit & significand ) {
            f += Pow(2.0, -i);
        }
        bit >>= 1;
    }
    string s;

    int32 sexponent;
    if ( exponent == 0x7E00 ) {
        if (significand) {
            SPrintf( s, "NaN" );
        } else if (sign) {
            SPrintf( s, "-Infinity" );
        } else {
            SPrintf( s, "+Infinity" );
        }
   } else if ( exponent != 0 ) {
        exponent >>= 9;
        sexponent = exponent;
        sexponent -= 47;
        f += 1.0;
        f *= Pow(2.0, sexponent);
        if (sign) {
            f *= -1.0;
        }
        SPrintf( s, "%e", f );
   } else {
        if (significand) { //denorm
            if( sign ) {
                f *= -1;
            }
            f *= Pow(2.0, -46.0);
            SPrintf( s, "%e", f );
        } else if (sign) {
            SPrintf( s, "-0.0" );
        } else {
            SPrintf( s, "0.0" );
        }
    }
    return s;
}


Values Mapping:

for Bayonetta triplets are always fully given, seems to be relaxed in Bayonetta 2

index 0: translation along axis 0
index 1: translation along axis 1
index 2: translation along axis 2

index 3: rotation around axis 0
index 4: rotation around axis 1
index 5: rotation around axis 2

index 7: scaling along axis 0
index 8: scaling along axis 1
index 9: scaling along axis 2

missing tripplets (or values for Bayonetta 2) have default values:
translations have bone relative offset as default values
rotations have 0.0 as default values
scalings have 1.0 as default values

in noesis rotations are applied in this order:
-rotate[2]
+rotate[1]
-rotate[0]

Interpolation Mechanism:

interpolation values are given as:
between keys i and i+1 and thus index between keys[i].index and keys[i+1].index

Code: Select all

float p0 = values.p + values.dp * keys[i].cp
float m0 = values.m1 + values.dm1 * keys[i].cm1
float p1 = values.p + values.dp * keys[i+1].cp
float m1 = values.m0 + values.dm0 * keys[i+1].cm0
float t = (float)(index - keys[i].index)/(keys[i+1].index - keys[i].index)
float val = (2*t^3 - 3*t^2 + 1)*p0 + (t^3 - 2*t^2 + t)*m0 + (-2*t^3 + 3*t^2)*p1 + (t^3 - t^2)*m1;


Bone Index Translation Table:

at offset 0x40 of the wmb model file is a 3 level bone lookup table:
here is the c function I use for conversion (0x0fff is used as forbidden bone value):

Code: Select all

static inline short int Model_Bayo_DecodeMotionIndex(const short int *table, const short int boneIndex) {
   short int index = table[(boneIndex >> 8) & 0xf];
   if ( index != -1 ) {
      index = table[((boneIndex >> 4) & 0xf) + index];
      if ( index != -1 ) {
         index = table[(boneIndex & 0xf) + index];
         return index;
      }
   }
   return 0x0fff;
}
Kerilk
Posts: 18
Joined: Sun Aug 27, 2017 1:21 pm

Re: Bayonetta/Bayonetta 2/Nier Automata Motion Files

Post by Kerilk »

Added Nier Automata motion format. I have encountered most of the flags.
Checked that TRANSFORMERS: Devastation motion format is identical to Nier's. Didn't encounter flag 8 yet.
ByStander23
Posts: 13
Joined: Tue Jun 16, 2015 2:09 am

Re: Bayonetta/Bayonetta 2/Nier Automata/TD Motion Files

Post by ByStander23 »

Can you add compatibility to MGRR files?

Here's a few decompresses samples:
http://www.mediafire.com/file/ccz78066q ... l.rar/file
And the .DAT/DTT files for pl0a00:
http://www.mediafire.com/file/djyss962q ... 0.rar/file

Thank you for your time.
Kerilk
Posts: 18
Joined: Sun Aug 27, 2017 1:21 pm

Re: Bayonetta/Bayonetta 2/Nier Automata/TD Motion Files/MGRR

Post by Kerilk »

I added support int the Noesis plugin for Metal Gear Rising: Revengeance. It supports animations, the format being the same as Bayonetta 2.
Kerilk
Posts: 18
Joined: Sun Aug 27, 2017 1:21 pm

Re: Bayonetta/Bayonetta 2/Nier Automata/TD Motion Files/MGRR

Post by Kerilk »

I added support for Astral Chain to the Noesis plugin, animation and model format are identical to Nier Automata. Textures are different so no texture support for now.
Kerilk
Posts: 18
Joined: Sun Aug 27, 2017 1:21 pm

Re: Bayonetta/Bayonetta 2/Nier Automata/TD/TMNT: MM/MGRR/Astral Chain Motion Files

Post by Kerilk »

I added support for TW101 in the Noesis plugin. Motion format is the same as Bayonetta 2.