SRB2/src/hardware/hw_trick.c

918 lines
20 KiB
C

// 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 <math.h>
#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 = &sectors[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 = &sectors[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 (*(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 (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 (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 || !backSector)
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;
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 (!adjSector) // assume floating sectors have surrounding sectors
return false;
#ifdef ESLOPE
if (adjSector->c_slope) // Don't bother with slopes
return false;
#endif
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)
return false;
}
// now check for walltextures
if (!areToptexturesMissing(thisSector))
return false;
return true;
}
//
// 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;
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 (!adjSector) // assume floating sectors have surrounding sectors
return false;
#ifdef ESLOPE
if (adjSector->f_slope) // Don't bother with slopes
return false;
#endif
if (!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)
return false;
}
// now check for walltextures
if (!areBottomtexturesMissing(thisSector))
return false;
return true;
}
//
// 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 (!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 = &sectors[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 = &sectors[i];
// correct height of floating sectors
if (isCeilingFloating(floatSector))
{
floatSector->virtualCeilingheight = estimateCeilHeight(floatSector);
floatSector->virtualCeiling = true;
}
if (isFloorFloating(floatSector))
{
floatSector->virtualFloorheight = estimateFloorHeight(floatSector);
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