#include <string.h> #include <math.h> #include "Render.h" // #define USEBOUNDINGBOX const double PI = 3.141592654; Renderer::Renderer( HWND hWnd, GameWad* Wad ) { // Copy over the items we want to know about in the outside world. WadFile = Wad; ghWnd = hWnd; // Allocate the vertex cache. VerticesCache = new Vertex[WadFile->NbrVertices]; BitMapInfo = NULL; // Initialize the lookup tables. initTables(); // Initialize the double buffer DIB. init_double_buffer(); } Renderer::~Renderer(void) { // Clean up the vertex cache. if( NULL != VerticesCache) { delete VerticesCache; VerticesCache = NULL; } return; } short Renderer::initTables( void ) { double rads; int i; // Generate the trig tables. for( i=0; i < 8192; ++i ) { rads = (i * PI)/4096.0; SinTable[i] = (long) floor(::sin(rads) * 65536.0); CosTable[i] = (long) floor(::cos(rads) * 65536.0); TanTable[i] = (long) floor(::tan(rads) * 65536.0); } // Generate the column angle table (for texturing walls) for( i=0; i< 640; ++i ) { colangle[i] = (short)((i-320) * 0x4000/960.0); } // Calculate the inverse tangent table (Slope to Angle) // Calculate the texture scaling table. for( i = 1; i <= 8193; ++i ) DeltaTxtTbl[i-1] = 2097152l / i; return 1; } long Renderer::sin(short Ang) { // This takes an angle (Binary Angle Measurement, or BAM), and returns the sine function from the table. // return SinTable[((unsigned short)Ang)>>3]; //return 65536l * ::sin(Ang * PI / (184.044444 * 180)); } long Renderer::cos(short Ang) { // This takes a BAM angle, and returns the cosine, from the SinTable. // return CosTable[((unsigned short)Ang)>>3]; // return 65536l * ::cos(Ang * PI / (184.044444 * 180)); } long Renderer::tan(short Ang) { // This takes a BAM angle, and returns the tangent, from the TanTable. // return TanTable[((unsigned short)Ang)>>3]; //return 65536l * ::tan(Ang * PI / (184.044444 * 180)); } short Renderer::invTan(long y, long x) { // This will take the rise and run and calculate the angle (inverse tangent) return (short)((180.0 * atan2((float)y,(float)x) * 184.044444)/PI) ; } void Renderer::init_double_buffer(void) { // // Initialize the double buffer and windows bitmaps that will be used to // render the double buffer. // HDC hDC; if ( NULL == BitMapInfo ) BitMapInfo = (BITMAPINFO *)malloc(sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD))); hDC = GetDC(ghWnd); BitMapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); BitMapInfo->bmiHeader.biWidth = 640; BitMapInfo->bmiHeader.biHeight = -400; BitMapInfo->bmiHeader.biPlanes = 1; BitMapInfo->bmiHeader.biBitCount = 8; BitMapInfo->bmiHeader.biCompression = BI_RGB; BitMapInfo->bmiHeader.biSizeImage = 0; BitMapInfo->bmiHeader.biXPelsPerMeter = 0; BitMapInfo->bmiHeader.biYPelsPerMeter = 0; BitMapInfo->bmiHeader.biClrUsed = 0; BitMapInfo->bmiHeader.biClrImportant = 0; // Load the palette for (int i= 0; i < 256; ++i) { BitMapInfo->bmiColors[i].rgbRed = WadFile->GamePalettes[0][i*3]; BitMapInfo->bmiColors[i].rgbGreen = WadFile->GamePalettes[0][i*3+1]; BitMapInfo->bmiColors[i].rgbBlue = WadFile->GamePalettes[0][i*3+2]; } if (DblBuff != NULL) DeleteObject(DblBuff); DblBuff = CreateDIBSection(hDC, (BITMAPINFO *)BitMapInfo, DIB_RGB_COLORS, (VOID **)&DoubleBuffer, 0, 0); ReleaseDC(ghWnd,hDC); } void Renderer::clearToColor(char c) { // // This function clears te screen to the color "C", by blasting the // byte to the double buffer. This used to use a rep movsw, but now we're // in windows, and I don't know if that will work anymore. // memset(DoubleBuffer, c, 256000ul); } void Renderer::ChangePalette ( short PaletteNbr ) { // // This modifies the double buffers palette. It is used to select one of the 14 palettes // stored in the WAD file. // HDC hDC, hMemDC; HBITMAP DefaultBitmap; RGBQUAD tmpPalette[256]; if ((PaletteNbr > 14) || (PaletteNbr < 1)) return; hDC = GetDC(ghWnd); // Create memory device context compatible with the engines window dc hMemDC = CreateCompatibleDC(GetDC(ghWnd)); // Save the default bitmap and select the engines double buffer into the memory dc DefaultBitmap = SelectObject(hMemDC, DblBuff); // Now get the palette for the selected bitmap. GetDIBColorTable(hMemDC, 0, 256, tmpPalette); // Modify the palette by the percentage. for (int i=0; i<256; ++i) { tmpPalette[i].rgbRed = (unsigned char )WadFile->GamePalettes[PaletteNbr][i*3]; tmpPalette[i].rgbGreen = (unsigned char )WadFile->GamePalettes[PaletteNbr][i*3+1]; tmpPalette[i].rgbBlue = (unsigned char )WadFile->GamePalettes[PaletteNbr][i*3+2]; } // and now put it back into the bitmap. SetDIBColorTable(hMemDC, 0, 256, tmpPalette); // put back the default bitmap SelectObject(hMemDC, DefaultBitmap); // delete the memory dc DeleteDC(hMemDC); // delete the default bitmap DeleteObject(DefaultBitmap); // release the engines window dc ReleaseDC(ghWnd,hDC); } void Renderer::copyscrn (char *Buffer) { // // This method copies a loaded buffer (read PCX file) into the double buffer. // memcpy(DoubleBuffer, Buffer, 266000ul); } int Renderer::Save_Pcx(char *filename) { return WadFile->Save_Pcx(filename, DoubleBuffer, 640, 400); } long Renderer::DistToPoint( long x, long y ) { long dx; long dy; long dist; dx = abs(x); dy = abs(y); if( dy == 0 ) dist = dx; else if (dx == 0) dist = dy; else { // dist = (long)(65536l * sqrt ( dx * dx + dy * dy )); short ang = invTan(dy,dx); dist = (dx * 65536l) / cos(ang); } return dist; } // // Project the bounding box, and determine if the area is at least partially // visible. // short Renderer::CheckBoundingBox ( short UpperX, short UpperY, short LowerX, short LowerY ) { #ifdef USEBOUNDINGBOX // There will be 4 points associated with the bounding box: short P1x = UpperX - ViewerX; short P1y = UpperY - ViewerY; short P2x = P1x; short P2y = LowerY - ViewerY; short P3x = LowerX - ViewerX; short P3y = P2y; short P4x = P3x; short P4y = P1y; long tempX; long tempY; #else return 1; #endif #ifdef USEBOUNDINGBOX // Step 2. Rotate the points. register long cosine = this->cos(-ViewerAngle); register long sine = this->sin(-ViewerAngle); // Transform all vertices to view coordinates, and place new vertices in cache. // Now rotate the vertex so that the player is looking down the positive X axis. tempX = (long)P1x * cosine - (long)P1y * sine; tempY = (long)P1x * sine + (long)P1y * cosine; P1x = (short)(tempX >> 16); P1y = (short)(tempY >> 16); tempX = (long)P2x * cosine - (long)P2y * sine; tempY = (long)P2x * sine + (long)P2y * cosine; P2x = (short)(tempX >> 16); P2y = (short)(tempY >> 16); tempX = (long)P3x * cosine - (long)P3y * sine; tempY = (long)P3x * sine + (long)P3y * cosine; P3x = (short)(tempX >> 16); P3y = (short)(tempY >> 16); tempX = (long)P4x * cosine - (long)P4y * sine; tempY = (long)P4x * sine + (long)P4y * cosine; P4x = (short)(tempX >> 16); P4y = (short)(tempY >> 16); // If the entire box is behind us, there is no need to continue... if( P1x < 1 && P2x < 1 && P3x < 1 && P4x < 1 ) return 0; if( P1x >= 1 ) P1y = 320l - ((long)P1y * 554l) / (long)P1x; else P1y = 0; if( P2x >= 1 ) P2y = 320l - ((long)P2y * 554l) / (long)P2x; else P2y = 0; if( P3x >= 1 ) P3y = 320l - ((long)P3y * 554l) / (long)P3x; else P3y = 0; if( P4x >= 1 ) P4y = 320l - ((long)P4y * 554l) / (long)P4x; else P4y = 0; // Step 4. Are there any points visible on the screen? // If so, then we need to render this area. if ((P1y > 0 && P1y < 640) || (P2y > 0 && P2y < 640) || (P3y > 0 && P3y < 640) || (P4y > 0 && P4y < 640)) return 1; // Else, return False (0). return 0; #endif } short Renderer::render( short x, short y ) { // // This method will use the loaded WAD file, mainly the nodes, sidedefs, and textures, and render // a view of the walls from position <x,v> looking at the specified angle. Height will be 64 plus // the sector height of the sector we're standing in. The vertices will have already been translated // and rotated by this point. // // Clear the double buffer. clearToColor(0); // Call TraverseBSPTree() with the root node of the BSP tree. return traverseBSPTree(WadFile->MaxNode); } short Renderer::getSideofCutPlane ( short X, short Y, short Node ) { // // Determines which side of a particular Node cutting plane a point <X,Y> is on. // returns: 0 - Left // 1 - Right long left; long right; if( 0 == WadFile->Nodes[Node].CutPlaneDy ) { if( 0 < WadFile->Nodes[Node].CutPlaneDx ) return ( Y < WadFile->Nodes[Node].CutPlaneStartY ); else return ( Y > WadFile->Nodes[Node].CutPlaneStartY ); } else if( 0 == WadFile->Nodes[Node].CutPlaneDx ) { if ( 0 < WadFile->Nodes[Node].CutPlaneDy ) return ( X > WadFile->Nodes[Node].CutPlaneStartX ); else return ( X < WadFile->Nodes[Node].CutPlaneStartX ); } else { left = (long)WadFile->Nodes[Node].CutPlaneDy * (long)(X - WadFile->Nodes[Node].CutPlaneStartX); right = (long)WadFile->Nodes[Node].CutPlaneDx * (long)(Y - WadFile->Nodes[Node].CutPlaneStartY); return ( left > right ); } } short Renderer::getSideofLinedef( short X, short Y, short LineDef ) { // // Determines which side of a particular LineDef a point <X,Y> is on. // returns: 0 - Left // 1 - Right long left; long right; short StartX, StartY; short Dx, Dy; StartX = WadFile->Vertices[WadFile->Linedefs[LineDef].StartVertex].x; StartY = WadFile->Vertices[WadFile->Linedefs[LineDef].StartVertex].y; Dx = WadFile->Vertices[WadFile->Linedefs[LineDef].EndVertex].x - StartX; Dy = WadFile->Vertices[WadFile->Linedefs[LineDef].EndVertex].y - StartY; if( 0 == Dy ) { if( 0 < Dx ) return ( Y < StartY ); else return ( Y > StartY ); } else if( 0 == Dx ) { if ( 0 < Dy ) return ( X > StartX ); else return ( X < StartX ); } else { left = ((long)Dy) * (long)(X - StartX); right = ((long)Dx) * (long)(Y - StartY); return ( left > right ); } } short Renderer::getSectorfromXY( short X, short Y ) { // // This method will traverse the BSP tree and determine which sector the <X,Y> point falls into. // short CurrentNode; short SegNbr; short LinedefNbr; // Start at the top node of the BSP tree. CurrentNode = WadFile->MaxNode; // Traverse the tree (in Forward order) until a SSector is reached. // a SSector is indicated with bit 16. while(0 == (unsigned short)(CurrentNode & 0x8000)) { if ( 0 == getSideofCutPlane(X, Y, CurrentNode) ) CurrentNode = WadFile->Nodes[CurrentNode].LeftChild; else CurrentNode = WadFile->Nodes[CurrentNode].RightChild; } // Return the sector number from the SSector // by way of the Seg, Linedef, and appropriate SideDef... SegNbr = WadFile->SSectors[(unsigned)(CurrentNode & 0x7fff)].StartSeg; LinedefNbr = WadFile->Segs[SegNbr].Linedef; if( 1 == (getSideofLinedef(X, Y, LinedefNbr))) { // If on the right side return WadFile->Sidedefs[WadFile->Linedefs[LinedefNbr].RightSideDef].SectorNumber; } // Else left side return WadFile->Sidedefs[WadFile->Linedefs[LinedefNbr].LeftSideDef].SectorNumber; } short Renderer::traverseBSPTree( short RootNode ) { short CurrentNode; short NextNode; // Check this pointer. Is it a node or a ssector. // If Node, then recursively call traversal again on it.. // If SSector, then call RenderSSector() if ( 0 != (unsigned short)(RootNode & 0x8000) ) return renderSSector( RootNode & 0x7fff ); // Start at the root node. Determine if we're on the left side, or the right side of it. if(0 == getSideofCutPlane(ViewerX, ViewerY, RootNode)) { // If on the left side, traverse the right sub tree first, then the left. CurrentNode = WadFile->Nodes[RootNode].RightChild; NextNode = WadFile->Nodes[RootNode].LeftChild; } else { // Else on the right, then traverse the left sub tree first, then the right. CurrentNode = WadFile->Nodes[RootNode].LeftChild; NextNode = WadFile->Nodes[RootNode].RightChild; } // // Check bounding box to see of we even need consider this node. // if ( CheckBoundingBox( WadFile->Nodes[RootNode].LeftBBUpperX, WadFile->Nodes[RootNode].LeftBBUpperY, WadFile->Nodes[RootNode].LeftBBLowerX, WadFile->Nodes[RootNode].LeftBBLowerY )) traverseBSPTree(CurrentNode); // // Check bounding box to see of we even need consider this node. // if ( CheckBoundingBox( WadFile->Nodes[RootNode].LeftBBUpperX, WadFile->Nodes[RootNode].LeftBBUpperY, WadFile->Nodes[RootNode].LeftBBLowerX, WadFile->Nodes[RootNode].LeftBBLowerY )) traverseBSPTree(NextNode); return 1; } short Renderer::renderSSector( short SSector ) { short SegNbr; short SegDir; short LinedefNbr; short ThisSide; short BackSide; short ThisCeilHeight; short BackCeilHeight; short ThisFloorHeight; short BackFloorHeight; long Pdist; long BOffset; // For each SEG in the SSector for (int i = 0; i < WadFile->SSectors[SSector].NumSegs; ++i) { // Get the segment number, and the segment direction. SegNbr = WadFile->SSectors[SSector].StartSeg + i; SegDir = WadFile->Segs[SegNbr].Direction; // Get the Linedef for the segment LinedefNbr = WadFile->Segs[SegNbr].Linedef; // Get the Sidedef(s) for this Seg and Direction. if (1 == SegDir) { ThisSide = WadFile->Linedefs[LinedefNbr].LeftSideDef; BackSide = WadFile->Linedefs[LinedefNbr].RightSideDef; } else { ThisSide = WadFile->Linedefs[LinedefNbr].RightSideDef; BackSide = WadFile->Linedefs[LinedefNbr].LeftSideDef; } ThisCeilHeight = WadFile->Sectors[WadFile->Sidedefs[ThisSide].SectorNumber].CeilingHeight; ThisFloorHeight = WadFile->Sectors[WadFile->Sidedefs[ThisSide].SectorNumber].FloorHeight; // Calculate the shortest distance to the line from the view point. short StartAngle = invTan( VerticesCache[WadFile->Segs[SegNbr].StartVertex].y, VerticesCache[WadFile->Segs[SegNbr].StartVertex].x ); short NormalAngle = WadFile->Segs[SegNbr].Angle + ANGLE_90 - ViewerAngle; long distToStart = DistToPoint(VerticesCache[WadFile->Segs[SegNbr].StartVertex].x, VerticesCache[WadFile->Segs[SegNbr].StartVertex].y); Pdist = (distToStart * sin(ANGLE_90 + NormalAngle - StartAngle)) >> 16; // Now calculate the offset from the above point to the start of the wall. BOffset = (distToStart * sin(NormalAngle - StartAngle)) >> 16; // Calculate side we're on for backface removal. if( 1 == getSideofLinedef(ViewerX,ViewerY,LinedefNbr) ^ SegDir) { // If 2 sided linedef, then if( BackSide > 0) { // Get the ceiling and floor height of this sector and the adjoining sector. BackCeilHeight = WadFile->Sectors[WadFile->Sidedefs[BackSide].SectorNumber].CeilingHeight; BackFloorHeight = WadFile->Sectors[WadFile->Sidedefs[BackSide].SectorNumber].FloorHeight; // If the ceiling heights are different, if ( ThisCeilHeight != BackCeilHeight ) { // Build upper polygon (Length of seg, from back height to front height) drawWallTopPegged( VerticesCache[WadFile->Segs[SegNbr].StartVertex].x, VerticesCache[WadFile->Segs[SegNbr].StartVertex].y, VerticesCache[WadFile->Segs[SegNbr].EndVertex].x, VerticesCache[WadFile->Segs[SegNbr].EndVertex].y, ThisCeilHeight, BackCeilHeight, WadFile->Segs[SegNbr].Angle, WadFile->Sidedefs[ThisSide].UpperTxtName, WadFile->Sidedefs[ThisSide].TxtureXOffset + WadFile->Segs[SegNbr].StartOffset, WadFile->Sidedefs[ThisSide].TxtureYOffset, Pdist, BOffset ); } // If the floor heights are different, if( ThisFloorHeight != BackFloorHeight) { // Build lower polygon (Length of seg, from back floor height to front floor height) drawWallBtmPegged( VerticesCache[WadFile->Segs[SegNbr].StartVertex].x, VerticesCache[WadFile->Segs[SegNbr].StartVertex].y, VerticesCache[WadFile->Segs[SegNbr].EndVertex].x, VerticesCache[WadFile->Segs[SegNbr].EndVertex].y, BackFloorHeight, ThisFloorHeight, WadFile->Segs[SegNbr].Angle, WadFile->Sidedefs[ThisSide].LowerTxtName, WadFile->Sidedefs[ThisSide].TxtureXOffset + WadFile->Segs[SegNbr].StartOffset, WadFile->Sidedefs[ThisSide].TxtureYOffset, Pdist, BOffset ); } } // Now draw the center texture. drawWallTopPegged( VerticesCache[WadFile->Segs[SegNbr].StartVertex].x, VerticesCache[WadFile->Segs[SegNbr].StartVertex].y, VerticesCache[WadFile->Segs[SegNbr].EndVertex].x, VerticesCache[WadFile->Segs[SegNbr].EndVertex].y, ThisCeilHeight, ThisFloorHeight, WadFile->Segs[SegNbr].Angle, WadFile->Sidedefs[ThisSide].NormalTxtName, WadFile->Sidedefs[ThisSide].TxtureXOffset + WadFile->Segs[SegNbr].StartOffset, WadFile->Sidedefs[ThisSide].TxtureYOffset, Pdist, BOffset ); } } // drawFlat(WadFile->SSectors[SSector].StartSeg, WadFile->SSectors[SSector].NumSegs, ThisCeilHeight); // drawFlat(WadFile->SSectors[SSector].StartSeg, WadFile->SSectors[SSector].NumSegs, ThisFloorHeight); // *** Implement Later *** // Z-sort the list of Things in this SSector // For each Thing, render it. return 1; } struct Edge { long StartR, StartC; long EndR, EndC; Edge* nextEdge; Edge( long Sr, long Sc, long Er, long Ec ) { StartR = Sr; StartC = Sc; EndR = Er; EndC = Ec;}; ~Edge( void) { if( NULL != nextEdge ) delete nextEdge; }; }; short Renderer::drawFlat(unsigned short StartSeg, unsigned short numSegs, short Height) { // A flat is a convex polygon bounded by the segments in a SSector. It has a height, and a texture. // The texture is a 64x64 bitmap, and it's origin lies on a 64x64 boundary on the x/y plane. We will // probably use a general polygon filling routine here, noting that X (depth) is constant across the rows. // // Note: This version does not do the texturing, and is still a work-in-progress... // short cntr; short currSeg; short StartX; short StartY; short EndX; short EndY; long ColStart; long ColEnd; long RowStart; long RowEnd; long DeltaL; long DeltaR; long StartCol = 0; long EndCol = 0; long EndRow = 0; Edge *tmpEdgeList = NULL; Edge *newEdge = NULL; #ifdef DEBUG // DEBUG: ******************************************************************** FILE* OutFile; OutFile = fopen("c:\\temp\\edgelist.out","a+"); fprintf(OutFile, " ----------------------NEW POLYGON----------------------- \n"); // *************************************************************************** #endif // Step 1. Create a sorted list of SEGs, sorted on startX (view space)... Insertion Sort. // * Remove any horizontal edges. // * Remove any edges with startX < 1 && endX < 1 // * Clip any partially visible edges against X=1 plane. for( cntr = 0; cntr < numSegs; ++cntr ) { currSeg = StartSeg + cntr; // Get the points of interest for this next edge. StartX = VerticesCache[WadFile->Segs[currSeg].StartVertex].x; StartY = VerticesCache[WadFile->Segs[currSeg].StartVertex].y; EndX = VerticesCache[WadFile->Segs[currSeg].EndVertex].x; EndY = VerticesCache[WadFile->Segs[currSeg].EndVertex].y; #ifdef DEBUG // DEBUG: ****************************************************************************** fprintf(OutFile, "Seg: <%d,%d,%d> -> <%d,%d,%d> \n", StartY, Height, StartX, EndY, Height, EndX); // ************************************************************************************** #endif if (( StartX < 1) && (EndX < 1 )) continue; // We need to clip the incoming segment at the X=1 plane... if( StartX < 1 && EndX >= 1 ) { if( StartY != EndY ) StartY = StartY + ((1-StartX) * ( EndY - StartY )) / (EndX - StartX); StartX = 1; } if( StartX >= 1 && EndX < 1) { if( StartY != EndY ) EndY = StartY + ((1-StartX) * ( EndY - StartY )) / (EndX - StartX); EndX = 1; } #ifdef DEBUG // DEBUG: ****************************************************************************** fprintf(OutFile, "Clipped: <%d,%d,%d> -> <%d,%d,%d> \n", StartY, (Height - ViewerHeight), StartX, EndY, (Height - ViewerHeight), EndX); // ************************************************************************************** #endif // Transform the points. ColStart = 320l - (((long)StartY * 554l) / (long)StartX); RowStart = 240l - (((long)(Height - ViewerHeight) * 554l) / (long)StartX); ColEnd = 320l - (((long)EndY * 554l) / (long)EndX); RowEnd = 240l - (((long)(Height - ViewerHeight) * 554l) / (long)EndX); #ifdef DEBUG // DEBUG: ****************************************************************************** fprintf(OutFile, "Xformed: <%ld,%ld> -> <%ld,%ld> \n", ColStart,RowStart,ColEnd,RowEnd); // ************************************************************************************** #endif // More trivial rejection (Clipping polygons that can not be seen). if ((ColStart == ColEnd) || (( ColStart < 0) && (ColEnd < 0)) || ((ColStart > 640) && (ColEnd > 640))) continue; // Now, insert the edge into the edge list. if (RowStart != RowEnd ) { if (RowStart > RowEnd ) newEdge = new Edge( RowEnd, ColEnd, RowStart, ColStart ); else newEdge = new Edge( RowStart, ColStart, RowEnd, ColEnd ); if (RowEnd > EndRow) EndRow = RowEnd; if (RowStart > EndRow) EndRow = RowStart; // Now insert the edge into the list. if( tmpEdgeList == NULL) { newEdge->nextEdge = NULL; tmpEdgeList = newEdge; } else { Edge* srch = tmpEdgeList; Edge* prev = NULL; while(srch != NULL) { if( srch->StartR < newEdge->StartR ) { // insert Node Here. if (NULL == prev) { newEdge->nextEdge = tmpEdgeList; tmpEdgeList = newEdge; } else { newEdge->nextEdge = srch; prev->nextEdge = newEdge; } break; } prev = srch; srch = srch->nextEdge; } if( srch == NULL) { newEdge->nextEdge = srch; prev->nextEdge = newEdge; } } } } #ifdef DEBUG // DEBUG: ************************************************************************************************ for (Edge* node = tmpEdgeList; node != NULL; node = node->nextEdge) fprintf(OutFile, " Edge: <%ld,%ld> -> <%ld,%ld> \n",node->StartC, node->StartR, node->EndC, node->EndR); fclose(OutFile); // ******************************************************************************************************* #endif // Step 2. Determine if the SSector is partially visible. If not, then return. // ( If the list contains any edges ) if (( tmpEdgeList != NULL ) && ( tmpEdgeList->nextEdge != NULL )) { Edge *left, *right, *nextEdge; // Step 3. Start with the 1st 2 edges. Calculate the deltas. if( tmpEdgeList->StartC < (tmpEdgeList->nextEdge)->StartC ) { left = tmpEdgeList; right = tmpEdgeList->nextEdge; nextEdge = right->nextEdge; } else { right = tmpEdgeList; left = tmpEdgeList->nextEdge; nextEdge = left->nextEdge; } StartCol = left->StartC * 65536l; EndCol = right->StartC * 65536l; DeltaL = (65536l * (left->EndC - left->StartC)) / (left->EndR - left->StartR); DeltaR = (65536l * (right->EndC - right->StartC)) / (right->EndR - right->StartR); // Step 4. Start rendering the polygon... EndRow = (EndRow > 400)?400: EndRow; short StartRow = ((left->StartR) > 0)? (short)(left->StartR):0; for(short row = StartRow; row < EndRow; ++row) { // Step 4a. If we go past an edge, get the next one in the sorted list, and // recompute the deltas. if (( row > left->EndR ) && (row <= right->EndR )) { if ( nextEdge == NULL ) return 1; left = nextEdge; nextEdge = left->nextEdge; StartCol = left->StartC * 65536l; DeltaL = (65536l * (left->EndC - left->StartC)) / (left->EndR - left->StartR); } else if ((row > right->EndR) && (row <= left->EndR)) { if ( nextEdge == NULL ) return 1; right = nextEdge; nextEdge = right->nextEdge; EndCol = right->StartC * 65536l; DeltaR = (65536l * (right->EndC - right->StartC)) / (right->EndR - right->StartR); } else if ((row > left->EndR) && (row > right->EndR)) { if ( nextEdge == NULL || nextEdge->nextEdge == NULL) return 1; if( nextEdge->StartC < (nextEdge->nextEdge)->StartC ) { left = nextEdge; right = left->nextEdge; nextEdge = right->nextEdge; } else { right = nextEdge; left = right->nextEdge; nextEdge = left->nextEdge; } StartCol = left->StartC * 65536l; EndCol = right->StartC * 65536l; DeltaL = (65536l * (left->EndC - left->StartC)) / (left->EndR - left->StartR); DeltaR = (65536l * (right->EndC - right->StartC)) / (right->EndR - right->StartR); } // Step 4b. Render the row sliver. for (short col = (short)(StartCol>>16); col <= (short)(EndCol>>16); ++col) { if(( col > 0 ) && ( col < 640)) { *(DoubleBuffer + col + row * 640l) = 30; } } StartCol += DeltaL; EndCol += DeltaR; } } return 1; } short Renderer::drawWallTopPegged(short StartX, short StartY, short EndX, short EndY, short top, short bottom, short Angle, char* TxtName, short XOffset, short YOffset, long Pdist, long BOffset ) { // // This will actually draw the textured wall polygon on the screen buffer. (320x200 buffer size). // long ColStart; // Starting column for the polygon. long ColEnd; // Ending column for the polygon. long StartTop; // Starting row for the 1st column. long StartBtm; // Ending row for the 1st column. long EndTop; // Starting row for the last column. long EndBtm; // Ending row for the last column. long DeltaTop; // Amount to change Ytop for each column. long DeltaBtm; // Amount to change the Ysize for each column. long CurrentRow; long CurrentCol; long CurrentTop; long CurrentBtm; short TxtCol = 0; short txtwidth = 0; short txtheight = 0; char* txtPtr = NULL; char* txtrowPtr = NULL; long DeltaTxt = 0; long currTxtl = 0; unsigned char * ScrnPtr; long tmptop; long tmpbottom; long CurrentScale; long EndScale; long DeltaScale; long TempTop; // Try to do trivial rejection (If this is a clear polygon, or it is completely behind the view window). if ((TxtName[0] == '-') || ( StartX < 1 && EndX < 1 )) return 0; // We need to clip the incoming segment at the X=1 plane... if( StartX < 1 && EndX >= 1 ) { if( StartY != EndY ) StartY = StartY + ((1-StartX) * ( EndY - StartY )) / (EndX - StartX); StartX = 1; } if( StartX >= 1 && EndX < 1) { if( StartY != EndY ) EndY = StartY + ((1-StartX) * ( EndY - StartY )) / (EndX - StartX); EndX = 1; } // Transform the starting / ending points to screen coordinates. Looking down +X axis. // tmptop = ((long)top - ViewerHeight) * 554l; tmpbottom =((long)bottom - ViewerHeight) * 554l; ColStart = 320l - ((long)StartY * 554l) / (long)StartX; ColEnd = 320l - ((long)EndY * 554l) / (long)EndX; EndTop = 240l - (tmptop / (long)EndX); StartTop = 240l - (tmptop / (long)StartX); EndBtm = 240l - (tmpbottom / (long)EndX); StartBtm = 240l - (tmpbottom / (long)StartX); // More trivial rejection (Clipping polygons that can not be seen). if ((ColStart == ColEnd) || (( ColStart < 0) && (ColEnd < 0)) || ((ColStart > 640) && (ColEnd > 640))) return 0; // Calculate the deltas to interpolate the Ystart and Yend for each screen column. // // Future change: Use a stepping table (1<<14)/DeltaWidth, where DeltaWidth is in [1,1024]. // txtPtr = WadFile->getTexturePtr(TxtName, txtwidth, txtheight); CurrentTop = StartTop <<14; CurrentBtm = StartBtm <<14; DeltaTop = ((EndTop - StartTop) * 1l<<14) / (ColEnd - ColStart); DeltaBtm = ((EndBtm - StartBtm) * 1l<<14) / (ColEnd - ColStart); CurrentScale = (1161822208l / (long)StartX); // ( 128 * 554 * 1<<14 ) / Distance EndScale = (1161822208l / (long)EndX ); DeltaScale = ( EndScale - CurrentScale ) / (ColEnd - ColStart); // For each column of the polygon, for ( CurrentCol = ColStart; CurrentCol <= ColEnd; ++CurrentCol ) { // Scale the texture column to size (Yend-Ystart), starting at row Ystart. if ((CurrentCol > 0) && (CurrentCol < 640)) { // The texture column is given by TxtCol = (Pdist * Tan(Angle of column ray to Pdist vector)) - Bo TxtCol = (short)(((Pdist * tan((colangle[CurrentCol] + (Angle - ViewerAngle + ANGLE_90)))) - BOffset)>> 16) - XOffset; TxtCol = (unsigned)TxtCol % txtwidth; txtrowPtr = txtPtr + (long)TxtCol * txtheight; if (( (CurrentBtm - CurrentTop) >> 14) > 0) { // // This could be in a table of (1<<14)/dHeight, dHeight goes from 1 to ... // Then DeltaTxt = txtheight * ScaleTable[( CurrentBtm - CurrentTop) >> 14]. // DeltaTxt = 2097152l /(CurrentScale >> 14); currTxtl = ((long)-YOffset) << 14; if(( currTxtl >> 14 ) < 0 ) currTxtl += (txtheight - YOffset) << 14; if ( ( currTxtl >> 14 ) > txtheight ) currTxtl -= ((long)txtheight) << 14; TempTop = CurrentTop >> 14; if ( TempTop < 0l ) { currTxtl += DeltaTxt * (0 - TempTop); if(( currTxtl >> 14 ) < 0 ) currTxtl += (txtheight - YOffset) << 14; if ( ( currTxtl >> 14 ) > txtheight ) currTxtl -= ((long)txtheight) << 14; if ( ( currTxtl >> 14 ) > txtheight ) currTxtl -= ((long)txtheight) << 14; if ( ( currTxtl >> 14 ) > txtheight ) currTxtl -= ((long)txtheight) << 14; TempTop = 0l; } ScrnPtr = (DoubleBuffer + CurrentCol + TempTop * 640l); for ( CurrentRow = TempTop; CurrentRow < (CurrentBtm >> 14) && CurrentRow < 400; ++CurrentRow ) { *(ScrnPtr) = *(txtrowPtr + (currTxtl >> 14) ); ScrnPtr += 640l; currTxtl += DeltaTxt; if(( currTxtl >> 14 ) < 0 ) currTxtl += (txtheight - YOffset) << 14; if ( ( currTxtl >> 14 ) > txtheight ) currTxtl -= ((long)txtheight) << 14; } } } // Increment the deltas for the top and bottom edges. CurrentTop += DeltaTop; CurrentBtm += DeltaBtm; CurrentScale += DeltaScale; } return 1; } short Renderer::drawWallBtmPegged(short StartX, short StartY, short EndX, short EndY, short top, short bottom, short Angle, char* TxtName, short XOffset, short YOffset, long Pdist, long BOffset ) { // // This will actually draw the textured wall polygon on the screen buffer. (320x200 buffer size). // long ColStart; // Starting column for the polygon. long ColEnd; // Ending column for the polygon. long StartTop; // Starting row for the 1st column. long StartBtm; // Ending row for the 1st column. long EndTop; // Starting row for the last column. long EndBtm; // Ending row for the last column. long DeltaTop; // Amount to change Ytop for each column. long DeltaBtm; // Amount to change the Ysize for each column. long CurrentRow; long CurrentCol; long CurrentTop; long CurrentBtm; short TxtCol = 0; short txtwidth = 0; short txtheight = 0; char* txtPtr = NULL; char* txtRowPtr = NULL; long DeltaTxt = 0; long currTxtl = 0; unsigned char * ScrnPtr; long tmptop; long tmpbottom; long CurrentScale; long EndScale; long DeltaScale; long TempBottom; // Try to do trivial rejection (If this is a clear polygon, or it is completely behind the view window). if ((TxtName[0] == '-') || ( StartX < 1 && EndX < 1 )) return 0; // We need to clip the incoming segment at the X=1 plane... if( StartX < 1 && EndX >= 1 ) { if( StartY != EndY ) StartY = StartY + ((1-StartX) * ( EndY - StartY )) / (EndX - StartX); StartX = 1; } if( StartX >= 1 && EndX < 1) { if( StartY != EndY ) EndY = StartY + ((1-StartX) * ( EndY - StartY )) / (EndX - StartX); EndX = 1; } // Transform the starting / ending points to screen coordinates. Looking down +X axis. // // Optimization: Possibly precalculate 65536/StartX and 65536/EndX... // tmptop = ((long)top - ViewerHeight) * 554l; tmpbottom =((long)bottom - ViewerHeight) * 554l; ColStart = 320l - ((long)StartY * 554l) / (long)StartX; ColEnd = 320l - ((long)EndY * 554l) / (long)EndX; EndTop = 240l - (tmptop / (long)EndX); StartTop = 240l - (tmptop / (long)StartX); EndBtm = 240l - (tmpbottom / (long)EndX); StartBtm = 240l - (tmpbottom / (long)StartX); // More trivial rejection (Clipping polygons that can not be seen). if ((ColStart == ColEnd) || (( ColStart < 0) && (ColEnd < 0)) || ((ColStart > 640) && (ColEnd > 640))) return 0; // Calculate the deltas to interpolate the Ystart and Yend for each screen column. // // Future change: Use a stepping table (1<<14)/DeltaWidth, where DeltaWidth is in [1,1024]. // CurrentTop = StartTop <<14; CurrentBtm = StartBtm <<14; DeltaTop = ((EndTop - StartTop) * 1l<<14) / (ColEnd - ColStart); DeltaBtm = ((EndBtm - StartBtm) * 1l<<14) / (ColEnd - ColStart); // We can do this because 1/z is linear in screen space. CurrentScale = (1161822208l / (long)StartX); // 128 (texture height) * 554 (scaling factor) * (1 << 14) / Distance. EndScale = (1161822208l / (long)EndX ); DeltaScale = ( EndScale - CurrentScale ) / (ColEnd - ColStart); txtPtr = WadFile->getTexturePtr(TxtName, txtwidth, txtheight); // For each column of the polygon, for ( CurrentCol = ColStart; CurrentCol <= ColEnd; ++CurrentCol ) { // Scale the texture column to size (Yend-Ystart), starting at row Ystart. if ((CurrentCol > 0) && (CurrentCol < 640)) { // The texture column is given by TxtCol = (Pdist * Tan(Angle of column ray to Pdist vector)) - Bo TxtCol = (unsigned short)((((Pdist * tan((colangle[CurrentCol] + (Angle - ViewerAngle + ANGLE_90)))) - BOffset)>> 16) - XOffset) % txtwidth; txtRowPtr = txtPtr + (long)TxtCol * txtheight; if (( (CurrentBtm - CurrentTop) >> 14) > 0) { // // This could be in a table of (1<<14)/dHeight, dHeight goes from 1 to 1024... // Then DeltaTxt = txtheight * ScaleTable[( CurrentBtm - CurrentTop) >> 14]. // // Currently DeltaTxt = ( 128 * 1<<14 ) / Height of the texture at the specific distance. // since 128 is guarenteed to be the texture height, and we're using 18:14 fixed point. DeltaTxt = 2097152l /(CurrentScale >> 14); currTxtl = (txtheight + YOffset) << 14; if(( currTxtl >> 14 ) < 0 ) currTxtl += (txtheight - YOffset) << 14; if ( ( currTxtl >> 14 ) > txtheight ) currTxtl -= ((long)txtheight) << 14; TempBottom = CurrentBtm >> 14; if ( TempBottom > 399 ) { currTxtl -= DeltaTxt * (TempBottom - 399l); if(( currTxtl >> 14 ) < 0 ) currTxtl += (long)txtheight << 14; if ( ( currTxtl >> 14 ) > txtheight ) currTxtl -= (long)txtheight << 14; if ( ( currTxtl >> 14 ) > txtheight ) currTxtl -= (long)txtheight << 14; if ( ( currTxtl >> 14 ) > txtheight ) currTxtl -= (long)txtheight << 14; if ( ( currTxtl >> 14 ) > txtheight ) currTxtl -= (long)txtheight << 14; TempBottom = 399l; } ScrnPtr = (DoubleBuffer + CurrentCol + TempBottom * 640l); for ( CurrentRow = TempBottom; CurrentRow >= (CurrentTop >> 14) && CurrentRow >= 0; --CurrentRow ) { *(ScrnPtr) = *( txtRowPtr + (currTxtl >> 14) ); ScrnPtr -= 640l; currTxtl -= DeltaTxt; if(( currTxtl >> 14 ) < 0 ) currTxtl += (long)txtheight << 14; if ( ( currTxtl >> 14 ) > txtheight ) currTxtl -= (long)txtheight << 14; } } } // Increment the deltas for the top and bottom edges. CurrentTop += DeltaTop; CurrentBtm += DeltaBtm; CurrentScale += DeltaScale; } return 1; } short Renderer::changeViewPosn( short X, short Y, short Angle ) { // This routine has the effect of translating and rotating all of the vertices // and placing them in a temporary holding area, which will be used in rendering the // final view. long tempX; long tempY; register long cosine = this->cos(-Angle); register long sine = this->sin(-Angle); // Transform all vertices to view coordinates, and place new vertices in cache. for (int i=0; i < WadFile->NbrVertices; ++i) { // Copy the world vertex to the cache. VerticesCache[i].x = WadFile->Vertices[i].x - X; VerticesCache[i].y = WadFile->Vertices[i].y - Y; // Now rotate the vertex so that the player is looking down the positive X axis. tempX = (long)VerticesCache[i].x * cosine - (long)VerticesCache[i].y * sine; tempY = (long)VerticesCache[i].x * sine + (long)VerticesCache[i].y * cosine; VerticesCache[i].x = (short)(tempX >> 16); VerticesCache[i].y = (short)(tempY >> 16); } // We need to get the floor height at this place, and set it. ViewerX = X; ViewerY = Y; ViewerHeight = WadFile->Sectors[getSectorfromXY(X, Y)].FloorHeight + 56; ViewerAngle = Angle; return ViewerHeight; }