home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Cutting-Edge 3D Game Programming with C++
/
CE3DC++.ISO
/
BOOK
/
CHAP03
/
EXTRA
/
3DCLASS.CPP
< prev
next >
Wrap
C/C++ Source or Header
|
1995-10-24
|
19KB
|
662 lines
//
// File name: 3Dclass.CPP
//
// Description: The support file for Polydemo
//
// Author: John De Goes
//
// Project: Cutting Edge 3D Game Programming
//
// ------------------------------------------------------------
// | Global include files: |
// ------------------------------------------------------------
#include <Math.h>
#include <CType.h>
#include <Conio.h>
#include <Stdio.h>
#include <Stdlib.h>
#include <Iostream.h>
// ------------------------------------------------------------
// | Local includes: |
// ------------------------------------------------------------
#include "3DClass.HPP"
// ------------------------------------------------------------
// | Global variables/constants: |
// ------------------------------------------------------------
double CosTable [ DEGREECOUNT ];
double SinTable [ DEGREECOUNT ];
// ------------------------------------------------------------
// | Local structs/classes: |
// ------------------------------------------------------------
// A structure representing a screen edge:
struct EdgeScreen {
long X;
} Left [ 200 ], Right [ 200 ];
// ------------------------------------------------------------
// | Function section: |
// ------------------------------------------------------------
void inline CalcFloorRemainder ( long Numerator, long Denominator,
long &Floor, long &Remainder )
{
// If Numerator is less than zero, result will not
// be handled properly; calulate the corrected
// values:
if ( Numerator < 0 )
{
// Calculate a temporary floor:
Floor = -((-Numerator) / Denominator);
// Calculate the remainder of Numerator / Denominator
Remainder = (-Numerator) % Denominator;
if ( Remainder )
{
--Floor; Remainder = Denominator - Remainder;
}
}
// Else Numerator is positive; result handled
// properly:
else {
Floor = Numerator / Denominator;
Remainder = Numerator % Denominator;
}
}
// Initiate math function - calculates trig tables:
void InitMath()
{
long double Unit = (long double)( PI * 2.0F ) /
(long double)DEGREECOUNT;
for ( unsigned int i = 0; i < DEGREECOUNT; i++ )
{
long double Degree = (long double)i;
CosTable[i] = cos ( Unit * Degree );
SinTable[i] = sin ( Unit * Degree );
}
}
// Function designed to get a word from a file:
char *GetWord ( FILE *File, char *String )
{
int NextChar = fgetc ( File );
int Index = 0;
// Skip the spaces:
while ( isspace ( NextChar ) )
{
NextChar = fgetc ( File );
if ( NextChar == EOF )
{
String [ Index ] = NULL;
return NULL;
}
}
// Record the characters:
while ( !isspace ( NextChar ) )
{
String [ Index++ ] = ( char ) NextChar;
NextChar = fgetc ( File );
if ( NextChar == EOF )
{
String [ Index ] = NULL;
return NULL;
}
}
String [ Index ] = NULL;
return String;
}
// Function designed to count the number of triangles
// in a RAW file:
int CountTriangles ( char *FileName )
{
char Dummy [ 100 ];
unsigned int VertCount = 0;
FILE *File;
if ( ( File = fopen ( FileName, "rt" ) ) == 0 )
return 0;
while ( GetWord ( File, Dummy ) != NULL )
++VertCount;
fclose ( File );
unsigned int TriCount = VertCount / 9; // Nine verts per
return TriCount; // triangle
}
void inline ClipX1X2 ( long &X1, long &X2 )
{
// Clip a horizontal line:
if ( X1 < 0 )
X1 = 0;
if ( X1 > 319 )
X1 = 319;
if ( X2 < 0 )
X2 = 0;
if ( X2 > 319 )
X2 = 319;
}
void inline ClipY1Y2 ( long &Y1, long &Y2 )
{
// Clip a vertical line:
if ( Y1 < 0 )
Y1 = 0;
if ( Y1 > 199 )
Y1 = 199;
if ( Y2 < 0 )
Y2 = 0;
if ( Y2 > 199 )
Y2 = 199;
}
// Function designed to step along polygon edge:
void inline PolyEdge::Step ()
{
X += StepX; Y += StepY;
ErrorTerm += Numerator;
if ( ErrorTerm >= Denominator )
{
++X;
ErrorTerm -= Denominator;
}
--EdgeHeight;
}
// Function designed to initialize stepping values for
// a polygon edge:
void PolyEdge::Initialize ( ScreenVertex &P1,
ScreenVertex &P2 )
{
long Width = P2.X - P1.X;
Y = P1.Y; StepY = 1;
EdgeHeight = P2.Y - P1.Y;
// Make sure the polygon edge has a definite height
// before we make some rather complex calculations:
if ( EdgeHeight > 0 )
{
CalcFloorRemainder ( -1, EdgeHeight, X, ErrorTerm);
X += P1.X + 1;
CalcFloorRemainder ( Width, EdgeHeight, StepX, Numerator );
Denominator = EdgeHeight;
}
}
// Function designed to set matrix to identity matrix:
void Matrix3D::Initialize ()
{
Matrix[0][0] = 1; Matrix[0][1] = 0; Matrix[0][2] = 0; Matrix[0][3] = 0;
Matrix[1][0] = 0; Matrix[1][1] = 1; Matrix[1][2] = 0; Matrix[1][3] = 0;
Matrix[2][0] = 0; Matrix[2][1] = 0; Matrix[2][2] = 1; Matrix[2][3] = 0;
Matrix[3][0] = 0; Matrix[3][1] = 0; Matrix[3][2] = 0; Matrix[3][3] = 1;
}
// Function that merges two matrices - try to avoid calling this function
// very often.
void Matrix3D::MergeMatrix ( double NewMatrix [ 4 ] [ 4 ] )
{
// Multiply NewMatirx by Matrix; store result in TempMatrix
double TempMatrix [ 4 ] [ 4 ];
for (short unsigned int i = 0; i < 4; i++)
for (short unsigned int j = 0; j < 4; j++)
TempMatrix[i][j] = (Matrix[i][0] * NewMatrix[0][j])
+ (Matrix[i][1] * NewMatrix[1][j])
+ (Matrix[i][2] * NewMatrix[2][j])
+ (Matrix[i][3] * NewMatrix[3][j]);
// Copy TempMatrix to Matrix
for (i = 0; i < 4; i++)
{
Matrix[i][0] = TempMatrix[i][0];
Matrix[i][1] = TempMatrix[i][1];
Matrix[i][2] = TempMatrix[i][2];
Matrix[i][3] = TempMatrix[i][3];
}
}
// Function designed to merge rotation matrices with master
// matrix:
void Matrix3D::Rotate ( int Xa, int Ya, int Za )
{
Xr = Xa; Yr = Ya; Zr = Za;
double Rmat [ 4 ] [ 4 ];
// Initialize Z rotation matrix - Note: we perform Z
// rotation first to align the 3D Z axis with the 2D Z axis.
Rmat[0][0]=COS(Za); Rmat[0][1]=SIN(Za); Rmat[0][2]=0; Rmat[0][3]=0;
Rmat[1][0]=-SIN(Za); Rmat[1][1]=COS(Za); Rmat[1][2]=0; Rmat[1][3]=0;
Rmat[2][0]=0; Rmat[2][1]=0; Rmat[2][2]=1; Rmat[2][3]=0;
Rmat[3][0]=0; Rmat[3][1]=0; Rmat[3][2]=0; Rmat[3][3]=1;
// Merge matrix with master matrix:
MergeMatrix ( Rmat );
// Initialize X rotation matrix:
Rmat[0][0]=1; Rmat[0][1]=0; Rmat[0][2]=0; Rmat[0][3]=0;
Rmat[1][0]=0; Rmat[1][1]=COS(Xa); Rmat[1][2]=SIN(Xa); Rmat[1][3]=0;
Rmat[2][0]=0; Rmat[2][1]=-SIN(Xa); Rmat[2][2]=COS(Xa); Rmat[2][3]=0;
Rmat[3][0]=0; Rmat[3][1]=0; Rmat[3][2]=0; Rmat[3][3]=1;
// Merge matrix with master matrix:
MergeMatrix ( Rmat );
// Initialize Y rotation matrix:
Rmat[0][0]=COS(Ya); Rmat[0][1]=0; Rmat[0][2]=-SIN(Ya); Rmat[0][3]=0;
Rmat[1][0]=0; Rmat[1][1]=1; Rmat[1][2]=0; Rmat[1][3]=0;
Rmat[2][0]=SIN(Ya); Rmat[2][1]=0; Rmat[2][2]=COS(Ya); Rmat[2][3]=0;
Rmat[3][0]=0; Rmat[3][1]=0; Rmat[3][2]=0; Rmat[3][3]=1;
// Merge matrix with master matrix:
MergeMatrix ( Rmat );
}
// Function designed to merge translation matrix with master
// matrix:
void Matrix3D::Translate ( double Xt, double Yt, double Zt )
{
double Tmat [ 4 ] [ 4 ];
// Initialize translation matrix:
Tmat[0][0]=1; Tmat[0][1]=0; Tmat[0][2]=0; Tmat[0][3]=0;
Tmat[1][0]=0; Tmat[1][1]=1; Tmat[1][2]=0; Tmat[1][3]=0;
Tmat[2][0]=0; Tmat[2][1]=0; Tmat[2][2]=1; Tmat[2][3]=0;
Tmat[3][0]=Xt; Tmat[3][1]=Yt; Tmat[3][2]=Zt; Tmat[3][3]=1;
// Merge matrix with master matrix:
MergeMatrix ( Tmat );
}
// Function designed to merge scaling matrix with master
// matrix:
void Matrix3D::Scale ( double Xs, double Ys, double Zs )
{
double Smat [ 4 ] [ 4 ];
// Initialize scaling matrix:
Smat[0][0] = Xs; Smat[0][1] = 0; Smat[0][2] = 0; Smat[0][3] = 0;
Smat[1][0] = 0; Smat[1][1] = Ys; Smat[1][2] = 0; Smat[1][3] = 0;
Smat[2][0] = 0; Smat[2][1] = 0; Smat[2][2] = Zs; Smat[2][3] = 0;
Smat[3][0] = 0; Smat[3][1] = 0; Smat[3][2] = 0; Smat[3][3] = 1;
// Merge matrix with master matrix:
MergeMatrix ( Smat );
}
// Function designed to merge shearing matrix with master
// matrix - warps true x, y values:
void Matrix3D::Shear ( double Xs, double Ys )
{
double Smat [ 4 ] [ 4 ];
// Initialize shearing matrix:
Smat[0][0] = 1; Smat[0][1] = 0; Smat[0][2] = Xs; Smat[0][3] = 0;
Smat[1][0] = 0; Smat[1][1] = 1; Smat[1][2] = Ys; Smat[1][3] = 0;
Smat[2][0] = 0; Smat[2][1] = 0; Smat[2][2] = 1; Smat[2][3] = 0;
Smat[3][0] = 0; Smat[3][1] = 0; Smat[3][2] = 0; Smat[3][3] = 1;
// Merge matrix with master matrix:
MergeMatrix ( Smat );
}
// Function designed to multiply a vertex by the master
// matrix:
Vertex inline &Matrix3D::Transform ( Vertex &V )
{
// Initialize temporary variables:
double OldX = V.Wx;
double OldY = V.Wy;
double OldZ = V.Wz;
// Transform vertex by master matrix:
V.Wx = ( ( OldX * Matrix[0][0]) )
+ ( (OldY * Matrix[1][0]) )
+ ( (OldZ * Matrix[2][0]) )
+ Matrix[3][0];
V.Wy = ( (OldX * Matrix[0][1]) )
+ ( (OldY * Matrix[1][1]) )
+ ( (OldZ * Matrix[2][1]) )
+ Matrix[3][1];
V.Wz = ( (OldX * Matrix[0][2]) )
+ ( (OldY * Matrix[1][2]) )
+ ( (OldZ * Matrix[2][2]) )
+ Matrix[3][2];
return V;
}
// Function designed to multiply a vector by the master
// matrix:
Vector inline &Matrix3D::Transform ( Vector &V )
{
// Initialize temporary variables:
double OldX = V.X;
double OldY = V.Y;
double OldZ = V.Z;
// Rotate normal around Z axis:
V.X = OldX * COS ( Zr ) - OldY * SIN ( Zr );
V.Y = OldX * SIN ( Zr ) + OldY * COS ( Zr );
// Rotate normal around X axis:
OldY = V.Y;
V.Y = OldY * COS ( Xr ) - OldZ * SIN ( Xr );
V.Z = OldY * SIN ( Xr ) + OldZ * COS ( Xr );
// Rotate normal around Y axis:
OldX = V.X; OldZ = V.Z;
V.X = OldZ * SIN ( Yr ) + OldX * COS ( Yr );
V.Z = OldZ * COS ( Yr ) - OldX * SIN ( Yr );
return V;
}
// Function designed to multiply a triangle's vertices by matrix
// "Matrix":
void Triangle::Transform ( Matrix3D &Matrix )
{
// Transform the surface normal:
Matrix.Transform ( Normal );
// Update the triangle's vertices:
Matrix.Transform ( VPoint [ 0 ] );
Matrix.Transform ( VPoint [ 1 ] );
Matrix.Transform ( VPoint [ 2 ] );
}
// Function designed to determine if triangle is a back-
// face:
void Triangle::Backface ()
{
const TRUE = 1, FALSE = 0;
double VX = ( VPoint [ 0 ].Wx );
double VY = ( VPoint [ 0 ].Wy );
double VZ = ( VPoint [ 0 ].Wz );
double CosA = ( VX * Normal.X + VY * Normal.Y +
VZ * Normal.Z );
if ( CosA > 0.0F )
Visible = FALSE;
else Visible = TRUE;
}
// Function designed to calculate a polygon normal:
void Triangle::CalculateNormal ()
{
long double X1 = VPoint [0].Wx;
long double Y1 = VPoint [0].Wy;
long double Z1 = VPoint [0].Wz;
long double X2 = VPoint [1].Wx;
long double Y2 = VPoint [1].Wy;
long double Z2 = VPoint [1].Wz;
long double X3 = VPoint [2].Wx;
long double Y3 = VPoint [2].Wy;
long double Z3 = VPoint [2].Wz;
// Use plane equation to determine plane's orientation:
long double A = Y1 * ( Z2 - Z3 ) + Y2 * ( Z3 - Z1 ) + Y3 * ( Z1 - Z2 );
long double B = Z1 * ( X2 - X3 ) + Z2 * ( X3 - X1 ) + Z3 * ( X1 - X2 );
long double C = X1 * ( Y2 - Y3 ) + X2 * ( Y3 - Y1 ) + X3 * ( Y1 - Y2 );
long double Distance = sqrt ( A*A + B*B + C*C );
// Normalize the normal:
Normal.X = A / Distance;
Normal.Y = B / Distance;
Normal.Z = C / Distance;
}
// Function designed to load a triangle from a
// RAW file:
int Triangle::Load ( FILE *File )
{
int RValue = 1;
char String [ 100 ];
// Load nine floating-point values:
for ( unsigned int Index = 0; Index < 3; Index++ )
{
VPoint [ Index ].Wx = atof ( GetWord ( File, String ) );
VPoint [ Index ].Wy = atof ( GetWord ( File, String ) );
VPoint [ Index ].Wz = atof ( GetWord ( File, String ) );
if ( String == NULL )
{
RValue = 0;
break;
}
}
// Assign a random shade of gray to the triangle:
Color = ( unsigned char ) ( random ( 5 ) + 20 );
// Calculate triangle's normal:
CalculateNormal ();
return RValue;
}
// Function designed to project a triangle onto
// the viewport:
void Triangle::Project ()
{
for ( unsigned int Count = 0; Count < 3; Count++ )
{
double X = VPoint [ Count ].Wx;
double Y = VPoint [ Count ].Wy;
double Z = VPoint [ Count ].Wz;
// If Z is less than or equal to zero...
if ( Z <= 0.0F )
{
// ...cheat! No one will ever know...
Z = 1.0F;
}
// Perform perspective projection:
double OneOverZ = 1.0F / Z;
SPoint [ Count ].X = X * 120.0F * OneOverZ + 160;
SPoint [ Count ].Y = Y * -120.0F * OneOverZ + 100;
}
}
// Function designed to draw a triangle on a linear buffer:
void Triangle::Rasterize ( unsigned char *Dest )
{
PolyEdge ScanEdge;
// Assume element 0 is top/bottom of polygon:
long TopE = 0, BotE = 0, VertCount = 3, Vert1, Vert2;
long Top, Bot, X1, X2, Width, YIndex, Y, Temp, Check;
unsigned Height, Yh;
// Assume SPoint[0] is top/bottom of polygon:
long TopY = SPoint [ 0 ].Y, BotY = SPoint [ 0 ].Y;
// Attempt to verify previous assumptions:
for ( Check = 1; Check < VertCount; Check++ )
{
if ( TopY > SPoint [ Check ].Y )
{
TopY = SPoint [ Check ].Y;
TopE = Check;
}
else if ( BotY < SPoint [ Check ].Y )
{
BotY = SPoint [ Check ].Y;
BotE = Check;
}
}
Vert1 = TopE, Vert2 = TopE - 1;
// Check for wrap:
if ( Vert2 < 0 )
Vert2 = (VertCount - 1);
// Scan convert the left polygon edge:
while ( Vert1 != BotE )
{
ScanEdge.Initialize ( SPoint [ Vert1 ],
SPoint [ Vert2 ] );
Height = ScanEdge.Height ();
// Loop for the height of the polygon edge:
for ( Yh = 0; Yh < Height; Yh++ )
{
Y = ScanEdge.GetY ();
// If the scanline's on the scren....
if ( Y >= 0 )
{
if ( Y <= 199 )
{
// ...record it:
Left [ Y ].X = ScanEdge.GetX ();
}
else break;
}
++ScanEdge;
}
Temp = Vert1;
Vert1 = Vert2;
Vert2 = Temp - 1;
// Check for wrap:
if ( Vert2 < 0 )
Vert2 = (VertCount - 1);
}
Vert1 = TopE, Vert2 = ( TopE + 1 );
// Check for wrap:
if ( Vert2 > ( VertCount - 1 ) )
Vert2 = 0;
// Scan convert the right polygon edge:
while ( Vert1 != BotE )
{
ScanEdge.Initialize ( SPoint [ Vert1 ],
SPoint [ Vert2 ] );
Height = ScanEdge.Height ();
// Loop for the height of the polygon edge:
for ( Yh = 0; Yh < Height; Yh++ )
{
Y = ScanEdge.GetY ();
// If the scanline's on the screen....
if ( Y >= 0 )
{
if ( Y <= 199 )
{
// ...record it:
Right [ Y ].X = ScanEdge.GetX ();
}
else break;
}
++ScanEdge;
}
Temp = Vert1;
Vert1 = Vert2;
Vert2 = ( Temp + 1 );
// Check for wrap:
if ( Vert2 > ( VertCount - 1 ) )
Vert2 = 0;
}
Top = TopY; Bot = BotY;
// Clip the top and bottom coordinates:
ClipY1Y2 ( Top, Bot );
// Locate the Y start of the first scanline:
YIndex = Top * 320;
// Loop for the height of the polygon:
for ( Y = Top; Y < Bot; Y++ )
{
// Find the coordinates of the left and right
// polygons edges:
X1 = Left [ Y ].X;
X2 = Right [ Y ].X;
// Clip same:
ClipX1X2 ( X1, X2 );
// Determine the width of the scanline:
Width = ( X2 - X1 );
// Make sure there's something to draw:
if ( Width > 0 )
{
// Draw the scanline:
unsigned int Index = YIndex + X1;
setmem ( Dest + Index, Width, Color );
}
// Increment the buffer index:
YIndex += 320;
}
}
// Function designed to display a triangle on a linear buffer:
void inline Triangle::Display ( unsigned char *Buffer )
{
Backface ();
if ( Visible )
{
Project ();
Rasterize ( Buffer );
}
}
// Function designed to load an entire triangle world:
int TriangleWorld::Load ( char *FileName )
{
FILE *File;
TriCount = CountTriangles ( FileName );
if ( TriCount <= 0 )
return 0;
if ( ( File = fopen ( FileName, "rt" ) ) == 0 )
return 0;
if ( ( World = new Triangle [ TriCount ] ) == 0 )
{
fclose ( File );
return 0;
}
for ( unsigned int Count = 0; Count < TriCount; Count++ )
{
if ( !World [ Count ].Load ( File ) )
{
fclose ( File );
delete [] World;
return 0;
}
}
fclose ( File );
return 1;
}
// Function designed to transform an entire triangle world:
void TriangleWorld::Transform ( Matrix3D &Matrix )
{
for ( unsigned int Count = 0; Count < TriCount; Count++ )
{
World [ Count ].Transform ( Matrix );
}
}
// Function designed to display an entire world:
void TriangleWorld::Display ( unsigned char *Buffer )
{
for ( unsigned int Count = 0; Count < TriCount; Count++ )
{
World [ Count ].Display ( Buffer );
}
}