Hello! By popular request, this part is all about animation. I will be going over three methods of doing animation on a PC, and will concentrate specifically on one. Although not often used in demo coding, animation is usually used in games coding, which can be almost as rewarding.

DENTHOR, coder for ...
_____   _____   ____   __   __  ___  ___ ___  ___  __   _____
/  _  \ /  ___> |  _ \ |  |_|  | \  \/  / \  \/  / |  | /  _  \
|  _  | \___  \ |  __/ |   _   |  \    /   >    <  |  | |  _  |
\_/ \_/ <_____/ |__|   |__| |__|   |__|   /__/\__\ |__| \_/ \_/
The great South African Demo Team! Contact us for info/code exchange!  

Grant Smith, alias Denthor of Asphyxia, wrote up several articles on the creation of demo effects in the 90s. I reproduce them here, as they offer so much insight into the demo scene of the time.

These articles apply some formatting to Denthor's original ASCII files, plus a few typo fixes.

The Principles of Animation

I am sure all of you have seen a computer game with animation at one or other time. There are a few things that an animation sequence must do in order to give an impression of realism. Firstly, it must move, preferably using different frames to add to the realism (for example, with a man walking you should have different frames with the arms an legs in different positions). Secondly, it must not destroy the background, but restore it after it has passed over it.

This sounds obvious enough, but can be very difficult to code when you have no idea of how to go about achieving that.

In this trainer I will discuss various methods of meeting these two objectives.

Frames and Object Control

It is quite obvious that for most animation to succeed, you must have numerous frames of the object in various poses (such as a man with several frames of him walking). When shown one after the other, these give the impression of natural movement.

“So, how do we store these frames?” I hear you cry. Well, the obvious method is to store them in arrays. After drawing a frame in Autodesk Animator and saving it as a .CEL, we usually use the following code to load it in:

TYPE icon = Array [1..50,1..50] of byte;

VAR tree : icon;

Procedure LoadCEL (FileName :  string; ScrPtr : pointer);
  Fil : file;
  Buf : array [1..1024] of byte;
  BlocksRead, Count : word;
  assign (Fil, FileName);
  reset (Fil, 1);
  BlockRead (Fil, Buf, 800);    { Read and ignore the 800 byte header }
  Count := 0; BlocksRead := $FFFF;
  while (not eof (Fil)) and (BlocksRead <> 0) do begin
    BlockRead (Fil, mem [seg (ScrPtr^): ofs (ScrPtr^) + Count], 1024, BlocksRead);
    Count := Count + 1024;
  close (Fil);

  Loadcel ('Tree.CEL',addr (tree));

We now have the 50x50 picture of TREE.CEL in our array tree. We may access this array in the usual manner (e.g. col:=tree [25,30]). If the frame is large, or if you have many frames, try using pointers (see previous parts).

Now that we have the picture, how do we control the object? What if we want multiple trees wandering around doing their own thing? The solution is to have a record of information for each tree. A typical data structure may look like the following :

TYPE Treeinfo = Record
                  x,y:word;       { Where the tree is }
                  speed:byte;     { How fast the tree is moving }
                  Direction:byte; { Where the tree is facing }
                  frame:byte      { Which animation frame the tree is
                                    currently involved in }
                  active:boolean; { Is the tree actually supposed to be
                                    shown/used? }

VAR Forest : Array [1..20] of Treeinfo;

You now have 20 trees, each with their own information, location etc. These are accessed using the following means:

Forest [15].x:=100;

This would set the 15th tree’s x coordinate to 100.

Restoring the Overwritten Background

I will discuss three methods of doing this. These are NOT NECESSARILY THE ONLY OR BEST WAYS TO DO THIS! You must experiment and decide which is the best for your particular type of program.


  • Step 1: Create two virtual pages, Vaddr and Vaddr2.
  • Step 2: Draw the background to Vaddr2.
  • Step 3: Flip Vaddr2 to Vaddr.
  • Step 4: Draw all the foreground objects onto Vaddr.
  • Step 5: Flip Vaddr to VGA.
  • Step 6: Repeat from 3 continuously.

In ASCII, it looks like follows …

    +---------+           +---------+           +---------+
    |         |           |         |           |         |
    |  VGA    | <=======  |  VADDR  |  <======  |  VADDR2 |
    |         |           | (bckgnd)|           | (bckgnd)|
    |         |           |+(icons) |           |         |
    +---------+           +---------+           +---------+

The advantages of this approach is that it is straightforward, continual reading of the background is not needed, there is no flicker and it is simple to implement. The disadvantages are that two 64000 byte virtual screens are needed, and the procedure is not very fast because of the slow speed of flipping.


  • Step 1: Draw background to VGA.
  • Step 2: Grab portion of background that icon will be placed on.
  • Step 3: Place icon.
  • Step 4: Replace portion of background from Step 2 over icon.
  • Step 5: Repeat from step 2 continuously.

In terms of ASCII…

      |      +--|------- + Background restored (3)
      |      * -|------> * Background saved to memory (1)
      |      ^  |
      |      +--|------- # Icon placed (2)

The advantages of this method is that very little extra memory is needed. The disadvantages are that writing to VGA is slower than writing to memory, and there may be large amounts of flicker.


  • Step 1: Set up one virtual screen, VADDR.
  • Step 2: Draw background to VADDR.
  • Step 3: Flip VADDR to VGA.
  • Step 4: Draw icon to VGA.
  • Step 5: Transfer background portion from VADDR to VGA.
  • Step 6: Repeat from step 4 continuously.


     +---------+           +---------+
     |         |           |         |
     |   VGA   |           |  VADDR  |
     |         |           | (bckgnd)|
     | Icon>* <|-----------|--+      |
     +---------+           +---------+

The advantages are that writing from the virtual screen is quicker then from VGA, and there is less flicker then in Method 2. Disadvantages are that you are using a 64000 byte virtual screen, and flickering occurs with large numbers of objects.

In the attached sample program, a mixture of Method 3 and Method 1 is used. It is faster than Method 1, and has no flicker, unlike Method 3. What I do is I use VADDR2 for background, but only restore the background that has been changed to VADDR, before flipping to VGA.

In the sample program, you will see that I restore the entire background of each of the icons, and then place all the icons. This is because if I replace the background then place the icon on each object individually, if two objects are overlapping, one is partially overwritten.

The following sections are explanations of how the various assembler routines work. This will probably be fairly boring for you if you already know assembler, but should help beginners and dabblers alike.

The ASM Putpixel

To begin with, I will explain a few of the ASM variables and functions:

NOTE THAT THIS IS AN EXTREMELY SIMPLISTIC VIEW OF ASSEMBLY LANGUAGE! There are numerous books to advance your knowledge, and the Norton Guides assembler guide may be invaluable for people beginning to code in assembler. I haven’t given you the pretty pictures you are supposed to have to help you understand it easier, I have merely laid it out like a programming language with its own special procedures.

There are 4 register variables: AX, BX, CX, DX. These are words (double bytes) with a range from 0 to 65535. You may access the high and low bytes of these by replacing the X with a “H” for high or “L” for low. For example, AL has a range from 0-255.

You also have two pointers: ES:DI and DS:SI. The part on the left is the segment to which you are pointing (e.g. $a000), and the right hand part is the offset, which is how far into the segment you are pointing. Turbo Pascal places a variable over 16k into the base of a segment, i.e. DI or SI will be zero at the start of the variable.

If you wish to be pointing to pixel number 3000 on the VGA screen (see previous parts for the layout of the VGA screen), ES would be equal to $a000 and DI would be equal to 3000. You can quite as easily make ES or DS be equal to the offset of a virtual screen.

Here are a few functions that you will need to know:

mov   destination,source       ; This moves the value in source to
                               ; destination. e.g.  mov ax,50
add   destination,source       ; This adds source to destination,
                               ; the result being stored in destination
mul   source                   ; This multiplies AX by source. If
                               ; source is a byte, the source is
                               ; multiplied by AL, the result being
                               ; stored in AX. If source is a word,
                               ; the source is multiplied by AX, the
                               ; result being stored in DX:AX
movsb                          ; This moves the byte that DS:SI is
                               ; pointing to into ES:DI, and
                               ; increments SI and DI.
movsw                          ; Same as movsb except it moves a
                               ; word instead of a byte.
stosw                          ; This moves AX into ES:DI. stosb
                               ; moves AL into ES:DI. DI is then
                               ; incremented.
push register                  ; This saves the value of register by
                               ; pushing it onto the stack. The
                               ; register may then be altered, but
                               ; will be restored to it's original
                               ; value when popped.
pop register                   ; This restores the value of a pushed
                               ; register. NOTE : Pushed values must
                               ; be popped in the SAME ORDER but
                               ; REVERSED.
rep  command                   ; This repeats Command by as many
                               ; times as the value in CX
SHL  destination,count      
SHR  destination,count  

SHL and SHR need a bit more explaining. As you know, computers think in ones and zeroes. Each number may be represented in this base 2 operation. A byte consists of 8 ones and zeroes (bits), and has a range from 0 to 255. A word consists of 16 ones and zeroes (bits), and has a range from 0 to 65535. A double word consists of 32 bits.

The number 53 may be represented as follows: 00110101. Ask someone who looks clever to explain to you how to convert from binary to decimal and vice-versa.

What happens if you shift everything to the left? Drop the leftmost number and add a zero to the right? This is what happens:

                00110101     =  53
                01101010     =  106

As you can see, the value has doubled! In the same way, by shifting one to the right, you halve the value! This is a VERY quick way of multiplying or dividing by 2. (note that for dividing by shifting, we get the trunc of the result… i.e. 15 shr 1 = 7)

In assembler the format is SHL destination,count. This shifts destination by as many bits in count (1=*2, 2=*4, 3=*8, 4=*16 etc) Note that a shift takes only 2 clock cycles, while a mul can take up to 133 clock cycles. Quite a difference, no? Only 286es or above may have count being greater than one.

This is why to do the following to calculate the screen coordinates for a putpixel is very slow:

           mov    ax,[Y]
           mov    bx,320
           mul    bx
           add    ax,[X]
           mov    di,ax

“But alas!” I hear you cry. 320 is not a value you may shift by, as you may only shift by 2, 4, 8, 16, 32, 64, 128, 256, 512 etc. The solution is very cunning. Watch.

mov     bx,[X]
mov     dx,[Y]
push    bx
mov     bx, dx                  {; bx = dx = Y}
mov     dh, dl                  {; dh = dl = Y}
xor     dl, dl                  {; These 2 lines equal dx*256 }
shl     bx, 1
shl     bx, 1
shl     bx, 1
shl     bx, 1
shl     bx, 1
shl     bx, 1                   {; bx = bx * 64}
add     dx, bx                  {; dx = dx + bx (ie y*320)}
pop     bx                      {; get back our x}
add     bx, dx                  {; finalise location}
mov     di, bx

Let us have a look at this a bit closer shall we?

bx=dx=y        dx=dx*256  ;   bx=bx*64     ( Note, 256+64 = 320 )

dx+bx=Correct y value, just add X!

As you can see, in assembler, the shortest code is often not the fastest.

The complete putpixel procedure is as follows:

Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);
  { This puts a pixel on the screen by writing directly to memory. }
    push    ds                      {; Make sure these two go out the }
    push    es                      {; same they went in }
    mov     ax,[where]
    mov     es,ax                   {; Point to segment of screen }
    mov     bx,[X]
    mov     dx,[Y]
    push    bx                      {; and this again for later}
    mov     bx, dx                  {; bx = dx}
    mov     dh, dl                  {; dx = dx * 256}
    xor     dl, dl
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1
    shl     bx, 1                   {; bx = bx * 64}
    add     dx, bx                  {; dx = dx + bx (ie y*320)}
    pop     bx                      {; get back our x}
    add     bx, dx                  {; finalise location}
    mov     di, bx                  {; di = offset }
    {; es:di = where to go}
    xor     al,al
    mov     ah, [Col]
    mov     es:[di],ah              {; move the value in ah to screen
                                       point es:[di] }
    pop     es
    pop     ds

Note that with DI and SI, when you use them:

mov   di,50      Moves di to position 50
mov   [di],50    Moves 50 into the place di is pointing to

The Flip Procedure

This is fairly straightforward. We get ES:DI to point to the start of the destination screen, and DS:SI to point to the start of the source screen, then do 32000 movsw (64000 bytes).

procedure flip(source,dest:Word);
  { This copies the entire screen at "source" to destination }
    push    ds
    mov     ax, [Dest]
    mov     es, ax                  { ES = Segment of source }
    mov     ax, [Source]
    mov     ds, ax                  { DS = Segment of source }
    xor     si, si                  { SI = 0   Faster then mov si,0 }
    xor     di, di                  { DI = 0 }
    mov     cx, 32000
    rep     movsw                   { Repeat movsw 32000 times }
    pop     ds

The cls procedure works in much the same way, only it moves the color into AX then uses a rep stosw (see program for details)

The PAL command is almost exactly the same as its Pascal equivalent (see previous tutorials). Look in the sample code to see how it uses the out and in commands.

In Closing

The assembler procedures presented to you in here are not at their best. Most of these are procedures ASPHYXIA abandoned for better ones after months of use. But, as you will soon see, they are all MUCH faster then the original Pascal equivalents I originally gave you. In future, I hope to give you more and more assembler procedures for your ever growing collections. But, as you know, I am not always very prompt with this series (I don’t know if even one has been released within one week of the previous one), so if you want to get any stuff done, try do it yourself. What do you have to lose, aside from your temper and a few rather inventive reboots ;-)