/******************************************************************** FILENAME: VIEW.CPP AUTHOR : JAKE HILL DATE : 12/1/94 Copyright (c) 1994 by Jake Hill: If you use any part of this code in your own project, please credit me in your documentation and source code. Thanks. ********************************************************************/ #include "VIEW.HPP" #include "TRIG.HPP" #include #include #include #include #define SKY_COLOR 1 #define FLOOR_COLOR 2 #define WALL_COLOR 3 #define RED_COLOR 4 #define LEDGE_COLOR 5 #define ZMIN 20L #define UPPER_TYPE 0 #define WALL_TYPE 1 #define LOWER_TYPE 2 // These are some yucky global variables. They should probably // be moved into the class's member data. short ColCount; short WallCount; short WallRunCount; short FirstSSector; // Elements of this array indicate if a screen column is completely drawn. short far Col_Done[320]; // Elements of this array hold indexes into the wall_run array. short far intersections[50][320]; // The number of wall_runs visible on a particular screen column. short far int_count[320]; // MaxY & MinY are the active edge lists for the top & bottom of the screen. short far MaxY[320]; short far MinY[320]; // This is the wall_run array. It contains all of the wall_runs which // are visible in a single frame. wall_run far walls[8000]; // 320*50 = 16000 // The next two arrays are used for both floors and ceilings. // Elements of this array hold indexes into the floor_run array. floor_run far floorlist[200][40]; // The number of floor_runs visible on a particular screen column. short far runcount[200]; // The offscreen buffer. char *screenbuf; // The vga memory on the vga card. char *vgabuf = (char *) 0xA0000000L; // This function draws a single color column into the // offscreen buffer. void ColDraw(short x, short top, short bottom, char color) { if (top < 0) top = 0; if (bottom > 200) bottom = 200; char *pixel = &screenbuf[((top<<6)+(top<<8)) + x]; for (short y=top; y 320) right = 320; char *pixel = &screenbuf[((y<<6)+(y<<8)) + left]; for (short x=left; x 318) return; if ( !OnRight( node_num ) ) { if ( LeftSideInCone( node_num ) ) DrawNode( PNode_Array[node_num]->left ); if ( RightSideInCone( node_num ) ) DrawNode( PNode_Array[node_num]->right ); } else { if ( RightSideInCone( node_num ) ) DrawNode( PNode_Array[node_num]->right ); if ( LeftSideInCone( node_num ) ) DrawNode( PNode_Array[node_num]->left ); } }; // This function obtains data common to all segs in the SSector // as well as doing the backface elimination via OnRight. void View::DrawSSector( short SS ) { short i; short LineSide, Sector; short SegCount, FirstSeg; // Load FirstSeg and SegCount locally for speed improvement. FirstSeg = SSector_Array[SS].first_seg; SegCount = SSector_Array[SS].num_segs; segment *ThisSeg = &Seg_Array[FirstSeg]; LineSide = ThisSeg->line_side; Sector = Side_Array[ Line_Array[ ThisSeg->line ].side[LineSide] ].sector; // Load the floor and ceiling height. sector *ThisSector = &Sector_Array[Sector]; floor_ht = ThisSector->floor_ht; ceiling_ht = ThisSector->ceiling_ht; // Here we determine the viewers height relative to all the walls, etc. if ( FirstSSector ) { FirstSSector = 0; Ph += floor_ht; } // Draw each SEG in the SSECTOR which can be seen. for (i=0; ifrom, ThisSeg->to ) ) LoadSeg( FirstSeg + i ); ThisSeg++; } }; // This function is where all of the Rotations and Transformations // are done. This is my very first 3D program, so there is probably // a LOT here which can be optimized for speed. void View::LoadSeg( short seg ) { segment *ThisSeg = &Seg_Array[seg]; short To = ThisSeg->to; short From = ThisSeg->from; unsigned short Angle = ThisSeg->angle - Pangle; // Store the World Space Coordinates vertex *Vertex = &Vertex_Array[To]; short Wtx = Vertex->x; short Wty = Vertex->y; Vertex = &Vertex_Array[From]; short Wfx = Vertex->x; short Wfy = Vertex->y; // Rotate the World Space Coordinates relative to player. long Rfz = (((Wfx-Px)*CosPangle) - ((Wfy-Py)*SinPangle)) >> 16; long Rtz = (((Wtx-Px)*CosPangle) - ((Wty-Py)*SinPangle)) >> 16; // If the seg is completely behind the player, exit the fn. if ((Rfz> 16; long Rtx = (((Wtx-Px)*SinPangle) + ((Wty-Py)*CosPangle)) >> 16; // Perform Z-clipping if necessary. long TanAngle = tangent(Angle); if (Rfx > Rfz) // Clip Rfx, Rfz to line x = z. { if (TanAngle == 65536L) return; // Prevent a divide by zero. long XZ = ((Rfx<<16) - (Rfz*TanAngle)) / (65536L-TanAngle); Rfx = Rfz = XZ; } if (Rfz < ZMIN) // Clip Rfx, Rfz to zmin. { Rfx = Rfx + (((ZMIN-Rfz)*TanAngle) >> 16); Rfz = ZMIN; } if (Rtz < ZMIN) // Clip Rtx, Rtz to zmin. { Rtx = Rtx + (((ZMIN-Rtz)*TanAngle) >> 16); Rtz = ZMIN; } if (Rfz > 9999L) Rfz = 9999L; // We don't want to go out of if (Rtz > 9999L) Rtz = 9999L; // bounds with these values. // Project the World Space -X- Coordinates to screen space coordinates. // MAKE SURE that Z-Clipping is done before this or we may get a // negative value for Rfz or Rtz - this would be out of bounds. short x1, x2, sx1, sx2; sx1 = x1 = (short) (160L - ((Rfx*invdistance(Rfz))>>16)); sx2 = x2 = (short) (160L - ((Rtx*invdistance(Rtz))>>16)); // Check if wall segment is on screen or is wide enough to see. if ( sx2 <= 0 ) return; if ( sx1 > 319 ) return; if ( sx1 == sx2 ) return; // Check if wall segment is completely occluded. if ( x1 < 0 ) x1 = 0; if ( x2 > 320 ) x2 = 320; char ExitNow = 1; for (int x=x1; xline ].flags & 0x0004); short Side = ThisSeg->line_side; line *ThisLine = &Line_Array[ ThisSeg->line ]; side *ThisSide = &Side_Array[ ThisLine->side[Side] ]; Wall.opaque = 0; // If there is a main_tx then there will not be an upper // or lower, so we can exit when done with this. if ( ThisSide->main_tx[0] != '-' ) { Rb = Ph - floor_ht; Rt = Ph - ceiling_ht; Wall.type = WALL_TYPE; AddWall(sx1, sx2, Rb, Rt, (short)Rfz, (short)Rtz); if ( SingleSided ) { Wall.opaque = 1; for (int x=x1; xside[!Side] ]; if ( ThisSide->lower_tx[0] != '-' ) Rt = Ph - Sector_Array[ ThatSide->sector ].floor_ht; else Rt = Ph - floor_ht; Rb = Ph - floor_ht; Wall.type = LOWER_TYPE; AddWall(sx1, sx2, Rb, Rt, (short)Rfz, (short)Rtz); if ( ThisSide->upper_tx[0] != '-' ) Rb = Ph - Sector_Array[ ThatSide->sector ].ceiling_ht; else Rb = Ph - ceiling_ht; Rt = Ph - ceiling_ht; Wall.type = UPPER_TYPE; AddWall(sx1, sx2, Rb, Rt, (short)Rfz, (short)Rtz); }; // This function adds the wall_runs and floor_runs to the // lists so that they may be drawn to the screen. It also does // the perspective calculations on the wall heights. void View::AddWall(short sx1, short sx2, short Rb, short Rt, short Rfz, short Rtz) { // Project to determine the four y screen coordinates. long sy1 = 100L + ((Rb*invdistance(Rfz))>>16); // bottom left. long sy2 = 100L + ((Rb*invdistance(Rtz))>>16); // bottom right. long sy3 = 100L + ((Rt*invdistance(Rtz))>>16); // top right. long sy4 = 100L + ((Rt*invdistance(Rfz))>>16); // top left. Wall.y1 = sy4 << 16; Wall.y2 = sy1 << 16; Wall.dy1 = ((sy3-sy4) << 16) / (sx2-sx1); Wall.dy2 = ((sy2-sy1) << 16) / (sx2-sx1); if ( sx1 < 0 ) { Wall.y1 -= sx1 * Wall.dy1; Wall.y2 -= sx1 * Wall.dy2; sx1 = 0; } if ( sx2 > 320 ) sx2 = 320; short last_maxy = MaxY[sx1]; short last_bottom = MaxY[sx1]; short top, bottom, miny, maxy; short last_top = MinY[sx1]; short last_miny = MinY[sx1]; for (short x=sx1; x> 16); bottom = (short) (Wall.y2 >> 16); if ((Wall.type==WALL_TYPE)||(Wall.type==LOWER_TYPE)) if (bottom < maxy) { if ( bottom < last_bottom ) AddFloorUp(last_bottom, bottom, x); if ( maxy > last_maxy ) AddFloorDown(last_maxy, maxy, x); if ( bottom > last_bottom ) EndFloorDown(last_bottom, bottom, x); if ( maxy < last_maxy ) EndFloorUp(last_maxy, maxy, x); } else if ( last_bottom < last_maxy ) EndFloorUp(last_maxy, last_bottom, x); if ((Wall.type==WALL_TYPE)||(Wall.type==UPPER_TYPE)) if (top > miny) { if ( top > last_top ) AddFloorDown(last_top, top, x); if ( miny < last_miny ) AddFloorUp(last_miny, miny, x); if ( top < last_top ) EndFloorUp(last_top, top, x); if ( miny > last_miny ) EndFloorDown(last_miny, miny, x); } else if ( last_top > last_miny ) EndFloorDown(last_miny, last_top, x); last_top = top; last_miny = miny; last_maxy = maxy; last_bottom = bottom; if (miny < maxy) if ((topminy)) { if (top < miny) top = miny; if (bottom > maxy) bottom = maxy; if (top < bottom) { walls[WallRunCount].top = top; walls[WallRunCount].bottom = bottom; intersections[ int_count[x] ][x] = WallRunCount; int_count[x]++; } } if ( Wall.type == UPPER_TYPE ) { walls[WallRunCount].tex_num = LEDGE_COLOR; if (bottom>miny) MinY[x] = bottom; } else if ( Wall.type == LOWER_TYPE ) { walls[WallRunCount].tex_num = LEDGE_COLOR; if (topminy) MinY[x] = bottom; if (top last_miny ) EndFloorDown(last_miny, last_top, x); }; // Here is where we blast all of the runs to the screen buffer. void View::DrawSegs(void) { short row; for (row=0; row<100; row++) for (short run=0; runtop, ThisWallRun->bottom, (char) ThisWallRun->tex_num); } }; // lb > b void View::AddFloorUp(short lb, short b, short start) { for (short row=b; row=0) && (row<200)) floorlist[row][ runcount[row] ].start = start; } // b > lb void View::AddFloorDown(short lb, short b, short start) { for (short row=lb; row=0) && (row<200)) floorlist[row][ runcount[row] ].start = start; } // b < lb void View::EndFloorUp(short lb, short b, short end) { for (short row=b; row=0) && (row<200)) { floorlist[row][ runcount[row] ].end = end; runcount[row]++; } } // b > lb void View::EndFloorDown(short lb, short b, short end) { for (short row=lb; row=0) && (row<200)) { floorlist[row][ runcount[row] ].end = end; runcount[row]++; } }