World Wide Guide | Knowledge BankKukushkin's Notebook | Design Fundamentals


Creating animated objects

It is often desirable to put some movement into the scenery. This can be done either by means of dynamic scenery or by inserting animated objects into the visual scenery.

Using Dynamic Scenery

Dynamic scenery is most suitable for defining moving objects, like other aircraft, fuel trucks, sailboats etc. It offers a very limited choice of objects and it's impossible to design new ones. However, it is (relatively) easy to program, because FS5 does a major part of simulating the motion and displaying objects itself. The dynamic scenery is programmed in a very natural way by describing the motion of each object. Using the Flight Recorder/DynaGen combination, it is even possible simply to "fly" the dynamic scenery by recording the flight data and later converting them into a SCASM source file.

Dynamic scenery objects are displayed as 3-D objects using PerspectiveCall()s. Because their RefPoints are constantly moving, they can cause visibility conflicts with the static scenery even if there are no such conflicts within the static scenery itself. Such visibility conflicts can be avoided or at least reduced either by keeping the dynamic scenery away from other 3-D objects or by re-programming these 3-D objects in a more accurate way. In particular, splitting large objects into smaller parts can help.

Creating animated objects

The rest of this section is dedicated to inserting animated objects into the visual scenery.

The visual scenery does not allow to implement complex motion of objects, at least not without using 8086 assembly language subroutines. Thus, it is almost unsuitable for implementing moving objects. However, it can be used for implementing objects that change their appearance depending from different factors. For example, a radar dish can rotate with a constant speed, hangar doors can open themselves when the aircraft comes close, and the price tag at a refueling station should be inversely proportional to the amount of fuel left in the aircraft tank.

Unlike dynamic scenery objects, where FS5 does most of the work itself, animated objects must always be able to draw themselves. The animation is implemented by making the drawing routine behave differently depending on values of certain FS5 variables.

Timers help control animation

Most animated objects, like rotating or blinking objects, change their appearance depending on the time. FS5 has several variables called timers. These variables periodically change their value. By testing their values from drawing routines, it is possible to implement this kind of objects.

The variable 0282 (FSASM: vTimer1) is a so-called runningbit timer. Its binary representation contains one bit that periodically changes its position:

0000000000000001 = 0001 hex

0000000000000010 = 0002 hex 0000000000000100 = 0004 hex ...

1000000000000000 = 8000 hex

0000000000000001 = 0001 hex again and so on...

So, there are 16 possible values. This variable changes its value approximately 3 times a second. Thus, the period is approximately 5.3 seconds.

The running-bit timer is normally used for blinking and some rotated objects. Its advantage is the simplicity of use. The value is normally checked using the IfVarAnd() instruction, which allows multiple timer states to be tested in one instructions. For example, a light that blinks twice and then remains off for a while can be implemented like this:

IfVarAnd( :No_light 0282 0005 ) ; 0005 hex is 0000 0000 0000 0101 binary
... ; Draw the light here
:No_light

When the timer has the value of 0001, IfVarAnd() does not perform a jump because its argument contains a bit 1 at this position. The light is displayed. After 1/3 seconds, the bit moves one position to the left. The IfVarAnd() argument contains a bit 0 at this position, so the jump is performed and therefore the light is not displayed. After another 1/3 seconds, the bit moves one position to the left once again. The IfVarAnd() argument contains a bit 1 at this position, and so the light is displayed again - for another 1/3 seconds. All other bits in the IfVarAnd() argument are 0, so the light remains disabled for 4.3 seconds remaining from the timer period. Then, the timer returns to the value 0001 again and the whole cycle is repeated.

This example shows the basic principle of using the running-bit timer. Its value is tested using the IfVarAnd() instruction. Each bit in the IfVarAnd() argument represents a 1/16th part of the timer period.

By setting some bits to 1, it is possible to execute the instructions immediately following IfVarAnd() only during parts of the period specified by these bits. This can be used for displaying blinking primitives, changing colors, textures, point coordinates etc.

In particular, the running-bit timer allows implementing cyclic transformations, like a rotation or a cyclic movement of an object. Because there are only 16 possibilities for the timer value, there are only 16 possible states for such transformations. However, sometimes it is sufficient for rotating objects or things like moving lights.

RotatedCall improves efficiency

The size of the code producing rotating or moving objects should be kept as low as possible in order not to deplete resources of FS5. For this reason, RotatedCall()s/TransformCall()s should be used wherever possible. For example, a code for a rotating object (or a part of) with 8 possible angles of rotation could look like this:

IfVarAnd( :Not_0003 0282 0003 )
Call( :Draw_it ) ; The 1st rotation angle is 0, so we spare a few bytes
Jump( :Done )
... ; Additional IfVarAnds and RotatedCalls with angles increasing by 45
:Not_0c00
IfVarAnd( :Not_3000 0282 3000 ) RotatedCall( :Draw_it 0 0 270 ) Jump( :Done )
:Not_3000 ; If all tests fail, this _must_ pass
RotatedCall( :Draw_it 0 0 315 ) ; so we spare an IfVarAnd() here
:Done ; Rotation could be done much smarter using linear timers

Note that RotatedCall()s/TransformCall()s have some unpleasant side effects that are described in the section dealing with constructing 3-D objects. These side effects must be avoided in all cases. In particular, parts rotating around a non-vertical axis cannot produce correct shadows, so they should either not be drawn inside of a ShadowCalled routine, or their rotation phases should be calculated manually and implemented without the help of transformations.

Linear timers offer more flexibility

The main disadvantages of the running-bit timer is its fixed period and only 16 possible states. FS5 has also several so-called linear timers. They are periodically increased by a constant value. These variables are often much more difficult to test than the running-bit timer, but they also offer a much greater flexibility.

Following linear timers are available:

05FC (FSASM: vTimer2) counts 1/18.2's of a second

0B3E (FSASM: CntLo) counts 1/65536's of a second

0B40 (FSASM: CntHi) counts seconds

Each linear timer is stored as a 16-bit value. If it reaches values above 65535, it starts counting from 0. This is called an overflow.

Because of the way 16-bit values are stored in memory, another 2 variable addresses can be useful as "timers": 0B3F (FSASM:?) counts 1/256's of a second (1/65536*256) 05FB (FSASM:?) counts 1/4659's of a second (1/18.2/256)

Note that the low byte in 05FB does not belong to any timer and thus normally doesn't change at all.

FS5 derives the values of all timers from the variable 05FC, so they are only updated 18.2 times a second.

Thus, the maximum animation rate possible is 18.2 fps. This can be lower than the frame rate on faster PCs.

Linear timers can be used for rotating objects in a very comfortable way. Besides fixed displacement and angles of rotation, the TransformCall() instruction also allows specifying of addresses of up to 3 variables. If a nonzero address is specified, the value of the variable is added to the corresponding angle of rotation.

Angles in FS5 are internally stored not in degrees, but in units of (360/65536) of a degree. This allows specifying angles with the maximum precision possible for 16-bit values. 65536 such units are equal to 360 degrees, and the overflow causes this angle to be stored again as 0.

Speed of rotations

For this reason, linear timers can be specified in the TransformCall() instruction in order to obtain a smooth rotation. The period of such a rotation is determined by the time needed by the timer to overflow. Linear timers produce the following periods:

0B3E (CntLo): 1 second/rotation

05FC (vTimer2): 3600 seconds/rotation (for patient observers)

0B40: 65536 seconds/rotation (for very patient observers)

0B3F: 256 seconds/rotation

05FB: 14.06 seconds/rotation

It is possible to increase the rotation speed by nesting TransformCall()s. For example, 3 nested TransformCall()s with the variable 0B3F would produce a 4.68-second period. However, too many nested TransformCall()s can lead to a stack overflow (FS5 would most likely lock up) and would also have negative effect on the frame rate. I think their number should not exceed 3-4.

Besides implementing a smooth rotation of objects, linear timers can be implementing animations in a usual way - by displaying different phases. They allows almost unlimited periods of the animation and also many phases with different durations. However, the testing of linear counters for finding out these phases is much more difficult. Also, not all phase durations and animation periods are possible.

An example of detecting phases

The first step here is to select a vector of adjacent bits in the timer that would be used for detecting phases. A number composed only of these bits also acts like a linear timer. For example, one can select bits 712 (bits are numbered from 0) of the timer 0B3F. This would result in a (1/256)*2^7 = 1/2-second phase duration with a total of 2^(12-7+1) = 64 phases.

Then, the phase of this 'sub-timer' should be detected. First, its topmost bit should be tested. If it is 0, then the current phase belongs to the first half of all phases possible, otherwise to the second half. Then, its next bit should be tested in each of the cases separately, and so on until the first bit is reached. Then, all phases are distinguished.

In the example above, this would lead to the following decision tree:

Bit 12 set?
No: ; means the phase is between 0 and 31
Bit 11 set?
No: ; means the phase is between 0 and 15
Bit 10 set?
No: ; between 0 and 7
... ... ... ... ... ...
Yes: ; between 8 and 15
... ... ... ... ... ...
Yes: ; between 16 and 31
... ... ... ... ... ...
Yes: ; between 32 and 63
... ... ... ... ... ...

In most cases, however, not all phases of the timer need to be distinguished, so unnecessary instructions can easily be spared. For example, for a light that should be lit for 1.5 seconds and then remain off for 30.5 seconds, the following code is sufficient:

IfVarAnd( :Test_bit_8 0b3F 1e00 ) ; The light is visible only if
Jump( :No_light )                 ; high 4 bits are zero :Test_bit_8 ; Is the bit 8 zero?
IfVarAnd( :Draw_light 0b3f 0100 ) ; If yes => phase 0 or 1 => visible
IfVarAnd( :Draw_light 0b3f 0080 ) ; If no, check bit 7
Jump( :No_light )                 ; Bit 7 is 1 => phase 3 => not visible
:Draw_light
... ; Executed each 30 seconds for 1.5 second
:No_light

Reacting to wind or aircraft position

Animated objects can also react to external factors, like wind direction or aircraft position. For example, a windsock pointing into the surface wind direction can be implemented like this:

TransformCall( :Windsock 0 0 0 0 0 0 0 0 0C74 ) ; Surface wind dir
...
:Windsock
... ; Draw a windsock pointing to the north
Return

A smarter windsock could also test the surface wind speed and change its appearance accordingly. A windmill could even change its rotation speed depending from the wind speed.

Objects reacting to the aircraft position should use the Monitor3D instruction to monitor it. Sometimes a "smooth" reaction on aircraft position change is required. For example, the hangar door should not open immediately, but should be seen moving. Such a movement can normally be implemented only using assembly language subroutines. However, it can sometimes be simulated. In the example with the hangar, the door can be drawn halfopen when the aircraft is certain distance away and fully open when the aircraft is really close to the hangar. As the aircraft moves towards the hangar, an illusion of the door movement would occur.

Responding to frequencies

It is possible to have drawing routines react to frequencies tuned in on different radios or to the transponder code. This is done simply by testing FS5 variables where these frequencies are stored:

IfVarRange( :Not_tuned_in ... ) ; Normally equal minimum/maximum values
... ; Draw something visible only when a certain frequency tuned in
:Not_tuned_in

Variables of interest are transponder code (07C4, FSASM: vTransponder), COM frequency (07BE, FSASM:vCOM1) and navigation frequencies as well. Their variables are listed in FS5STRUC. Frequencies and the transponder code are stored in the BCD format. More details can be found in APPENDIX.TXT.


Last updated 11 October 1996 by Gene Kraybill.