Animations with over 1024 frames
The FS2004 MakeMDL has a limitation build in that it can only export animations with a length of 1024 frames. Because the animation timer is a 18 Hz timer that means that the maximum animation length is only about 55 seconds. That might be fine for a hangar door that is opening or a radar that is rotating, but if you want to make some animated traffic on your airport for example 55 seconds is a bit short.
Unfortunately there is no workaround for this limitation from within GMax, but when you start editing the ASM code of your object it is possible to get beyond 1024 frames for an animation. So the limitation is purely in MakeMDL, not in FS2004 itself. This tutorial will explain which pieces of the ASM code you have to edit to make a longer animation.
Although I will try to explain everything as clearly as possible, I would say this is a task that a novice scenery designer might find a bit hard. But if you are a little bit familiar with ASM tweaking then I hope this tutorial will give you enough knowledge of how the animations are defined to be able to tweak them. Let's get started.
Exporting the animation
Basically exporting an animation with more than 1024 from gmax is not possible. MakeMDL simply ignore the animation from the export if the animation is longer than 1024. If we want to export the animation we need to export it by every 1024 frames. If the export is ready we have to combine the parts to one complete file manually. In this section we try to explain how to do it.
Step1: First of all create your animation in gmax with the full length. Let's say your animation is 2048 frames long.
Step2: If it is ready open the time configuration panel and change the end-frame to 1023. Press OK. Export your model, name it e.g. anim1.mdl (of course you have to keep the asm file with the exporter as we will tweak the asm).
Step3: When ready open the time configuration panel in gmax again and set the time parameters start frame :1024- end frame 2048. Press okay and open the time configuration button again. Press the Re-Scale button and re-scale the animation, start frame 0, end frame 1023. This step will forward the second part of the animation to the 0 point to be able to export it.
WARNING! After this step do not save your gmax file otherwise you will loose the first part of your animation!
This step becomes more complex if your animation is longer than 2048 frames. In that case in the Re-Scale part you have to set the end date of the previous end date minus 1024 frames not to loose the remaining animation. Do not forget! One part what you export can not be longer than 1024 frames! Maybe if you want to make it easier export your animation by 1000 frames.
Step4: Export the second part now to anim2.mdl.
Re-Number the animation keyframes
Our next steps is to combine the two animation into one again in the asm manually. We need to copy the animation tables from the anim2.mdl and paste them to anim1.mdl. That is unfortunately is not so easy. As the second part we have exported with start frame of 0, of course in our asm file the frame numbers will start with 0 as well. Of course this would cause a problem as we have already the same frame numbers in the first mdl. We need to re-number the second part asm frame numbers. To do this there is an MS Excel file attached.
Open your anim2_0.asm copy the keyframes section and paste it to the excel file (Column 'A'). In the excel file you can find a field 'Start Frame'. Enter the starting frame what you wish re-number your keyframes, in our case it is the 1024. The Excel will change all the keyframes and he will give the result in column 'B'. Select the column 'B' result and copy it, than paste it to your anim1_0.asm to the right position. All animation has two animation kayframe table, both has to be changed by the excel, it can handle both. Of course if you have several parts you have to repeat this step till you get a continuous sequenced animation keyframe table in anim1_0.asm.
The method will be more complex if in your animation several objects has animation. Every object has a separate animation keyframe table and you have to take care that you do not mix-up the object in your final anim1_0.asm.
The next sections describes what else need to be changed in the final asm file. Bold text
Tweaking the animation
There are two sections of the ASM code where you will have to make changes. The first is the section with the animation tables (ANIP) and the second is the section where the actual interpolation for the animation is performed (ANIC). Which changes to make in these two seconds is discussed next.
In this section I will describe which changes you need to make in the animation tables. There are two sorts of tables you will have to change. The first is the table that stores your animation keyframes and the second is the table that is used to determine the frame for the animation timer variable.
The animation keyframe table stores all the keyframes of your animation. They contain of an frame number and then the actual data. This can be 3 values for a position or 4 for a rotation (given as a quaternion). Below you see a sample of how the keyframe table can look for a simple translation of 1024 frames.
anim_long_trans_1 label word dw 1 ; 1: Point (translation) real4 -1.0 ; Previous panim value real4 16 dup (0.0) ; Cached matrix dw 2 ; number of entries real4 0.0, 0.000000, 0.000000, 0.000000 ; frame/x/y/z values real4 1023.0, 100.000000, 0.000000, 0.000000 ; frame/x/y/z values
As we want to get a longer animation, it is obvious that we need to add more keyframes to this table. So you will have to add additional lines until you reach the length in frames you want for your animation. Below you see an example where I extended the table to 4096 frames in total.
anim_long_trans_1 label word dw 1 ; 1: Point (translation) real4 -1.0 ; Previous panim value real4 16 dup (0.0) ; Cached matrix dw 5 ; number of entries real4 0.0, 0.000000, 0.000000, 0.000000 ; frame/x/y/z values real4 1024.0, 100.000000, 0.000000, 0.000000 ; frame/x/y/z values real4 2048.0, 100.000000, 100.000000, 0.000000 ; frame/x/y/z values real4 3072.0, 200.000000, 100.000000, 0.000000 ; frame/x/y/z values real4 4095.0, 200.000000, 0.000000, 0.000000 ; frame/x/y/z values
How you get the data to extend the keyframe table is up to you. Obviously GMax can not export the longer table for you. So you either have to type in the new data manually, or you could export multiple sections of 1024 frames from GMax and stitch them all together manually. In the last case you need to be sure that the frame value continues nicely.
Timer interpolation table
The second type of table that you will have to change is the interpolation table that is used to determine the correct frame number for the animation timer. The animation timer is nothing more than a variable that is increased by 1 every 1/18th of a second. It runs between a value of -32768 and 32767. That is not something we can use to interpolate our keyframes with, so therefore there is an interpolation table that is used to interpolate from the timer variable to a variable that is running between 0 and the amount of frames we want in our animation.
For the situation with 1024 frames that we exported from GMax you see the interpolation table below.
anim_long_1_tick18_mod_1 label word dw 8 dw -32768,0,-16386,16383 dw -16384,0,-2,16383 dw 0,0,16382,16383 dw 16384,0,32766,16383 anim_long_1_tick18_mod_2 label word dw 32 dw 0,0,1023,1023 dw 1024,0,2047,1023 dw 2048,0,3071,1023 dw 3072,0,4095,1023 dw 4096,0,5119,1023 dw 5120,0,6143,1023 dw 6144,0,7167,1023 dw 7168,0,8191,1023 dw 8192,0,9215,1023 dw 9216,0,10239,1023 dw 10240,0,11263,1023 dw 11264,0,12287,1023 dw 12288,0,13311,1023 dw 13312,0,14335,1023 dw 14336,0,15359,1023 dw 15360,0,16383,1023 anim_long_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
As you can see this table actually consists of three sections. Let's take a look at what these actually do before we start tweaking them. The first part (named mod_1) is used to cut the timer running between -32768 and 32767 into sections running from 0 to 16383. This two step approach is only done because else the second interpolation table would become very long. The picture illustrates how this sawtooth pattern works for interpolating from the timer variable to a temporarily variable running between 0 and 16383.
The second table (named mod_2) does the same, but it takes a variable running between 0 and 16383, which is the output of the first table, and then interpolates it so that we get a variable as output that is running between 0 and 1023. Below you see an image of this interpolation pattern as well.
The third part of the table (named convert_hi) is used to convert from the integer frame number we have determined in the previous step, to a floating point representation of that frame number. This is because the keyframe data is stored as floating point numbers. In the section about the interpolation we will see in more detail how this works.
So now we know how the tables that GMax has written for us work and we can start to adjust them for the situation with longer animations. The first part (named mod_1) does not have to be changed, unless you want an animation with over 16384 frames of course. Let's assume that is not the case now.
The second part (named mod_2) will have to be changed, as our animation now has 4096 instead of 1024 frames. So we need to interpolate between 0 and 4095 now. The picture below shows how our sawtooth of the interpolation needs to look now.
The third part we will also have to change as our interpolation runs till a higher frame number now. Here we will just show what needs to be changed, in the section about the interpolation you will learn what it actually means.
Below you see how the total interpolation table looks after these changes.
anim_long_1_tick18_mod_1 label word dw 8 dw -32768,0,-16386,16383 dw -16384,0,-2,16383 dw 0,0,16382,16383 dw 16384,0,32766,16383 anim_long_1_tick18_mod_2 label word dw 8 dw 0,0,4095,4095 dw 4096,0,8191,4095 dw 8192,0,12287,4095 dw 12288,0,16383,4095 anim_long_1_float_convert_hi label word dw 14 ; 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 dw 2048,17664 dw 4096,17792
After these changes we are done with tweaking the animation tables, so we can continue to alter the code where the actual interpolation is performed.
In the ANIC section of the MDL file you will find that code that performs the actual interpolation, using the tables defined in the ANIP section. These table we already tweaking in the previous section, so let's look at the interpolation itself now.
For the original 1024 frames animation the code looks like this:
bgl_animation_command_start_anim_long_1 label BGLCODE ; usrvar = tick18 mod 1024 LOCAL_BASE_32 anim_long_1_tick18_mod_1 BGL_INTERPOLATE VAR_BASE_GLOBAL,BGL_TICK18,\ VAR_BASE_GLOBAL,usrvar,\ VAR_BASE_LOCAL,\ (offset anim_long_1_tick18_mod_1 - offset anim_long_1_tick18_mod_1) LOCAL_BASE_32 anim_long_1_tick18_mod_2 BGL_INTERPOLATE VAR_BASE_GLOBAL,usrvar,\ VAR_BASE_GLOBAL,usrvar,\ VAR_BASE_LOCAL,\ (offset anim_long_1_tick18_mod_2 - offset anim_long_1_tick18_mod_2) ; convert usrvar to float and store in usrvr2-usrvr3 LOCAL_BASE_32 anim_long_1_float_convert_hi BGL_INTERPOLATE VAR_BASE_GLOBAL,usrvar,\ VAR_BASE_GLOBAL,usrvr3,\ VAR_BASE_LOCAL,\ (offset anim_long_1_float_convert_hi - anim_long_1_float_convert_hi) VAR_BASE_32 VAR_BASE_GLOBAL SETWRD usrvr2,0 IFIN1 anim_long_1_fc_512, usrvar,257,512 IFMSK anim_long_1_fc_512, usrvar,0001h SETWRD usrvr2,08000h anim_long_1_fc_512 label word IFIN1 anim_long_1_fc_1024, usrvar,513,1024 IFMSK anim_long_1_fc_1024, usrvar,0003h SETWRD usrvr2,04000h IFMSK anim_long_1_fc_1024, usrvar,0002h SETWRD usrvr2,08000h IFMSK anim_long_1_fc_1024, usrvar,0001h SETWRD usrvr2,0C000h anim_long_1_fc_1024 label word LOCAL_BASE_32 anim_long_trans_1 BGL_ANIMATE_INDIRECT VAR_BASE_GLOBAL,usrvr2,VAR_BASE_LOCAL,\ (offset anim_long_trans_1 - offset anim_long_trans_1),0.0,0.0,0.0,1
This code looks quite complex, so lets go through it line by line to see what is happening. The LOCAL_BASE_32 command is used to define which piece of memory should be used for local variables. This is the way that the animation tables with all the data will be referred to.
Then we see that three times the BGL_INTERPOLATE command is used. As the name suggests this command performs an interpolation. It takes three sets of arguments, first the input variable, then the output variable and lastly the table with the interpolation data. Each of these sets consists of two arguments, the first determines if we use the local or the global variable space. So this sets if we use the local variables, set with the LOCAL_BASE_32 command or if we refer to the global FS variables, which is where the timer variable is for example.
As mentioned there are three interpolations in the code. The first takes the 18 Hz timer as input and using first interpolation we discussed before. It writes its temporarily output to the usrvar global variable. The second interpolation takes this temporarily variable and interpolates it again using the second interpolation table. The result is stored in the usrvar variable again. Now we have the frame number stored as an integer.
The third interpolation is a bit more difficult. In the table with the keyframes for our animation we store the animation number as a floating point value. So we need to convert the integer value we have now to a floating point. The table below lists how these floating point value would look for a number of frames:
|Value||Hex represenatation||Value||Hex represenatation|
|0.0||0000 0000||258.0||4381 0000|
|1.0||3F80 0000||512.0||4400 0000|
|2.0||4000 0000||513.0||4400 4000|
|3.0||4040 0000||514.0||4400 8000|
|4.0||4080 0000||1024.0||4480 0000|
|8.0||4100 0000||1025.0||4480 2000|
|16.0||4180 0000||1026.0||4480 4000|
|32.0||4200 0000||2048.0||4500 0000|
|64.0||4280 0000||2049.0||4500 1000|
|128.0||4300 0000||2050.0||4500 2000|
|256.0||4380 0000||4096.0||4580 0000|
|257.0||4380 8000||8192.0||4600 0000|
A floating point number is two words long, while the integer is stored in only one word. So the floating point value will be stored in usrvr2 and usrvr3, with the most significant word stored in usrvr3. If you look at the third interpolation you see that it does exactly that. It interpolates from the integer frame number to the most significant part (the first part of the hex representation) of the floating point value. The convert_hi table stores this most signification part as decimal value instead of the integer one.
But after this interpolation we have only half of the floating point value filled. We still have to fill the least significant word. For frame number below 256 the least significant word is always zero, only for higher frame number it has to be filled. This is done with the IF structure that follows the last interpolation. Let's take a closer look at what happens exactly.
First the SETWRD command is used to set usrvr2 at a value of zero, this is the default value. The next IFIN1 command checks if the frame number is between 257 and 512. If the frame number is between these values than for the even frame numbers the least significant word should be 8000h. This is exactly what the following IFMSK and SETWRD commands do.
The next IFIN1 command than checks if the frame number is between 513 and 1024. For these frames the least significant word does not depend on the frame number being odd or even, but it differs for each 4 frame numbers in a row. So the following IFMSK and SETWRD commands make sure that the right value is written based on the fact is the bits corresponding to the values 1, 2 or 3 have been set.
The last (and most important) command in this section is the BGL_ANIMATE_INDIRECT command which performs the actual animation. It takes the frame number (as a floating point value) as input and the table that contains the keyframes. The last four arguments are optional position offets for the animation and the number that the resulting transformation matrix is stored under. This number is being referred to when by the BGL_SET_MATRIX_INDIRECT command when assigning the animation to a certain part of the geometry of your object.
Now it is clear that the logic of setting the least significant word has to be extended if we want to get frame numbers higher than 1024. As you will understand by now the structure will get more and more complex, between 1025 and 2048 there are 8 different options and between 2049 and 4096 even 16. This is the only part of the interpolation code that we have to change to be able to extend your animation. Below you find how it should look:
bgl_animation_command_start_anim_long_1 label BGLCODE ; usrvar = tick18 mod 1024 LOCAL_BASE_32 anim_long_1_tick18_mod_1 BGL_INTERPOLATE VAR_BASE_GLOBAL,BGL_TICK18,\ VAR_BASE_GLOBAL,usrvar,\ VAR_BASE_LOCAL,\ (offset anim_long_1_tick18_mod_1 - offset anim_long_1_tick18_mod_1) LOCAL_BASE_32 anim_long_1_tick18_mod_2 BGL_INTERPOLATE VAR_BASE_GLOBAL,usrvar,\ VAR_BASE_GLOBAL,usrvar,\ VAR_BASE_LOCAL,\ (offset anim_long_1_tick18_mod_2 - offset anim_long_1_tick18_mod_2) ; convert usrvar to float and store in usrvr2-usrvr3 LOCAL_BASE_32 anim_long_1_float_convert_hi BGL_INTERPOLATE VAR_BASE_GLOBAL,usrvar,\ VAR_BASE_GLOBAL,usrvr3,\ VAR_BASE_LOCAL,\ (offset anim_long_1_float_convert_hi - anim_long_1_float_convert_hi) VAR_BASE_32 VAR_BASE_GLOBAL SETWRD usrvr2,0 IFIN1 anim_long_1_fc_512, usrvar,257,512 IFMSK anim_long_1_fc_512, usrvar,0001h SETWRD usrvr2,08000h anim_long_1_fc_512 label word IFIN1 anim_long_1_fc_1024, usrvar,513,1024 IFMSK anim_long_1_fc_1024, usrvar,0003h SETWRD usrvr2,04000h IFMSK anim_long_1_fc_1024, usrvar,0002h SETWRD usrvr2,08000h IFMSK anim_long_1_fc_1024, usrvar,0001h SETWRD usrvr2,0C000h anim_long_1_fc_1024 label word IFIN1 test_1_fc_2048, usrvar,1025,2048 IFMSK test_1_fc_2048, usrvar,0007h SETWRD usrvr2,02000h IFMSK test_1_fc_2048, usrvar,0006h SETWRD usrvr2,04000h IFMSK test_1_fc_2048, usrvar,0005h SETWRD usrvr2,06000h IFMSK test_1_fc_2048, usrvar,0004h SETWRD usrvr2,08000h IFMSK test_1_fc_2048, usrvar,0003h SETWRD usrvr2,0A000h IFMSK test_1_fc_2048, usrvar,0002h SETWRD usrvr2,0C000h IFMSK test_1_fc_2048, usrvar,0001h SETWRD usrvr2,0E000h test_1_fc_2048 label word IFIN1 test_1_fc_4096, usrvar,2049,4096 IFMSK test_1_fc_4096, usrvar,000Fh SETWRD usrvr2,01000h IFMSK test_1_fc_4096, usrvar,000Eh SETWRD usrvr2,02000h IFMSK test_1_fc_4096, usrvar,000Dh SETWRD usrvr2,03000h IFMSK test_1_fc_4096, usrvar,000Ch SETWRD usrvr2,04000h IFMSK test_1_fc_4096, usrvar,000Bh SETWRD usrvr2,05000h IFMSK test_1_fc_4096, usrvar,000Ah SETWRD usrvr2,06000h IFMSK test_1_fc_4096, usrvar,0009h SETWRD usrvr2,07000h IFMSK test_1_fc_4096, usrvar,0008h SETWRD usrvr2,08000h IFMSK test_1_fc_4096, usrvar,0007h SETWRD usrvr2,09000h IFMSK test_1_fc_4096, usrvar,0006h SETWRD usrvr2,0A000h IFMSK test_1_fc_4096, usrvar,0005h SETWRD usrvr2,0B000h IFMSK test_1_fc_4096, usrvar,0004h SETWRD usrvr2,0C000h IFMSK test_1_fc_4096, usrvar,0003h SETWRD usrvr2,0D000h IFMSK test_1_fc_4096, usrvar,0002h SETWRD usrvr2,0E000h IFMSK test_1_fc_4096, usrvar,0001h SETWRD usrvr2,0F000h test_1_fc_4096 label word LOCAL_BASE_32 anim_long_trans_1 BGL_ANIMATE_INDIRECT VAR_BASE_GLOBAL,usrvr2,VAR_BASE_LOCAL,\ (offset anim_long_trans_1 - offset anim_long_trans_1),0.0,0.0,0.0,1
And with those changes applied we are done tweaking the interpolation part of our code. We are almost there now.
Using your new animation
To be able to use your tweaked animation you will have to recompile the ASM files you just tweaked into a MDL file. Use BGL_9 to do this. After that you can use the MDL file like you would do with any other MDL object.
If you want you can download the original and tweaked ASM code of my test object. It is just a box that will move around a bit. Bit hopefully comparing the files will make it more clear what you need to change. You can download them here.