mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-22 12:11:25 +00:00
598 lines
15 KiB
C++
598 lines
15 KiB
C++
/*
|
||
** gl_builddraw.cpp
|
||
** a build-like rendering algorithm
|
||
** Uses the sections created in gl_sections.cpp
|
||
**
|
||
** NOTE: Although this code generally works, it clearly shows the limitations
|
||
** of Build's algorithm. This requires constant sorting of the collected geometry
|
||
** and that causes extreme slowdowns on larger maps.
|
||
**
|
||
**---------------------------------------------------------------------------
|
||
** Copyright 2008 Christoph Oelckers
|
||
** All rights reserved.
|
||
**
|
||
** Redistribution and use in source and binary forms, with or without
|
||
** modification, are permitted provided that the following conditions
|
||
** are met:
|
||
**
|
||
** 1. Redistributions of source code must retain the above copyright
|
||
** notice, this list of conditions and the following disclaimer.
|
||
** 2. Redistributions in binary form must reproduce the above copyright
|
||
** notice, this list of conditions and the following disclaimer in the
|
||
** documentation and/or other materials provided with the distribution.
|
||
** 3. The name of the author may not be used to endorse or promote products
|
||
** derived from this software without specific prior written permission.
|
||
** 4. When not used as part of GZDoom or a GZDoom derivative, this code will be
|
||
** covered by the terms of the GNU Lesser General Public License as published
|
||
** by the Free Software Foundation; either version 2.1 of the License, or (at
|
||
** your option) any later version.
|
||
** 5. Full disclosure of the entire project's source code, except for third
|
||
** party libraries is mandatory. (NOTE: This clause is non-negotiable!)
|
||
**
|
||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
**---------------------------------------------------------------------------
|
||
**
|
||
*/
|
||
|
||
#include "i_system.h"
|
||
#include "p_local.h"
|
||
#include "c_dispatch.h"
|
||
#include "gl/renderer/gl_renderer.h"
|
||
#include "gl/scene/gl_clipper.h"
|
||
#include "gl/utility/gl_clock.h"
|
||
#include "gl/data/gl_sections.h"
|
||
#include "gl/scene/gl_wall.h"
|
||
|
||
#ifdef BUILD_TEST
|
||
#define D(x) x
|
||
#else
|
||
#define D(x) do{}while(0)
|
||
#endif
|
||
|
||
EXTERN_CVAR (Bool, dumpsections)
|
||
|
||
struct FBunch
|
||
{
|
||
int startline;
|
||
int endline;
|
||
angle_t startangle;
|
||
angle_t endangle;
|
||
fixed_t minviewdist;
|
||
fixed_t maxviewdist;
|
||
};
|
||
|
||
void DoSubsector(subsector_t * sub, bool handlelines);
|
||
|
||
EXTERN_CVAR(Bool, gl_render_walls)
|
||
|
||
//==========================================================================
|
||
//
|
||
// From Build but changed to use doubles to prevent overflows
|
||
//
|
||
//==========================================================================
|
||
|
||
static int WallInFront(FGLSectionLine *wal1, FGLSectionLine *wal2)
|
||
{
|
||
double x11, y11, x21, y21, x12, y12, x22, y22, dx, dy, t1, t2;
|
||
|
||
x11 = wal1->start->x;
|
||
y11 = wal1->start->y;
|
||
x21 = wal1->end->x;
|
||
y21 = wal1->end->y;
|
||
x12 = wal2->start->x;
|
||
y12 = wal2->start->y;
|
||
x22 = wal2->end->x;
|
||
y22 = wal2->end->y;
|
||
|
||
dx = x21-x11; dy = y21-y11;
|
||
|
||
t1 = (x12-x11)*dy - (y12-y11)*dx;
|
||
t2 = (x22-x11)*dy - (y22-y11)*dx;
|
||
if (t1 == 0)
|
||
{
|
||
t1 = t2;
|
||
if (t1 == 0) return(-1);
|
||
}
|
||
if (t2 == 0) t2 = t1;
|
||
|
||
if ((t1*t2) >= 0)
|
||
{
|
||
t2 = (double(viewx)-x11) * dy - (double(viewy)-y11)*dx;
|
||
return((t2*t1) < 0);
|
||
}
|
||
|
||
dx = x22-x12; dy = y22-y12;
|
||
t1 = (x11-x12)*dy - (y11-y12)*dx;
|
||
t2 = (x21-x12)*dy - (y21-y12)*dx;
|
||
if (t1 == 0)
|
||
{
|
||
t1 = t2;
|
||
if (t1 == 0) return(-1);
|
||
}
|
||
if (t2 == 0) t2 = t1;
|
||
if ((t1*t2) >= 0)
|
||
{
|
||
t2 = (double(viewx)-x12) * dy - (double(viewy)-y12)*dx;
|
||
return((t2*t1) >= 0);
|
||
}
|
||
return(-2);
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// This is a bit more complicated than it looks because angles can wrap
|
||
// around so we can only compare angle differences.
|
||
//
|
||
// Rules:
|
||
// 1. Any bunch can span at most 180<38>.
|
||
// 2. 2 bunches can never overlap at both ends
|
||
// 3. if there is an overlap one of the 2 starting points must be in the
|
||
// overlapping area.
|
||
//
|
||
//==========================================================================
|
||
|
||
static int BunchInFront(FBunch *b1, FBunch *b2)
|
||
{
|
||
angle_t anglecheck, endang;
|
||
|
||
if (b2->startangle - b1->startangle < b1->endangle - b1->startangle)
|
||
{
|
||
// we have an overlap at b2->startangle
|
||
anglecheck = b2->startangle - b1->startangle;
|
||
|
||
// Find the wall in b1 that overlaps b2->startangle
|
||
for(int i = b1->startline; i <= b1->endline; i++)
|
||
{
|
||
#ifdef _DEBUG
|
||
angle_t startang = SectionLines[i].start->GetClipAngleInverse() - b1->startangle;
|
||
#endif
|
||
endang = SectionLines[i].end->GetClipAngleInverse() - b1->startangle;
|
||
if (endang > anglecheck)
|
||
{
|
||
assert (startang <= anglecheck);
|
||
|
||
// found a line
|
||
int ret = WallInFront(&SectionLines[b2->startline], &SectionLines[i]);
|
||
|
||
D(Printf (PRINT_LOG, "Line %d <-> line %d: Result = %d.\n",
|
||
SectionLines[b2->startline].linedef-lines,
|
||
SectionLines[i].linedef-lines, ret));
|
||
|
||
return ret;
|
||
}
|
||
}
|
||
}
|
||
else if (b1->startangle - b2->startangle < b2->endangle - b2->startangle)
|
||
{
|
||
// we have an overlap at b1->startangle
|
||
anglecheck = b1->startangle - b2->startangle;
|
||
|
||
// Find the wall in b2 that overlaps b1->startangle
|
||
for(int i = b2->startline; i <= b2->endline; i++)
|
||
{
|
||
#ifdef _DEBUG
|
||
angle_t startang = SectionLines[i].start->GetClipAngleInverse() - b2->startangle;
|
||
#endif
|
||
endang = SectionLines[i].end->GetClipAngleInverse() - b2->startangle;
|
||
if (endang > anglecheck)
|
||
{
|
||
assert (startang <= anglecheck);
|
||
|
||
// found a line
|
||
int ret = WallInFront(&SectionLines[i], &SectionLines[b1->startline]);
|
||
|
||
D(Printf (PRINT_LOG, "Line %d <-> line %d: Result = %d,\n",
|
||
SectionLines[i].linedef-lines,
|
||
SectionLines[b1->endline].linedef-lines, ret));
|
||
|
||
return ret;
|
||
}
|
||
}
|
||
}
|
||
// we have no overlap
|
||
return -1;
|
||
}
|
||
|
||
|
||
// ----------------------------------------------------------------------------
|
||
//
|
||
// Bunches are groups of continuous lines
|
||
// This array stores the amount of points per bunch,
|
||
// the view angles for each point and the line index for the starting line
|
||
//
|
||
// ----------------------------------------------------------------------------
|
||
|
||
class BunchDrawer
|
||
{
|
||
int LastBunch;
|
||
int StartTime;
|
||
TArray<FBunch> Bunches;
|
||
TArray<int> CompareData;
|
||
sector_t fakebacksec;
|
||
|
||
//==========================================================================
|
||
//
|
||
//
|
||
//
|
||
//==========================================================================
|
||
public:
|
||
BunchDrawer()
|
||
{
|
||
StartScene();
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
//
|
||
//
|
||
//==========================================================================
|
||
private:
|
||
void StartScene()
|
||
{
|
||
LastBunch = 0;
|
||
StartTime = I_MSTime();
|
||
Bunches.Clear();
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
//
|
||
//
|
||
//==========================================================================
|
||
|
||
void StartBunch(int linenum, angle_t startan, angle_t endan, vertex_t *startpt, vertex_t *endpt)
|
||
{
|
||
FBunch *bunch = &Bunches[LastBunch = Bunches.Reserve(1)];
|
||
|
||
bunch->startline = bunch->endline = linenum;
|
||
bunch->startangle = startan;
|
||
bunch->endangle = endan;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
//
|
||
//
|
||
//==========================================================================
|
||
|
||
void AddLineToBunch(int newan)
|
||
{
|
||
Bunches[LastBunch].endline++;
|
||
Bunches[LastBunch].endangle = newan;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
//
|
||
//
|
||
//==========================================================================
|
||
|
||
void DeleteBunch(int index)
|
||
{
|
||
Bunches.Delete(index);
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// ClipLine
|
||
// Clips the given segment
|
||
//
|
||
//==========================================================================
|
||
|
||
enum
|
||
{
|
||
CL_Skip = 0,
|
||
CL_Draw = 1,
|
||
CL_Pass = 2,
|
||
};
|
||
|
||
|
||
int ClipLine (FGLSectionLine *line, sector_t * sector, sector_t **pbacksector)
|
||
{
|
||
angle_t startAngle, endAngle;
|
||
sector_t * backsector = NULL;
|
||
bool blocking;
|
||
|
||
startAngle = line->end->GetClipAngle();
|
||
endAngle = line->start->GetClipAngle();
|
||
*pbacksector = NULL;
|
||
|
||
// Back side, i.e. backface culling - read: endAngle >= startAngle!
|
||
if (startAngle-endAngle<ANGLE_180)
|
||
{
|
||
return CL_Skip;
|
||
}
|
||
|
||
if (!clipper.SafeCheckRange(startAngle, endAngle))
|
||
{
|
||
return CL_Skip;
|
||
}
|
||
|
||
if (line->otherside == -1)
|
||
{
|
||
// one-sided
|
||
clipper.SafeAddClipRange(startAngle, endAngle);
|
||
return CL_Draw;
|
||
}
|
||
else if (line->polysub == NULL)
|
||
{
|
||
// two sided and not a polyobject
|
||
if (line->linedef == NULL)
|
||
{
|
||
// Miniseg
|
||
return CL_Pass;
|
||
}
|
||
if (sector->sectornum == line->refseg->backsector->sectornum)
|
||
{
|
||
FTexture *tex = TexMan(line->sidedef->GetTexture(side_t::mid));
|
||
if (!tex || tex->UseType==FTexture::TEX_Null)
|
||
{
|
||
// no mid texture: nothing to do here
|
||
return CL_Pass;
|
||
}
|
||
*pbacksector = sector;
|
||
return CL_Draw|CL_Pass;
|
||
}
|
||
else
|
||
{
|
||
// clipping checks are only needed when the backsector is not the same as the front sector
|
||
gl_CheckViewArea(line->start, line->end, line->refseg->frontsector, line->refseg->backsector);
|
||
|
||
*pbacksector = backsector = gl_FakeFlat(line->refseg->backsector, &fakebacksec, true);
|
||
|
||
blocking = gl_CheckClip(line->sidedef, sector, backsector);
|
||
if (blocking)
|
||
{
|
||
clipper.SafeAddClipRange(startAngle, endAngle);
|
||
return CL_Draw;
|
||
}
|
||
return CL_Draw|CL_Pass;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
*pbacksector = sector;
|
||
return CL_Draw;
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
//
|
||
//
|
||
//==========================================================================
|
||
|
||
void ProcessBunch(int bnch)
|
||
{
|
||
FBunch *bunch = &Bunches[bnch];
|
||
|
||
sector_t fake;
|
||
sector_t *sec;
|
||
sector_t *backsector;
|
||
|
||
D(Printf(PRINT_LOG, "------------------------------\nProcessing bunch %d (Startline %d)\n",bnch,SectionLines[bunch->startline].linedef-lines));
|
||
ClipWall.Clock();
|
||
for(int i=bunch->startline; i <= bunch->endline; i++)
|
||
{
|
||
FGLSectionLine *ln = &SectionLines[i];
|
||
|
||
|
||
// Draw this line. todo: optimize
|
||
sec = gl_FakeFlat(ln->refseg->frontsector, &fake, false);
|
||
|
||
int clipped = ClipLine(ln, sec, &backsector);
|
||
|
||
D(Printf(PRINT_LOG, "line %d clip result is %d\n", ln->linedef - lines, clipped));
|
||
|
||
if (clipped & CL_Draw)
|
||
{
|
||
ln->linedef->flags |= ML_MAPPED;
|
||
|
||
if (ln->linedef->validcount!=validcount)
|
||
{
|
||
ln->linedef->validcount=validcount;
|
||
|
||
#ifndef BUILD_TEST
|
||
if (gl_render_walls)
|
||
{
|
||
SetupWall.Clock();
|
||
|
||
GLWall wall;
|
||
wall.Process(ln->refseg, sec, backsector, ln->polysub);
|
||
rendered_lines++;
|
||
|
||
SetupWall.Unclock();
|
||
}
|
||
#endif
|
||
}
|
||
}
|
||
|
||
if (clipped & CL_Pass)
|
||
{
|
||
ClipWall.Unclock();
|
||
ProcessSection(ln->otherside);
|
||
ClipWall.Clock();
|
||
}
|
||
}
|
||
D(Printf(PRINT_LOG, "Bunch %d done\n------------------------------\n",bnch));
|
||
ClipWall.Unclock();
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
//
|
||
//
|
||
//==========================================================================
|
||
|
||
int FindClosestBunch()
|
||
{
|
||
int closest = 0; //Almost works, but not quite :(
|
||
|
||
CompareData.Clear();
|
||
for(unsigned i = 1; i < Bunches.Size(); i++)
|
||
{
|
||
switch (BunchInFront(&Bunches[i], &Bunches[closest]))
|
||
{
|
||
case 0: // i is in front
|
||
closest = i;
|
||
continue;
|
||
|
||
case 1: // i is behind
|
||
continue;
|
||
|
||
default: // can't determine
|
||
CompareData.Push(i); // mark for later comparison
|
||
continue;
|
||
}
|
||
}
|
||
|
||
// we need to do a second pass to see how the marked bunches relate to the currently closest one.
|
||
for(unsigned i = 0; i < CompareData.Size(); i++)
|
||
{
|
||
switch (BunchInFront(&Bunches[CompareData[i]], &Bunches[closest]))
|
||
{
|
||
case 0: // is in front
|
||
closest = i;
|
||
CompareData.Delete(i);
|
||
i = 0; // we need to recheck everything that's still marked.
|
||
continue;
|
||
|
||
case 1: // is behind
|
||
CompareData.Delete(i);
|
||
i--;
|
||
continue;
|
||
|
||
default:
|
||
continue;
|
||
|
||
}
|
||
}
|
||
return closest;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
//
|
||
//
|
||
//==========================================================================
|
||
|
||
void ProcessSection(int sectnum)
|
||
{
|
||
FGLSection *sect = &Sections[sectnum];
|
||
bool inbunch;
|
||
angle_t startangle;
|
||
|
||
if (sect->validcount == StartTime) return;
|
||
sect->validcount = StartTime;
|
||
D(Printf(PRINT_LOG, "------------------------------\nProcessing section %d (sector %d)\n",sectnum, sect->sector->sectornum));
|
||
|
||
#ifndef BUILD_TEST
|
||
for(unsigned i = 0; i < sect->subsectors.Size(); i++)
|
||
{
|
||
DoSubsector(sect->subsectors[i], false);
|
||
if (sect->subsectors[i]->poly != NULL)
|
||
{
|
||
// ProcessPolyobject()
|
||
}
|
||
}
|
||
#endif
|
||
|
||
//Todo: process subsectors
|
||
for(int i=0; i<sect->numloops; i++)
|
||
{
|
||
FGLSectionLoop *loop = sect->GetLoop(i);
|
||
inbunch = false;
|
||
|
||
for(int j=0; j<loop->numlines; j++)
|
||
{
|
||
FGLSectionLine *ln = loop->GetLine(j);
|
||
|
||
angle_t ang1 = ln->start->GetClipAngle();
|
||
angle_t ang2 = ln->end->GetClipAngle();
|
||
|
||
if (ang2 - ang1 < ANGLE_180)
|
||
{
|
||
// Backside
|
||
D(Printf(PRINT_LOG, "line %d facing backwards\n", ln->linedef - lines));
|
||
inbunch = false;
|
||
}
|
||
else if (!clipper.SafeCheckRange(ang2, ang1))
|
||
{
|
||
// is it visible?
|
||
D(Printf(PRINT_LOG, "line %d not in view\n", ln->linedef - lines));
|
||
inbunch = false;
|
||
}
|
||
else if (!inbunch || startangle - ang2 >= ANGLE_180)
|
||
{
|
||
// don't let a bunch span more than 180<38> to avoid problems.
|
||
// This limitation ensures that the combined range of 2
|
||
// bunches will always be less than 360<36> which simplifies
|
||
// the distance comparison code because it prevents a
|
||
// situation where 2 bunches may overlap at both ends.
|
||
D(Printf(PRINT_LOG, "Starting bunch %d at line %d\n",Bunches.Size(), ln->linedef - lines));
|
||
|
||
startangle = ang2;
|
||
// Clipping angles are backward which makes this code very hard to read so let's use the inverse
|
||
StartBunch(loop->startline + j, 0 - ang1, 0 - ang2);
|
||
inbunch = true;
|
||
}
|
||
else
|
||
{
|
||
D(Printf(PRINT_LOG, " Adding line %d\n", ln->linedef - lines));
|
||
AddLineToBunch(0 - ang2);
|
||
}
|
||
}
|
||
}
|
||
D(Printf(PRINT_LOG, "Section %d done\n------------------------------\n",sectnum));
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
//
|
||
//
|
||
//==========================================================================
|
||
|
||
public:
|
||
void RenderScene(int viewsection)
|
||
{
|
||
ProcessSection(viewsection);
|
||
while (Bunches.Size() > 0)
|
||
{
|
||
int closest = FindClosestBunch();
|
||
ProcessBunch(closest);
|
||
DeleteBunch(closest);
|
||
}
|
||
}
|
||
};
|
||
|
||
|
||
void gl_RenderBuild()
|
||
{
|
||
subsector_t *sub = R_PointInSubsector(viewx, viewy);
|
||
|
||
clipper.Clear();
|
||
angle_t a1 = GLRenderer->FrustumAngle();
|
||
clipper.SafeAddClipRangeRealAngles(viewangle+a1, viewangle-a1);
|
||
if (Sections.Size() == 0) gl_CreateSections();
|
||
|
||
int startsection = SectionForSubsector[sub-subsectors];
|
||
|
||
BunchDrawer bd;
|
||
bd.RenderScene(startsection);
|
||
}
|
||
|
||
#ifdef BUILD_TEST
|
||
CCMD(testrender)
|
||
{
|
||
gl_RenderBuild();
|
||
}
|
||
#endif
|