_GRAPHICS PROGRAMMING COLUMN_ by Michael Abrash [LISTING ONE] /* 3D animation program to rotate 12 cubes. Uses fixed point. All C code tested with Borland C++ 3.0 in C compilation mode and the small model. */ #include #include #include "polygon.h" /* 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; static unsigned int PageStartOffsets[2] = {PAGE0_START_OFFSET,PAGE1_START_OFFSET}; int DisplayedPage, NonDisplayedPage; int RecalcAllXforms = 1, NumObjects = 0; Xform WorldViewXform; /* initialized from floats */ /* Pointers to objects */ Object *ObjectList[MAX_OBJECTS]; void main() { int Done = 0, i; Object *ObjectPtr; union REGS regset; InitializeFixedPoint(); /* set up fixed-point data */ InitializeCubes(); /* set up cubes and add them to object list; other objects would be initialized now, if there were any */ 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 { /* For each object, regenerate viewing info, if necessary */ for (i=0; iRecalcXform || RecalcAllXforms) { ObjectPtr->RecalcFunc(ObjectPtr); ObjectPtr->RecalcXform = 0; } } RecalcAllXforms = 0; CurrentPageBase = /* select other page for drawing to */ PageStartOffsets[NonDisplayedPage = DisplayedPage ^ 1]; /* For each object, clear the portion of the non-displayed page that was drawn to last time, then reset the erase extent */ for (i=0; iEraseRect[NonDisplayedPage].Left, ObjectPtr->EraseRect[NonDisplayedPage].Top, ObjectPtr->EraseRect[NonDisplayedPage].Right, ObjectPtr->EraseRect[NonDisplayedPage].Bottom, CurrentPageBase, 0); ObjectPtr->EraseRect[NonDisplayedPage].Left = ObjectPtr->EraseRect[NonDisplayedPage].Top = 0x7FFF; ObjectPtr->EraseRect[NonDisplayedPage].Right = ObjectPtr->EraseRect[NonDisplayedPage].Bottom = 0; } /* Draw all objects */ for (i=0; iDrawFunc(ObjectList[i]); /* Flip to display the page into which we just drew */ ShowPage(PageStartOffsets[DisplayedPage = NonDisplayedPage]); /* Move and reorient each object */ for (i=0; iMoveFunc(ObjectList[i]); if (kbhit()) if (getch() == 0x1B) Done = 1; /* Esc to exit */ } while (!Done); /* Return to text mode and exit */ regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */ int86(0x10, ®set, ®set); exit(1); } [LISTING TWO] /* Transforms all vertices in the specified polygon-based object into view space, then perspective projects them to screen space and maps them to screen coordinates, storing results in the object. Recalculates object->view transformation because only if transform changes would we bother to retransform the vertices. */ #include #include "polygon.h" void XformAndProjectPObject(PObject * ObjectToXform) { int i, NumPoints = ObjectToXform->NumVerts; Point3 * Points = ObjectToXform->VertexList; Point3 * XformedPoints = ObjectToXform->XformedVertexList; Point3 * ProjectedPoints = ObjectToXform->ProjectedVertexList; Point * ScreenPoints = ObjectToXform->ScreenVertexList; /* Recalculate the object->view transform */ ConcatXforms(WorldViewXform, ObjectToXform->XformToWorld, ObjectToXform->XformToView); /* Apply that new transformation and project the points */ for (i=0; iXformToView, (Fixedpoint *) Points, (Fixedpoint *) XformedPoints); /* Perspective-project to screen space */ ProjectedPoints->X = FixedMul(FixedDiv(XformedPoints->X, XformedPoints->Z), DOUBLE_TO_FIXED(PROJECTION_RATIO * (SCREEN_WIDTH/2))); ProjectedPoints->Y = FixedMul(FixedDiv(XformedPoints->Y, XformedPoints->Z), DOUBLE_TO_FIXED(PROJECTION_RATIO * (SCREEN_WIDTH/2))); 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 polygon filler. Add in half the screen width and height to center on screen */ ScreenPoints->X = ((int) ((ProjectedPoints->X + DOUBLE_TO_FIXED(0.5)) >> 16)) + SCREEN_WIDTH/2; ScreenPoints->Y = (-((int) ((ProjectedPoints->Y + DOUBLE_TO_FIXED(0.5)) >> 16))) + SCREEN_HEIGHT/2; } } [LISTING THREE] /* Routines to perform incremental rotations around the three axes. */ #include #include "polygon.h" /* Concatenate a rotation by Angle around the X axis to transformation in XformToChange, placing the result back into XformToChange. */ void AppendRotationX(Xform XformToChange, double Angle) { Fixedpoint Temp10, Temp11, Temp12, Temp20, Temp21, Temp22; Fixedpoint CosTemp = DOUBLE_TO_FIXED(cos(Angle)); Fixedpoint SinTemp = DOUBLE_TO_FIXED(sin(Angle)); /* Calculate the new values of the six affected matrix entries */ Temp10 = FixedMul(CosTemp, XformToChange[1][0]) + FixedMul(-SinTemp, XformToChange[2][0]); Temp11 = FixedMul(CosTemp, XformToChange[1][1]) + FixedMul(-SinTemp, XformToChange[2][1]); Temp12 = FixedMul(CosTemp, XformToChange[1][2]) + FixedMul(-SinTemp, XformToChange[2][2]); Temp20 = FixedMul(SinTemp, XformToChange[1][0]) + FixedMul(CosTemp, XformToChange[2][0]); Temp21 = FixedMul(SinTemp, XformToChange[1][1]) + FixedMul(CosTemp, XformToChange[2][1]); Temp22 = FixedMul(SinTemp, XformToChange[1][2]) + FixedMul(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 transformation in XformToChange, placing the result back into XformToChange. */ void AppendRotationY(Xform XformToChange, double Angle) { Fixedpoint Temp00, Temp01, Temp02, Temp20, Temp21, Temp22; Fixedpoint CosTemp = DOUBLE_TO_FIXED(cos(Angle)); Fixedpoint SinTemp = DOUBLE_TO_FIXED(sin(Angle)); /* Calculate the new values of the six affected matrix entries */ Temp00 = FixedMul(CosTemp, XformToChange[0][0]) + FixedMul(SinTemp, XformToChange[2][0]); Temp01 = FixedMul(CosTemp, XformToChange[0][1]) + FixedMul(SinTemp, XformToChange[2][1]); Temp02 = FixedMul(CosTemp, XformToChange[0][2]) + FixedMul(SinTemp, XformToChange[2][2]); Temp20 = FixedMul(-SinTemp, XformToChange[0][0]) + FixedMul( CosTemp, XformToChange[2][0]); Temp21 = FixedMul(-SinTemp, XformToChange[0][1]) + FixedMul(CosTemp, XformToChange[2][1]); Temp22 = FixedMul(-SinTemp, XformToChange[0][2]) + FixedMul(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 transformation in XformToChange, placing the result back into XformToChange. */ void AppendRotationZ(Xform XformToChange, double Angle) { Fixedpoint Temp00, Temp01, Temp02, Temp10, Temp11, Temp12; Fixedpoint CosTemp = DOUBLE_TO_FIXED(cos(Angle)); Fixedpoint SinTemp = DOUBLE_TO_FIXED(sin(Angle)); /* Calculate the new values of the six affected matrix entries */ Temp00 = FixedMul(CosTemp, XformToChange[0][0]) + FixedMul(-SinTemp, XformToChange[1][0]); Temp01 = FixedMul(CosTemp, XformToChange[0][1]) + FixedMul(-SinTemp, XformToChange[1][1]); Temp02 = FixedMul(CosTemp, XformToChange[0][2]) + FixedMul(-SinTemp, XformToChange[1][2]); Temp10 = FixedMul(SinTemp, XformToChange[0][0]) + FixedMul(CosTemp, XformToChange[1][0]); Temp11 = FixedMul(SinTemp, XformToChange[0][1]) + FixedMul(CosTemp, XformToChange[1][1]); Temp12 = FixedMul(SinTemp, XformToChange[0][2]) + FixedMul(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 FOUR] /* Fixed point matrix arithmetic functions */ #include "polygon.h" /* Matrix multiplies Xform by SourceVec, and stores the result in DestVec. Multiplies a 4x4 matrix times a 4x1 matrix; the result is a 4x1 matrix. Cheats by assuming the W coord is 1 and bottom row of matrix is 0 0 0 1, and doesn't bother to set the W coordinate of the destination */ void XformVec(Xform WorkingXform, Fixedpoint *SourceVec, Fixedpoint *DestVec) { int i; for (i=0; i<3; i++) DestVec[i] = FixedMul(WorkingXform[i][0], SourceVec[0]) + FixedMul(WorkingXform[i][1], SourceVec[1]) + FixedMul(WorkingXform[i][2], SourceVec[2]) + WorkingXform[i][3]; /* no need to multiply by W = 1 */ } /* Matrix multiplies SourceXform1 by SourceXform2 and stores result in DestXform. Multiplies a 4x4 matrix times a 4x4 matrix; result is a 4x4 matrix. Cheats by assuming bottom row of each matrix is 0 0 0 1, and doesn't bother to set the bottom row of the destination */ void ConcatXforms(Xform SourceXform1, Xform SourceXform2, Xform DestXform) { int i, j; for (i=0; i<3; i++) { for (j=0; j<4; j++) DestXform[i][j] = FixedMul(SourceXform1[i][0], SourceXform2[0][j]) + FixedMul(SourceXform1[i][1], SourceXform2[1][j]) + FixedMul(SourceXform1[i][2], SourceXform2[2][j]) + SourceXform1[i][3]; } } [LISTING FIVE] /* Set up basic data that needs to be in fixed point, to avoid data definition hassles. */ #include "polygon.h" /* All vertices in the basic cube */ static IntPoint3 IntCubeVerts[NUM_CUBE_VERTS] = { {15,15,15},{15,15,-15},{15,-15,15},{15,-15,-15}, {-15,15,15},{-15,15,-15},{-15,-15,15},{-15,-15,-15} }; /* Transformation from world space into view space (no transformation, currently) */ static int IntWorldViewXform[3][4] = { {1,0,0,0}, {0,1,0,0}, {0,0,1,0}}; void InitializeFixedPoint() { int i, j; for (i=0; i<3; i++) for (j=0; j<4; j++) WorldViewXform[i][j] = INT_TO_FIXED(IntWorldViewXform[i][j]); for (i=0; iRDelayCount == 0) { /* rotate */ ObjectToMove->RDelayCount = ObjectToMove->RDelayCountBase; if (ObjectToMove->Rotate.RotateX != 0.0) AppendRotationX(ObjectToMove->XformToWorld, ObjectToMove->Rotate.RotateX); if (ObjectToMove->Rotate.RotateY != 0.0) AppendRotationY(ObjectToMove->XformToWorld, ObjectToMove->Rotate.RotateY); if (ObjectToMove->Rotate.RotateZ != 0.0) AppendRotationZ(ObjectToMove->XformToWorld, ObjectToMove->Rotate.RotateZ); ObjectToMove->RecalcXform = 1; } /* Move in Z, checking for bouncing and stopping */ if (--ObjectToMove->MDelayCount == 0) { ObjectToMove->MDelayCount = ObjectToMove->MDelayCountBase; ObjectToMove->XformToWorld[2][3] += ObjectToMove->Move.MoveZ; if (ObjectToMove->XformToWorld[2][3]>ObjectToMove->Move.MaxZ) ObjectToMove->Move.MoveZ = 0; /* stop if close enough */ ObjectToMove->RecalcXform = 1; } } [LISTING SEVEN] /* Draws all visible faces in specified polygon-based object. Object must have previously been transformed and projected, so that ScreenVertexList array is filled in. */ #include "polygon.h" void DrawPObject(PObject * ObjectToXform) { int i, j, NumFaces = ObjectToXform->NumFaces, NumVertices; int * VertNumsPtr; Face * FacePtr = ObjectToXform->FaceList; Point * ScreenPoints = ObjectToXform->ScreenVertexList; long v1, v2, w1, w2; Point Vertices[MAX_POLY_LENGTH]; PointListHeader Polygon; /* Draw each visible face (polygon) of the object in turn */ for (i=0; iNumVerts; /* Copy over the face's vertices from the vertex list */ for (j=0, VertNumsPtr=FacePtr->VertNums; j 0) { /* It is facing the screen, so draw */ /* Appropriately adjust the extent of the rectangle used to erase this object later */ for (j=0; j ObjectToXform->EraseRect[NonDisplayedPage].Right) if (Vertices[j].X < SCREEN_WIDTH) ObjectToXform->EraseRect[NonDisplayedPage].Right = Vertices[j].X; else ObjectToXform->EraseRect[NonDisplayedPage].Right = SCREEN_WIDTH; if (Vertices[j].Y > ObjectToXform->EraseRect[NonDisplayedPage].Bottom) if (Vertices[j].Y < SCREEN_HEIGHT) ObjectToXform->EraseRect[NonDisplayedPage].Bottom = Vertices[j].Y; else ObjectToXform->EraseRect[NonDisplayedPage].Bottom= SCREEN_HEIGHT; if (Vertices[j].X < ObjectToXform->EraseRect[NonDisplayedPage].Left) if (Vertices[j].X > 0) ObjectToXform->EraseRect[NonDisplayedPage].Left = Vertices[j].X; else ObjectToXform->EraseRect[NonDisplayedPage].Left=0; if (Vertices[j].Y < ObjectToXform->EraseRect[NonDisplayedPage].Top) if (Vertices[j].Y > 0) ObjectToXform->EraseRect[NonDisplayedPage].Top = Vertices[j].Y; else ObjectToXform->EraseRect[NonDisplayedPage].Top=0; } /* Draw the polygon */ DRAW_POLYGON(Vertices, NumVertices, FacePtr->Color, 0, 0); } } } [LISTING EIGHT] /* Initializes the cubes and adds them to the object list. */ #include #include #include "polygon.h" #define ROT_6 (M_PI / 30.0) /* rotate 6 degrees at a time */ #define ROT_3 (M_PI / 60.0) /* rotate 3 degrees at a time */ #define ROT_2 (M_PI / 90.0) /* rotate 2 degrees at a time */ #define NUM_CUBES 12 /* # of cubes */ Point3 CubeVerts[NUM_CUBE_VERTS]; /* set elsewhere, from floats */ /* Vertex indices for individual cube 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}; static int *VertNumList[]={Face1, Face2, Face3, Face4, Face5, Face6}; static int VertsInFace[]={ sizeof(Face1)/sizeof(int), sizeof(Face2)/sizeof(int), sizeof(Face3)/sizeof(int), sizeof(Face4)/sizeof(int), sizeof(Face5)/sizeof(int), sizeof(Face6)/sizeof(int) }; /* X, Y, Z rotations for cubes */ static RotateControl InitialRotate[NUM_CUBES] = { {0.0,ROT_6,ROT_6},{ROT_3,0.0,ROT_3},{ROT_3,ROT_3,0.0}, {ROT_3,-ROT_3,0.0},{-ROT_3,ROT_2,0.0},{-ROT_6,-ROT_3,0.0}, {ROT_3,0.0,-ROT_6},{-ROT_2,0.0,ROT_3},{-ROT_3,0.0,-ROT_3}, {0.0,ROT_2,-ROT_2},{0.0,-ROT_3,ROT_3},{0.0,-ROT_6,-ROT_6},}; static MoveControl InitialMove[NUM_CUBES] = { {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, {0,0,80,0,0,0,0,0,-350},{0,0,80,0,0,0,0,0,-350}, }; /* Face colors for various cubes */ static int Colors[NUM_CUBES][NUM_CUBE_FACES] = { {15,14,12,11,10,9},{1,2,3,4,5,6},{35,37,39,41,43,45}, {47,49,51,53,55,57},{59,61,63,65,67,69},{71,73,75,77,79,81}, {83,85,87,89,91,93},{95,97,99,101,103,105}, {107,109,111,113,115,117},{119,121,123,125,127,129}, {131,133,135,137,139,141},{143,145,147,149,151,153} }; /* Starting coordinates for cubes in world space */ static int CubeStartCoords[NUM_CUBES][3] = { {100,0,-6000}, {100,70,-6000}, {100,-70,-6000}, {33,0,-6000}, {33,70,-6000}, {33,-70,-6000}, {-33,0,-6000}, {-33,70,-6000}, {-33,-70,-6000},{-100,0,-6000}, {-100,70,-6000}, {-100,-70,-6000}}; /* Delay counts (speed control) for cubes */ static int InitRDelayCounts[NUM_CUBES] = {1,2,1,2,1,1,1,1,1,2,1,1}; static int BaseRDelayCounts[NUM_CUBES] = {1,2,1,2,2,1,1,1,2,2,2,1}; static int InitMDelayCounts[NUM_CUBES] = {1,1,1,1,1,1,1,1,1,1,1,1}; static int BaseMDelayCounts[NUM_CUBES] = {1,1,1,1,1,1,1,1,1,1,1,1}; void InitializeCubes() { int i, j, k; PObject *WorkingCube; for (i=0; iDrawFunc = DrawPObject; WorkingCube->RecalcFunc = XformAndProjectPObject; WorkingCube->MoveFunc = RotateAndMovePObject; WorkingCube->RecalcXform = 1; for (k=0; k<2; k++) { WorkingCube->EraseRect[k].Left = WorkingCube->EraseRect[k].Top = 0x7FFF; WorkingCube->EraseRect[k].Right = 0; WorkingCube->EraseRect[k].Bottom = 0; } WorkingCube->RDelayCount = InitRDelayCounts[i]; WorkingCube->RDelayCountBase = BaseRDelayCounts[i]; WorkingCube->MDelayCount = InitMDelayCounts[i]; WorkingCube->MDelayCountBase = BaseMDelayCounts[i]; /* Set the object->world xform to none */ for (j=0; j<3; j++) for (k=0; k<4; k++) WorkingCube->XformToWorld[j][k] = INT_TO_FIXED(0); WorkingCube->XformToWorld[0][0] = WorkingCube->XformToWorld[1][1] = WorkingCube->XformToWorld[2][2] = WorkingCube->XformToWorld[3][3] = INT_TO_FIXED(1); /* Set the initial location */ for (j=0; j<3; j++) WorkingCube->XformToWorld[j][3] = INT_TO_FIXED(CubeStartCoords[i][j]); WorkingCube->NumVerts = NUM_CUBE_VERTS; WorkingCube->VertexList = CubeVerts; WorkingCube->NumFaces = NUM_CUBE_FACES; WorkingCube->Rotate = InitialRotate[i]; WorkingCube->Move.MoveX = INT_TO_FIXED(InitialMove[i].MoveX); WorkingCube->Move.MoveY = INT_TO_FIXED(InitialMove[i].MoveY); WorkingCube->Move.MoveZ = INT_TO_FIXED(InitialMove[i].MoveZ); WorkingCube->Move.MinX = INT_TO_FIXED(InitialMove[i].MinX); WorkingCube->Move.MinY = INT_TO_FIXED(InitialMove[i].MinY); WorkingCube->Move.MinZ = INT_TO_FIXED(InitialMove[i].MinZ); WorkingCube->Move.MaxX = INT_TO_FIXED(InitialMove[i].MaxX); WorkingCube->Move.MaxY = INT_TO_FIXED(InitialMove[i].MaxY); WorkingCube->Move.MaxZ = INT_TO_FIXED(InitialMove[i].MaxZ); if ((WorkingCube->XformedVertexList = malloc(NUM_CUBE_VERTS*sizeof(Point3))) == NULL) { printf("Couldn't get memory\n"); exit(1); } if ((WorkingCube->ProjectedVertexList = malloc(NUM_CUBE_VERTS*sizeof(Point3))) == NULL) { printf("Couldn't get memory\n"); exit(1); } if ((WorkingCube->ScreenVertexList = malloc(NUM_CUBE_VERTS*sizeof(Point))) == NULL) { printf("Couldn't get memory\n"); exit(1); } if ((WorkingCube->FaceList = malloc(NUM_CUBE_FACES*sizeof(Face))) == NULL) { printf("Couldn't get memory\n"); exit(1); } /* Initialize the faces */ for (j=0; jFaceList[j].VertNums = VertNumList[j]; WorkingCube->FaceList[j].NumVerts = VertsInFace[j]; WorkingCube->FaceList[j].Color = Colors[i][j]; } ObjectList[NumObjects++] = (Object *)WorkingCube; } } [LISTING NINE] ; 386-specific fixed point multiply and divide. ; C near-callable as: Fixedpoint FixedMul(Fixedpoint M1, Fixedpoint M2); ; Fixedpoint FixedDiv(Fixedpoint Dividend, Fixedpoint Divisor); ; Tested with TASM 3.0. .model small .386 .code public _FixedMul,_FixedDiv ; Multiplies two fixed-point values together. FMparms struc dw 2 dup(?) ;return address & pushed BP M1 dd ? M2 dd ? FMparms ends align 2 _FixedMul proc near push bp mov bp,sp mov eax,[bp+M1] imul dword ptr [bp+M2] ;multiply add eax,8000h ;round by adding 2^(-16) adc edx,0 ;whole part of result is in DX shr eax,16 ;put the fractional part in AX pop bp ret _FixedMul endp ; Divides one fixed-point value by another. FDparms struc dw 2 dup(?) ;return address & pushed BP Dividend dd ? Divisor dd ? FDparms ends align 2 _FixedDiv proc near push bp mov bp,sp sub cx,cx ;assume positive result mov eax,[bp+Dividend] and eax,eax ;positive dividend? jns FDP1 ;yes inc cx ;mark it's a negative dividend neg eax ;make the dividend positive FDP1: sub edx,edx ;make it a 64-bit dividend, then shift ; left 16 bits so that result will be in EAX rol eax,16 ;put fractional part of dividend in ; high word of EAX mov dx,ax ;put whole part of dividend in DX sub ax,ax ;clear low word of EAX mov ebx,dword ptr [bp+Divisor] and ebx,ebx ;positive divisor? jns FDP2 ;yes dec cx ;mark it's a negative divisor neg ebx ;make divisor positive FDP2: div ebx ;divide shr ebx,1 ;divisor/2, minus 1 if the divisor is adc ebx,0 ; even dec ebx cmp ebx,edx ;set Carry if remainder is at least adc eax,0 ; half as large as the divisor, then ; use that to round up if necessary and cx,cx ;should the result be made negative? jz FDP3 ;no neg eax ;yes, negate it FDP3: mov edx,eax ;return result in DX:AX; fractional ; part is already in AX shr edx,16 ;whole part of result in DX pop bp ret _FixedDiv endp end [LISTING TEN] /* POLYGON.H: Header file for polygon-filling code, also includes a number of useful items for 3D animation. */ #define MAX_OBJECTS 100 /* max simultaneous # objects supported */ #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) #define NUM_CUBE_VERTS 8 /* # of vertices per cube */ #define NUM_CUBE_FACES 6 /* # of faces per cube */ /* 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); #define INT_TO_FIXED(x) (((long)(int)x) << 16) #define DOUBLE_TO_FIXED(x) ((long) (x * 65536.0 + 0.5)) typedef long Fixedpoint; typedef Fixedpoint Xform[3][4]; /* Describes a single 2D point */ typedef struct { int X; int Y; } Point; /* Describes a single 3D point in homogeneous coordinates; the W coordinate isn't present, though; assumed to be 1 and implied */ typedef struct { Fixedpoint X, Y, Z; } Point3; typedef struct { int X; int Y; int Z; } IntPoint3; /* 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; last vertex is assumed to connect to first) */ typedef struct { int Length; Point * PointPtr; } PointListHeader; /* Describes the beginning and ending X coordinates of a single horizontal line */ typedef struct { int XStart; int XEnd; } HLine; /* Describes a Length-long series of horizontal lines, all assumed to be on contiguous scan lines starting at YStart and proceeding downward (used to describe a scan-converted polygon to the low-level hardware-dependent drawing code) */ typedef struct { int Length; int YStart; HLine * HLinePtr;} HLineList; typedef struct { int Left, Top, Right, Bottom; } Rect; /* Structure describing one face of an object (one polygon) */ typedef struct { int * VertNums; int NumVerts; int Color; } Face; typedef struct { double RotateX, RotateY, RotateZ; } RotateControl; typedef struct { Fixedpoint MoveX, MoveY, MoveZ, MinX, MinY, MinZ, MaxX, MaxY, MaxZ; } MoveControl; /* Fields common to every object */ #define BASE_OBJECT \ void (*DrawFunc)(); /* draws object */ \ void (*RecalcFunc)(); /* prepares object for drawing */ \ void (*MoveFunc)(); /* moves object */ \ int RecalcXform; /* 1 to indicate need to recalc */ \ Rect EraseRect[2]; /* rectangle to erase in each page */ /* Basic object */ typedef struct { BASE_OBJECT } Object; /* Structure describing a polygon-based object */ typedef struct { BASE_OBJECT int RDelayCount, RDelayCountBase; /* controls rotation speed */ int MDelayCount, MDelayCountBase; /* controls movement speed */ Xform XformToWorld; /* transform from object->world space */ Xform XformToView; /* transform from object->view space */ RotateControl Rotate; /* controls rotation change over time */ MoveControl Move; /* controls object movement over time */ int NumVerts; /* # vertices in VertexList */ Point3 * VertexList; /* untransformed vertices */ Point3 * XformedVertexList; /* transformed into view space */ Point3 * ProjectedVertexList; /* projected into screen space */ Point * ScreenVertexList; /* converted to screen coordinates */ int NumFaces; /* # of faces in object */ Face * FaceList; /* pointer to face info */ } PObject; extern void XformVec(Xform, Fixedpoint *, Fixedpoint *); extern void ConcatXforms(Xform, Xform, Xform); extern int FillConvexPolygon(PointListHeader *, int, int, int); extern void Set320x240Mode(void); extern void ShowPage(unsigned int); extern void FillRectangleX(int, int, int, int, unsigned int, int); extern void XformAndProjectPObject(PObject *); extern void DrawPObject(PObject *); extern void AppendRotationX(Xform, double); extern void AppendRotationY(Xform, double); extern void AppendRotationZ(Xform, double); extern near Fixedpoint FixedMul(Fixedpoint, Fixedpoint); extern near Fixedpoint FixedDiv(Fixedpoint, Fixedpoint); extern void InitializeFixedPoint(void); extern void RotateAndMovePObject(PObject *); extern void InitializeCubes(void); extern int DisplayedPage, NonDisplayedPage, RecalcAllXforms; extern int NumObjects; extern Xform WorldViewXform; extern Object *ObjectList[]; extern Point3 CubeVerts[];