polly.gif (5136 bytes)

 

3D Polygon Flythrough

 

Screen Shots

Our main demo:

multiple.gif (1817 bytes)

A big cube:

cube2.gif (2884 bytes)

Our custom intro:

intro1.gif (1775 bytes)

A torus:

intro2.gif (4803 bytes)

Our Menu:

menu.gif (22152 bytes)

Our credits:

credits1.gif (64417 bytes)

credits2.gif (95797 bytes)

Our Title:

title.gif (38073 bytes)

The Polygon Engine

The polygon engine keeps track of 3d objects using an object hierarchy: the top-level object contains a number of attributes about itself and a polygon list, each polygon in the list contains information about itself and a list of vertices, and finally each vertex contains the x, y, and z coordinates that define its location.

The engine allows for object rotations in local coordinates, object movement in world coordinates, and viewer camera movement by tracking objects in a camera coordinate system. The engine loads an object with PLG_Load (code provided by Andre LaMonthe). The PLG format is a simple way to store 3d objects to disk. The objects are stored in local coordinates as a matter of convenience in the design. See the description of the PLG format below for more details.

The "engine" description comes into play when processing these objects. First the objects have their polygons extracted into a list. This polygon list in then z-sorted (sorted in distance from the viewer).  All polygons falling inside of the viewing frustum (the x,y viewport depth extended) are "clipped" into visible polygons and the rest are completely deleted from the polygon list.  Here the polygons are also flat shaded using an intensity calculation based on a ray intersection with the polygon's surface normal. The z-sort takes the vertices of each polygon, averages the z components of each vertex that describes the polygon, and then orders the list. The order of the list determines the order in which the polygons are rendered onto the screen.

All of the objects (and thus each object's polygons and vertices) are first translated from the local coordinate system of each object to the world coordinate system (the running mathematical model of our world that the engine processes on). In the world coordinate system, rotations and other translations of the objects are made. The objects are then translated to a camera coordinate system. This is the final coordinate system that the objects are rendered in.

Our primary source for the 3d mathematics routines and program structure is Andre LaMothe's Black Art of 3D Game Programming. This book suggested the use of the PLG 3d object format and the object hierarchy.

Structures

The following are the structures used in the engine:


vector_3d struc
    x REAL4 ?
    y REAL4 ?
    z REAL4 ?
    w REAL4 ?
vector_3d ends

point_3d struc
    x REAL4 ?
    y REAL4 ?
    z REAL4 ?
    w REAL4 ?
point_3d ends

dir_3d struct
    ang_x SWORD ?
    ang_y SWORD ?
    ang_z SWORD ?
dir_3d ends

polygon struc
    num_points SWORD ?
    vertex_list SWORD 4t DUP (?)
    color SWORD ?
    shade SWORD ?
    shading SWORD ?
    two_sided SWORD ?
    visible SWORD ?
    active SWORD ?
    clipped SWORD ?
normal_length REAL4 ?
polygon ends

object struc
    id SWORD ?
    num_vertices SWORD ?
    vertices_local point_3d 32t DUP (<>)
    vertices_world point_3d 32t DUP (<>)
    vertices_camera point_3d 32t DUP (<>)
    num_polys SWORD ?
    polys polygon 16t DUP (<>)
    radius REAL4 ?
    state SWORD ?
    world_pos point_3d <>
object ends

The difficulty in using c structures to track objects in our 3d world arises when operations must be performed upon them by external assembly routines. We will vaguely disclose how we plan on accomplishing this. Because working without the object heirarchy described above would lead to a coding hazard, and the fact that LaMothe's book includes a PLG loader that interperates PLG files into his object style (the same style we've adopted for our polygon rederer), we decided to code our main loop in c, which takes care of the pointer passing to our far functions, and write the mission critical graphics and mathematics routines in no-holds-barred assembly.

The interfacing between c and assembly has caused some difficulty that was somewhat alleviated with the help of Professor Lockwood. To make a far call to an assembly routine that operates upon a structure involves passing a far pointer to the structure instantiation. This passes a code segment: offset to the assembly routine that can be taken off of the stack. The object is then referenced in a manner very similar to asm arrays. This we foresee as our major obstacle in contruction of this engine.

 

Mathematical Manipulation

Graphics Pipeline

  1. local to world transformation
    • Because the rotation and scaling transformations are done relative to an 
    objects local coordinate system, it must be transformed to the world 
    coordinate system.  This is done by simply adding the objects position to 
    each of the vertices that define the object in local space.
  2. lighting and back-face removal
    • Each polygon has an attribute flag that states whether it is visible or
      
      not.  This flag is determined by computing the surface normal of the
      
      polygon and then finding the intersection of a ray cast from the camera
      
      position (a line of sight vector).  If the intersection angles with
      
      respect to the surface normal of the line of sight vector are less than 90
      
      degrees, then the face is visible.  At this time the color of the polygon
      
      is determined as well.  The color is an expression of the ambient light
      
      that suffuses the entire world and the intensity of light from a point
      
      source.
  3. world to camera transformation
    • The world to camera transformation is complex.  We have to rotate all
      
      objects so that the view angles seem to be at (0,0,0) and move all objects
      
      so that the viewpoint seems to be at (0,0,0).  This is done with the
      
      following calculations:
      
      T_inverse=the translation matrix to the viewpoint's position
      
      R_?_inverse=the rotation matrix to the viewangles
      
      The world to camera transformation = WC = T_inverse * R_x_inverse *
      
                                                            R_y_inverse *
      
                                                            R_z_inverse;
  4. clip polygons to viewing volume
    • All polygons outside of the viewing frustrum are removed if wholly
      outside or only partially rendered by the triangle routine which takes
      screen boudaries into account.
  5. perspective projection
  6. redering to screen.

 

PLG Files

The PLG file format is used to store all of the objects in our world. Basically, it contains a list of the verticies, polygons, and colors for one object. The version LaMothe uses also provides for blank lines and comments. An example file for a cube is as follows:



; CUBE object
cube 8 6

; vertice list
-20 20 20
20 20 20
20 20 -20
-20 20 -20
-20 -20 20
20 -20 20
20 -20 -20
-20 -20 -20

; polygon list
; cube has six sides all flat shaded
0x109f 4 0 3 2 1
0x109f 4 2 3 7 6
0x109f 4 4 5 6 7
0x109f 4 0 1 5 4
0x109f 4 0 4 7 3
0x109f 4 1 2 6 5




Segments

In order to reduce the screen flicker when we draw to the screen we will use double-buffering. After an entire image has been created, it will be quickly transferred directly to the screen by the ShowScreenBuffer routine. Because of double buffering, you will not see this image while it is being changed.

To hold all the graphics for this final project, the following segments have been defined:

Keyboard Control

This final project uses the keyboard to control movement. To recognize keys that are pressed simultaneously, we need to replace the default keyboard handler with our own code. Our 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).

When a key is down, the appropriate keyboard variable is set to one. When the key is released, the same variable is then set back to zero.

_A_KEY        db 0 ; Any key was pressed
_TURN_RIGHT   db 0 ; Right Arrow (1 = Button is down, 0 = Button is up)
_TURN_LEFT    db 0 ; Left Arrow
_DIVE_DOWN    db 0 ; Up Arrow
_CLIMB_UP     db 0 ; Down Arrow 
_SPEED_INC    db 0 ; 
_SPEED_DEC    db 0 ;
_Exit_Flag    db 0 ; Set to 1 when time to exit  (ESC)

Look-Up Tables

In order to optimize the execution speed of the ray caster, the following look-up tables have been created:

    cos_look     :  cosine lookup table
    sin_look      :  sine lookup table

Defined Variables

_global_view REAL4 64 dup(?)
_view_point point_3d <0.0, 0.0, 0.0, 1.0>
_view_angle dir_3d <0, 0, 0>

_light_source vector_3d <-0.913913, 0.389759, -0.113369, 1.0> ; position of point light source
_ambient_light REAL4 6.0 ; ambient light level

_clip_near_z REAL4 100.0
_clip_far_z REAL4 3000.0

HALF_SCREEN_WIDTH REAL4 160.0
HALF_SCREEN_HEIGHT REAL4 100.0
INVERSE_ASPECT_RATIO REAL4 1.25
_viewing_distance WORD 200.0

Procedures

  • All procedures will be C-style procedures callable from the C framework and other assembly functions.  The only C functions in our final game will be the main loop and the function that calls the procedures for our graphics pipeline.

 

 

 

  • TMode proc far C PUBLIC
    • Purpose: Switch to text Mode.
    • Inputs:
      • none
    • Outputs:
      • none

 

  • InstKey / MyKeyInt / DeInstallKey
    • Purpose: Replace default keyboard interrupt routine.
    • Inputs:
      • Button presses.
    • Outputs:
      • _A_KEY set to 1 when any key is pressed
      • _Exit_Flag set to 1 when Escape pressed
      • TURN_LEFT set to 1 when Left Arrow pressed, 0 when released
      • DIVE_DOWN set to 1 when Up Arrow pressed, 0 when released
      • CLIMB_UP set to 1 when Down Arrow pressed, 0 when released
      • SPEED_INC set to 1 when Left Ctrl is pressed, 0 when released
      • SPEED_DEC set to 1 when Left Shift is pressed, 0 when

 

  • void Build_Look_Up_Tables(void)
    • Purpose: Builds those lookup tables for fast access.
    • Inputs:
      • None
    • Outputs:
      • cos_look[angle] = (float)cos(rad)
      • sin_look[angle] = (float)sin(rad)

 

  • int PLG_Load_Object(object_ptr the_object, char *filename, float scale)
    • Purpose: Loads an object from a PLG file into specified location and scales it in the process.
    • Inputs:
      • A pointer to an object structure into which it should load the object
      • A string representing the filename
      • A scaling factor.
    • Outputs:
      • All information is filled in the structure called "the_object".
      • This information includes all of the polygons the object uses, the verticies of these polygons, and the base color and a shading mode of each polygon.
      • This information about the verticies is "local coordinate" information and will not be modified throughout the rest of the program.  The transformation calculations, however, will use this information.
      • This routine also pre-calculates surface normals for later shading.

 

  • char *PLG_Get_Line(char *string, int max_length, FILE *fp)
    • Purpose: A helper function for the Load_PLG routine  It parses each line of the file.
    • Inputs:
      • String to get
      • Maximum length of the string.
      • File pointer for easy access.
    • Outputs:
      • Returns the next line of the PLG file with all of the comments taken out.

 

  • void Make_Vector_3D(point_3d_ptr init, point_3d_ptr term, vector_3d_ptr result)
    • Purpose: Creates a vector from two points.
    • Inputs:
      • The two points
      • A pointer to the vector it retunrs
    • Outputs:
      • Simply takes the different of the two points and returns it in the vector

 

  • void Cross_Product_3D(vector_3d_ptr u, vector_3d_ptr v, vector_3d_ptr normal)
    • Purpose: Takes the cross product of two vectors.
    • Inputs:
      • The two vectors
      • A pointer to the vector it retunrs
    • Outputs:
      • Quickly calculate the cross product of the two vectors

 

  • float Dot_Product_3D(vector_3d_ptr u,vector_3d_ptr v)
    • Purpose: Takes the dot product of two vectors.
    • Inputs:
      • The two vectors
    • Outputs:
      • The dot product of the vectors as a float

 

  • float Compute_Object_Radius(object_ptr the_object)
    • Purpose: Computes the radius of an object for colision detection.
    • Inputs:
      • A pointer to the object structore
    • Outputs:
      • The radius as a float.

 

  • C: Mat_Identity_4x4 proc far C PUBLIC uses DI SI DS ES, a:far ptr REAL4
    ASM: MatIdentity4x4 proc PUBLIC
    • Purpose: Fills in the matrix "a" with the identity matrix
    • Inputs:
      • A pointer to a 4x4 matrix
      • bx=offset to array
    • Outputs:
      • The identity matrix

 

  • Mat_Copy_4x4 proc far C PUBLIC uses SI DI DS, source:far ptr REAL4, destination:far ptr REAL4
    • Purpose: This is a dificult one to understand conceptually.  (Duh.  It copies a matrix!)
    • Inputs:
      • A pointer to source matrix
      • A pointer to the destination matrix
    • Outputs:
      • The destination matrix is now a copy of the source matrix

 

  • void Mat_Mul_4x4_4x4(matrix_4x4 a, matrix_4x4 b, matrix_4x4 result)
    • Purpose: Does the matrix multiplication operation.
    • Inputs:
      • A pointer to the the first multiplyer
      • A pointer to the multiplicand
      • A pointer to the product.
    • Outputs:
      • The result matrix contains the matrix product of the multiplier and multiplicand

 

  • MatMul4x44x4 proc PUBLIC
    • Purpose: Does the matrix multiplication operation.
    • Inputs:
      • si = offset to matrix a
      • di = offset to matrix b
      • bx = offset to matrix result
    • Outputs:
      • The result matrix contains the matrix product of the multiplier and multiplicand

 

  • Rotate_Object proc far C PUBLIC uses DI SI DS, the_object:far ptr object, angle_x:word, angle_y:word, angle_z:word
    • Purpose: Rotates a 3D object
    • Inputs:
      • A pointer to the object to be rotated
      • The three angles of rotation.
    • Outputs:
      • The object has been rotated.
      • The local coordinates of each of the verticies get modified.
    • Calls:
      • This calls all of the matrix math functions.

 

  • Local_To_World_Object proc far C PUBLIC uses SI DS , the_object:far ptr object
    • Purpose: Transforms an object from local into world coordinates
    • Inputs:
      • A pointer to the object structore
    • Outputs:
      • The world_coords array of verticies of each polygon has been filled in based off of the object's local_coords and the transformation matrix.

 

  • World_To_Camera_Object proc far C PUBLIC uses DI SI DS, the_object:far ptr object
    • Purpose: Transforms an object from world into camera coordinates
    • Inputs:
      • A pointer to the object structure
    • Outputs:
      • The camera_coords array of verticies of each polygon has been filled in based off of the object's world _coords and the transformation matrix.

 

  • Create_World_To_Camera proc far C PUBLIC uses EAX BX DI SI DS
    • Purpose: Transforms the world coordinates of the polygon list into camera coordinates
    • Inputs:
      • The value of the world coordinates (taken out of the polygon list.)
    • Outputs:
      • The transformed world coordinates (put into the camera_coors part of each polygon's vertex structure.)

 

  • Remove_Object proc far C PUBLIC uses DI SI DS, the_object:far ptr object, mode:far ptr word
    • Purpose: Removes objects that won't be drawn
    • Inputs:
      • A pointer to the object structure
      • The mode of processing.
    • Outputs:
      • Sets the flag in the object structure so that when it gets examined for inclusion in the polygon list it can be determined whether or not to add this particular objects's polygons.

 

  • Clip_Object_3D proc far C PUBLIC uses AX BX DI SI DS, the_object:far ptr object, mode:WORD
    • Purpose: Used after a call to remove object to furthur prune the number of polygons rendered
    • Inputs:
      • A pointer to the object structure
      • The mode of processing.  (Clip just the Z or clip the entire polygon.)
    • Outputs:
      • More flags have been set so fewer polygons get added to the polygon list.

 

  • Remove_Backfaces_And_Shade proc far C PUBLIC uses SI DS, the_object:far ptr object
    • Purpose: Performed back face removal test and computes the shading
    • Inputs:
      • A pointer to an object to tested and shaded
    • Outputs:
      • The visibility and shading field of the polygons that make up the object will be set.
      • These two functions are performed together snice the calculations are very similar.

 

  • Draw_Triangle_2D PROC far C PUBLIC uses ds, x1:word, y1:word, x2:word, y2:word, x3:word, y3:word, color:word
    • Purpose: The least significant of all routines -- simply draws a triangle in wimpy two dimensions.  (I mean, how hard could that be?)
    • Inputs:
      • The points on the triangle to draw.
      • The color which to draw it.
    • Outputs:
      • Something finally gets drawn on the screen.

 

  • Draw_Bottom_Triangle PROC near C PUBLIC uses ds, x1:word, y1:word, x2:word, y2:word, x3:word, y3:word, color:word
    • Purpose: Draws the triangle from the flat top down
    • Inputs:
      • The points on the triangle to draw.
      • The color which to draw it.
    • Outputs:
      • Something finally gets drawn on the screen.

 

  • Draw_Top_Triangle PROC near C PUBLIC uses ds, x1:word, y1:word, x2:word,y2:word, x3:word, y3:word, color:word
    • Purpose: Draws the top triangle from the flat line up
    • Inputs:
      • The points on the triangle to draw.
      • The color which to draw it.
    • Outputs:
      • Something finally gets drawn on the screen.

 

  • HLine proc far C PUBLIC uses AX BX CX DI ES, Y:word, LeftX:word, RightX:word, Color:word
    • Purpose: Draws a horizontal line
    • Inputs:
      • The points on the triangle to draw.
    • Outputs:
      • Something finally gets drawn on the screen.

 

  • void Init_MIDI()
    • Purpose: Initializes MIDI music, using MIDPAK driver, function 704h on INT 66h.
    • Inputs:
      • none
    • Outputs:
      • none

   

  • void Load_Voc(ax)
    • Purpose: Loads voc file into memory. Throws away the header and stores only the raw data.
    • Inputs:
      • ax - offset to the string with the file name
    • Outputs:
      • none.

 

  • void Play_MIDI(ax)
    • Purpose: Plays MIDI file, using MIDKAP driver, function 702h.
    • Inputs:
      • none
    • Outputs:
      • none

 

  • void Load_MIDI(ax)
    • Purpose: Loads MIDI file into memory
    • Inputs:
      • ax - indicates which MIDI file to load
    • Outputs:
      • none

 

  • void SoundFX(ax)
    • Purpose: Loads the WAV file into the mamory and plays it, using DMA.
    • Inputs:
      • ax - # of the sound to play
    • Outputs:
      • cool sounds

 

  • void Stop_MIDI()
    • Purpose: Stops playing MIDI, using MIDPAK driver, function 705h.
    • Inputs:
      • none
    • Outputs:
      • none

  

  • void Cont_MIDI()
    • Purpose: Continues playing MIDI, using MIDPAK driver, function 70Bh.
    • Inputs:
      • none
    • Outputs:
      • none
  • void Loop_MIDI()
    • Purpose: Checks if the music is over, if it is, it starts to play the same MIDI file again.
    • Inputs:
      • none
    • Outputs:
      • none
  • void ResetDSP()
    • Purpose: Resets DSP - initializes Sound Blaster for sound FX. It is a helper function for SoundFX
    • Inputs:
      • none
    • Outputs:
      • none

 

  • void WriteDSP()
    • Purpose: Writes to DSP. A helper function of SoundFX
    • Inputs:
      • none
    • Outputs:
      • none

 

  • CopyRect MACRO srcS, srcO, destS, destO, cols, rows
    • Purpose: copy one rectangular area in the source segment to the destination segment
    • Inputs: srcS - source segment
      srcO - source ofset
      destS - destination segment
      destO - destination offset
      cols - width of rectangle
      rows - height of rectangle
    • Outputs: none - rectangles are copie

     

  • CopySegsNoBlack MACRO srcS, srcO, destS, destO, cols, rows
    • Purpose: copy one rectangular area in the source segment to the destination segment but skip black pixels
    • Inputs: srcS - source segment
      srcO - source ofset
      destS - destination segment
      destO - destination offset
      cols - width of rectangle
      rows - height of rectangle
    • Outputs: none - rectangles are copied
  • ApplySmearM MACRO srcS, srcO, destS, destO, cols, rows
    • Purpose: a macro to set up registers ApplySmear routine
    • Inputs:
      • srcS - source segment
      • srcO - source ofset
      • destS - destination segment
      • destO - destination offset
      • cols - width of rectangle
      • rows - height of rectangle
    • Outputs:
      • none

 

  • WipeBuffer PROC NEAR uses AX CX ES DI
    • Purpose: clear screen buffer
    • Inputs:
      • none
    • Outputs:
      • none

 

  • Delay PROC NEAR uses AX CX
    • Purpose: force a delay by burning clock cycles - used to make things show up on the screen at different times
    • Inputs:
      • CX - amount of time to burn (a linear scaling factor)
    • Outputs:
      • none

 

  • DrawObject PROC NEAR USES AX BX CX DX ES DS SI DI
    • Purpose: fusing an object definition, draw it to the screen buffer
    • Inputs:
      • BX - offset of object definition
    • Outputs:
      • none

 

  • DrawButtons PROC NEAR uses AX BX DX
    • Purpose: draw the correct buttons to the screen bufer
    • Inputs:
      • actBut variable - this variable holds which button is presently active (mouse is over it)
    • Outputs:
      • none

 

  • BlastBuffer PROC NEAR uses AX CX ES DI DS SI
    • Purpose: blast up the screen buffer into the VGA card
    • Inputs:
      • none
    • Outputs:
      • none

 

  • CheckButtons PROC NEAR uses ax bx cx
    • Purpose: takes mouse position and modifies actBut
    • Inputs:
      • CX:DX - col:row of click
    • Outputs:
      • updated actBut

 

  • IteratePoly PROC NEAR uses AX BX CX DX ES DI DS SI
    • Purpose: calls the fading routine and moves the buffers to the right places
    • Inputs:
      • none
    • Outputs:
      • none

 

  • DissolveScreen proc near uses ax bx cx dx es ds si di
    • Purpose: after mouse is clicked, this routine is called to handle the screen fade away
    • Inputs:
      • none
    • Outputs:
      • none

 

  • LoadPCXMenu proc near uses ax bx cx dx ds es si di
    • Purpose: loads pcx files into buffers and tinker with the palettes so things look right
    • Inputs:
      • ax - Destination segment address
      • dx - pointer to a null-terminated string containing the filename
    • Outputs:
      • none