ECE291 Computer Engineering II Lockwood, Fall 1997

Machine Problem 4: Battletank Simulator:
Part I (graphics)

Assigned Thursday, October 30, 1997
Due Date Friday, November 14, 1997
Purpose VGA Graphics, Bresenham's Line Algorithm
Points 50

Introduction

In the near future, battles will be fought in virtual environments. The nastiest of these battles may very well be in the ECE291 laboratory. For this assignment, we will render a 3-dimensional battlefield and each be the captain of our own tank.

This is the beginning of a two-part machine problem. In this machine problem, you will code the graphic routines and write a keyboard interrupt handler for a 3-D tank simulator. For the graphics, you will write a routine to draw polygons on the screen, load PCX files, and use efficient string operations to create smooth-motion (30+ frames/second) animation.

Our battlefield will be a square region with vertical walls. As we move our tank toward a wall, the wall will appear larger. As we move away, it will appear smaller. At any given time, we can see up to three walls on the screen. A screen dump of the running program (using calls from libmp4) is shown below.

Drawing Polygons

We will render the walls in our 3-D world using multiple instances of a single graphic primitive: a four-sided polygon. Figure 1(a) shows the type of polygon that we will be using for this assignment.

The poloygon is most easily drawn by breaking the problem into three filled regions, as shown in Figure 1.b. Region 2 is the easiest, you just need to draw a rectangular box from X1 to X2 going from Y2 down to Y4. Recall that the string instructions covered in class for Lecture 17 are most efficient for fast graphics.

Drawing regions 1 and 3, are more difficult. You must use Bresenham's Line Algorithm to determine the points connecting (X1, Y1) to (X2, Y2) and (X1, Y3) to (X2, Y4). Once you know the points on the line, you can draw horizontal lines to fill in the region. The derivation of Bresenham's algorithm was covered in class in Lecture 19.

Once you determine which pixel appears on the diagonal; you can simply draw a fill line back to the other end of the triangle. If the slope of the line is greater than 1, you will need to swap your independent axis. You will want to code this complex routine using several simplier subroutines. It is up to you to determine a good set of modular subroutines. Points will be deducted for routines that attempt to draw the entire polygon using one, large, ugly procedure.

Video Graphics

In order to reduce the amount of screen flicker inherent in programs that use VGA graphics, we are going to use double-buffering to drawn on the screen. After an entire image has been created, it will be quickly transferred with the MoveScreen routine directly to the screen. With this implementation, you don't see this image while it is being changed.

Because one page of graphics alone requires 64,000 bytes of information, more than one segment must be defined. Four variables in different segments have been defined as follows:

A few steps are required to draw a video screen. First, we call the AnimateScreen routine to transfer a horizontally shifted version of the Background image to the ScreenBuffer. This routine not only erases the old images from ScreenBuffer, but it also creates the illusion of moving clouds. Next, we call DrawPoly to draw polygons on ScreenBuffer to represent the vertical walls. Once an entire image has been created in ScreenBuffer, MoveScreen is called to blast the data from ScreenBuffer to the VGA's video RAM (at A0000h).

Image Data

It would be tedious to define images using assembly code. It is much more convienient to create an image using a standard image editor (such as PhotoPaint) and save the results in a file. Our program then need only load this data when it executes.

For this MP, we will write the LoadPCX routine to open and read a .PCX file; run-length decode it; then save the uncompressed data in a memory segment. DOS file services (software interrupts) are described in the lab manual and in your textbook. (You will need to open a file, point a register to the scratch segment, and issue the read command). Details of the PCX image format were discussed in class in Lecture 22. PCX files use a slight variation of the Run-Length encoding technique covered in MP2. A "run" of data corresponds to horizontal lines in the image. You will find some very helpful example PCX code in your lab manual.

Keyboard Control

The last part of this machine problem involves reading input from the user's keyboard. To read of keys pressed simultaneously, we need to replace the default keyboard handler with our own code. Your InstKey routine is called to install a new handler into the vector table. The DeInstallKey routine is used to remove it (and restore the DOS default routine).

Key presses affect the variables ExitFlag, Xoffset, and Yoffset. ExitFlag should be set to 1 when the ESC key is pressed. The Xoffseg variable holds {-1, 0, 1} for {left key, no horizontal movement, right key} and Yoffset holds {-1, 0, 1} for {down key, no vertical movement, up key}. The value of offset is undefined when arrows in opposite directions are pressed together. The topic of interrupts was discussed in great detail during the Lecture 14 class period. We discussed the operation of the keyboard controller during Lecture 16

C-Style Procedures

The routines in this MP should be implemented so that they can be called from C. As discussed in Lecture 10, this involves making FAR procedure calls, pushing arguments to the stack, and using BP to access the arguments passed to a procedure. Recall that C expects ASM functions to be prefixed with an underscore.

Look at the code given in this assignment for examples of procedure calls with INVOKE.

Details of the INVOKE command can be found in the MASM on-line help (MASM /h) and in the MASM manual.

Program Debugging

For your convenience, the program provides several debugging modes. By invoking the program with a command-line argument between 1 and 5, you can specify which part of the code to debug. Typing MP4 5, for example, exercises all features of the program. Typing MP4 1 just exercises the drawing of a single polygon. Read and understand the MP4.ASM framework (given at the end of the assignment) see how the program operations.

Procedures

Preliminary Procedure

Errata

Final Steps

  1. Demonstrate MP4.EXE to a TA or to the instructor. Be sure that you can run the all the test cases that exercise each routine.
  2. Be prepared to answer questions about any aspect of the operation of your program.  The TA's will not accept an MP if you cannot fully explain the operation of any part of your algorithm or code.
  3. Copy you programs to the handin floppy: A:\HANDIN YourWindowsLogin
  4. Print MP4.ASM. Use small fonts to save paper.

MP4.ASM (Program framework)

        PAGE 75, 132
        TITLE Tank Simulation (Part I) - Your Name - Current Date

.MODEL LARGE  ; Allow multiple segments
.486          ; Enable use of 32-bit registers (EAX, EBX, etc.) 

COMMENT %
        Battletank Simulator
        --------------------
	ECE291: MP4
	Prof. John W. Lockwood
        Unversity of Illinois, Dept. of Electrical & Computer Engineering
        ; Assistant Guest Authors: Mike Carter, Johanna Canniff
        Fall 1997
	Revision 1.0
        %

;====== Constants =========================================================

VIDGRSEG   EQU 0A000h    ; VGA Video Graphics Segment Adddress
VIDTEXTSEG EQU 0B800h    ; VGA Video Text Segment Adddress

CR         EQU 13
LF         EQU 10


GMODE MACRO ; Switch to 320x200 - 256 colors graphics (Mode 13h)
          mov     ax, VidGrSeg  ; Point Extra Segment to Video Screen
          mov     es, ax
          mov     ax, 0013h     ; Set VGA to Mode 13 graphics
          int     10h
ENDM

TMODE MACRO ; Switch to 80x50 Text Mode Video
          mov  ax, VidTextSeg   ; Point Extra Segment to Video Screen
          mov  es, ax
          MOV  AX, 1202h
          MOV  BL, 30h
          int  10h
          MOV  AX, 3
          INT  10h
          MOV  AX, 1112h
          MOV  BL, 0
          INT  10h
ENDM

;====== Externals =========================================================

; -- LIB291 Routines (Free) ---
extrn rsave:near, rrest:near, dspout:near, dspmsg:near, binasc:near, kbdin:near

; -- LIBMP4 Routines --

; These procedures are written so that they can be invoked from C
; Recall from Lecture 10 how function arguments are passed via the stack.
; Consult MASM Programmers guide or use 'MASM /h' for help on command syntax
;   for the INVOKE and PROTO directives.

_DrawPoly PROTO far C \
  X1:word, Y1:word, Y3:word, X2:word, Y2:word, Y4:word, Color:byte

_LoadPCX  PROTO far C      DestSeg:word, Fname:far ptr byte

_MoveScreen PROTO far C    DestSeg:word, SourceSeg:word

_AnimateScreen PROTO far C DestSeg:word, SourceSeg:word, HorShift:word

_InstKey PROTO far C

_DeInstallKey PROTO far C

_mp4xit PROTO far C

;====== Stack Segment =====================================================
stkseg  segment stack
        db 64 dup ('STACK   ')
stkseg	ends

;====== Additional Data Segments ==========================================

SBSeg segment PUBLIC 'DATA1'
    ScreenBuffer DB 65535 dup(?)  ; Buffered Screen Segment
SBSeg ENDS

BGSeg segment PUBLIC 'DATA2'
    Background   DB 65535 dup(?)  ; Background Graphics Buffer
BGSeg ENDS

FGSeg segment PUBLIC 'DATA3'
    ForeGround   DB 65535 dup(?)  ; Foreground Graphics Buffer
FGSeg ENDS

ScrSeg segment PUBLIC 'DATA4'
    ScratchPad   DB 65535 dup(?)  ; Scratch (temporary) buffer
ScrSeg ENDS

;====== Code/Data segment ================================================
cseg    segment public 'CODE' 
        assume  cs:cseg, ds:cseg, ss:stkseg, es:nothing

;====== Variables ========================================================

ExitFlag    db 0        ; 0=Play, 1=Exit - Set by MyKeyInt

CRLF      db CR, LF, '$'
pbuf      db 7 dup(?)   
                             
OldVector dd ?          ; Pointer to old keyboard routine  ; Set by InstKey

Xoffset   dw 0 ;        ; {-1,0,1} for left, stop, right   ; Set by MyKeyInt
Yoffset   dw 0 ;        ; {-1,0,1} for forward, stop, back ; Set by MyKeyInt

PUBLIC ExitFlag, OldVector, Xoffset, Yoffset
PUBLIC ScreenBuffer, BackGround, ForeGround, ScratchPad

; ======== Your Code ======================================================

; Uncomment these routines as you write your own code!

; _DrawPoly proc far C PRIVATE uses BX CX DX SI DI DS ES,
;            X1:word, Y1:word, Y3:word, X2:word, Y2:word, Y4:word, Color:byte
;        ...
;        RET
; _DrawPoly endp

;_LoadPCX  proc far C PRIVATE     DestSeg:word, Fname:far ptr byte
;        ...
;        RET
;_LoadPCX endp

;_MoveScreen proc far C PRIVATE    DestSeg:word, SourceSeg:word
;        ...
;        RET
;_MoveScreen endp

;_AnimateScreen proc far C PRIVATE DestSeg:word, SourceSeg:word, HorShift:word
;        ...
;        RET
;_AnimateScreen endp

;_InstKey proc far C PRIVATE 
;        ...
;        RET
;_InstKey endp

;_DeInstallKey proc far C PRIVATE 
;        ...
;        RET
;_DeInstallKey endp

; ======== Given Procedures ===============================================

WaitRetrace proc near

       ; Waits for the monitor beam to make one full pass of the screen.
       ; Combats flicker, but also slows down the redraw rate considerably.
       ; (Free code)

	push	dx
	push	ax

        mov     dx, 3DAh ; VGA Refresh Status Register

WRLoop: in      al, dx
        and     al, 08h  ; Vertical Refresh bit
        jnz     WRLoop

	pop	ax
	pop	dx

	ret
WaitRetrace endp

; ======== Given Test Cases  =============================================

PCXHorizon  db 'horizon.PCX', 0
TRBadMsg    db 'Run the program as: MP4 n, where n=test case {1..5}',CR,LF
            db '[Press ESC to exit]',CR,LF,'$'

Xl          dw  100  ; X Left
Xr          dw  220  ; X Right
Yt          dw   20  ; Y Top
Yb          dw  180  ; Y Bottom
Ymt         dw   50  ; Y mid-Top
Ymb         dw  150  ; Y mid-Bottom

_TestRoutine proc far C testcase:word

            ; A robust set of individual test cases.
            ; Be sure that you *understand and study* this free code,
            ;   as it illustrates how your routines will be called.

            cmp testcase,'5'  ; Check Input Range
            ja  TRBad       ; Avoid Jumping on bad input
            cmp testcase,'1'
            jb  TRBad

            mov bx, testcase
            sub bx,'1'   ; Map '1','2','3','4','5' to 0,2,4,6,8 for jump table
            shl bx,1
            JMP TestFunction[BX]

            TestFunction dw offset TestR12 ; 1 = Single Polygon
                         dw offset TestR12 ; 2 = Multiple Polygons
                         dw offset TestR3  ; 3 = PCX File Loading & Animation
                         dw offset TestR4  ; 4 = Keyboard Interrupt Routine
                         dw offset TestR5  ; 5 = All of the above

TRBad:      mov dx, offset TRBadMsg
            call DSPMSG
            call kbdin
            JMP TRDone

TestR12:    ; Debug a single polygon
            GMODE

            MOV CX,64000/4          ; Clear ScreenBuffer
            MOV DI,0
            MOV AX, SEG ScreenBuffer
            MOV ES,AX
            MOV EAX,0
            REP STOSD

            Invoke _DrawPoly,   0, 20, 180, 120, 40, 160, 8+4 ; bright red

            CMP BX,0*2              ; Draw only one Polygon for case 0
            JE TR0Done              ; Draw Three polygons for case 1

            Invoke _DrawPoly, 120,  0, 200, 160, 80, 120, 8+6 ; bright yellow
            Invoke _DrawPoly, 160, 80, 120, 200,  0, 200, 8+2 ; bright green
            Invoke _DrawPoly, 200, 40, 160, 320, 20, 180, 8+1 ; bright blue

   TR0Done: Invoke _MoveScreen, VidGrSeg, SEG ScreenBuffer
            Call Kbdin ; Press any key
            JMP TRDone

TestR3:     ; Load a PCX File and show it on the screen
            GMODE
            Invoke _LoadPCX, Seg Background, ADDR PCXHorizon
            Invoke _MoveScreen, SEG ScreenBuffer, SEG Background
            Invoke _MoveScreen, VidGrSeg, SEG ScreenBuffer
            Call Kbdin
            JMP TRDone                                                    

TestR4:     ; Test keyboard handler
            TMODE
            Invoke _InstKey

            MOV CL,'*'  ; '*'   ; Indicate position with red *
            MOV CH,4    ; red

            MOV DL,' '  ; space ; Erase old position
            MOV DH,4    ; red
            MOV SI,0    ; Previous position 
                      

   KeyLoop: MOV AX, 160 ; Row * {-1,0,1}
            MUL YOffset
            MOV DI,AX

            MOV AX, 2   ; Col * {-1,0,1}
            MUL XOffset

            ADD DI,AX
            MOV ES:[SI+162],DX
            MOV ES:[DI+162],CX
            MOV SI,DI
            
            cmp     ExitFlag, 0
            je      KeyLoop
            Invoke  _DeInstallKey
            JMP TRDone

TestR5:     ; Everything
            GMODE
            Invoke _InstKey
            Invoke _LoadPCX, Seg Background, ADDR PCXHorizon
            mov     si, 0
            
    ReDraw: cmp     ExitFlag, 1
            je      TR4Done

            add     si, 1
            cmp     si, 320
            jbe     BackShift
            mov     si, 0

 BackShift: Invoke _AnimateScreen, SEG ScreenBuffer, SEG Background, SI

            mov     cx, XOffset
            mov     dx, YOffset

            add xl, cx ; horizontal pan
            add xr, cx

            sub xl, dx ; forward/back (zoom in/out)
            add xr, dx
            sub yt, dx
            add yb, dx
            sub ymt,dx
            add ymb,dx

            Invoke _DrawPoly,   0,  yt,  yb,  xl, ymt, ymb,   4 ; red
            Invoke _DrawPoly,  xl, ymt, ymb,  xr, ymt, ymb, 8+4 ; bright red
            Invoke _DrawPoly,  xr, ymt, ymb, 320,  yt,  yb,   4 ;  red

            call    WaitRetrace  ; Avoid flicker
            Invoke  _MoveScreen, VidGrSeg, SEG ScreenBuffer
            jmp     ReDraw

   TR4Done: Invoke  _DeInstallKey

TRDone:     ret

_TestRoutine endp



;====== SECTION 6: Main procedure =========================================

main	proc	far

        ; Run the program as 'MP4 n', where n = test case 1 .. 5 

        mov  si,82h  ; Read one-digit command line argument from PSP 
        mov  dl,[si] ; See Brey p.380 and Figure Appendix-A5 for details

        mov     ax, cseg  ; Initialize DS=CS
        mov     ds, ax

        MOV DH,0
        Invoke _TestRoutine, DX  ; Component test cases 1..5 

        ; Invoke is like CALL, but MASM will automatically generate
        ; the PUSH commands to put the values on the stack.

        Invoke  _mp4xit

main	endp

cseg    ends
	end	main