Animations explained (ASM code)
This article is not yet finished
This article describes how the Fs2004 style scenery animations work. But not from the point of view of a design program like GMax or FSDS3, but from the ASM source code point of view. So this article is mainly of interest if you are not afraid of the ASM source code and want to understand the structure behind the animations a bit better.
I want to note that this article describes my current knowledge of the animations. So it could be that certain parts are not really explained correctly yet, as I do not fully understand them. Any comments are welcome of course.
Basic animations
In the first part of this article I will explain how a simple animation can be defined in the source code and what the different commands actually do. I use the source code of a simple animated box as example. This box has an animation that contains of a translation in the first 50 frames and a rotation in the next 50.
Animating your object
After you have defined your object in the source code, it is rather easy to apply a certain animation to it. Below you find the piece of source code that defines my box. To give this object a certain translation or animation all you have to do is to apply a matrix to it using the BGL_SET_MATRIX_INDIRECT command. In the code below you see that three matrices are applied. The first is a static translation that defines the coordinate system to use for the animations. The other two are the translation and the rotation part of the animation itself. And that is all you have to do in the BGL section of the MDL file to create an animation.
test_tutorial_MasterScale_1 label BGLCODE
BGL_SET_MATRIX_INDIRECT 0
test_tutorial_anim_1 label BGLCODE
BGL_SET_MATRIX_INDIRECT 1
BGL_SET_MATRIX_INDIRECT 2
VAR_BASE_32 VAR_BASE_PARAMS
MATERIAL 0 ; <134,110,8,255>
DRAW_TRI_BEGIN 0, 24
DRAW_TRI 2, 7, 18 ; poly=1 part=4
DRAW_TRI 18, 13, 2 ; poly=2 part=4
DRAW_TRI 5, 16, 21 ; poly=3 part=4
DRAW_TRI 21, 10, 5 ; poly=4 part=4
DRAW_TRI 1, 12, 15 ; poly=5 part=4
DRAW_TRI 15, 4, 1 ; poly=6 part=4
DRAW_TRI 14, 20, 23 ; poly=7 part=4
DRAW_TRI 23, 17, 14 ; poly=8 part=4
DRAW_TRI 19, 8, 11 ; poly=9 part=4
DRAW_TRI 11, 22, 19 ; poly=10 part=4
DRAW_TRI 6, 0, 3 ; poly=11 part=4
DRAW_TRI 3, 9, 6 ; poly=12 part=4
DRAW_TRI_END
BGL_RETURN
Matrix relations
In the previous section is sounded really simple, how to make an animation. But of course the scenery engine needs to know a little more than the matrix number to apply. It needs to know the kind of transformation these matrices define and also how the different transformations should act together. This is defined in the SCEN section of the MDL file.
Below you see the code defining the SCEN section of my example file. As you can see it is a sort of table with three entries, one for each matrix.
scene_graph_riff_start_test_tutorial label word
db 'S','C','E','N'
dd scene_graph_riff_end_test_tutorial - $ - 4
dw 3
bgl_scene_graph_entry_test_tutorial_0 label byte
BGL_SCENEGRAPH_ENTRY 0, 1, -1, bgl_animation_command_end_test_tutorial_0 - bgl_animation_command_start_test_tutorial_0, bgl_animation_command_start_test_tutorial_0 - bgl_scene_graph_entry_test_tutorial_0
bgl_scene_graph_entry_test_tutorial_1 label byte
BGL_SCENEGRAPH_ENTRY 1, 2, -1, bgl_animation_command_end_test_tutorial_1 - bgl_animation_command_start_test_tutorial_1, bgl_animation_command_start_test_tutorial_1 - bgl_scene_graph_entry_test_tutorial_1
bgl_scene_graph_entry_test_tutorial_2 label byte
BGL_SCENEGRAPH_ENTRY 2, -1, -1, bgl_animation_command_end_test_tutorial_2 - bgl_animation_command_start_test_tutorial_2, bgl_animation_command_start_test_tutorial_2 - bgl_scene_graph_entry_test_tutorial_2
scene_graph_riff_end_test_tutorial label word
When you take a look at the BGL_SCENEGRAPH_ENTRY command you see that it has 5 parameters. The last two define which part of the animation command (ANIC) section of the MDL file creates the actual matrices. So that is where the actual transformation that needs to be applied is defined. We will take a look at the ANIC section later on.
The first three parameters define the relation between the different matrices and that is what we will discuss here. The first parameter is the actual matrix number, this is also the number that we called with the BGL_SET_MATRIX_INDIRECT command in the BGL section. The second parameter defines the next child matrix of this matrix, while the third one defines the next peer matrix.
So what do these child and peer relations do? When two matrices have a child relation, this means that if you apply them after each other the resulting transformation is the combination of both of them. When you look at the example code above, you can see that the matrix 1 and matrix 2 have a child relation (these were the translation and rotation matrix), so when you apply them both as we did in the BGL section you object will get both the translation and the rotation.
When two matrices have a peer relation this means that the transformation state is popped back till before the next matrix is applied. So in this case only the last matrix will have an effect on your final transformation of the object.
So in the example animation you can see that the matrix 1 is a child of matrix 0, while matrix 2 is a child again of matrix 1. So when you apply them all three, as happened in the BGL section, the total transformation is the combination of all three matrices.
Creating the transformation matrix
As we saw in the previous section, the SCEN table points to part of the MDL ANIC section. In the ANIC section the animation matrix, that we want to apply, is actually calculated.
For the static transformation the code to calculate this matrix is rather easy, you can see it below. The command points to a table storing the transformation we want and assigns it to the matrix number that we can then call afterwards. The table that stores the transformation will be discussed later.
bgl_animation_command_start_test_tutorial_0 label BGLCODE
BGL_TRANSFORM_INDIRECT 0, 0
bgl_animation_command_end_test_tutorial_0 label BGLCODE
For the animations matrices the code to calculate them is a bit more complex. The basic idea is that we have a timer that increases its value each 1/18th of a second (thus at 18Hz) and we want to use that to drive the animation. So our first task is to generate a frame number from this running timer.
Below you see the first piece of the code that does this. The BGL_INTERPOLATE command is used to interpolate a given table based on another value. So in the code below we use the table at the location test_tutorial_1_tick18_mod_1 to interpolate the value of BGL_TICK18 and we store the result in the variable usrvar. How this table actually works will be discussed later on, for now it is enough to remember that we use it to split the running timer into smaller pieces. So that we can get a frame number that is running in the range we want (0 to 100 frames in this example).
bgl_animation_command_start_test_tutorial_1 label BGLCODE
LOCAL_BASE_32 test_tutorial_1_tick18_mod_1
BGL_INTERPOLATE VAR_BASE_GLOBAL,BGL_TICK18,\
VAR_BASE_GLOBAL,usrvar,\
VAR_BASE_LOCAL,(offset test_tutorial_1_tick18_mod_1 - offset test_tutorial_1_tick18_mod_1)
The next part of the code performs a similar task, but this time we read the result of the previous interpolation and interpolate this again. When we discuss the tables used to do this, it will become clear why splitting this in two pieces is easier.
LOCAL_BASE_32 test_tutorial_1_tick18_mod_2
BGL_INTERPOLATE VAR_BASE_GLOBAL,usrvar,\
VAR_BASE_GLOBAL,usrvar,\
VAR_BASE_LOCAL,(offset test_tutorial_1_tick18_mod_2 - offset test_tutorial_1_tick18_mod_2)
The third and last interpolation then converts the output of the previous interpolation, which was an integer number, into a float. This is because the actual animation command that we use next requires a float.
LOCAL_BASE_32 test_tutorial_1_float_convert_hi
BGL_INTERPOLATE VAR_BASE_GLOBAL,usrvar,\
VAR_BASE_GLOBAL,usrvr3,\
VAR_BASE_LOCAL,(offset test_tutorial_1_float_convert_hi - test_tutorial_1_float_convert_hi)
VAR_BASE_32 VAR_BASE_GLOBAL
SETWRD usrvr2,0
The final piece of code than does the actual calculation of the matrix we want to use for our animation. The floating frame number we calculated before is the input, together with the table that defines the animation we want. This table is discussed in the next section in more detail. The three parameters at the end, that are set to 0.0 in this example seem to define an offset for the animation result. The last parameter is the matrix number that we use to call this animation.
LOCAL_BASE_32 test_tutorial_trans_1
BGL_ANIMATE_INDIRECT VAR_BASE_GLOBAL,usrvr2,VAR_BASE_LOCAL,(offset test_tutorial_trans_1 - offset test_tutorial_trans_1),0.0,0.0,0.0,1
bgl_animation_command_end_test_tutorial_1 label BGLCODE
Storing the animation data
We have now seen how the transformation matrices are calculated and how their relations are defined. But we have not yet seen in which format their data is stored. That will be discussed in this section.
We will start the static transformation matrices. These are stored in the TRAN section of the MDL file. Below you see the lines of code that define such a matrix. There are no opcodes in this section, just a serie of 16 floating values that define one matrix. If you find 32 values, you know there must be two matrices defined.
; Static matrix parameters for transform: 0
real4 1.000000, 0.000000, 0.000000, 0.000000
real4 0.000000, 0.000000, 1.000000, 0.000000
real4 0.000000, -1.000000, 0.000000, 0.000000
real4 0.000000, 0.000000, 0.000000, 1.000000
What do these 16 values define? The upper 3x3 matrix defines the rotation of the coordinate system and the bottom 1x3 matrix is the translation. If you want a more mathematical explanation, including some nice sine and cosine formulas on how to calculate the rotation part, have a look at this article from the DirectX SDK.
When we look at the source code given above, you can see that a XZY coordinate system is changed into a XYZ coordinate system with this matrix. And also the positive direction of the Z-axis is changed.
test_tutorial_1_tick18_mod_1 label word
dw 82
dw -32768,0,-31154,1615
dw -31152,0,-29538,1615
dw -29536,0,-27922,1615
dw -27920,0,-26306,1615
dw -26304,0,-24690,1615
dw -24688,0,-23074,1615
dw -23072,0,-21458,1615
dw -21456,0,-19842,1615
dw -19840,0,-18226,1615
dw -18224,0,-16610,1615
dw -16608,0,-14994,1615
dw -14992,0,-13378,1615
dw -13376,0,-11762,1615
dw -11760,0,-10146,1615
dw -10144,0,-8530,1615
dw -8528,0,-6914,1615
dw -6912,0,-5298,1615
dw -5296,0,-3682,1615
dw -3680,0,-2066,1615
dw -2064,0,-450,1615
dw -448,0,1166,1615
dw 1168,0,2782,1615
dw 2784,0,4398,1615
dw 4400,0,6014,1615
dw 6016,0,7630,1615
dw 7632,0,9246,1615
dw 9248,0,10862,1615
dw 10864,0,12478,1615
dw 12480,0,14094,1615
dw 14096,0,15710,1615
dw 15712,0,17326,1615
dw 17328,0,18942,1615
dw 18944,0,20558,1615
dw 20560,0,22174,1615
dw 22176,0,23790,1615
dw 23792,0,25406,1615
dw 25408,0,27022,1615
dw 27024,0,28638,1615
dw 28640,0,30254,1615
dw 30256,0,31870,1615
dw 31872,0,32767,896
test_tutorial_1_tick18_mod_2 label word
dw 32
dw 0,0,100,100
dw 101,0,201,100
dw 202,0,302,100
dw 303,0,403,100
dw 404,0,504,100
dw 505,0,605,100
dw 606,0,706,100
dw 707,0,807,100
dw 808,0,908,100
dw 909,0,1009,100
dw 1010,0,1110,100
dw 1111,0,1211,100
dw 1212,0,1312,100
dw 1313,0,1413,100
dw 1414,0,1514,100
dw 1515,0,1615,100
test_tutorial_1_float_convert_hi label word
dw 12 ; num entries
dw 0,0
dw 1,16256
dw 2,16384
dw 4,16512
dw 8,16640
dw 16,16768
dw 32,16896
dw 64,17024
dw 128,17152
dw 256,17280
dw 512,17408
dw 1024,17536
test_tutorial_trans_1 label word
dw 1 ; 1: Point (translation)
real4 -1.0 ; Previous panim value
real4 16 dup (0.0) ; Cached matrix
dw 3 ; number of entries
real4 0.0, 0.000000, 0.000000, 0.000000 ; frame/x/y/z values
real4 50.0, 20.000000, 0.000000, 0.000000 ; frame/x/y/z values
real4 100.0, 20.000000, 0.000000, 0.000000 ; frame/x/y/z values
test_tutorial_quat_1 label word
dw 3 ; 3: Quaternion (rotation)
real4 -1.0 ; Previous panim value
real4 16 dup (0.0) ; Cached matrix
dw 27 ; number of entries
real4 0.0, 0.000000, 0.000000, 0.000000, 1.000000 ; frame/x/y/z/w values
real4 51.0, 0.000000, 0.000000, -0.015707, 0.999877 ; frame/x/y/z/w values
real4 53.0, 0.000000, 0.000000, -0.047106, 0.998890 ; frame/x/y/z/w values
real4 55.0, 0.000000, 0.000000, -0.078459, 0.996917 ; frame/x/y/z/w values
real4 57.0, 0.000000, 0.000000, -0.109734, 0.993961 ; frame/x/y/z/w values
real4 59.0, 0.000000, 0.000000, -0.140901, 0.990024 ; frame/x/y/z/w values
real4 61.0, 0.000000, 0.000000, -0.171929, 0.985109 ; frame/x/y/z/w values
real4 63.0, 0.000000, 0.000000, -0.202787, 0.979223 ; frame/x/y/z/w values
real4 65.0, 0.000000, 0.000000, -0.233445, 0.972370 ; frame/x/y/z/w values
real4 67.0, 0.000000, 0.000000, -0.263873, 0.964557 ; frame/x/y/z/w values
real4 69.0, 0.000000, 0.000000, -0.294040, 0.955793 ; frame/x/y/z/w values
real4 71.0, 0.000000, 0.000000, -0.323917, 0.946085 ; frame/x/y/z/w values
real4 73.0, 0.000000, 0.000000, -0.353475, 0.935444 ; frame/x/y/z/w values
real4 75.0, 0.000000, 0.000000, -0.382683, 0.923880 ; frame/x/y/z/w values
real4 77.0, 0.000000, 0.000000, -0.411514, 0.911403 ; frame/x/y/z/w values
real4 79.0, 0.000000, 0.000000, -0.439939, 0.898028 ; frame/x/y/z/w values
real4 81.0, 0.000000, 0.000000, -0.467930, 0.883766 ; frame/x/y/z/w values
real4 83.0, 0.000000, 0.000000, -0.495459, 0.868631 ; frame/x/y/z/w values
real4 85.0, 0.000000, 0.000000, -0.522499, 0.852640 ; frame/x/y/z/w values
real4 87.0, 0.000000, 0.000000, -0.549023, 0.835807 ; frame/x/y/z/w values
real4 89.0, 0.000000, 0.000000, -0.575005, 0.818150 ; frame/x/y/z/w values
real4 91.0, 0.000000, 0.000000, -0.600420, 0.799685 ; frame/x/y/z/w values
real4 93.0, 0.000000, 0.000000, -0.625243, 0.780430 ; frame/x/y/z/w values
real4 95.0, 0.000000, 0.000000, -0.649448, 0.760406 ; frame/x/y/z/w values
real4 97.0, 0.000000, 0.000000, -0.673012, 0.739631 ; frame/x/y/z/w values
real4 99.0, 0.000000, 0.000000, -0.695913, 0.718126 ; frame/x/y/z/w values
real4 100.0, 0.000000, 0.000000, -0.707107, 0.707107 ; frame/x/y/z/w values
Tweaking animations
Bla

