Difference between revisions of "Animations with over 1024 frames"

From FSDeveloper Wiki
Jump to: navigation, search
(New page: {{:Ambox-Content-WIP}} {{Infobox-Applicable-FSVersion | FSXI = false | FSXA = false | FSX = false | FS2004 = true | FS2002 = false | FS2000 = false | FS98 = false }} [[Category:Tweaking]...)
 
(Undo revision 10085 by MichaelCox (talk))
 
(31 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{:Ambox-Content-WIP}}
 
 
 
{{Infobox-Applicable-FSVersion
 
{{Infobox-Applicable-FSVersion
 
| FSXI = false
 
| FSXI = false
Line 10: Line 8:
 
| FS98 = false
 
| FS98 = false
 
}}
 
}}
 +
 +
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 [http://www.fsdeveloper.com/downloads/animation_tweak_helping_tool.zip 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.
 +
 +
=== Animation tables ===
 +
 +
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.
 +
 +
==== Keyframes table ====
 +
 +
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.
 +
 +
[[Image:anim_mod_1.jpg|thumb|The sawtooth pattern for the first interpolation]]
 +
 +
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.     
 +
 +
[[Image:anim_mod_2.jpg|thumb|The sawtooth pattern for the second interpolation]]
 +
 +
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.
 +
 +
[[Image:anim_mod_2_changed.jpg|thumb|The sawtooth pattern for the modified second interpolation]]
 +
 +
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.
 +
 +
=== Animation interpolation ===
 +
 +
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:
 +
 +
{| border="1" cellpadding="5" cellspacing="2"
 +
| '''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.
 +
 +
== Downloads ==
 +
 +
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 [http://www.fsdeveloper.com/downloads/anim_long_sources.zip here].
  
 
[[Category:Tweaking]]
 
[[Category:Tweaking]]
 +
[[Category:Scenery_Design]]

Latest revision as of 15:18, 3 March 2016

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.

Animation tables

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.

Keyframes table

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 sawtooth pattern for the first interpolation

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 sawtooth pattern for the second interpolation

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 sawtooth pattern for the modified second interpolation

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.

Animation interpolation

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.

Downloads

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.