Final Project
Programmers:
Project Leader: Gregg
Miskelly - 3D code (BSP)
Project Members: Dave Bellia
- FPU and VGA, Rob Ewald - Input
and Serial communication
Introduction:
In a sentence, our project will enable you to 'walk' around a 3D world
with six degrees of freedom using a 3D input device that we make. The concept
of a six-degree of freedom 3D world is not a new one. Even at the PC level,
games that utilize this kind of technology have existed for two or three
years. However, it is always difficult to come up with an input system
to take advantage of the power of a six-degree environment. Many of the
input systems can be described as awkward at best. The CAVE at Beckman
and a want to do something interesting that combined both hardware and
software inspired us to come up with a new way of looking around a 3D world.
We hope you will like it.
Here's our program: final.zip
Implementation
Hardware Summary:
We will produce a 3D input device. This device will be pen shaped with
two buttons on it, and two emitters. Using detectors in a box, we will
be able to determine the orientation and position of the pen. This will
be calculated by the ECE 249 microcontroller. The two buttons will be used
to calibrate and pause the input device. The input device will then communicate
with a PC through a Com or Parallel port.
Software Summary:
There are several pieces of software that we must write (this list does
not include software that runs on the microcontroller). First, we must
have a piece of software to interface with the microcontroller over a serial
or parallel port. This data will be input to the control unit of our simulator.
The control unit will also accept mouse and keyboard input. The control
unit will then render a 3D mesh (probably a proprietary format that will
contain a BSP created by importing a DXF) to our current position. We will
probably write to an SVGA display.
Block Diagram:
Microcontroller-+ Mouse Input----+
| |
| Keyboard Input-+ |
| | |
+------------------+ +-----------------+
| Serial Interface |--| Control Unit |-----+
+------------------+ +-----------------+ |
| | |
| | |
Rendered BSP Tree--+ | +------------------+
| | Recurse BSP Tree |
| +------------------+
| |
| +--------------------+
+---Init Disp Sig---+ | 3D Transformations |-+
| +--------------------+ |
| |
| +-----------------+ +--------------+ |
+--| SVGA Controller |<----| Draw Polygon |<-------+
+-----------------+ +--------------+
Block Diagram for DSP renderer
+------------------+ +---------------+
DXF File---| BSP Tree Creator |----| Split Polygon |---+
+------------------+ +---------------+ |
| | |
| +---------------------------------+
|
+--------- BSP File
Global Variables and Types:
The data structures for this project are not complex, but they are more
complicated then in most projects. For the mesh, we need the polygons to
be stored into a BSP tree. Memory is acquired by decrementing the stack
pointer by the appropriate amount at load time. Other global variables
are our transformation matrix, our current viewpoint, and vectors to the
old mouse and keyboard handlers.
Types:
struct Polynode { // polygon and node to a BSP tree
Point3D rgPoints[3];
Vector vNormal;
struct Polynode *Front, *Back;
}
struct Point3D
{
float x,y,z;
}
typedef Vector Point3D;
struct Point2D
{
int x,y;
}
typedef Matrix float[16]; // 4x4 matix
Variables:
Matrix g_ProjM
; ProjM - Projection Matrix, this value is constant
;
; XRES
; --------- 0
; SK XRES 0 2 ZSCREEN
;
; YRES
; --------- 0
; 0 -SK YRES 2 ZSCREEN
;
;
;
; 0 0 1 -Zscreen
;
; 1
; ------- 0
; 0 0 ZSCREEN
g_VAngleM LABEL real4
g_StrifeUV real4 1.0,0.0,0.0,0.0 ; Strife Vector (to your side)
g_UpUV real4 0.0,1.0,0.0,0.0 ; Up Vector
g_ViewUV real4 0.0,0.0,1.0,0.0 ; View or Face Vector
real4 0.0,0.0,0.0,1.0 ; Remaing data to finish off the Matrix
; VAngleM - Rotation Matrix about the current view angle
g_VPointM real4 1.0,0.0,0.0,0.0, 0.0,1.0,0.0,0.0, 0.0,0.0,1.0,0.0, 0.0,0.0,0.0,1.0
; VPointM - Computed Each time the position changes
; 1 0 0 -xp
; 0 1 0 -yp
; 0 0 1 -zp
; 0 0 0 1
TempMatrix real4 16 dup(?)
g_BltM real4 1.0,0.0,0.0,0.0, 0.0,-0.625,0.0,0.0, 2.13333,1.33333,1.0,0.0133333,
160.0, 100.0, 0.0, 1.0
; Blit Matrix = ProjM*VAngleM*VPointM
; This Matrix is used to Transform from World Points to ScreenPoints
g_rgPoints3D POINT3D NUMPOINTS dup(<>)
; List of 3D Points (each point is 12 bytes)
g_rgBSP BSPNode NUMPOLYS dup(<>)
; This is a pointer to the Root of the BSP Tree
g_rgPoints2D POINT2D NUMPOINTS dup(<>)
; List of transformed Points (each point is 4 bytes)
g_rgPntsStatus db 4*((NUMPOINTS+15)/16) dup (?)
; Table saying if a point has been transformed (2 bits/point)
ZeroOne real4 -0.01
g_Color - the color of the polygon
Point3D FrontPoint
Point3D BackPoint
PolyNode *g_Root; // pointer to root node
Matrix g_Matrix; // 2D to 3D transformation Matrix
Screen Shot
Procedures
-
BSP Compiler
-
Source code from Dr. Dobbs Journal (edited by Gregg Miskelly)
-
Main Loop/program control (Main.asm)
-
Main() - Gregg Miskelly
-
Purpose: Program framework
-
Input: None
-
Output: None
-
Side Effects: Initialize hardware. All other effects from subroutines
-
Floating Point (float.asm)
-
MatrixMultiply4x4() - David Bellia
-
Purpose: Multiply two 4x4 Matries and places the contence in a new
matrix
-
Input: Dest:word, Op1:word, Op2:word
-
Output: Matrix at ds:Dest = Matrix at ds:Op1 * Matrix at ds:Op2
-
Side Effects: None
-
MatrixMultiply4x1() - David Bellia
-
Purpose: Transfrom a point (x,y,z) from world coordinates and project
it onto the screen by multipling g_ProjM and the input 4x1 matrix (the
four elements are X, Y, Z, and 1)
-
Input: Pnt3D:Offset to a Point3D (three floating point values)
-
Output: return the new values of ax:sign of zp, bx:X, and cx:Y.
where X = xp/h, Y = yp/h from {{xp},{yp},{zp},{h}} = [g_ProjM]*Input
-
Side Effects: None
-
ViewpointToMatrix() - David Bellia
-
Purpose: Calculate g_PointM using g_ViewPoint
-
Input: g_ViewPoint
-
Output: None
-
Side Effects: modifies g_PointM updated
-
ViewangleToMatrix() - David Bellia
-
Purpose: Calculate g_AngleM using g_ViewAngle.
-
Input: g_ViewAngle
-
Output: None
-
Side Effects: modifies g_AngleM updated; g_AngleM = Rz(Zangle)*Ry(Yangle)*Rx(Xangle)
-
SubtractAndDot() - Gregg Miskelly
-
Purpose: Take the dot product of a normal to a polygon and the vector
formed by subtracting the first point of a polygon from the current location
-
Input: bx = index to BSPNode
-
Output: The Sign of (CameraLoc - Polygon->Point0) dot Polygon->Normal
-
Side Effects: None
-
RotateAboutVector() - Gregg Miskelly
-
Purpose: Rotate the g_VAngleM about an arbitrary vector (pVector)
-
Input: pVector:far ptr Point3D, Theta:real4
-
Output: None
-
Side Effects: g_VAngleM contains { g_StrifeUV, g_UpUV, g_ViewUV
}
-
CalcTM1() - Gregg Miskelly
-
Purpose: Calculates half of the rotation transform
-
Input: bx - offset to a vector, Theta:word
-
Output: None
-
Side Effects: TM1 (real4 matrix) is updated
-
CalcTM2() - David Bellia
-
Purpose: Calculate the second half of the rotation transform
-
Input: bx - offset to vector, Theta:word
-
Output: None
-
Side Effects: TM2 matrix updated
-
VGA Graphics (vga.asm)
-
InitSVGA() - David Bellia
-
Purpose: Initialize VGA graphics (320x200 @ 256)
-
Input: None
-
Output: None
-
Side Effects: No longer in text mode
-
WaitRetrace() - Given (MP4)
-
Purpose: Waits for the monitor beam to make one full pass of the
screen
-
Input: None
-
Output: None
-
Side Effects: None
-
MoveScreen() - David Bellia
-
Purpose: Move 320x200 pixels from SourceSeg to DestSeg segments
-
Input: DestSeg:word, SourceSeg:word
-
Output: Transfers 64,000 bytes of data from SourceSeg to DestSeg
-
Side Effects: None
-
LoadPCX() - David Bellia (My implementation used in MP4; slightly modified)
-
Purpose: Loads and decodes a 320x200 PCX file into memory and sets
VGA palette registers to those used by the image
-
Input: DestSeg:word, Fname:far ptr byte
-
Output: None
-
Side Effects: Moves image data into DestSeg which will be moved
to the screen with MoveScreen
-
InitText() - David Bellia
-
Purpose: Initialize Text mode (leaves VGA mode)
-
Input: None
-
Output: None
-
Side Effects: No longer in graphics mode
-
3D Code (3d.asm)
-
LoadBSPFile() - Gregg Miskelly
-
Purpose: Loads BSP file into g_rgPoint3D and g_rgBSP
-
Input: bspfname:far ptr byte
-
Output: None
-
Side Effects: g_rgPoint3D and g_rgBSP updated
-
SetBSPPointers() - Gregg Miskelly
-
Purpose: Set all the BSP pointers to point to the appropriate element
-
Input: root:word
-
Output: None
-
Side Effects: FrontPoint, BackPoint
-
RenderBSP() - Gregg Miskelly
-
Purpose: Traverses a BSP file and outputs transform to ScreenBuffer
-
Input: None
-
Output: None
-
Side Effects: polygon sent to ScreenBuffer
-
TraverseBSP() - Gregg Miskelly
-
Purpose: Traverse the BSP Tree to draw all the polygons in the screen
-
Input: Root - the root of the BSP Tree; The memory location of the
back buffer
-
Output: None
-
Side Effects: Scene rendered to viewpoint and the back buffer filled
with the render
-
Draw3DPolygon() - Gregg Miskelly
-
Purpose: Translate a Polygon from 3D to 2D and display it
-
Input: si - A pointer to a polygon
-
Output: None
-
Side Effects: Polygon is rendered to display
-
Transform3DPoint() - Gregg Miskelly
-
Purpose: Transforms the point using MatrixMultiply4x4
-
Input: di = point number*16
-
Output: bx = x, cx = y, ax = continue (ax > 0 if continue)
-
Side Effects: None
-
Polygon Code (polygon.asm)
-
DrawPoly() - Gregg Miskelly (general polygon draw, not MP4)
-
Purpose: Render a triangle to the Frame Buffer specified in es of
color g_Color
-
Input: Coordinates of a triangle; es: FrameBuffer; g_Color: color
to draw
-
Output: Frame buffer filled with some new data
-
Side Effects: g_Color Set to new value
-
_DrawSortedTriangle - Gregg Miskelly
-
Purpose: Render a Triangle to the Frame Buffer specified in es of
color g_Color
-
Input: Count:dword, Xpos:dword, y3:dword, x3:dword, y2:dword, x2:dword,
y1:word, x1:dword; Sorted coordinates of a Triangle; y3 is at the top,
y1 at the bottom, Xpos, Count are not really parameters, it was just easy
to work with them like that, they are really local variables and I will
eventually deal with them like BrokenSeg, deltaX and deltaY (via an equate)
-
Output: None
-
Side Effects: Frame buffer filled with some new data
-
_FlatTriangleDraw - Gregg Miskelly
-
Purpose: This function is slightly a misnomer, it will draw anything
with a flat bottom and flat top, it draws to the screen buffer
-
Input: p:word, ka:word, kb:word, Minv:word, BrokenSeg:word, deltaX:word,
deltaY:word; p - deciding factor for the long line in the triangle (see
any Bresenham derivation for more info), ka and kb are the two possible
numbers that we will add to p at each iteration, (ka if p < 0; else
kb) Minv is the incrementing factor for the long segment. BrokenSeg - which
segment is the long one (left = 1, right = 2), deltaX, deltaY - the deltaX
and deltaY for the non-long segment.
-
Register Input: ax - Starting Position (y*WIDTH), bx - x position,
cx - count, dx - height, otherwise stated: var pos_start = ax, var Xpos
= bx, var count = cx, Height = dx
-
Output: ax, bx, and cx should be updated with new value. Also, p
should be updated.
-
Side Effects: Triangle Drawn
-
_fClipSortHeight - Gregg Miskelly
-
Purpose: Sort the points in increasing y value order (decreasing
height) and return 0 if the polygon is clipped
-
Input: y3:word, x3:word, y2:word, x2:word, y1:word, x1:word
-
Output: sorted points; ax = 0 if the polygon is clipped, 1 if the
polygon is not clipped
-
Side Effects: None
-
_DrawSegment - Gregg Miskelly
-
Purpose: Draw one scan line while clipping in the x direction
-
Input: pos_start = ax, Xpos = bx, count = cx
-
Output: None
-
Side Effects: A scale line of the polygon is drawn
-
_GetSegmentInfo - Gregg Miskelly
-
Purpose: Generating the information we will need to draw the line
(segment) specified by delta x, delta y
-
Input: cx=delta x, dx=delta y
-
Output: p (deciding factor), ka, and kb, Minv passed back
-
Side Effects: None
Keyboard Inputs
InstKey
Saves the old keyboard interrup vector to
the variables "OldVector" and
installs the new procedure, KeyInterrupt.
DeInstallKey
Restores the old keyboard interrupt vector
saved by OldVector.
KeyInterrupt
Reads in the scan code from port 60h.
First it checks if the key was ESC and
sets ExitFlag to 1 if so. Otherwise it checks for movement using
comparisons to
divide the keyboard into sections instead of checking each key
individually.
The controls are:
|
Q
Rotate Counter Clockwise
|
W
Move Forward
|
EEE
Rotate Clockwise
|
|
A
Rotate Left
|
S
(nothing)
|
D
Rotate Right
|
|
Z
Sidestep Left
|
X
Move Backward
|
C
Sidestep Right
|
Additionally, P moves up and L moves down.
If the key pressed is a valid input, the correct bit is set in either
the Movement or Rotation byte flags, or cleared if the interrupt was a
key release. The bits are defined as:
Movement: x x DOWN UP LEFT RIGHT BACK FORWARD
Roatation: x x DOWN UP LEFT RIGHT ROLLCCL ROLLCL
KeyMovement
This is called when the keyboard input is to be
used to move within the scene. It first tests the bits of the movement
flag to see if there is motion in each axis, where 0 or both bits being
set is no movement at all. A copy of the flag is then modified to be used
in jump tables to take the appropriate action for each axis. The Rotation
flag is tested next using the same procedure. This function calls the SetCameraPosition
function with the vector to move in and direction (forward or back) when
movement is detected and the RotateAboutAxis with the vector and rotation
amout (a constant here) to rotate about when rotation is detected.
Mouse Inputs
MouseProc
First INT 33h is called to get the position and
button status. The flags LeftStatus and RightStatus are set to correspond
to the buton being on (1) or off (0). Next SetCamerAngle is called with
the new position of the mouse still in CX and DX. Next SetUnitVector is
called to adjust the g_ViewUV vector to the new angle. Then the button
status variables are put into one byte, mouseDir, with bit 1 representing
the left button and bit 0 representing the right bit. Since the mouse can
only move forward or back, SetCameraPosition is called with the direction
in mouseDir and the vector g_ViewUV, the forward facing direction. Finally
the new mouse position is saved to the mouseRow and mouseColumn variables,
used in SetCameraAngle.
SetCameraAngle
Just a wrapper for calling SetYaw and SetPitch.
SetYaw
The difference between the current column (CX) and
the previous column (mouseColumn) is loaded into the FPU. It is then divided
by a scaling constant and the result is the number of radians to rotate.
Then RotateAboutVector is called with this data and the g_UpUV vector to
rotate about.
SetPitch
The difference between the current row (DX) and
the previous row (mouseRow) is loaded into the FPU. It is then divided
by a scaling constant and the result is the number of radians to rotate.
Then RotateAboutVector is called with this data and the g_StrifeUV vector
to rotate about.
SetCameraPosition
Checks the direction byte passed to it to see if
we are moving forward or back and calls the _SetNewX functions to
move in each axis.
SetNewX
Loads the X portion of the vector to move in and
adds it to the current X position. The direction vector can be divided
by an constant to slow movement.
SetNewY and SetNewZ
Same procedures as X but uses the Y and Z components
of the direction vector.
SetUnitVector
Using x = CosX SinY, y = -SinX,
and z = CosX CosY, calculate the new unit vector the camera is facing according
to the new angles set by SetCameraAngle. Store the X, Y, and Z results
in g_ViewUV.
Serial Input
SerialProc
Wrapper for all the serial functions. Calls SerialWrite,
SerialRead, InterpetData, and SerialAngle.
SerialInit
Called in main, it sets the serial port in COMPORT
to 9600 bps, 8 data bits, no parity, 1 stop bit. Uses INT 14h.
SerialWrite
Using INT 14h, send the byte FFh out the serial
port to the microcontroller. This is interpeted by the controller as a
request for position data.
SerialRead
Using BIOS interrupt 14h, it reads in 8 bytes
of data. They are all A to D conversions of the intensity received by the
sensor. After the bytes are stored as byte memory values, there is a small
loop that stores the bytes as different word values so they can be loaded
by the FPU.
InterpetData
Using the mathmatical principle that 4 intersecting
spheres = 1 point, find the position of the two ends of the input device.
Each sensor reading is proportional to the distance from the sensor to
the light. (I = 1/r^2). The equations used to find the points from the
distances to the four sensors are:
y=(A-B)/4y1 x=-[2y12-A-B+2C]/8x1
z=z1 + [D-x2-y2]^0.5
where A, B, C, and D are the values read from the four sensors and x1,
y1, and z1 are the coordinates of sensor
#1. The first loop performs the conversion from intensity to distance,
the second finds the location of the two points. The inputs for the equations
are the variables Front1-4 and Back1-4. The results are written to g_Xpos,
g_Ypos, and g_Zpos.
SerialAngle
Using the two points just calculated, find the angle
between them. The formulas used are Phi=arctan(y/x) and Theta=arctan[(x2+y2)^.5/z].
The point used is the difference between the two X, Y, and Z values (g_Xpos
and Backx). The results are written to g_UpUV and g_StrifeUV.