
3D Polygon Flythrough
Screen Shots
Our main demo:

A big cube:

Our
custom intro:

A torus:

Our Menu:

Our credits:


Our Title:

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
- Translation:
- Translation is simply a movement in space with a delta_X, delta_Y,
and delta_Z. To translate the points of an object, we look at the vertex list of
every polygon that composes the object and add the appropriate deltas.
- To do this with matrix mathematics requires that a 4x4 matrix be
used so that the 3d coordinates become homogenous. This means that the 3d
coordinates are scaled relative to a normalizing factor, which is necessary for the matrix
multiplication.
- The translation matrix looks like this:
[1 0 0
0]
[0 1 0
0]
[0 0 1
0]
[delta_x delta_y delta_z 1]
- Scaling:
- Scaling an object is as simple as multiplying each point that
defines the object by a scaling_factor. However, the object must be scaled in its
local coordinate system, less unwanted translation occur if the object is scaled when not
oriented about (0,0,0).
- The scaling matrix looks like this:
[scale 0 0 0]
[0 scale 0 0]
[0 0 scale 0]
[0 0 0 1]
- Rotation:
- Despite the complexity of the mathematics behind rotation, the idea
is really quite simple. We have three ways to rotate an object, with respect to the
x, y, and z axes.
- Now, keeping in mind that LaMothe used a left-handed coordinate
system, lets look at the matrices necessary for rotating an object, relative to its own
local coordinate system, relative to one axis at a time.
- The z-rotation matrix looks like this:
[ cos(theta_z) sin(theta_z) 0 0]
[-sin(theta_z) cos(theta_z) 0 0]
[ 0 0
1 0]
[ 0 0
0 1]
- The y-rotation matrix looks like this:
[cos(theta_y) 0 -sin(theta_y) 0]
[0 1 0
0]
[sin(theta_y) 0 cos(theta_y) 0]
[0 0 0
1]
- The x-rotation matrix looks like this:
[1 0 0
0]
[0 cos(theta_x) sin(theta_x) 0]
[0 -sin(theta_x) cos(theta_x) 0]
[0 0 0
1]
- All of these matrices are simply multiplied (using standard matrix
multiplication rules) by the point vectors (matrix_1x4 type) that describe an object to
achieve the desired translation, rotation, scaling, or combination of the previous.
Graphics Pipeline
local to world transformation
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.
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.
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;
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.
perspective projection
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:
- BufferSeg segment PUBLIC 'DATA1' ; Main buffer
- BufferSeg2 segment PUBLIC 'DATA2' ;
Secondary buffer for fading
- ImageSeg
segment PUBLIC 'DATA3' ; Pictures for credits
- TextSeg1 segment PUBLIC 'DATA4' ; Text for credits
- TextSeg2
segment PUBLIC 'DATA4' ; Text for menu
- MIDISeg segment PUBLIC 'MIDI' ; Segment for MIDI
data
- WAVSeg
segment PUBLIC 'VOC' ; Segment for sound data
- STKSEG SEGMENT STACK ; *** STACK SEGMENT ***
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.
void LoadPCX(char *filename)
- Purpose: Loads and decodes a 320x200 PCX file into memory and sets VGA Palette
registers to those used by the image.
- Inputs:
- A pointer to the filename of PCX image
- The image shoud contain the background (controls)
- The pallete should be the one our polygon engine expects for it's shading.
- Outputs:
- Fills PCXSeg with image data.
- Fills VGA palette registers with image colors
- Uses ScratchPad to hold compressed image data
DisplayDoubleBuffer far C PUBLIC
- Purpose: Blits the double buffer to the screen.
- Inputs:
- The BufferSeg global variable.
- The Screen location
- Outputs:
- Uses 32 bit string operations to move data as fast as possible.
GMode
proc far C PUBLIC
- Purpose: Switch to Graphics Mode.
- Inputs:
- Outputs:
TMode proc far C PUBLIC
- Purpose: Switch to text Mode.
- Inputs:
- Outputs:
InstKey / MyKeyInt / DeInstallKey
- Purpose: Replace default keyboard interrupt routine.
- Inputs:
- 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:
- 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:
- 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:
: 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:
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:
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:wordPurpose: 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:wordPurpose: 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:wordPurpose: 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:
- Outputs:
- 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:
- void Play_MIDI(ax)
- Purpose: Plays MIDI file, using MIDKAP driver, function
702h.
- Inputs:
- Outputs:
- void Load_MIDI(ax)
- Purpose: Loads MIDI file into memory
- Inputs:
- ax - indicates which MIDI file to load
- Outputs:
- void SoundFX(ax)
- Purpose: Loads the WAV file into the mamory and plays it,
using DMA.
- Inputs:
- ax - # of the sound to play
- Outputs:
- void Stop_MIDI()
- Purpose: Stops playing MIDI, using MIDPAK driver, function
705h.
- Inputs:
- Outputs:
- void Cont_MIDI()
- Purpose: Continues playing MIDI, using MIDPAK driver,
function 70Bh.
- Inputs:
- Outputs:
- void Loop_MIDI()
- Purpose: Checks if the music is over, if it is, it starts to
play the same MIDI file again.
- Inputs:
- Outputs:
- void ResetDSP()
- Purpose: Resets DSP - initializes Sound Blaster for sound
FX. It is a helper function for SoundFX
- Inputs:
- Outputs:
- void WriteDSP()
- Purpose: Writes to DSP. A helper function of SoundFX
- Inputs:
- Outputs:
- 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:
- WipeBuffer PROC NEAR uses AX CX ES DI
- Purpose: clear screen buffer
- Inputs:
- Outputs:
- 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:
- 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:
- 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:
- BlastBuffer PROC NEAR uses AX CX ES DI DS SI
- Purpose: blast up the screen buffer into the VGA card
- Inputs:
- Outputs:
- CheckButtons PROC NEAR uses ax bx cx
- Purpose: takes mouse position and modifies actBut
- Inputs:
- Outputs:
- 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:
- Outputs:
- 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:
- Outputs:
- 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: