View Single Post
  #9  
Old 10-02-2004, 07:10 AM
Windcatcher
Demi-God
 
Join Date: Jan 2002
Posts: 1,175
Default

Not to worry...I've been working on mob animation and I believe I've figured it out...

In the 0x12 fragment, we *thought* that the fields were like this:

RotateDenominator
RotateXNumerator
RotateYNumerator
RotateZNumerator
TranslateXNumerator
TranslateYNumerator
TranslateZNumerator
TranslateDenominator

Where they were either 4 bytes or eight bytes, depending on a flag.

The translate stuff is okay, but the rotation stuff isn't. Actually they are Euler parameters, and the fields should be like this:

E0
E1
E2
E3
TranslateXNumerator
TranslateYNumerator
TranslateZNumerator
TranslateDenominator

You animate a mob in this way: from the Euler parameters E0...E3, form a 3x3 transformation matrix. The matrix describes a rotation around an arbitrary axis. Then, instead of rotating around X, Y, and Z as before, multiply the matrix by the (X, Y, Z) position vector for the vertex:

V' = VM

Then translate as before. The matrix multiplications, like the rotations we did earlier as well as the translations, are cumulative. Here is an example code snippet (this routine is actually nested in a larger one and not all variables it uses are declared here):

Code:
    Procedure CalculatePieceInfo(Frame: Single; Index,LastIndex: Integer; XOfs,YOfs,ZOfs: Double);
    // ------------------------------------------------------------------------------------------
    // Routine for calculating skeleton piece information as soon as the skeleton has been
    // loaded.  The idea is to precalculate information for each piece but calculate final
    // (X, Y, Z) vertex values on the fly with information stored here.  It requires way too
    // much RAM to cache final vertex values for each piece of each skeleton so this is the
    // next best solution.
    //
    // Frame ranges from 0 to 1 and represents the animation frame.  Generally this routine
    // will always be called such that frame references a unique frame (e.g. an animation with
    // three frames will be called only three times).
    //
    // Index is the piece index as taken from the Data10 tree.  0 is the first piece index.
    //
    // LastIndex is like Index but is the index for the previous piece, or -1 if we are working
    // on the first piece in the skeleton tree.
    //
    // XOfs, YOfs, and ZOfs are the cumulative translations from the last piece we did in the
    // skeleton tree, or (0, 0, 0) if we are working on the first piece.
    // ------------------------------------------------------------------------------------------
    Var
      I           : Integer;
      D12         : Data12;
      XOfs1       : Double;
      YOfs1       : Double;
      ZOfs1       : Double;
      FrameNum    : Integer;
      E0,E1,E2,E3 : Single;
      Len         : Single;

    Begin
      If (Index <= High(Pieces)) Then
      Begin
        D12      := Pieces[Index].D12;

        // Figure out the actual frame from the animation value we passed 

        FrameNum := Round(Frame * (D12.Size1 - 1));

        // Calculate the rotation angle of this piece

        If (D12.Flags And 8) <> 0 Then
        Begin
          // The Data12 structure contains a vector in the form of signed 16-bit values.
          // The Euler parameters represent an arbitrary rotation around a unit normal
          // (see http://mathworld.wolfram.com/EulerAngles.html).
          //
          // We can to convert them to properly scaled floating-point values by dividing
          // by the length of the given vector (essentially normalizing the values).

          E0 := D12.Data4[FrameNum].E0;
          E1 := D12.Data4[FrameNum].E1;
          E2 := D12.Data4[FrameNum].E2;
          E3 := D12.Data4[FrameNum].E3;

          Len := Sqrt(Sqr(E0) + Sqr(E1) + Sqr(E2) + Sqr(E3));
          E0  := E0 / Len;
          E1  := E1 / Len;
          E2  := E2 / Len;
          E3  := E3 / Len;
        End
        Else
        Begin
          // The Euler parameters are passed as 32-bit floating-point values.  No
          // normalization is necessary.

          E0 := D12.Data8[FrameNum].E0;
          E1 := D12.Data8[FrameNum].E1;
          E2 := D12.Data8[FrameNum].E2;
          E3 := D12.Data8[FrameNum].E3;
        End;

        // Get the transformation matrix values from the Euler parameters, transposing the matrix because
        // of the way we multiply things (my multiply does V' = MV instead of V' = VM, and my matrix
        // multiplication routines do M' = M1 x M instead of M' = M x M1)

        Pieces[Index].A11 := Sqr(E0) + Sqr(E1) - Sqr(E2) - Sqr(E3);
        Pieces[Index].A21 := 2 * (E1 * E2 + E0 * E3);
        Pieces[Index].A31 := 2 * (E1 * E3 - E0 * E2);
        Pieces[Index].A12 := 2 * (E1 * E2 - E0 * E3);
        Pieces[Index].A22 := Sqr(E0) - Sqr(E1) + Sqr(E2) - Sqr(E3);
        Pieces[Index].A32 := 2 * (E2 * E3 + E0 * E1);
        Pieces[Index].A13 := 2 * (E1 * E3 + E0 * E2);
        Pieces[Index].A23 := 2 * (E2 * E3 - E0 * E1);
        Pieces[Index].A33 := Sqr(E0) - Sqr(E1) - Sqr(E2) + Sqr(E3);

        // Make this matrix cumulative with the one from the previous piece

        If LastIndex >= 0 Then
        Begin
          M1 := T3x3Matrix.Create(Pieces[Index].A11,Pieces[Index].A12,Pieces[Index].A13,
                                  Pieces[Index].A21,Pieces[Index].A22,Pieces[Index].A23,
                                  Pieces[Index].A31,Pieces[Index].A32,Pieces[Index].A33);
          M2 := T3x3Matrix.Create(Pieces[LastIndex].A11,Pieces[LastIndex].A12,Pieces[LastIndex].A13,
                                  Pieces[LastIndex].A21,Pieces[LastIndex].A22,Pieces[LastIndex].A23,
                                  Pieces[LastIndex].A31,Pieces[LastIndex].A32,Pieces[LastIndex].A33);
          M1.Multiply(M2); // M1 = M2 x M1

          // Store the matrix parameters for this piece.  It's too expensive from a RAM standpoint
          // to save every vertex for every frame of every skeleton, so we're going to save the
          // information we need to create the transformed vertices on the fly.  Translation information
          // will be saved below.

          Pieces[Index].A11 := M1.M[1,1];
          Pieces[Index].A12 := M1.M[1,2];
          Pieces[Index].A13 := M1.M[1,3];
          Pieces[Index].A21 := M1.M[2,1];
          Pieces[Index].A22 := M1.M[2,2];
          Pieces[Index].A23 := M1.M[2,3];
          Pieces[Index].A31 := M1.M[3,1];
          Pieces[Index].A32 := M1.M[3,2];
          Pieces[Index].A33 := M1.M[3,3];
          M1.Free;
          M2.Free;
        End;

        // Get the translation vector for this piece

        If (D12.Flags And 8) <> 0 Then
        Begin
          XOfs1 := D12.Data4[FrameNum].MoveXNumerator / D12.Data4[FrameNum].MoveDenominator;
          YOfs1 := D12.Data4[FrameNum].MoveYNumerator / D12.Data4[FrameNum].MoveDenominator;
          ZOfs1 := D12.Data4[FrameNum].MoveZNumerator / D12.Data4[FrameNum].MoveDenominator;
        End
        Else
        Begin
          XOfs1 := D12.Data8[FrameNum].MoveXNumerator / D12.Data8[FrameNum].MoveDenominator;
          YOfs1 := D12.Data8[FrameNum].MoveYNumerator / D12.Data8[FrameNum].MoveDenominator;
          ZOfs1 := D12.Data8[FrameNum].MoveZNumerator / D12.Data8[FrameNum].MoveDenominator;
        End;

        // Rotate the translation vector using the matrix transformation from the previous skeleton piece

        If LastIndex >= 0 Then
        Begin
          M2 := T3x3Matrix.Create(Pieces[LastIndex].A11,Pieces[LastIndex].A12,Pieces[LastIndex].A13,
                                  Pieces[LastIndex].A21,Pieces[LastIndex].A22,Pieces[LastIndex].A23,
                                  Pieces[LastIndex].A31,Pieces[LastIndex].A32,Pieces[LastIndex].A33);
          M2.Multiply(XOfs1,YOfs1,ZOfs1);
          M2.Free;
        End;

        // Add the cumulative translation from all previous pieces as we walked the skeleton tree

        XOfs := XOfs + XOfs1;
        YOfs := YOfs + YOfs1;
        ZOfs := ZOfs + ZOfs1;

        // Save the translation information in the piece so we can animate on the fly

        Pieces[Index].XOfs := XOfs;
        Pieces[Index].YOfs := YOfs;
        Pieces[Index].ZOfs := ZOfs;

        // Recursively calculate for any skeleton pieces that this piece references

        For I := 0 To D10.Data1[Index].Size - 1 Do CalculatePieceInfo(Frame,D10.Data1[Index].Data[I],Index,XOfs,YOfs,ZOfs);
      End;
    End; // CalculatePieceInfo
Then to animate, take the given vector from the Data36/Data2C/Data37 structure, multiply by the stored matrix, and add the stored translation vector for the piece that vector goes with. If anyone has any questions I'd be happy to discuss it. I've tested this with loads of models and it works really well.

WC
Reply With Quote