Output of file : GRAPHPRG.ASC contained in archive : DDJ0292.ZIP
_GRAPHICS PROGRAMMING COLUMN_
by Michael Abrash

[LISTING ONE]

/* 3D animation program to view a cube as it rotates in mode X. The viewpoint
is fixed at the origin (0,0,0) of world space, looking in the direction of
increasingly negative Z. A right-handed coordinate system is used throughout.
All C code tested with Borland C++ 2.0 in C compilation mode */
#include
#include
#include
#include "polygon.h"

#define ROTATION (M_PI / 30.0) /* rotate by 6 degrees at a time */

/* Base offset of page to which to draw */
unsigned int CurrentPageBase = 0;
/* Clip rectangle; clips to the screen */
int ClipMinX=0, ClipMinY=0;
int ClipMaxX=SCREEN_WIDTH, ClipMaxY=SCREEN_HEIGHT;
/* Rectangle specifying extent to be erased in each page */
struct Rect EraseRect[2] = { {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT},
{0, 0, SCREEN_WIDTH, SCREEN_HEIGHT} };
static unsigned int PageStartOffsets[2] =
{PAGE0_START_OFFSET,PAGE1_START_OFFSET};
int DisplayedPage, NonDisplayedPage;
/* Transformation from cube's object space to world space. Initially
set up to perform no rotation and to move the cube into world
space -100 units away from the origin down the Z axis. Given the
viewing point, -100 down the Z axis means 100 units away in the
direction of view. The program dynamically changes both the
translation and the rotation. */
static double CubeWorldXform[4][4] = {
{1.0, 0.0, 0.0, 0.0},
{0.0, 1.0, 0.0, 0.0},
{0.0, 0.0, 1.0, -100.0},
{0.0, 0.0, 0.0, 1.0} };
/* Transformation from world space into view space. Because in this
application the view point is fixed at the origin of world space,
looking down the Z axis in the direction of increasing Z, view space is
identical to world space, and this is the identity matrix */
static double WorldViewXform[4][4] = {
{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}
};
/* All vertices in the cube */
static struct Point3 CubeVerts[] = {

{15,15,15,1},{15,15,-15,1},{15,-15,15,1},{15,-15,-15,1},
{-15,15,15,1},{-15,15,-15,1},{-15,-15,15,1},{-15,-15,-15,1}};
/* Vertices after transformation */
static struct Point3
XformedCubeVerts[sizeof(CubeVerts)/sizeof(struct Point3)];
/* Vertices after projection */
static struct Point3
ProjectedCubeVerts[sizeof(CubeVerts)/sizeof(struct Point3)];
/* Vertices in screen coordinates */
static struct Point
ScreenCubeVerts[sizeof(CubeVerts)/sizeof(struct Point3)];
/* Vertex indices for individual faces */
static int Face1[] = {1,3,2,0};
static int Face2[] = {5,7,3,1};
static int Face3[] = {4,5,1,0};
static int Face4[] = {3,7,6,2};
static int Face5[] = {5,4,6,7};
static int Face6[] = {0,2,6,4};
/* List of cube faces */
static struct Face CubeFaces[] = {{Face1,4,15},{Face2,4,14},
{Face3,4,12},{Face4,4,11},{Face5,4,10},{Face6,4,9}};
/* Master description for cube */
static struct Object Cube = {sizeof(CubeVerts)/sizeof(struct Point3),
CubeVerts, XformedCubeVerts, ProjectedCubeVerts, ScreenCubeVerts,
sizeof(CubeFaces)/sizeof(struct Face), CubeFaces};

void main() {
int Done = 0, RecalcXform = 1;
double WorkingXform[4][4];
union REGS regset;

/* Set up the initial transformation */
Set320x240Mode(); /* set the screen to mode X */
ShowPage(PageStartOffsets[DisplayedPage = 0]);
/* Keep transforming the cube, drawing it to the undisplayed page,
and flipping the page to show it */
do {
/* Regenerate the object->view transformation and
retransform/project if necessary */
if (RecalcXform) {
ConcatXforms(WorldViewXform, CubeWorldXform, WorkingXform);
/* Transform and project all the vertices in the cube */
XformAndProjectPoints(WorkingXform, &Cube);
RecalcXform = 0;
}
CurrentPageBase = /* select other page for drawing to */
PageStartOffsets[NonDisplayedPage = DisplayedPage ^ 1];
/* Clear the portion of the non-displayed page that was drawn
to last time, then reset the erase extent */
FillRectangleX(EraseRect[NonDisplayedPage].Left,
EraseRect[NonDisplayedPage].Top,
EraseRect[NonDisplayedPage].Right,
EraseRect[NonDisplayedPage].Bottom, CurrentPageBase, 0);
EraseRect[NonDisplayedPage].Left =
EraseRect[NonDisplayedPage].Top = 0x7FFF;
EraseRect[NonDisplayedPage].Right =
EraseRect[NonDisplayedPage].Bottom = 0;
/* Draw all visible faces of the cube */
DrawVisibleFaces(&Cube);
/* Flip to display the page into which we just drew */
ShowPage(PageStartOffsets[DisplayedPage = NonDisplayedPage]);
while (kbhit()) {
switch (getch()) {
case 0x1B: /* Esc to exit */
Done = 1; break;
case 'A': case 'a': /* away (-Z) */
CubeWorldXform[2][3] -= 3.0; RecalcXform = 1; break;
case 'T': /* towards (+Z). Don't allow to get too */
case 't': /* close, so Z clipping isn't needed */
if (CubeWorldXform[2][3] < -40.0) {
CubeWorldXform[2][3] += 3.0;
RecalcXform = 1;
}
break;
case '4': /* rotate clockwise around Y */
AppendRotationY(CubeWorldXform, -ROTATION);
RecalcXform=1; break;
case '6': /* rotate counterclockwise around Y */
AppendRotationY(CubeWorldXform, ROTATION);
RecalcXform=1; break;
case '8': /* rotate clockwise around X */
AppendRotationX(CubeWorldXform, -ROTATION);
RecalcXform=1; break;
case '2': /* rotate counterclockwise around X */
AppendRotationX(CubeWorldXform, ROTATION);
RecalcXform=1; break;
case 0: /* extended code */
switch (getch()) {
case 0x3B: /* rotate counterclockwise around Z */
AppendRotationZ(CubeWorldXform, ROTATION);
RecalcXform=1; break;
case 0x3C: /* rotate clockwise around Z */
AppendRotationZ(CubeWorldXform, -ROTATION);
RecalcXform=1; break;
case 0x4B: /* left (-X) */
CubeWorldXform[0][3] -= 3.0; RecalcXform=1; break;
case 0x4D: /* right (+X) */
CubeWorldXform[0][3] += 3.0; RecalcXform=1; break;
case 0x48: /* up (+Y) */
CubeWorldXform[1][3] += 3.0; RecalcXform=1; break;
case 0x50: /* down (-Y) */
CubeWorldXform[1][3] -= 3.0; RecalcXform=1; break;
default:
break;
}
break;
default: /* any other key to pause */
getch(); break;
}
}
} while (!Done);
regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */
int86(0x10, ®set, ®set);
}

[LISTING TWO]

/* Transforms all vertices in the specified object into view space, then
perspective projects them to screen space and maps them to screen coordinates,
storing the results in the object. */
#include
#include "polygon.h"

void XformAndProjectPoints(double Xform[4][4],
struct Object * ObjectToXform)
{
int i, NumPoints = ObjectToXform->NumVerts;
struct Point3 * Points = ObjectToXform->VertexList;
struct Point3 * XformedPoints = ObjectToXform->XformedVertexList;
struct Point3 * ProjectedPoints =
ObjectToXform->ProjectedVertexList;
struct Point * ScreenPoints = ObjectToXform->ScreenVertexList;

for (i=0; i ProjectedPoints++, ScreenPoints++) {
/* Transform to view space */
XformVec(Xform, (double *)Points, (double *)XformedPoints);
/* Perspective-project to screen space */
ProjectedPoints->X = XformedPoints->X / XformedPoints->Z *
PROJECTION_RATIO * (SCREEN_WIDTH / 2.0);
ProjectedPoints->Y = XformedPoints->Y / XformedPoints->Z *
PROJECTION_RATIO * (SCREEN_WIDTH / 2.0);
ProjectedPoints->Z = XformedPoints->Z;
/* Convert to screen coordinates. The Y coord is negated to
flip from increasing Y being up to increasing Y being down,
as expected by the polygon filler. Add in half the screen
width and height to center on the screen */
ScreenPoints->X = ((int) floor(ProjectedPoints->X + 0.5)) +
SCREEN_WIDTH/2;
ScreenPoints->Y = (-((int) floor(ProjectedPoints->Y + 0.5))) +
SCREEN_HEIGHT/2;
}
}

[LISTING THREE]

/* Draws all visible faces (faces pointing toward the viewer) in the specified
object. The object must have previously been transformed and projected, so
that the ScreenVertexList array is filled in. */
#include "polygon.h"

void DrawVisibleFaces(struct Object * ObjectToXform)
{
int i, j, NumFaces = ObjectToXform->NumFaces, NumVertices;
int * VertNumsPtr;
struct Face * FacePtr = ObjectToXform->FaceList;
struct Point * ScreenPoints = ObjectToXform->ScreenVertexList;
long v1,v2,w1,w2;
struct Point Vertices[MAX_POLY_LENGTH];

/* Draw each visible face (polygon) of the object in turn */
for (i=0; i NumVertices = FacePtr->NumVerts;
/* Copy over the face's vertices from the vertex list */
for (j=0, VertNumsPtr=FacePtr->VertNums; j Vertices[j] = ScreenPoints[*VertNumsPtr++];
/* Draw only if outside face showing (if the normal to the
polygon points toward the viewer; that is, has a positive
Z component) */
v1 = Vertices[1].X - Vertices[0].X;
w1 = Vertices[NumVertices-1].X - Vertices[0].X;
v2 = Vertices[1].Y - Vertices[0].Y;
w2 = Vertices[NumVertices-1].Y - Vertices[0].Y;
if ((v1*w2 - v2*w1) > 0) {
/* It is facing the screen, so draw */
/* Appropriately adjust the extent of the rectangle used to
for (j=0; j if (Vertices[j].X > EraseRect[NonDisplayedPage].Right)
if (Vertices[j].X < SCREEN_WIDTH)
EraseRect[NonDisplayedPage].Right = Vertices[j].X;
else EraseRect[NonDisplayedPage].Right = SCREEN_WIDTH;
if (Vertices[j].Y > EraseRect[NonDisplayedPage].Bottom)
if (Vertices[j].Y < SCREEN_HEIGHT)
EraseRect[NonDisplayedPage].Bottom = Vertices[j].Y;
else EraseRect[NonDisplayedPage].Bottom=SCREEN_HEIGHT;
if (Vertices[j].X < EraseRect[NonDisplayedPage].Left)
if (Vertices[j].X > 0)
EraseRect[NonDisplayedPage].Left = Vertices[j].X;
else EraseRect[NonDisplayedPage].Left = 0;
if (Vertices[j].Y < EraseRect[NonDisplayedPage].Top)
if (Vertices[j].Y > 0)
EraseRect[NonDisplayedPage].Top = Vertices[j].Y;
else EraseRect[NonDisplayedPage].Top = 0;
}
/* Draw the polygon */
DRAW_POLYGON(Vertices, NumVertices, FacePtr->Color, 0, 0);
}
}
}

[LISTING FOUR]

/* Routines to perform incremental rotations around the three axes */
#include
#include "polygon.h"

/* Concatenate a rotation by Angle around the X axis to the transformation in
XformToChange, placing result back in XformToChange. */
void AppendRotationX(double XformToChange[4][4], double Angle)
{
double Temp10, Temp11, Temp12, Temp20, Temp21, Temp22;
double CosTemp = cos(Angle), SinTemp = sin(Angle);

/* Calculate the new values of the four affected matrix entries */
Temp10 = CosTemp*XformToChange[1][0]+ -SinTemp*XformToChange[2][0];
Temp11 = CosTemp*XformToChange[1][1]+ -SinTemp*XformToChange[2][1];
Temp12 = CosTemp*XformToChange[1][2]+ -SinTemp*XformToChange[2][2];
Temp20 = SinTemp*XformToChange[1][0]+ CosTemp*XformToChange[2][0];
Temp21 = SinTemp*XformToChange[1][1]+ CosTemp*XformToChange[2][1];
Temp22 = SinTemp*XformToChange[1][2]+ CosTemp*XformToChange[2][2];
/* Put the results back into XformToChange */
XformToChange[1][0] = Temp10; XformToChange[1][1] = Temp11;
XformToChange[1][2] = Temp12; XformToChange[2][0] = Temp20;
XformToChange[2][1] = Temp21; XformToChange[2][2] = Temp22;
}

/* Concatenate a rotation by Angle around the Y axis to the transformation in
XformToChange, placing result back in XformToChange. */
void AppendRotationY(double XformToChange[4][4], double Angle)
{
double Temp00, Temp01, Temp02, Temp20, Temp21, Temp22;
double CosTemp = cos(Angle), SinTemp = sin(Angle);

/* Calculate the new values of the four affected matrix entries */
Temp00 = CosTemp*XformToChange[0][0]+ SinTemp*XformToChange[2][0];
Temp01 = CosTemp*XformToChange[0][1]+ SinTemp*XformToChange[2][1];
Temp02 = CosTemp*XformToChange[0][2]+ SinTemp*XformToChange[2][2];
Temp20 = -SinTemp*XformToChange[0][0]+ CosTemp*XformToChange[2][0];
Temp21 = -SinTemp*XformToChange[0][1]+ CosTemp*XformToChange[2][1];
Temp22 = -SinTemp*XformToChange[0][2]+ CosTemp*XformToChange[2][2];
/* Put the results back into XformToChange */
XformToChange[0][0] = Temp00; XformToChange[0][1] = Temp01;
XformToChange[0][2] = Temp02; XformToChange[2][0] = Temp20;
XformToChange[2][1] = Temp21; XformToChange[2][2] = Temp22;
}

/* Concatenate a rotation by Angle around the Z axis to the transformation in
XformToChange, placing result back in XformToChange. */
void AppendRotationZ(double XformToChange[4][4], double Angle)
{
double Temp00, Temp01, Temp02, Temp10, Temp11, Temp12;
double CosTemp = cos(Angle), SinTemp = sin(Angle);

/* Calculate the new values of the four affected matrix entries */
Temp00 = CosTemp*XformToChange[0][0]+ -SinTemp*XformToChange[1][0];
Temp01 = CosTemp*XformToChange[0][1]+ -SinTemp*XformToChange[1][1];
Temp02 = CosTemp*XformToChange[0][2]+ -SinTemp*XformToChange[1][2];
Temp10 = SinTemp*XformToChange[0][0]+ CosTemp*XformToChange[1][0];
Temp11 = SinTemp*XformToChange[0][1]+ CosTemp*XformToChange[1][1];
Temp12 = SinTemp*XformToChange[0][2]+ CosTemp*XformToChange[1][2];
/* Put the results back into XformToChange */
XformToChange[0][0] = Temp00; XformToChange[0][1] = Temp01;
XformToChange[0][2] = Temp02; XformToChange[1][0] = Temp10;
XformToChange[1][1] = Temp11; XformToChange[1][2] = Temp12;
}

[LISTING FIVE]

/* POLYGON.H: Header file for polygon-filling code, also includes a number of
useful items for 3D animation. */

#define MAX_POLY_LENGTH 4 /* four vertices is the max per poly */
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
#define PAGE0_START_OFFSET 0
#define PAGE1_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH)/4)

/* Ratio: distance from viewpoint to projection plane / width of projection
plane. Defines the width of the field of view. Lower absolute values = wider
fields of view; higher values = narrower */
#define PROJECTION_RATIO -2.0 /* negative because visible Z
coordinates are negative */
/* Draws the polygon described by the point list PointList in color Color with
all vertices offset by (X,Y) */
#define DRAW_POLYGON(PointList,NumPoints,Color,X,Y) \
Polygon.Length = NumPoints; Polygon.PointPtr = PointList; \
FillConvexPolygon(&Polygon, Color, X, Y);

/* Describes a single 2D point */
struct Point {
int X; /* X coordinate */
int Y; /* Y coordinate */
};
/* Describes a single 3D point in homogeneous coordinates */
struct Point3 {
double X; /* X coordinate */
double Y; /* Y coordinate */
double Z; /* Z coordinate */
double W;
};
/* Describes a series of points (used to store a list of vertices that
describe a polygon; each vertex is assumed to connect to the two adjacent
vertices, and the last vertex is assumed to connect to the first) */
int Length; /* # of points */
struct Point * PointPtr; /* pointer to list of points */
};
/* Describes beginning and ending X coordinates of a single horizontal line */
struct HLine {
int XStart; /* X coordinate of leftmost pixel in line */
int XEnd; /* X coordinate of rightmost pixel in line */
};
/* Describes a Length-long series of horizontal lines, all assumed to be on
contiguous scan lines starting at YStart and proceeding downward (describes
a scan-converted polygon to low-level hardware-dependent drawing code) */
struct HLineList {
int Length; /* # of horizontal lines */
int YStart; /* Y coordinate of topmost line */
struct HLine * HLinePtr; /* pointer to list of horz lines */
};
struct Rect { int Left, Top, Right, Bottom; };
/* Structure describing one face of an object (one polygon) */
struct Face {
int * VertNums; /* pointer to vertex ptrs */
int NumVerts; /* # of vertices */
int Color; /* polygon color */
};
/* Structure describing an object */
struct Object {
int NumVerts;
struct Point3 * VertexList;
struct Point3 * XformedVertexList;
struct Point3 * ProjectedVertexList;
struct Point * ScreenVertexList;
int NumFaces;
struct Face * FaceList;
};

extern void XformVec(double Xform[4][4], double * SourceVec,
double * DestVec);
extern void ConcatXforms(double SourceXform1[4][4],
double SourceXform2[4][4], double DestXform[4][4]);
extern void XformAndProjectPoly(double Xform[4][4],
struct Point3 * Poly, int PolyLength, int Color);
extern int FillConvexPolygon(struct PointListHeader *, int, int, int);
extern void Set320x240Mode(void);
extern void ShowPage(unsigned int StartOffset);
extern void FillRectangleX(int StartX, int StartY, int EndX,
int EndY, unsigned int PageBase, int Color);
extern void XformAndProjectPoints(double Xform[4][4],
struct Object * ObjectToXform);
extern void DrawVisibleFaces(struct Object * ObjectToXform);
extern void AppendRotationX(double XformToChange[4][4], double Angle);
extern void AppendRotationY(double XformToChange[4][4], double Angle);
extern void AppendRotationZ(double XformToChange[4][4], double Angle);
extern int DisplayedPage, NonDisplayedPage;
extern struct Rect EraseRect[];



### 3 Responses to “Category : Files from MagazinesArchive   : DDJ0292.ZIPFilename : GRAPHPRG.ASC”

1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/