Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Database

Real-Time Animation


JAN90: REAL-TIME ANIMATION

This article contains the following executables: RAHNER.EXE SPRITE.EXE

Rahner is an independent consultant living near Sacramento, Calif. He can be reached by phone at 916-722-1939 or through CompuServe at 71450,757.


"The distinction between a toy and a game is that the game has a goal; therefore, life is not a game, it is a toy." -- GOK

As a child, I could not differentiate between Bugs Bunny and Walter Cronkite. This is not to say that the man America most trusted had dental problems, but that the child did not have the experience to see the cartoon for what it was -- a stream of individually drawn pictures. Skilled professional help was required to deal with the trauma caused by the revealed truth. Even after the psychological defects were converted to scars, I had to wait until I could create two-dimensional life for myself.

The Time Has Come

Animation is achieved by showing a series of incrementally changing images. Depending on the duration of time that a single image is shown and the time it takes to switch to the next image increment, the viewer's visual persistence smooths out the image's transition. Anyone with a compiler and a graphics library can put a series of images on a computer display. But in order to create smooth, non-flickering, real-time animation, a fair amount of thought is necessary.

To support reasonable animation, the algorithm must conform to the following rules:

Coordinate Movement -- The routines must allow individual objects to be moved around on the screen and placed on any pixel boundary.

Independent Motion -- Each object in the image should be able to show a chain of sequences independent of its coordinate movement.

Smooth Transitions -- The transition between image frames should have no intermediate stages. This means that the viewer should see only complete images, not images that are half the first image and half the second image. These half-and-half images are perceived as a flicker and are distracting.

Regular Transitions -- All image transitions should occur at regular intervals. If this does not happen, the sequence will appear to jerk as if shown on an old projector.

Sprites -- If an object has a hole, any objects that are behind it should show through. Poorly done animation will not allow the viewer to see through a gap in an object.

Realistic Objects -- There is a difference between what is shown on the screen and what is perceived by the observer. Up to a point, jagged lines will be smoothed, imperfect colors accepted, and stairstep corners rounded. This point, the point of realism, is subjective and entirely dependent on the target audience. Below this point, other unrelated flaws can be magnified; above this point, the overall perception of the product is enhanced.

The elements of this list are all mutable by targeted hardware limitations. Accurate shading may be difficult on an AT-class machine. Absolute realism for anything but the simplest geometric shapes is impossible on CGA or EGA. The finished product's design constraints may adjust the significance of one element over the others. These are Rahner rules -- they may be burnt, bent, or beatified.

Zippy Tries His Hand at CGA Animation

When I first saw reasonable animated images on a CGA adapter, I was intrigued. It was a simple CGA sprite demo -- several helicopters flying aimlessly around the display. Because the demo allowed the helicopters to start only on a byte boundary (four pixels per byte) and did not allow them to exit smoothly from the side of the screen, I decided to try my hand at writing the ultimate animation driver for CGA.

After a lazy Sunday's work, the driver was finished. Given CGA's limitations (four colors and 320 x 200 resolution), ultimate is probably too strong a word. It was about the same as tying the ultimate shoelace or throwing the ultimate dirt clod. Anticlimactic would be the correct word for polite company.

Because the initial attempt was an unplanned, seat-of-the-pants, I've-got-Fritos-diet-Coke-and-plenty-of-time effort, my trial and error path would best illustrate some animation rudiments.

The first attempt was to whip a series of changing pictures past the monitor. This involved copying virtual screens, which I had prearranged, onto the CGA video RAM. This had the interesting effect of flickering the screen with seemingly random images caught in a blizzard.

The flicker and snowstorm were due to not waiting for the vertical retrace before displaying the next image. What is vertical retrace? A good question, because it is important later. The CRT monitor etches its pictures in the orbitals of fluorescent compounds with a single beam of electrons. This beam sweeps horizontally back and forth across the screen. In either direction -- back or forth -- the beam has to turn off, otherwise it looks funny. The off direction is called the horizontal retrace. The beam winds its way down the screen in this back and forth manner. When it reaches the bottom, it turns off and returns to the top of the screen: This off time is called the "vertical retrace." On the standard EGA setup, this vertical retrace occurs 60 times a second.

The standard CGA adapter has a bit that indicates when the horizontal refresh is occurring and another to show when the vertical refresh is occurring. It was a simple task to change the program so that it waited for the vertical retrace before blasting out the next virtual screen. -- I saw a series of pictures endlessly circling, but without snow or flicker.

Next I noticed that most of the screen was stationary and only a small fraction of the image moved at any one time. In fact, most of the things that did move were sets of pixels that did not reposition themselves with respect to one another, only with respect to the rest of the picture. After much deliberation (and lunch), I named these sets "sprites." Later I found that these sets had been noticed by others before and they had used my name for them. Rather than risking a protracted legal battle, I swallowed my pride and have allowed the others to take the credit.

The basic concept behind the sprite is simple. Cut a rectangular section out of the screen and store it. Then take the set of pixels that comprise the sprite and replace the cutout section with them.

Armed with my "new" creation, I reduced the series of virtual screens to a simple background and a few sprites. The background was displayed first, then the sprites were moved into position before the finish of the vertical retrace. Now I had the same cinematograph that I had before, but the program was more efficient and the storage requirements were reduced. Before I got bored with this endless video cycle, I noticed that when my sprites went in front of something, the background was completely covered, even in places that I should have been able to see through.

This meant another change. Instead of just storing the background, I masked off the solid areas of the sprite body. Instead of replacing the cutout, I ORed the sprite onto the masked background, then replaced the cutout around the finished product. This allowed me to have "holes" in the sprites, for increased realism.

With the inclusion of holes in the sprites, I had my ultimate CGA sprite routine. All the little fishes were swimming around in my video aquarium without the need for food. I was satisfied and went to bed.

You may be asking, "What does this have to do with EGA?" You may be getting sleepy and ready to close the magazine. You may just be hunting for good prices on software. Well, to the shopping sportsman, there are no good prices in this article; to the somnolent peruser, good night; and to the inquisitor in the back with his hand up, Everything!

The EGA and I

In its 640 x 350-pixel color graphics mode, an EGA adapter with 256K of RAM is set up as four planes of 28,000 bytes each. It also has two pages, one that is being viewed on the monitor and one that is in the ether. Both pages can be addressed directly by the CPU. The first page starts at memory address A000:0000h and the second page starts at A000:8000h. Each byte of EGA memory represents eight pixels with the most significant bit (MSB) being shown as the leftmost pixel. A byte or bit of any combination of the planes may be addressed depending on how the EGA registers have been set.

Superficially, there seemed to be little difference between a sprite driver for the CGA and EGA adapters. It seemed to be just another block of RAM that I needed to jam out bytes. Following that line of thought, the first code translation from CGA was conceptually simple. The sprites were placed on the visual page, a plane at a time. When I looked at the result for the first time, I found myself almost back to square one. No matter how efficiently I wrote the driver, there was a constant flicker. I ran to the bookstore, hoping for a tome of enlightenment. My hope was dashed by a limited selection. But a quick reread of the IBM EGA Technical Reference manual provided me with the answers: The EGA adapter can generate an interrupt and the visual page can be switched during the vertical retrace.

Writing a Bit Map to EGA Memory

The EGA adapter has a fair number of features. All the features and the way the board reacts to CPU memory manipulations are determined by the configuration registers. Most of the registers are set up in pairs. The first register accepts an index value that determines the functionality of the second register. The major register pairs that we need to concern ourselves with are the Sequencer registers and the Graphics 1 & 2 Address registers. Another register that is important for this discussion is Input Status Register One.

The Sequencer register is located at 3C4h with its index register at 3C5h. It has five indexed registers: Reset (0), Clocking Mode (1), Map Mask (2), Character Map Select (3), and Memory Mode (4). To access an indexed register, the index's number is output to the Sequencer register followed by the value for that index output to the index port. For example, if you want to place a 5 in the Clocking Mode index register, which is index number 1, the assembly code in Example 1 would do. Because all the register pairs are one right after the other, this code segment could be replaced by that in Example 2. This replacement is usually valid, except when slow ports cause timing difficulties.

Example 1: Sample code for writing bit map to EGA memory

  MOV    DX, 3C4h    ; DX - > Sequencer register
  MOV    AL, 1       ; AL = index 1, Clocking Mode
  OUT    DX, AL
  INC    DX          ; DX - > Sequencer index port
  MOV    AL, 5       ; AL = to put in Clocking Mode
  OUT    DX, AL

Example 2: Replacing the code in Example 1

  MOV    DX, 3C4h    ; DX - > Sequencer reg. pair
  MOV    AX, 501h    ; AL = index 1, AH = value 5
  OUT    DX, AX      ; Puts AL out 3C4h, then
                     ; AH out 3C5h

The important Sequencer index register, with respect to our driver, is the Map Mask Register (index 2). This register enables planes so that the CPU can write to them. Setting bit 0 enables plane 0, bit 1 enables plane 1, and so on. Because there are only four planes, the four MSBs are not used and are ignored. If you wanted to write the same information to multiple planes, multiple bits could be set. No easy assumptions can be made about the sprite data, so we can't really take advantage of this feature.

The Graphics 1 & 2 register set deals with colors, pixel masks, and the Boolean graphic operations the EGA can perform. This register is configured the same as the Sequencer register, with nine indexed registers: Set/Reset (0), Enable Set/Reset (1), Color Compare (2), Data Rotate (3), Read Map Select (4), Mode Register (5), Miscellaneous (6), Color Don't Care (7), and Bit Mask (8). The index registers of concern are the Data Rotate register and the Read Map Select register.

The Data Rotate register has two controls. Bits 0-2 represent the Rotate Count. The Rotate Count is a binary encoded number that represents the bit positions to shift any data written to a video plane. Because all our data will be unshifted at the hardware level, this value should be 0. Bits 3-4 represent the Function Select. The Function Select indicates which Boolean-type operation is desired for pixels written to display memory. Table 1 shows the available functions.

Table 1: Available functions

  Value    Description
  --------------------

  0 0      Written data is not modified
  0 1      Written data is ANDed with latched data
  1 0      Written data is ORed with latched data
  1 1      Written data is XORed with latched data

To diverge for a moment, a definition for "latched data"is in order. No matter how it appears, the video memory on the EGA adapter is never directly connected to the PC bus. When the registers are set properly, the program addresses the video memory in exactly the same manner it would any other portion of main memory. The memory can be accessed as a byte or a word, but those accesses are processed through the EGA's circuitry. The circuitry performs some gyrations on the data, then passes it on. In order to properly swing the binary song, that gyrating EGA circuitry latches the byte or word in its internal read/write buffer. A read of EGA memory will put that byte of pixels in the latch, which can then be operated on by some future operation. Because each plane has a separate latch buffer, if all four planes have been enabled, 32 bits at a time can be latched (read), operated on, and then rewritten to EGA memory with a single 8086 instruction.

Give me an inch and I'll take a while, diverging on to the 8086 instructions. When dealing with this aspect of the EGA adapter, a close look at how some 8086 instructions actually work would be in order. Let's start with the instruction:

  OR [DI], AL

When the 8086 sees this instruction, it loads the value pointed at by the register DI into the 8086 internal register, ORs the value in register AL onto that internal register, then writes the result back out to the location pointed to by DI. If DI happened to point to EGA memory, this would latch up a number of pixels, add in pixels, then write the latch data back out to video memory -- all in a single instruction. If an additional EGA function, such as a bit rotate, were added to the previous example, some interesting and possibly useful results could be achieved. I don't use this in my routine, but, by jingo, it's just too nifty to be ignored!

The last register of importance is Input Status Register One. It has a few informative bits, but we will be concerned with only the Vertical Retrace bit (3). As the name implies, this bit is set to 1 when the display is in a vertical retrace time. As I stated before, this was the time to write to the video memory with the CGA adapter. It has approximately the same value with the EGA adapter, but not exactly.

When Blazing Fast Is Not Fast Enough to Start a Fire

When I converted my CGA animation routines to work with the EGA, there was an unexpected problem. I found that no matter how fast I blasted my sprites out to video memory, the raster line (another name for that beam of electrons described earlier) would catch up to where I was writing pixels. When the raster caught up, the screen would flicker annoyingly. Having to deal with four planes and EGA's higher resolution just took too much time. I made my code the most efficient assembly routines I could. I made assumptions about the data I was displaying in order to cut corners. I got a 25-MHz 386 system. Nothing worked. I felt like a laundry soap commercial. I went back to the EGA Technical Reference manual.

Almost immediately the answer, written by the IBM ancients, made me question what I had been thinking about in the first place. The standard EGA has 256K of RAM -- enough for two pages of display memory. I could write to one page, wait for the next vertical retrace, then swap pages. I rewrote everything.

Planning ahead, I continued reading the Technical Reference manual. The EGA can generate an interrupt request 2. If the driver could just swap pages whenever the video went into vertical retrace, then I wouldn't have to waste time polling. I rewrote it, again.

Animation Structures

To become animated objects, sprites must have four basic degrees of freedom: Coordinate Motion, Self-relative Motion, Rotation, and Perceived Distance. Coordinate Motion is simply the movement from one point on the screen to another. Self-relative Motion is the movement that the sprite could make without moving to a new coordinate location. Rotation is rotation of the sprite around some center point in its body. Perceived Distance is basically sizing the sprite according to its apparent distance from the viewer.

The increment resolution of each degree of freedom is independent of the others. A sprite picture of a person may be pumping its arm up and down a pixel at a time and traversing the screen five pixels at time. Given a monitor/graphics adapter combination that refreshed the screen an infinite number of times, the smaller the movement increment, the more realistic its action would be. Because the standard EGA board refreshes the screen at 60 Hz (60 times a second), the movement increment should be judged relative to the apparent velocity of the sprite, the display resolution, and the level of the art. Because I can draw only crude stick figures, my resolution granularity can be boulder-size.

Two of the four degrees of freedom (Rotation and Perceived Distance) should not be a function of a sprite driver. A good, general-purpose rotation algorithm requires fairly heavy calculations. These calculations are burdensome enough to detract from the real-time nature of the animation driver. Although Perceived Distance does not need as much time from the CPU, it should be done at a higher level than the driver. Perceived Distance requires the sprite to be resized larger as it gets closer to the viewer and smaller as it gets farther away. As the size of the sprite approaches the minimum resolution of the monitor, details disappear. No algorithm can make perfect decisions about which features of an object are important to the visual integrity of that object.

Self-relative Motion deals with the movement of each of the individual pixels of the sprite with respect to each other, but not straying outside the boundary of the sprite. To illustrate Self-relative Motion without any other component, I have included a sprite of a flame. Each of the pixel groups that represent small flamelets rises to the top of the fire. The pixel groups that represent the edges of the flame billow in the updraft caused by the heated air. In my routine, the effect of the motion is created by a linked list of sprite frames. In the example, each successive frame shows the flame in the next point of time (without regard to mathematical proofs, in animation there is a quantum of time). Because motion in most biological or mechanical systems is cyclical, I join the terminal points of this linked list into a sprite circle. Each sprite can proceed through a cycle of self-relative motions whose complexity is determined by the circumference of the sprite circle.

Coordinate Motion involves moving the sprite circle from one point to another on the screen at some regular velocity. The sprite velocity is determined by the number of pixels that the sprite circle will move divided by the number of times per second the visual image will be changed. Say we make a sprite representation of a five-meter-long car that is drawn 32 pixels in length. To move that car from the right side of the screen to the center at an apparent velocity of 20 km/hour, the sprite would have to move to the left 176 pixels per second if everything is kept to scale. If our visual page changes come at 20 per second, the sprite would have to be moved nine pixels to the left for every page change.

How the Routines Work

The sprite routines are broken down into two parts. The portion that deals with the EGA ports, memory and interrupt service is written in 8086 assembly language. Listing One (page 82) shows the EGA sprite drivers and Listing Two (page 88), the sprite circle handler. The higher-level sprite circle and list managers are written in Microsoft C. Listing Three (page 92), SPRITES. C, displays a sprite file on an EGA screen, and Listing Four (page 93) is the make file.

Some assumptions were made about the nature of the sprites and the background. The sprite driver is written for sprites of any dimension, but the driver is optimized for a sprite that is 32 bits across. In my application, it was assumed that the observer could pan or tilt the viewing perspective. Because the perspective can be changing in smooth real time, the background is not a set quantity -- it is being regenerated in every visual frame. Additionally, because the 8086 family drops at least eight clock ticks every time it makes a JMP or CALL, the code favors execution speed (that is, very few jumps, calls, or loops) over program size.

The basic algorithm is simple. Before any operations can be performed, the sprite driver needs to be installed using the function EGA_INSTALL( ). This preps the adapter, initializes some variables, and installs the interrupt vector. The first sprite of a sprite circle is inserted into the linked list of circles by calling the function INSERT_SPRITE (START_X, START_Y, END_X, END_Y, SPEED_X, SPEED_Y, DEPTH, SPRITE_CIRCLE); where START_X and START_Y are the starting X, Y coordinates of the sprite, END_X and END_Y are the ending X, Y coordinates of the sprite, SPEED_X and SPEED_Y are the amounts that the X and Y coordinates will change per visual frame, DEPTH is the perceived distance from the observer, and SPRITE_CIRCLE is a pointer to the first entry of the sprite circle. Additional sprites can be added to the circumference of a sprite circle with the function ADD_SPRITE. Once all the sprite circles have been figured out and inserted into the sprite list, call DO_SPRITE_LIST( )whenever it is appropriate. DO_SPRITE_LIST( ) figures out the new sprite positions and places them on the nonvisual page. When it has placed all the sprites, it sets the flag DO_PAGE_FLIP, clears DONE_PAGE_FLIP, and returns. The main program body is then free to do anything with the sprite structures. At some time in the future, the EGA's vertical interrupts and the interrupt service routine decides whether to swap pages or not. If it does swap the pages, it clears DO_PAGE_FLIP and sets DONE_PAGE_FLIP. Although the routines are not completely reentrant, they are fairly immune to interrupts; they can be called from other interrupt service routines such as the timer tick or a mouse driver.

With regard to visual timing, movies project 24 frames per second on the silver screen. To decrease the cost of animation, some cartoon manufacturers will keep the same picture on the screen for more than one frame. The interrupt service routine has a counter that can be used to allow it to skip any number of vertical retraces between page changes. If your CPU is slow or the main body of your program needs more time to do its work, altering the skip count has the effect of smoothing out the movement of the sprites. To counteract the slowing effect that increasing the skip count would have, you must increase the velocity of any coordinate motion proportionately.

Finishing Thoughts

So much can be written about real-time animation that a conclusion at any point leaves us feeling a lot was left out. The scope of this article does not allow me to explore all the avenues with the depth they deserve. Maybe you can use the routines that have been provided with this article as learning aids to go beyond what has been written. Animation is a form for the presentation of ideas. Seminars, product demonstrations, and computer modeling programs can all be enhanced by the addition of animation graphics. Of course, the most obvious use only enhances what I have always said: The only useful thing someone can do with a computer is play a game on it.

_REAL-TIME ANIMATION_ by Rahner James

[LISTING ONE]

<a name="001f_0010">


   .model   small, c
   .286   ; This directive can be used to optimize procedure entry
      ; but not much else.  I avoided all non-8088 commands
   comment \
         EGA Sprite Drivers for C
      Copyright (c) February 1989, Ryu Consulting, Inc.
         (916) 722 - 1939 anytime

         Written by Rahner James, CS

   This is a full functioning sprite driver for EGA graphics
   adaptors.  The sprite are given to the routines as a linked list
   of sprite structures.  All the function are re-entrant and can be
   part of a multi-tasking system.  The sprite structures are intended
   to reside in far memory.  The sprite can exist on any pixel boundary.
   These were intended to be called from some C program, but probably
   can be modified for some other language.  This must be assembled with
   Microsoft MASM version 5.0 or later since I make use of local
   variables, forward/backward jumps and models.  Expect to get some
   incorrect size warnings because MASM doesn't seem to recognize its own
   "byte ptr" and "word ptr" operators when used with words and dwords.

   \
; ****************************************************************************
;   EQUATES
; ****************************************************************************

EOI      equ   20h      ; End Of Interrupt signal
EOI_PORT   equ   20h      ; Port to output the EOI
CRT_MODE   equ   49h
EGA_ADDRESS   equ   63h

EGA_PIXELS_WORD equ   16      ; Number of pixels per word
EGA_PIXELS_BYTE equ   8      ; Number of pixels per byte
NUMBER_OF_PLANES equ   4      ; Number of EGA color planes

EGA_RETRACE_STATUS   equ   3dah   ; EGA retrace status register
RETRACE_BIT      equ   1 shl 3   ; Bit set to signal a vertical retrace
SEQUENCE_REG      equ   3c4h   ; Sequencer register
GRAPHICS_12      equ   3ceh   ; Graphics 1 & 2 register
MAP_MASK_REG      equ   2   ; Map mask Indexed register
DATA_ROTATE_REG   equ   3   ; Data Rotate Indexed register
DATA_OR      equ   1 shl 4   ; Set to OR data on the EGA
DATA_MOVE      equ   0   ; Write data unmodified onto EGA

BOTTOM_LINE      equ   200   ; Lowest pixel line to allow a sprite
RIGHT_SIDE      equ   640   ; Right-most visual pixel allowed

; ****************************************************************************
;   STRUCTURES
; ****************************************************************************
; All sprites are stored on disk using the same internal format.
; The first word is the width of the sprites in bytes and the second
; word is the height of the sprite in widths.  Each byte represents
; one pixel's worth of information.  Bit 7 of the byte is the intensity
; bit, 0=off.  This allows for 128 colors, black will be 0 or 80h.  The
; intensity bit set indicates an opaque black surface.  All color
; translations are table driven.

internal_sprite_structure   struc   ; Storage structure used for sprites
int_width   dw   ?      ; Width in bytes for the sprite
int_height   dw   ?      ; Height in widths of the sprite
int_body   db   ?      ; Start of the sprite's body
internal_sprite_structure   ends

ega_sprite_structure   struc ; Internal EGA sprite structure
e_animate_ptr   dw   0,0   ; Far ptr to next sprite struct in animation seq.
e_width   dw   ?   ; Width of the sprite in words
e_height   dw   ?     ; Height of sprite in widths
e_body      dw   ?     ; Beginning of body
               ;.word 0: mask, word 1: sprite
               ;.throughout the body.  That way you can pull
               ;.the background up, mask it, OR the sprite,
               ;.then store the background
               ; The body is organized into four planes,
               ;.termed PLANE0 to PLANE3.  Each represents a
               ;.different color in the EGA spectrum, except
               ;.PLANE0 which is the intensity bit.
ega_sprite_structure   ends

style_structure   struc
style_width   dw   ?      ; Width of each style entry in bytes
style_height   dw   ?      ; Height of each style entry in pixels
style_body   db   ?      ; Start of the style entries
style_structure   ends

; ****************************************************************************
;   LOCAL DATA STORAGE for DS
; ****************************************************************************
.data
public      do_page_flip, done_page_flip
   even
do_page_flip   db   0   ; Set to -1 when non-visual page is completed
done_page_flip   db   -1   ; Set to -1 right after page has been swapped
flip_turn   db   4   ; # of interrupts before an EGA page flip

old_irq_mask   db   0   ; Old IRQ mask

EGA_settings   db   2bh,2bh,2bh,2bh,24h,24h,23h,2eh
      db   0,0,0,0,0,24h,23h,2eh,2bh

   even
ega_base_port      dw   ?
default_retrace   label   word
v_retrace_reg      db   11h
v_retrace_value   db   ?


.code
; ****************************************************************************
;   LOCAL DATA STORAGE for CS
; ****************************************************************************

   even

ega_segment   dw   0a800h ; EGA page memory segment being set up
old_vector   dw   0,0    ; Old IRQ-2 vector (as Checkov would say wecter)

; EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
; EGA sprite related routines below this line
; void   EGA_CONVERT( dest_ptr, source_ptr )
; Converts long storage format sprite into EGA structure sprite
; Given:
;   dest_ptr far -> EGA sprite buffer ready to go
;   source_ptr far -> disk sprite structure to convert
; Returns:
;   EGA sprite buffer set up accordingly

ega_convert proc near uses di si ds, dest:dword, source:dword
local   store_width:word, store_height:word
local   source_width:word
local   plane_size:word
   cld
   lds   si, source      ; DS:SI -> source sprite
   les   di, dest      ; ES:DI -> CGA sprite buffer


   mov   es:[di], di      ; Make the animate ptr point to itself
   mov   es:[di+2], es
   add   di, E_WIDTH

   lodsw            ; Get width in byte pixels
   mov   source_width, ax
   add   ax, EGA_PIXELS_WORD-1   ; Want to include those border pixels
   mov   cl, 4         ; Divide by sixteen to convert one byte
   shr   ax, cl         ;.per pixel to 16 pixels/word for EGA
   mov   bx, ax         ; Use this as our width count
   stosw            ; Store as words/pixels
   lodsw            ; Get height
   mov   store_height, ax
   stosw            ; Move height straight across
   mul   bx         ; AX = Body size in mask/sprite entries
   add   ax, ax         ; AX = body size in words
   add   ax, ax         ; AX = plane size in bytes
   mov   plane_size, ax      ; Save as our plane index

   mov   ax, ds         ; Swap DS:SI and ES:DI
   mov   bx, es
   mov   ds, bx
   mov   es, ax
   xchg   di, si

   sub   si, 4         ; This is to prep for the next INC
next_row:
   mov   cx, source_width   ; Get source row width
next_word:
   add   si, 4         ; SI -> next word in line
   mov   dx, -1         ; DX = the destination mask
   xor   ax, ax
   mov   [si], dx      ; Set the mask word
   mov   [si+2], ax      ; Clear the sprite word
   mov   bx, plane_size      ; BX offset to next plane
   mov   [si+bx], dx      ; Set the mask word
   mov   [si+bx+2], ax      ; Clear the sprite word
   add   bx, plane_size      ; BX offset to next plane
   mov   [si+bx], dx      ; Set the mask word
   mov   [si+bx+2], ax      ; Clear the sprite word
   add   bx, plane_size      ; BX offset to next plane
   mov   [si+bx], dx      ; Set the mask word
   mov   [si+bx+2], ax      ; Clear the sprite word

   mov   dx, 1 shl 7      ; Start at MSB which is pixel LSB
next_pixel_byte:
   jc   next_word      ; Only be set by pixel shift below
   mov   al, es:[di]      ; Get the source pixel byte
   inc   di         ; DI -> next source pixel byte
   or   al, al         ; See if it's anything at all
   jz   end_pixel_byte      ; Skip all the checks

   xor   [si], dx      ; Reset the mask bit
   test   al, 1 shl 4      ; Check bit 7
   jz   @F         ; Skip if nothing here
   or   [si+2], dx      ; Place the sprite bit
@@:   mov   bx, plane_size      ; BX -> plane 1 offset
   xor   [si+bx], dx      ; Clear the mask bit
   test   al, 1 shl 7      ; Check bit 6
   jz   @F         ; Skip if nothing here
   or   [si+bx+2], dx      ; Place the sprite bit
@@:   add   bx, plane_size      ; BX -> plane 2 offset
   xor   [si+bx], dx      ; Clear the mask bit
   test   al, 1 shl 6      ; Check bit 5
   jz   @F         ; Skip if nothing here
   or   [si+bx+2], dx      ; Place the sprite bit
@@:   add   bx, plane_size      ; BX -> plane 3 offset
   xor   [si+bx], dx      ; Clear the mask bit
   test   al, 1 shl 5      ; Check bit 4
   jz   end_pixel_byte      ; Skip if nothing here
   or   [si+bx+2], dx      ; Place the sprite bit

end_pixel_byte:
   shr   dl, 1         ; Move pixel bit toward MS pixel bit
   rcr   dh, 1
   loop   next_pixel_byte      ; Loop through the pixel bytes

   dec   store_height      ; One less row
   jnz   next_row

   ret
ega_convert   endp

; EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
; ui EGA_CALCULATE( source_ptr )
; Calculates the amount of storage needed for an unconverted sprite
; Given:
;   source_ptr far -> disk sprite structure to convert
; Returns:
;   AX = number of bytes needed to store the converted sprite

ega_calculate proc near uses si ds, source:dword
   lds   si, source     ; DS:SI -> disk sprite structure for calculation
   mov   ax, [si]       ; Get the width in bytes
   add   ax, EGA_PIXELS_WORD-1   ; Round up to nearest word
   shr   ax, 4         ; AX = number of words for stoarge
   add   ax, ax         ; AX = number of bytes storage
   add   ax, ax         ; AX = number of globs in one row
   add   ax, ax         ; AX = number of row/planes
   add   ax, ax
   mul   word ptr [si].int_height ; AX = bytes per row * number of rows
   add   ax, E_BODY      ; AX = body size + header size
   ret
ega_calculate   endp

; EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
; void PUT_SPRITE( ui X, ui Y, ega_sprite_structure far *SPRITE_PTR )
; Maps a sprite list into the display video buffer
; Sprite is assumed to be 32-bits wide
; Given:
;   X = X pixel location of sprite
;   Y = Y pixel location of sprite
;   SPRITE_PTR -> sprite structure to put on the screen
; Returns:
;   The sprite is mapped onto the screen buffer
put_sprite proc uses di si, x:word, y:word, sprite_ptr:dword

local   mask_msw:word         ; OR'ed with the mask dword
local   mask_lsw:word
local   or_msw:word         ; AND'ed with the sprite dword
local   or_lsw:word
local   start_di:word
local   start_si:word
local   sprite_plane_size:word
local   number_of_rows:word
local   shift:byte
local   plane_number:byte
   push   ds
   cmp   y, 200         ; See if below 200
   jc   @F
short_done:
   jmp   done
@@:   cmp   x, 640
   jnc   short_done

   cld
   mov   es, ega_segment
   lds   si, sprite_ptr      ; DS:SI -> sprite to be driven

   mov   ax, 80         ; Calculate the offset
   mul   y
   mov   cx, x
   mov   di, cx
   and   cl, 7         ; CL = shift value
   mov   shift, cl
   shr   di, 3
   add   di, ax         ; DI -> byte offset
   mov   start_di, di

   mov   ax, [si].e_height   ; AX = height of the sprite in rows
   mov   number_of_rows, ax   ; Save for later use
   shl   ax, 3         ; AX *= 8, to get size of 1 sprite plane
   mov   sprite_plane_size, ax

   mov   ax, number_of_rows   ; Let's see if it goes too low
   add   ax, y
   sub   ax, BOTTOM_LINE
   jbe   @F         ; Skip if it doesn't
   sub   number_of_rows, ax   ; Update the number of rows

@@:   add   si, E_BODY      ; SI -> start of sprite body
   mov   start_si, si

   cmp   x, 640-32      ; See if we are going to be right
   ja   short_mask      ; Skip if no mask
   cmp   shift, 0
   jnz   shifted

   mov   bl, NUMBER_OF_PLANES
next_plane:
   dec   bl

   mov   dx, GRAPHICS_12      ; Talk to EGA control logic
   mov   al, 4
   mov   ah, bl
   out   dx, ax

   mov   dx, SEQUENCE_REG   ; Set up the ports for writing as well
   mov   ax, 100h + MAP_MASK_REG
   mov   cl, bl
   shl   ah, cl
   out   dx, ax

   mov   cx, number_of_rows
@@:   mov   ax, es:[di]      ; Get the background dword
   mov   dx, es:[di+2]
   and   ax, [si]      ; Do the mask dword
   and   dx, [si+4]
   or   ax, [si+2]      ; Bring on the sprite
   or   dx, [si+6]
   mov   es:[di], ax      ; Replace with new graphic dword
   mov   es:[di+2], dx
   add   si, 8         ; Next row stuff
   add   di, 80
   loop   @B

   mov   di, start_di
   mov   si, start_si
   add   si, sprite_plane_size
   mov   start_si, si
   or   bl, bl
   jnz   next_plane
   jmp   done
short_mask:
   jmp   masked

shifted:
   mov   plane_number, NUMBER_OF_PLANES-1
   mov   cl, shift
   mov   bh, -1
   shr   bh, cl
next_shift_plane:
   mov   dx, GRAPHICS_12      ; Talk to EGA control logic
   mov   al, 4
   mov   ah, plane_number
   out   dx, ax

   mov   dx, SEQUENCE_REG   ; Set up the ports for writing as well
   mov   ax, 100h + MAP_MASK_REG
   mov   cl, plane_number
   shl   ah, cl
   out   dx, ax

   mov   ch, byte ptr number_of_rows
   mov   cl, shift
@@:   lodsw            ; Get the sprite mask
   xchg   ah, al         ; Switch them around
   ror   ax, cl
   mov   bl, ah         ; Top CL bits are ones to mask 3rd byte
   not   bh         ; BH = ~BH
   and   bl, bh
   or   ah, bh         ; Now set the top ones of source byte
   mov   dx, es:[di]      ; Get the first destination word
   xchg   ah, al         ; Re-order the mask bytes
   and   dx, ax         ; Mask it
   lodsw            ; Get the sprite
   xchg   ah, al
   ror   ax, cl
   or   dh, al         ; OR DH w/ old AH
   mov   al, ah         ; Save the upper bits
   and   ah, bh         ; Mask off other bits
   not   bh         ; BH = BH
   and   al, bh         ; Cut out the rotunds
   or   dl, al         ; OR least sig. bytes
   mov   es:[di], dx      ; Save that first word, whew!

   mov   dl, ah         ; DL = pushed up sprite bits

   lodsw            ; Get the next sprite mask
   xchg   ah, al         ; Switch them around
   ror   ax, cl
   mov   dh, ah         ; DH = MS shifted mask bits
   and   ah, bh         ; Get rid of shifted bits
   or   ah, bl         ; OR with shifted mask, previous byte
   or   dh, bh         ; Add on the mask
   xchg   ah, al         ; AH:AL back to normal
   and   es:[di+4], dh      ; Easy way to get rid of DH
   mov   bl, dl         ; BL = previous sprite bits
   mov   dx, es:[di+2]      ; Get the destination word
   and   dx, ax         ; Mask it
   lodsw            ; Get the sprite
   xchg   ah, al
   ror   ax, cl
   or   dh, al         ; OR DH w/ old AH
   mov   al, ah         ; Save the upper bits
   not   bh         ; BH = ~BH
   and   ah, bh         ; Mask off other bits
   not   bh         ; BH = BH
   and   al, bh         ; Cut out the rotunds
   or   al, bl
   or   dl, al         ; OR least sig. bytes
   mov   es:[di+2], dx      ; Save that first word, whew!
   or   es:[di+4], ah

   add   di, 80

   dec   ch
   jnz   @B

   mov   di, start_di
   mov   si, start_si
   add   si, sprite_plane_size
   mov   start_si, si
   sub   plane_number, 1
   jc   @F
   jmp   next_shift_plane
@@:   jmp   done

masked:
   xor   ax, ax         ; Set up masks and ORs
   mov   mask_lsw, ax
   mov   mask_msw, ax
   dec   ax
   mov   or_lsw, ax
   mov   or_msw, ax

   cmp   x, 640-24      ; See if we have masked it already
   jc   @F         ; Skip if we have
   mov   mask_msw, 0ff00h
   mov   or_msw, 0ffh

   cmp   x, 640-16
   jc   @F
   mov   mask_msw, -1      ; Make sure nothing gets masked
   mov   or_msw, 0

   cmp   x, 640-8      ; See if that's all
   jc   @F
   mov   mask_lsw, 0ff00h
   mov   or_lsw, 0ffh

@@:   mov   plane_number, NUMBER_OF_PLANES-1
next_mask_plane:
   mov   dx, GRAPHICS_12      ; Talk to EGA control logic
   mov   al, 4
   mov   ah, plane_number
   out   dx, ax

   mov   dx, SEQUENCE_REG   ; Set up the ports for writing as well
   mov   ax, 100h + MAP_MASK_REG
   mov   cl, plane_number
   shl   ah, cl
   out   dx, ax

   mov   cx, number_of_rows
@@:   mov   ax, [si]      ; Get the first mask
   mov   dx, [si+4]      ; Get the second mask
   or   ax, mask_lsw
   or   dx, mask_msw
   and   ax, es:[di]      ; Get the background dword
   and   dx, es:[di+2]
   mov   bx, or_lsw      ; Get the OR lsw
   and   bx, [si+2]      ; Bring on the sprite
   or   ax, bx
   mov   bx, or_msw
   and   bx, [si+6]
   or   dx, bx
   mov   es:[di], ax      ; Replace with new graphic dword
   mov   es:[di+2], dx
   add   si, 8         ; Next row stuff
   add   di, 80
   loop   @B

   mov   di, start_di
   mov   si, start_si
   add   si, sprite_plane_size
   mov   start_si, si
   sub   plane_number, 1
   jnc   next_mask_plane
   jmp   done

done:   pop   ds
   ret
put_sprite   endp

; EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
; void EGA_CLEAR_AREA( ui X1, ui Y1, ui X2, ui Y2 )
; Clears an area on the EGA display to black
; Given:
;   X1,Y1 = X,Y pixel coordinates of the upper left corner
;   X2,Y2 = X,Y pixel coordinates of the lower right corner
; Returns:
;   Rectangular area from X1,Y1 to X2,Y2 (inclusive) cleared to black

public ega_clear_area
ega_clear_area proc uses ds si di, x1:word, y1:word, x2:word, y2:word
local   height:word
local   di_start:word
local   word_columns:word
local   di_offset:word

   cld

   cmp   x1, 640         ; See if too far to the right
   jc   @F
   mov   x1, 0
@@:   cmp   x2, 640
   jc   @F
   mov   x2, 639

@@:   mov   ax, y2         ; Check out number of rows
   sub   ax, y1
   jnc   @F         ; See if jerk put them in backwards
   neg   ax
@@:   inc   ax
   mov   bx, ax         ; BX = number of rows
   mov   height, ax

   mov   ax, y1         ; Check our starting offset
   mov   cx, 80
   mov   di_offset, cx
   mul   cx
   mov   di, ax
   mov   ax, x1         ; See where we start
   shr   ax, 3
   add   di, ax

   mov   ax, x2
   sub   ax, x1
   jnc   @F
   neg   ax
@@:   add   ax, 16
   shr   ax, 4
   jnz   @F
   jmp   done

@@:   mov   word_columns, ax
   add   ax, ax
   sub   di_offset, ax
   mov   es, ega_segment

   mov   dx, SEQUENCE_REG   ; Set up the ports for writing
   mov   ax, 0f00h+MAP_MASK_REG
   out   dx, ax

   mov   dx, di_offset
   xor   ax, ax
   mov   di_start, di
@@:   mov   cx, word_columns
   rep   stosw
   add   di, dx
   dec   bx
   jnz   @B

done:   ret
ega_clear_area endp

; EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
; void EGA_INSTALL()
; Installs IRQ-2 vectors for EGA card
; Given:
;   nothing
; Returns:
;   -1 if neither EGA or VGA else
;   EGA interrupt vector installed and enabled
public   ega_install

ega_install proc
   mov   ax, 40h         ; ES -> video BIOS data area
   mov   es, ax

   mov   dx, es:[EGA_ADDRESS]   ; DX -> CRTC Address port
   mov   ega_base_port, dx   ; Save the port address

   mov   ax, 1a00h      ; Read display combination
   int   10h
   cmp   al, 1ah         ; See if EGA
   jne   ega_adaptor
   cmp   bl, 7         ; See if VGA
   je   vga_adaptor
   cmp   bl, 8
   je   vga_adaptor
error_out:
   mov   ax, -1
   jmp   short done

ega_adaptor:
   mov   al, es:[CRT_MODE]   ; AL = video BIOS mode number
   mov   bx, offset EGA_settings
   xlat
   jmp   short @F

vga_adaptor:
   mov   al, v_retrace_reg   ; AL = Vertical retrace register
   out   dx, al
   inc   dx
   in   al, dx

@@:   mov   v_retrace_value, al

   mov   done_page_flip, -1
   mov   do_page_flip, 0

   xor   ax, ax         ; ES -> base page
   mov   es, ax
   mov   bx, 0ah*4      ; Vector for IRQ 2
   mov   dx, cs
   mov   ax, offset ega_interrupt

   cli
   xchg   es:[bx], ax
   xchg   es:[bx+2], dx
   mov   old_vector, ax
   mov   old_vector+2, dx

   in   al, 21h         ; Get present mask
   mov   old_irq_mask, al
   and   al, 11111011b
   out   21h, al

   mov   dx, ega_base_port
   mov   ax, default_retrace
   and   ah, 11001111b
   out   dx, ax
   jmp   short $+2
   or   ah, 00010000b
   out   dx, ax
   sti

done:   ret
ega_install endp

; EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
; void EGA_RIP_OUT()
; Undoes all the interrupt processing for the EGA
; Given:
;   nothing
; Returns:
;   EGA interrupt vector removed
public   ega_rip_out
ega_rip_out proc uses es
   mov   ax, old_vector      ; See if installed
   or   ax, old_vector+2
   jz   done         ; Return if not installed

   xor   ax, ax         ; ES -> base page
   mov   es, ax
   mov   bx, 0ah*4      ; Vector for IRQ 2
   mov   ax, old_vector
   mov   dx, old_vector+2
   mov   old_vector, 0
   mov   old_vector+2, 0
   cli
   mov   es:[bx], ax
   mov   es:[bx+2], dx

   in   al, 21h
   mov   ah, old_irq_mask   ; Restore old interrupt mask
   and   ah, 1 shl 2
   and   al, 11111011b
   or   al, ah
   out   21h, al

   mov   dx, 3d4h
   mov   ax, 2b11h
   out   dx, ax
   sti
done:   ret
ega_rip_out endp

; EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
; void EGA_INTERRUPT( void )
; Handles all the interrupt processing for the EGA controller
; Given:
;   This is run at every IRQ-2 spike (ie. vertical retrace)
; Returns:
;   If FLIP_TURN is brought to zero, EGA visual pages are swapped
;   If swap is made, DO_PAGE_FLIP set to 0, DONE_PAGE_FLIP set to -1

ega_interrupt proc far
   push   ax
   push   dx
   push   ds

   mov   ax, @DATA
   mov   ds, ax

   mov   dx, 3c2h      ; DX -> I/O port for input status
   in   al, dx
   test   al, 1 shl 7
   jnz   @F         ; Interrupt is ours
   pushf
   call   dword ptr [old_vector]
   jmp   done

@@:   mov   dx, ega_base_port   ; DX -> EGA/VGA register port
   in   al, dx
   push   ax

   mov   ax, default_retrace
   and   ah, 11101111b
   out   dx, ax
   jmp   short $+2

   mov   al, EOI
   out   EOI_PORT, al
   jmp   short $+2
   sti

   dec   flip_turn
   jnz   @F
   mov   flip_turn, 4
   cmp   do_page_flip, 0      ; See if we need to do this
   jz   @F

   mov   al, 0ch         ; Select register for page MSB
   mov   ah, byte ptr ega_segment+1
   shl   ah, 4
   out   dx, ax         ; Output the most significant byte
   xor   ega_segment, 800h   ; Swap the active page
   jmp   short $+2

   mov   do_page_flip, 0
   mov   done_page_flip, -1

@@:   cli
   mov   ax, default_retrace
   and   ah, 11011111b
   or   ah, 00010000b
   out   dx, ax
   jmp   short $+2
   pop   ax
   out   dx, al

done:   pop   ds
   pop   dx
   pop   ax

   iret
ega_interrupt endp

   end




<a name="001f_0011"><a name="001f_0011">
<a name="001f_0012">
[LISTING TWO]
<a name="001f_0012">

   comment \
          Sprite Circle Handler
      Copyright (c) February 1989, Ryu Consulting, Inc.
         (916) 722 - 1939 anytime
         Written by Rahner James, CS
      \

; ****************************************************************************
;   EQUATES
; ****************************************************************************
MAX_SPRITES   equ   50
; ****************************************************************************
;   STRUCTURES
; ****************************************************************************
sprite_structure struc
animate_ptr   dw   0,0
sprite_width   dw   0      ; Width in words
sprite_height   dw   0      ; Height in pixels
sprite_body   db   ?
sprite_structure ends
struc
x      dw   0
y      dw   0
depth      dw   0
pre_x      dw   0
pre_y      dw   0
dest_x      dw   0
dest_y      dw   0
adder_x   dw   0
adder_y   dw   0
sprite_ptr   dw   0,0
next_node   dw   0
sprite_node   ends
.data
; ****************************************************************************
;   DATA VARIABLES and EXTERNAL DEFINITIONS
; ****************************************************************************
extrn   done_page_flip:byte, do_page_flip:byte
extrn   min_x:word, min_y:word, max_y:word, max_x:word

first_sprite   dw   0
public   sprite_list
sprite_list   sprite_node   MAX_SPRITES dup(<>)

pre_max_x   dw   639
pre_max_y   dw   199
pre_min_x   dw   0
pre_min_y   dw   0

.code
; ****************************************************************************
;   ROUTINES and EXTERNAL CODE DEFINITIONS
; ****************************************************************************
extrn   do_background:near, put_sprite:near

; ****************************************************************************
; void DO_SPRITE_LIST( void )
; Sets up the sprites on the unviewed back page
; Given:
;   A sprite list has been created and is stored in the array SPRITE_LIST
; Returns:
;   If DONE_PAGE_FLIP is 0, no processing is done
;   else all sprites in the sprite list are put on the non-visual page
;    then DONE_PAGE_FLIP is set to 0 and DO_PAGE_FLIP is set to -1

do_sprite_list proc uses si
   cmp   done_page_flip, 0   ; See if we need to do this
   jnz   @F
   jmp   done
@@:   call   do_background

   mov   ax, pre_max_x
   mov   max_x, ax
   mov   ax, pre_max_y
   mov   max_y, ax
   mov   ax, pre_min_x
   mov   min_x, ax
   mov   ax, pre_min_y
   mov   min_y, ax

   xor   ax, ax         ; Clear out some variables
   mov   pre_max_x, ax
   mov   pre_max_y, ax
   dec   ax
   mov   pre_min_x, ax
   mov   pre_min_y, ax

   mov   si, first_sprite   ; SI -> sprite node to start with
next_sprite:
   or   si, si         ; See if this is a NULL pointer
   jnz   @F
   jmp   almost_done

@@:   mov   ax, [si].x
   mov   [si].pre_x, ax

   cmp   [si].dest_x, ax      ; See if we are already there
   je   no_add_x      ; Skip if we are
   mov   cx, [si].dest_x      ; Get our absolute value
   sub   cx, [si].x
   jnc   @F
   neg   cx
@@:   add   ax, [si].adder_x
   mov   [si].x, ax
   sub   ax, [si].dest_x
   jnc   @F
   neg   ax
@@:   cmp   cx, ax
   jnc   no_add_x
   mov   ax, [si].dest_x
   mov   [si].x, ax
no_add_x:
   mov   ax, [si].y
   mov   [si].pre_y, ax

   cmp   [si].dest_y, ax      ; See if we are already there
   je   no_add_y      ; Skip if we are
   mov   cx, [si].dest_y      ; Get our absolute value
   sub   cx, [si].y
   jnc   @F
   neg   cx
@@:   add   ax, [si].adder_y
   mov   [si].y, ax
   sub   ax, [si].dest_y
   jnc   @F
   neg   ax
@@:   cmp   cx, ax
   jnc   no_add_y
   mov   ax, [si].dest_y
   mov   [si].y, ax
no_add_y:
   cmp   [si].y, 200      ; See if beyond bottom line
   jnc   pre_next_sprite
   mov   ax, [si].x      ; See if we need to update some things
   cmp   ax, 640         ; See if beyond right column
   jnc   pre_next_sprite
   cmp   ax, pre_min_x      ; See if pre_x < min_x
   jnc   @F         ; Skip if not
   mov   pre_min_x, ax      ; Update with new MIN_X

@@:   mov   ax, [si].y      ; See if need to update min_y
   cmp   ax, pre_min_y
   jnc   @F
   mov   pre_min_y, ax

@@:   les   bx, dword ptr [si].sprite_ptr   ; ES:BX -> sprite structure

   mov   ax, es:[bx].sprite_width
   inc   ax
   if   (@Cpu AND 2)
   shl   ax, 4         ; AX = AX * 16
   else
   rept   4
   add   ax, ax
   endm
   endif
   add   ax, [si].x
   cmp   pre_max_x, ax      ; See if > max_x
   jnc   @F         ; Jump if not
   mov   pre_max_x, ax      ; Assume we are going to save it

@@:   mov   ax, es:[bx].sprite_height
   add   ax, [si].y
   cmp   pre_max_y, ax      ; See if > max_x

   jnc   @F         ; Jump if not
   mov   pre_max_y, ax      ; Assume we are going to save it

@@:   push   es         ; Set up for call to put_sprite()
   push   bx
   push   [si].y
   push   [si].x
   call   put_sprite
   add   sp, 4         ; Clear the stack
   pop   bx         ; ES:BX -> sprite pointer
   pop   es
   les   bx, dword ptr es:[bx].animate_ptr
   mov   [si].sprite_ptr, bx
   mov   [si].sprite_ptr+2, es

pre_next_sprite:
   mov   si, [si].next_node   ; SI -> next sprite node in line
   jmp   next_sprite

almost_done:
   mov   ax, pre_max_x
   cmp   ax, 640
   jb   @F
   mov   ax, 639
   mov   pre_max_x, ax
@@:   cmp   pre_min_x, ax
   jb   @F
   dec   ax
   mov   pre_min_x, ax
@@:   mov   ax, pre_max_y
   cmp   ax, 200
   jb   @F
   mov   ax, 199
   mov   pre_max_y, ax
@@:   cmp   pre_min_y, ax
   jb   @F
   mov   pre_min_y, ax

@@:   mov   done_page_flip, 0
   mov   do_page_flip, -1

done:   ret
do_sprite_list endp

; ****************************************************************************
; void CLEAR_SPRITE_LIST( void )
; Zeros out the present sprite list
; Given:
;   nothing
; Returns:
;   FIRST_SPRITE and SPRITE_LIST array are zeroed

clear_sprite_list proc uses di

   cld
   mov   di, offset sprite_list
   mov   ax, ds
   mov   es, ax
   xor   ax, ax
   mov   first_sprite, ax
   mov   cx, (MAX_SPRITES * (size sprite_node))/2
   rep   stosw

   ret
clear_sprite_list endp

; ****************************************************************************
; int INSERT_SPRITE( ui X1,ui Y1, ui D_X,ui D_Y, ui PLUS_X,ui PLUS_Y,
;             ui THE_DEPTH, sprite_structure far *SPRITE )
; Inserts the first sprite of a sprite circle into the linked list of circles
; Given:
;   X1,Y1 = pixel location of the upper left corner of the sprite
;   D_X,D_Y = pixel location of the destination of the sprite
;   PLUS_X,PLUS_Y = pixels the sprite moves every page flip
;   THE_DEPTH = apparent distance of the sprite from the viewer
;   SPRITE -> sprite to insert
; Returns:
;   If 0, sprite pointer was inserted in the array
;   else !0 if there is no room

insert_sprite proc uses si, x1:word,y1:word, d_x:word,d_y:word,\
            plus_x:word,plus_y:word, the_depth:word,\
            sprite:dword

   mov   cx, MAX_SPRITES
   mov   bx, (offset sprite_list) - (size sprite_node)
@@:   add   bx, size sprite_node
   mov   ax, [bx].sprite_ptr   ; See if this has been set yet
   or   ax, [bx].sprite_ptr+2
   loopnz   @B
   jnz   done_bad

   les   ax, sprite      ; ES:AX -> sprite location
   mov   [bx].sprite_ptr, ax   ; Save it
   mov   [bx].sprite_ptr+2, es

   mov   ax, x1         ; Set up the structure
   mov   [bx].x, ax
   mov   [bx].pre_x, ax
   mov   ax, y1
   mov   [bx].y, ax
   mov   [bx].pre_y, ax
   mov   ax, d_x
   mov   [bx].dest_x, ax
   mov   ax, d_y
   mov   [bx].dest_y, ax
   mov   ax, plus_x
   mov   [bx].adder_x, ax
   mov   ax, plus_y
   mov   [bx].adder_y, ax
   mov   ax, the_depth
   mov   [bx].depth, ax      ; This is used in the following loop
   mov   dx, bx         ; Save this s[rite entry for later

   mov   si, first_sprite   ; SI -> sprite furthest from the viewer
   mov   bx, offset first_sprite   ; BX -> previous sprite entry
   mov   cx, MAX_SPRITES
@@:   or   si, si         ; See if it's a NULL ptr
   jz   @F         ; Skip out if it is
   cmp   ax, [si].depth      ; See if farther from observer
   jnc   @F
   mov   bx, si         ; BX = this pointer
   mov   si, [si].next_node   ; SI -> next sprite node in line
   loop   @B
done_bad:
   mov   ax, -1         ; Indicate we had a problem
   jmp   short done

@@:   xchg   si, dx         ; SI -> sprite_list[i]
   mov   [si].next_node, dx
   cmp   bx, offset first_sprite
   jne   @F
   mov   [bx], si
   jmp   short done_good
@@:   mov   [bx].next_node, si
done_good:
   xor   ax, ax
done:   ret

insert_sprite endp

; ****************************************************************************
; void ADD_SPRITE( sprite_structure far *DEST, far *SOURCE )
; Adds a self-relative sprite motion to the end of a sprite circle
; Given:
;   DEST -> sprite circle header
;   SOURCE -> sprite to add on
; Returns:
;   SOURCE is added to the end of the sprite linked list and the
;   circle ends are rejoined, may the circle be unbroken (ie Johnny Cash)

add_sprite proc uses si di ds, dest:dword, source:dword

   lds   si, dest
   les   di, source
next_sprite:
   mov   ax, [si].animate_ptr   ; See if this is the end of the line
   cmp   ax, word ptr dest
   jne   @F
   mov   ax, [si].animate_ptr+2
   cmp   ax, word ptr dest+2
   je   got_the_end
@@:   lds   si, [si].animate_ptr
   jmp   next_sprite

got_the_end:
   mov   [si].animate_ptr, di
   mov   [si].animate_ptr+2, es
   mov   ax, word ptr dest
   mov   es:[di].animate_ptr, ax
   mov   ax, word ptr dest+2
   mov   es:[di].animate_ptr+2, ax

   ret
add_sprite endp

   end




<a name="001f_0013"><a name="001f_0013">
<a name="001f_0014">
[LISTING THREE]
<a name="001f_0014">

/***************************************************************************
   TITLE:   SPRITES.C
   Displays a sprite file on an EGA screen
   Written by: Rahner James, CS
               of Ryu Consulting, Inc.
***************************************************************************/
#include <stdio.h>
#include <dos.h>
#include <fcntl.h>
/***************************************************************************
      VARIOUS DEFINITIONS
***************************************************************************/
#pragma pack(1)

typedef   unsigned char   uc;
typedef   unsigned int   ui;
typedef   unsigned long   ul;
/***************************************************************************
      EXTERNAL DECLARATIONS
***************************************************************************/
extern   void ega_convert();
extern   ui ega_calculate( uc far * );
extern   void ega_install();
extern   void ega_clear_area( ui, ui, ui, ui );
/***************************************************************************
      GLOBAL DATA
***************************************************************************/
ui   min_x=0, min_y=0, max_x=639, max_y=199;
/***************************************************************************
   long READ_ALL_FILE( uc *FILENAME, uc huge *BUFFER, ul BUFFER_SIZE )
   Opens and reads an entire sprite file
   Given:
      FILENAME -> name of sprite file to read
      BUFFER -> buffer to read the sprite file into
      BUFFER_SIZE = number of bytes the buffer can hold
   Returns:
      File is opened, read and closed
      Number of bytes read, if all went well
      If error, returns -1
***************************************************************************/
long read_all_file( uc *filename, uc huge *buffer, ul buffer_size )
{
   long   rv = 0;
   ui   handle, dos_return, amount_read;
   ui   amount_to_read;
   if ( _dos_open( filename, O_RDONLY, &handle ) )
      return -1;
   while ( buffer_size )
   {
      amount_to_read = buffer_size<60000L ? buffer_size : 60000L;
      if ( _dos_read( handle, buffer+rv, amount_to_read, &amount_read ) )
      {
         rv = -1;
         break;
      }
      rv += amount_read;
      if ( amount_read < 60000 )
         break;
      buffer_size -= amount_read;
   }
   _dos_close( handle );
   return rv;
}

/***************************************************************************
   void DO_BACKGROUND( void )
   Sets up the background for the sprite visual screen
   Given:
      nothing
   Returns:
      visual sprite screen erased
***************************************************************************/
void do_background( void )
{
   ega_clear_area( min_x, min_y, max_x, max_y );
}
/***************************************************************************
   uc SET_MODE( uc MODE_NUMBER )
   Sets the video mode
   Given:
      Mode number to set video to
   Returns:
      Present video mode number
***************************************************************************/
uc set_mode( uc mode_number )
{
   uc rv;
   union REGS regs;

   regs.h.ah = 15;
   int86( 0x10, ®s, ®s );
   rv = regs.h.al;

   regs.h.ah = 0;
   regs.h.al = mode_number;
   int86( 0x10, ®s, ®s );

   return rv;
}
/***************************************************************************
   MAIN( int ARGC, uc *ARGV[] )
   Allocates memory, reads in a sprite file, displays the sprites
   until a key is pressed, frees up memory and interrupt vectors
   Given:
      ARGC = number of command line values, must be > 1
      ARGV[1] -> file name of the sprite file to display
   Returns:
      0 if all went well, otherwise numbered according to error
***************************************************************************/
main( int argc, uc *argv[] )
{
   ui   i, x, y;
   uc   far *file_ptr, huge *sprite_start, huge *buffer_start;
   uc   huge *sprite_ptr[20];
   uc   old_mode;
   ui   memory_segment;
   ul   memory_size=0, file_size;
/* Check initial values and allocate memory for buffers */
   if ( argc<2 )
   {
      printf( "\nNo file name has been given\n" );
      exit( 1 );
   }

   if ( _dos_allocmem( -1, (ui *)&memory_size ) )
   {
      if ( _dos_allocmem( memory_size, &memory_segment ) )
      {
         printf( "\nMemory allocation error\n" );
         exit( 2 );
      }
   }
   else
   {
      memory_segment = memory_size;
      memory_size = 0xffff;
   }
   memory_size <<= 4;
   buffer_start = (uc huge *)((ul)memory_segment << 16L);
/* Read in the sprite file and then convert it to our intenal structure */
   file_ptr = buffer_start;
   if ( (file_size=read_all_file(argv[1],file_ptr,memory_size)) == -1 )
   {
      _dos_freemem( memory_segment );
      printf( "\nGot error reading %s.  Aborting.\n", argv[1] );
      exit( 3 );
   }
   clear_sprite_list();
   sprite_start = file_ptr + file_size;
   for ( i=0 ; i<20 && file_size ; ++i )
   {
      ega_convert( sprite_ptr[i]=sprite_start, file_ptr );
      x = ega_calculate( file_ptr );
      sprite_start += x;
      x = (ui)*file_ptr * (ui)*(file_ptr+2) + 4;
      file_ptr += x;
      if ( file_size > (ul)x )
         file_size -= (ul)x;
      else
         file_size = 0;
   }
/* Create linked list of sprite circles */
   insert_sprite( 100,100, 100,100, 0,0, 7, sprite_ptr[0] );
   for ( x=1 ; x<i ; ++x )
      add_sprite( sprite_ptr[0], sprite_ptr[x] );
/* Setup the EGA screen mode and interrupt vector */
   old_mode = set_mode( 0x10 );
   ega_install();
/* Process the sprite list until someone taps a key */
   while ( !kbhit() )
      do_sprite_list();
/* Restore screen and allocated memory to original state */
   ega_rip_out();
   set_mode( old_mode );
   _dos_freemem( memory_segment );
   exit( 0 );
}




<a name="001f_0015"><a name="001f_0015">
<a name="001f_0016">
[LISTING FOUR]
<a name="001f_0016">

sprite.obj:   sprite.c
   cl /c sprite.c

ega_drv.obj:   ega_drv.asm
   masm ega_drv;

gen_asm.obj:   gen_asm.asm
   masm gen_asm;

sprite.exe:   sprite.obj ega_drv.obj gen_asm.obj
   link sprite+gen_asm+ega_drv;


Example 1: Sample code for writing bit map to EGA memory

MOV       DX, 3C4h       ; DX -> Sequencer register
MOV       AL, 1          ; AL = index 1, Clocking Mode
OUT       DX, AL
INC       DX             ; DX -> Sequencer index port
MOV       AL, 5          ; AL = to put in Clocking Mode
OUT       DX, AL


Example 2: Replacing the code in Example 1

MOV       DX, 3C4h       ; DX -> Sequencer reg. pair
MOV       AX, 501h       ; AL = index 1, AH = value 5
OUT       DX, AX         ; Puts AL out 3C4h, then
                         ;  AH out 3C5h











Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.