// Emacs style mode select -*- C++ -*- //----------------------------------------------------------------------------- // // Copyright (C) 1998-2001 by DooM Legacy Team. // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. //----------------------------------------------------------------------------- /// \file /// \brief special trick routines to make some SW tricks look OK with /// HW rendering. This includes: /// - deepwatereffect (e.g. tnt/map02) /// - invisible staircase (e.g. eternal/map02) /// - floating ceilings (e.g. eternal/map03) /// /// It is not guaranteed that it looks identical to the SW mode, /// but it looks in most of the cases far better than having /// holes in the architecture, HOM, etc. /// /// It fixes as well missing textures, which are replaced by either /// a default texture or the midtexture. /// /// words of notice: /// pseudosectors, as mentioned in this file, are sectors where both /// sidedefs point to the same sector. This expression is also used /// for sectors which are enclosed by another sector but have no /// correct sidedefs at all /// /// if a vertex is inside a poly is determined by the angles between /// this vertex and all angles on the linedefs (imagine walking along /// a circle always facing a certain point inside/outside the circle; /// if inside, angle have taken all values [0..pi), otherwise the /// range was < pi/2 #include #include "../doomdef.h" #include "../doomstat.h" #ifdef HWRENDER #include "hw_glob.h" #include "hw_dll.h" #include "../r_local.h" #include "../i_system.h" // // add a line to a sectors list of lines // static void addLineToChain(sector_t *sector, line_t *line) { linechain_t *thisElem = NULL, *nextElem; if (!sector) return; nextElem = sector->sectorLines; while (nextElem) // walk through chain { thisElem = nextElem; nextElem = thisElem->next; } // add a new element into the chain if (thisElem) { thisElem->next = malloc(sizeof (linechain_t)); if (thisElem->next) { thisElem->next->line = line; thisElem->next->next = NULL; } else { I_Error("Out of memory in addLineToChain(.)\n"); } } else // first element in chain { sector->sectorLines = malloc(sizeof (linechain_t)); if (sector->sectorLines) { sector->sectorLines->line = line; sector->sectorLines->next = NULL; } else { I_Error("Out of memory in addLineToChain(.)\n"); } } } // // We dont want a memory hole, do we?;-) // static void releaseLineChains(void) { linechain_t *thisElem, *nextElem; sector_t *sector; size_t i; for (i = 0; i < numsectors; i++) { sector = §ors[i]; nextElem = sector->sectorLines; while (nextElem) { thisElem = nextElem; nextElem = thisElem->next; free(thisElem); } sector->sectorLines = NULL; } } // // check if a pseudo sector is valid by checking all its linedefs // static boolean isPSectorValid(sector_t *sector) { linechain_t *thisElem, *nextElem; if (!sector->pseudoSector) // check only pseudosectors, others dont care { #ifdef PARANOIA CONS_Debug(DBG_RENDER, "Alert! non-pseudosector fed to isPSectorClosed()\n"); #endif return false; } nextElem = sector->sectorLines; while (nextElem) { thisElem = nextElem; nextElem = thisElem->next; if (thisElem->line->frontsector != thisElem->line->backsector) return false; } return true; } // // angles are always phiMax-phiMin [0...2\pi) // FUNCMATH static double phiDiff(double phiMin, double phiMax) { double result; result = phiMax-phiMin; if (result < 0.0l) result += 2.0l*M_PIl; return result; } // // sort phi's so that enclosed angle < \pi // static void sortPhi(double phi1, double phi2, double *phiMin, double *phiMax) { if (phiDiff(phi1, phi2) < M_PIl) { *phiMin = phi1; *phiMax = phi2; } else { *phiMin = phi2; *phiMax = phi1; } } // // return if angle(phi1, phi2) is bigger than \pi // if so, the vertex lies inside the poly // FUNCMATH static boolean biggerThanPi(double phi1, double phi2) { if (phiDiff(phi1, phi2) > M_PIl) return true; return false; } #define DELTAPHI (M_PIl/100.0l) // some small phi << \pi // // calculate bounds for minimum angle // static void phiBounds(double phi1, double phi2, double *phiMin, double *phiMax) { double phi1Tmp, phi2Tmp; double psi1, psi2, psi3, psi4, psi5, psi6, psi7; // for optimization sortPhi(phi1, phi2, &phi1Tmp, &phi2Tmp); phi1 = phi1Tmp; phi2 = phi2Tmp; // check start condition if (*phiMin > M_PIl || *phiMax > M_PIl) { *phiMin = phi1; *phiMax = phi2; return; } // 6 cases: // new angles inbetween phiMin, phiMax -> forget it // new angles enclose phiMin -> set phiMin // new angles enclose phiMax -> set phiMax // new angles completely outside phiMin, phiMax -> leave largest area free // new angles close the range completely! // new angles enlarges range on both sides psi1 = phiDiff(*phiMin, phi1); psi2 = phiDiff(*phiMin, phi2); psi3 = phiDiff(*phiMax, phi1); psi4 = phiDiff(*phiMax, phi2); psi5 = phiDiff(*phiMin, *phiMax); psi6 = (double)(2.0l*M_PIl - psi5); // phiDiff(*phiMax, *phiMin); psi7 = (double)(2.0l*M_PIl - psi2); // phiDiff(phi2, *phiMin); // case 1 & 5! if ((psi1 <= psi5) && (psi2 <= psi5)) { if (psi1 <= psi2) // case 1 { return; } else // case 5 { // create some artificial interval here not to get into numerical trouble // in fact we know now the sector is completely enclosed -> base for computational optimization *phiMax = 0.0l; *phiMin = DELTAPHI; return; } } // case 2 if ((psi1 >= psi5) && (psi2 <= psi5)) { *phiMin = phi1; return; } // case 3 if ((psi3 >= psi6) && (psi4 <= psi6)) { *phiMax = phi2; return; } // case 4 & 6 #ifdef PARANOIA if ((psi3 <= psi6) && (psi4 <= psi6)) // FIXME: isn't this case implicitly true anyway?? #endif { if (psi3 <= psi4) //case 4 { if (psi3 >= psi7) { *phiMin = phi1; return; } else { *phiMax = phi2; return; } } else // case 6 { *phiMin = phi1; *phiMax = phi2; return; } } #ifdef PARANOIA CONS_Debug(DBG_RENDER, "phiMin = %f, phiMax = %f, phi1 = %f, phi2 = %f\n", *phiMin, *phiMax, phi1, phi2); I_Error("phiBounds() out of range!\n"); #endif } // // Check if a vertex lies inside a sector // This works for "well-behaved" convex polygons // If we need it mathematically correct, we need to sort the // linedefs first so we have them in a row, then walk along the linedefs, // but this is a bit overdone // static inline boolean isVertexInside(vertex_t *vertex, sector_t *sector) { double xa, ya, xe, ye; linechain_t *chain; double phiMin, phiMax; double phi1, phi2; chain = sector->sectorLines; phiMin = phiMax = 10.0l*M_PIl; // some value > \pi while (chain) { // start and end vertex xa = (double)chain->line->v1->x - (double)vertex->x; ya = (double)chain->line->v1->y - (double)vertex->y; xe = (double)chain->line->v2->x - (double)vertex->x; ye = (double)chain->line->v2->y - (double)vertex->y; // angle phi of connection between the vertices and the x-axis phi1 = atan2(ya, xa); phi2 = atan2(ye, xe); // if we have just started, we can have to create start bounds for phi phiBounds(phi1, phi2, &phiMin, &phiMax); chain = chain->next; } return biggerThanPi(phiMin, phiMax); } #define MAXSTACK 256 // Not more than 256 polys in each other? // // generate a list of sectors which enclose the given sector // static void generateStacklist(sector_t *thisSector) { size_t i, stackCnt = 0; sector_t *locStacklist[MAXSTACK]; sector_t *checkSector; for (i = 0; i < numsectors; i++) { checkSector = §ors[i]; if (checkSector == thisSector) // dont check self continue; // buggy sector? if (!thisSector->sectorLines) continue; // check if an arbitrary vertex of thisSector lies inside the checkSector if (isVertexInside(thisSector->sectorLines->line->v1, checkSector)) { // if so, the thisSector lies inside the checkSector locStacklist[stackCnt] = checkSector; stackCnt++; if (MAXSTACK-1 == stackCnt) // beware of the SIGSEGV! and consider terminating NULL! break; } } thisSector->stackList = malloc(sizeof (sector_t *) * (stackCnt+1)); if (NULL == thisSector->stackList) { I_Error("Out of memory error in generateStacklist()"); } locStacklist[stackCnt] = NULL; // terminating NULL memcpy(thisSector->stackList, locStacklist, sizeof (sector_t *) * (stackCnt+1)); } // // Bubble sort the stacklist with rising lineoutlengths // static void sortStacklist(sector_t *sector) { sector_t **list; sector_t *sec1, *sec2; boolean finished; size_t i; list = sector->stackList; finished = false; if (!*list) return; // nothing to sort while (!finished) { i = 0; finished = true; while (NULL != *(list+i+1)) { sec1 = *(list+i); sec2 = *(list+i+1); if (sec1->lineoutLength > sec2->lineoutLength) { *(list+i) = sec2; *(list+i+1) = sec1; finished = false; } i++; } } } // // length of a line in euclidian sense // static double lineLength(line_t *line) { double dx, dy, length; dx = (double) line->v1->x - (double) line->v2->x; dy = (double) line->v1->y - (double) line->v2->y; length = hypot(dx, dy); return length; } // // length of the sector lineout // static double calcLineoutLength(sector_t *sector) { linechain_t *chain; double length = 0.0L; chain = sector->sectorLines; while (NULL != chain) // sum up lengths of all lines { length += lineLength(chain->line); chain = chain->next; } return length; } // // Calculate length of the sectors lineout // static void calcLineouts(sector_t *sector) { size_t secCount = 0; sector_t *encSector = *(sector->stackList); while (NULL != encSector) { if (encSector->lineoutLength < 0.0L) // if length has not yet been calculated { encSector->lineoutLength = calcLineoutLength(encSector); } secCount++; encSector = *((sector->stackList) + secCount); } } // // Free Stacklists of all sectors // static void freeStacklists(void) { size_t i; for (i = 0; i < numsectors; i++) { if (sectors[i].stackList) { free(sectors[i].stackList); sectors[i].stackList = NULL; } } } // // if more than half of the toptextures are missing // static boolean areToptexturesMissing(sector_t *thisSector) { linechain_t *thisElem, *nextElem = thisSector->sectorLines; sector_t *frontSector, *backSector; size_t nomiss = 0; side_t *sidel, *sider; while (nextElem) // walk through chain { thisElem = nextElem; nextElem = thisElem->next; frontSector = thisElem->line->frontsector; backSector = thisElem->line->backsector; if (frontSector == backSector) // skip damn renderer tricks here continue; if (!frontSector || !backSector) continue; sider = &sides[thisElem->line->sidenum[0]]; sidel = &sides[thisElem->line->sidenum[1]]; if (backSector->ceilingheight < frontSector->ceilingheight) { if (sider->toptexture != 0) { nomiss++; break; // we can stop here if decision criterium is ==0 } } else if (backSector->ceilingheight > frontSector->ceilingheight) { if (sidel->toptexture != 0) { nomiss++; break; // we can stop here if decision criterium is ==0 } } } return nomiss == 0; } // // are more textures missing than present? // static boolean areBottomtexturesMissing(sector_t *thisSector) { linechain_t *thisElem, *nextElem = thisSector->sectorLines; sector_t *frontSector, *backSector; size_t nomiss = 0; side_t *sidel, *sider; while (nextElem) // walk through chain { thisElem = nextElem; nextElem = thisElem->next; frontSector = thisElem->line->frontsector; backSector = thisElem->line->backsector; if (frontSector == backSector) // skip damn renderer tricks here continue; if (frontSector == NULL || backSector == NULL) continue; sider = &sides[thisElem->line->sidenum[0]]; sidel = &sides[thisElem->line->sidenum[1]]; if (backSector->floorheight > frontSector->floorheight) { if (sider->bottomtexture != 0) { nomiss++; break; // we can stop here if decision criterium is ==0 } } else if (backSector->floorheight < frontSector->floorheight) { if (sidel->bottomtexture != 0) { nomiss++; break; // we can stop here if decision criterium is ==0 } } } // return missing >= nomiss; return nomiss == 0; } // // check if no adjacent sector has same ceiling height // static boolean isCeilingFloating(sector_t *thisSector) { sector_t *adjSector, *refSector = NULL, *frontSector, *backSector; boolean floating = true; linechain_t *thisElem, *nextElem; if (!thisSector) return false; nextElem = thisSector->sectorLines; while (NULL != nextElem) // walk through chain { thisElem = nextElem; nextElem = thisElem->next; frontSector = thisElem->line->frontsector; backSector = thisElem->line->backsector; if (frontSector == thisSector) adjSector = backSector; else adjSector = frontSector; if (!adjSector) // assume floating sectors have surrounding sectors { floating = false; break; } if (!refSector) { refSector = adjSector; continue; } // if adjacent sector has same height or more than one adjacent sector exists -> stop if (thisSector->ceilingheight == adjSector->ceilingheight || refSector != adjSector) { floating = false; break; } } // now check for walltextures if (floating) { if (!areToptexturesMissing(thisSector)) { floating = false; } } return floating; } // // check if no adjacent sector has same ceiling height // FIXME: throw that together with isCeilingFloating?? // static boolean isFloorFloating(sector_t *thisSector) { sector_t *adjSector, *refSector = NULL, *frontSector, *backSector; boolean floating = true; linechain_t *thisElem, *nextElem; if (!thisSector) return false; nextElem = thisSector->sectorLines; while (nextElem) // walk through chain { thisElem = nextElem; nextElem = thisElem->next; frontSector = thisElem->line->frontsector; backSector = thisElem->line->backsector; if (frontSector == thisSector) adjSector = backSector; else adjSector = frontSector; if (NULL == adjSector) // assume floating sectors have surrounding sectors { floating = false; break; } if (NULL == refSector) { refSector = adjSector; continue; } // if adjacent sector has same height or more than one adjacent sector exists -> stop if (thisSector->floorheight == adjSector->floorheight || refSector != adjSector) { floating = false; break; } } // now check for walltextures if (floating) { if (!areBottomtexturesMissing(thisSector)) { floating = false; } } return floating; } // // estimate ceilingheight according to height of adjacent sector // static fixed_t estimateCeilHeight(sector_t *thisSector) { sector_t *adjSector; if (!thisSector || !thisSector->sectorLines || !thisSector->sectorLines->line) return 0; adjSector = thisSector->sectorLines->line->frontsector; if (adjSector == thisSector) adjSector = thisSector->sectorLines->line->backsector; if (!adjSector) return 0; return adjSector->ceilingheight; } // // estimate ceilingheight according to height of adjacent sector // static fixed_t estimateFloorHeight(sector_t *thisSector) { sector_t *adjSector; if (!thisSector || !thisSector->sectorLines || !thisSector->sectorLines->line) return 0; adjSector = thisSector->sectorLines->line->frontsector; if (adjSector == thisSector) adjSector = thisSector->sectorLines->line->backsector; if (NULL == adjSector) return 0; return adjSector->floorheight; } #define CORRECT_FLOAT_EXPERIMENTAL // -------------------------------------------------------------------------- // Some levels have missing sidedefs, which produces HOM, so lets try to compensate for that // and some levels have deep water trick, invisible staircases etc. // -------------------------------------------------------------------------- // FIXME: put some nice default texture in legacy.dat and use it void HWR_CorrectSWTricks(void) { size_t i; size_t k; line_t *ld; side_t *sidel = NULL, *sider; sector_t *secl, *secr; sector_t **sectorList; sector_t *outSector; if ((0 == cv_grcorrecttricks.value)) return; // determine lines for sectors for (i = 0; i < numlines; i++) { ld = &lines[i]; secr = ld->frontsector; secl = ld->backsector; if (secr == secl) { secr->pseudoSector = true; // special renderer trick? addLineToChain(secr, ld); } else { addLineToChain(secr, ld); addLineToChain(secl, ld); } } // preprocessing for (i = 0; i < numsectors; i++) { sector_t *checkSector; checkSector = §ors[i]; // identify real pseudosectors first if (checkSector->pseudoSector) { if (!isPSectorValid(checkSector)) // drop invalid pseudo sectors { checkSector->pseudoSector = false; } } // determine enclosing sectors for pseudosectors ... used later if (checkSector->pseudoSector) { generateStacklist(checkSector); calcLineouts(checkSector); sortStacklist(checkSector); } } // set virtual floor heights for pseudo sectors // required for deep water effect e.g. for (i = 0; i < numsectors; i++) { if (sectors[i].pseudoSector) { sectorList = sectors[i].stackList; k = 0; while (*(sectorList+k)) { outSector = *(sectorList+k); if (!outSector->pseudoSector) { sectors[i].virtualFloorheight = outSector->floorheight; sectors[i].virtualCeilingheight = outSector->ceilingheight; break; } k++; } if (*(sectorList+k) == NULL) // sorry, did not work :( { sectors[i].virtualFloorheight = sectors[i].floorheight; sectors[i].virtualCeilingheight = sectors[i].ceilingheight; } } } #ifdef CORRECT_FLOAT_EXPERIMENTAL // correct ceiling/floor heights of totally floating sectors for (i = 0; i < numsectors; i++) { sector_t *floatSector; floatSector = §ors[i]; // correct height of floating sectors if (isCeilingFloating(floatSector)) { fixed_t corrheight; corrheight = estimateCeilHeight(floatSector); floatSector->virtualCeilingheight = corrheight; floatSector->virtualCeiling = true; } if (isFloorFloating(floatSector)) { fixed_t corrheight; corrheight = estimateFloorHeight(floatSector); floatSector->virtualFloorheight = corrheight; floatSector->virtualFloor = true; } } #endif // now for the missing textures for (i = 0; i < numlines; i++) { ld = &lines[i]; sider = &sides[ld->sidenum[0]]; if (ld->sidenum[1] != 0xffff) sidel = &sides[ld->sidenum[1]]; secr = ld->frontsector; secl = ld->backsector; if (secr == secl) // special renderer trick continue; // we cant correct missing textures here if (secl) // only if there is a backsector { if (secr->pseudoSector || secl->pseudoSector) continue; if (!secr->virtualFloor && !secl->virtualFloor) { if (secl->floorheight > secr->floorheight) { // now check if r-sidedef is correct if (sider->bottomtexture == 0) { if (sider->midtexture == 0) sider->bottomtexture = 0; // Tails // More redwall sky shenanigans else sider->bottomtexture = sider->midtexture; } } else if (secl->floorheight < secr->floorheight) { // now check if l-sidedef is correct if (sidel->bottomtexture == 0) { if (sidel->midtexture == 0) sidel->bottomtexture = 0; // Tails // More redwall sky shenanigans else sidel->bottomtexture = sidel->midtexture; } } } if (!secr->virtualCeiling && !secl->virtualCeiling) { if (secl->ceilingheight < secr->ceilingheight) { // now check if r-sidedef is correct if (sider->toptexture == 0) { if (sider->midtexture == 0) sider->toptexture = 0; // Tails // When this was REDWALL it was causing issues in the sky sometimes else sider->toptexture = sider->midtexture; } } else if (secl->ceilingheight > secr->ceilingheight) { // now check if l-sidedef is correct if (sidel->toptexture == 0) { if (sidel->midtexture == 0) sidel->toptexture = 0; // Tails // When this was REDWALL it was causing issues in the sky sometimes else sidel->toptexture = sidel->midtexture; } } } } // if (NULL != secl) } // for (i = 0; i < numlines; i++) // release all linechains releaseLineChains(); freeStacklists(); } #endif // HWRENDER