gzdoom/src/p_glnodes.cpp
Christoph Oelckers 9bdb0f2e49 - renamed the flag bits for sector_t::MoreFlags, so that they are easier to distinguish from sector_t::Flags.
- precalculate if a sector's floor and ceiling plane overlap. This avoids rechecking this for each single call of hw_FakeFlat.
- vertices must be marked dirty every time they change after map setup. That means that ChangePlaneTexZ must do this as well, because it cannot rely on interpolation taking care of it.
- Having a 'dirty' argument for SetPlaneTexZ's ZScript version makes no sense. If the value changes from the script side the vertices must always be marked to be recalculated.
2018-05-01 11:29:29 +02:00

1440 lines
34 KiB
C++

/*
** gl_nodes.cpp
**
**---------------------------------------------------------------------------
** Copyright 2005-2010 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.
**
** 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 <math.h>
#ifdef _MSC_VER
#include <malloc.h> // for alloca()
#endif
#ifndef _WIN32
#include <unistd.h>
#else
#include <direct.h>
#define rmdir _rmdir
#endif
#include <zlib.h>
#include "templates.h"
#include "m_argv.h"
#include "c_dispatch.h"
#include "m_swap.h"
#include "w_wad.h"
#include "p_local.h"
#include "nodebuild.h"
#include "doomstat.h"
#include "doomerrors.h"
#include "p_setup.h"
#include "version.h"
#include "md5.h"
#include "m_misc.h"
#include "cmdlib.h"
#include "g_levellocals.h"
#include "i_time.h"
void P_GetPolySpots (MapData * lump, TArray<FNodeBuilder::FPolyStart> &spots, TArray<FNodeBuilder::FPolyStart> &anchors);
CVAR(Bool, gl_cachenodes, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR(Float, gl_cachetime, 0.6f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
void P_LoadZNodes (FileReader &dalump, uint32_t id);
static bool CheckCachedNodes(MapData *map);
static void CreateCachedNodes(MapData *map);
// fixed 32 bit gl_vert format v2.0+ (glBsp 1.91)
struct mapglvertex_t
{
int32_t x,y;
};
struct gl3_mapsubsector_t
{
int32_t numsegs;
int32_t firstseg; // Index of first one; segs are stored sequentially.
};
struct glseg_t
{
uint16_t v1; // start vertex (16 bit)
uint16_t v2; // end vertex (16 bit)
uint16_t linedef; // linedef, or -1 for minisegs
uint16_t side; // side on linedef: 0 for right, 1 for left
uint16_t partner; // corresponding partner seg, or 0xffff on one-sided walls
};
struct glseg3_t
{
int32_t v1;
int32_t v2;
uint16_t linedef;
uint16_t side;
int32_t partner;
};
struct gl5_mapnode_t
{
int16_t x,y,dx,dy; // partition line
int16_t bbox[2][4]; // bounding box for each child
// If NF_SUBSECTOR is or'ed in, it's a subsector,
// else it's a node of another subtree.
uint32_t children[2];
};
//==========================================================================
//
// Collect all sidedefs which are not entirely covered by segs
// Old ZDBSPs could create such maps. If such a BSP is discovered
// a node rebuild must be done to ensure proper rendering
//
//==========================================================================
static int CheckForMissingSegs()
{
auto numsides = level.sides.Size();
double *added_seglen = new double[numsides];
int missing = 0;
memset(added_seglen, 0, sizeof(double)*numsides);
for (auto &seg : level.segs)
{
if (seg.sidedef != nullptr)
{
// check all the segs and calculate the length they occupy on their sidedef
DVector2 vec1(seg.v2->fX() - seg.v1->fX(), seg.v2->fY() - seg.v1->fY());
added_seglen[seg.sidedef->Index()] += vec1.Length();
}
}
for (unsigned i = 0; i < numsides; i++)
{
double linelen = level.sides[i].linedef->Delta().Length();
missing += (added_seglen[i] < linelen - 1.);
}
delete[] added_seglen;
return missing;
}
//==========================================================================
//
// Checks whether the nodes are suitable for GL rendering
//
//==========================================================================
bool P_CheckForGLNodes()
{
for(auto &sub : level.subsectors)
{
seg_t * firstseg = sub.firstline;
seg_t * lastseg = sub.firstline + sub.numlines - 1;
if (firstseg->v1 != lastseg->v2)
{
// This subsector is incomplete which means that these
// are normal nodes
return false;
}
else
{
for(uint32_t j=0;j<sub.numlines;j++)
{
if (level.segs[j].linedef==NULL) // miniseg
{
// We already have GL nodes. Great!
return true;
}
}
}
}
// all subsectors were closed but there are no minisegs
// Although unlikely this can happen. Such nodes are not a problem.
// all that is left is to check whether the BSP covers all sidedefs completely.
int missing = CheckForMissingSegs();
if (missing > 0)
{
Printf("%d missing segs counted\nThe BSP needs to be rebuilt.\n", missing);
}
return missing == 0;
}
//==========================================================================
//
// LoadGLVertexes
//
// loads GL vertices
//
//==========================================================================
#define gNd2 MAKE_ID('g','N','d','2')
#define gNd4 MAKE_ID('g','N','d','4')
#define gNd5 MAKE_ID('g','N','d','5')
#define GL_VERT_OFFSET 4
static int firstglvertex;
static bool format5;
static bool LoadGLVertexes(FileReader &lump)
{
uint8_t *gldata;
int i;
firstglvertex = level.vertexes.Size();
auto gllen=lump.GetLength();
gldata = new uint8_t[gllen];
lump.Seek(0, FileReader::SeekSet);
lump.Read(gldata, gllen);
if (*(int *)gldata == gNd5)
{
format5=true;
}
else if (*(int *)gldata != gNd2)
{
// GLNodes V1 and V4 are unsupported.
// V1 because the precision is insufficient and
// V4 due to the missing partner segs
Printf("GL nodes v%d found. This format is not supported by " GAMENAME "\n",
(*(int *)gldata == gNd4)? 4:1);
delete [] gldata;
return false;
}
else format5=false;
mapglvertex_t* mgl = (mapglvertex_t *)(gldata + GL_VERT_OFFSET);
unsigned numvertexes = (unsigned)(firstglvertex + (gllen - GL_VERT_OFFSET)/sizeof(mapglvertex_t));
auto oldvertexes = &level.vertexes[0];
level.vertexes.Resize(numvertexes);
for(auto &line : level.lines)
{
// Remap vertex pointers in linedefs
line.v1 = &level.vertexes[line.v1 - oldvertexes];
line.v2 = &level.vertexes[line.v2 - oldvertexes];
}
for (i = firstglvertex; i < (int)numvertexes; i++)
{
level.vertexes[i].set(LittleLong(mgl->x)/65536., LittleLong(mgl->y)/65536.);
mgl++;
}
delete[] gldata;
return true;
}
//==========================================================================
//
// GL Nodes utilities
//
//==========================================================================
static inline int checkGLVertex(int num)
{
if (num & 0x8000)
num = (num&0x7FFF)+firstglvertex;
return num;
}
static inline int checkGLVertex3(int num)
{
if (num & 0xc0000000)
num = (num&0x3FFFFFFF)+firstglvertex;
return num;
}
//==========================================================================
//
// LoadGLSegs
//
//==========================================================================
static bool LoadGLSegs(FileReader &lump)
{
char *data;
int i;
line_t *ldef=NULL;
int numsegs = (int)lump.GetLength();
data= new char[numsegs];
lump.Seek(0, FileReader::SeekSet);
lump.Read(data, numsegs);
auto &segs = level.segs;
#ifdef _MSC_VER
__try
#endif
{
if (!format5 && memcmp(data, "gNd3", 4))
{
numsegs/=sizeof(glseg_t);
level.segs.Alloc(numsegs);
memset(&segs[0],0,sizeof(seg_t)*numsegs);
glseg_t * ml = (glseg_t*)data;
for(i = 0; i < numsegs; i++)
{
// check for gl-vertices
segs[i].v1 = &level.vertexes[checkGLVertex(LittleShort(ml->v1))];
segs[i].v2 = &level.vertexes[checkGLVertex(LittleShort(ml->v2))];
segs[i].PartnerSeg = ml->partner == 0xFFFF ? nullptr : &segs[LittleShort(ml->partner)];
if(ml->linedef != 0xffff)
{
ldef = &level.lines[LittleShort(ml->linedef)];
segs[i].linedef = ldef;
ml->side=LittleShort(ml->side);
segs[i].sidedef = ldef->sidedef[ml->side];
if (ldef->sidedef[ml->side] != NULL)
{
segs[i].frontsector = ldef->sidedef[ml->side]->sector;
}
else
{
segs[i].frontsector = NULL;
}
if (ldef->flags & ML_TWOSIDED && ldef->sidedef[ml->side^1] != NULL)
{
segs[i].backsector = ldef->sidedef[ml->side^1]->sector;
}
else
{
ldef->flags &= ~ML_TWOSIDED;
segs[i].backsector = NULL;
}
}
else
{
segs[i].linedef = NULL;
segs[i].sidedef = NULL;
segs[i].frontsector = NULL;
segs[i].backsector = NULL;
}
ml++;
}
}
else
{
if (!format5) numsegs-=4;
numsegs/=sizeof(glseg3_t);
level.segs.Alloc(numsegs);
memset(&segs[0],0,sizeof(seg_t)*numsegs);
glseg3_t * ml = (glseg3_t*)(data+ (format5? 0:4));
for(i = 0; i < numsegs; i++)
{ // check for gl-vertices
segs[i].v1 = &level.vertexes[checkGLVertex3(LittleLong(ml->v1))];
segs[i].v2 = &level.vertexes[checkGLVertex3(LittleLong(ml->v2))];
const uint32_t partner = LittleLong(ml->partner);
segs[i].PartnerSeg = DWORD_MAX == partner ? nullptr : &segs[partner];
if(ml->linedef != 0xffff) // skip minisegs
{
ldef = &level.lines[LittleLong(ml->linedef)];
segs[i].linedef = ldef;
ml->side=LittleShort(ml->side);
segs[i].sidedef = ldef->sidedef[ml->side];
if (ldef->sidedef[ml->side] != NULL)
{
segs[i].frontsector = ldef->sidedef[ml->side]->sector;
}
else
{
segs[i].frontsector = NULL;
}
if (ldef->flags & ML_TWOSIDED && ldef->sidedef[ml->side^1] != NULL)
{
segs[i].backsector = ldef->sidedef[ml->side^1]->sector;
}
else
{
ldef->flags &= ~ML_TWOSIDED;
segs[i].backsector = NULL;
}
}
else
{
segs[i].linedef = NULL;
segs[i].sidedef = NULL;
segs[i].frontsector = NULL;
segs[i].backsector = NULL;
}
ml++;
}
}
delete [] data;
return true;
}
#ifdef _MSC_VER
__except(1)
{
// Invalid data has the bad habit of requiring extensive checks here
// so let's just catch anything invalid and output a message.
// (at least under MSVC. GCC can't do SEH even for Windows... :( )
Printf("Invalid GL segs. The BSP will have to be rebuilt.\n");
delete [] data;
level.segs.Clear();
return false;
}
#endif
}
//==========================================================================
//
// LoadGLSubsectors
//
//==========================================================================
static bool LoadGLSubsectors(FileReader &lump)
{
char * datab;
int i;
int numsubsectors = (int)lump.GetLength();
datab = new char[numsubsectors];
lump.Seek(0, FileReader::SeekSet);
lump.Read(datab, numsubsectors);
if (numsubsectors == 0)
{
delete [] datab;
return false;
}
if (!format5 && memcmp(datab, "gNd3", 4))
{
mapsubsector_t * data = (mapsubsector_t*) datab;
numsubsectors /= sizeof(mapsubsector_t);
level.subsectors.Alloc(numsubsectors);
auto &subsectors = level.subsectors;
memset(&subsectors[0],0,numsubsectors * sizeof(subsector_t));
for (i=0; i<numsubsectors; i++)
{
subsectors[i].numlines = LittleShort(data[i].numsegs );
subsectors[i].firstline = &level.segs[LittleShort(data[i].firstseg)];
if (subsectors[i].numlines == 0)
{
delete [] datab;
return false;
}
}
}
else
{
gl3_mapsubsector_t * data = (gl3_mapsubsector_t*) (datab+(format5? 0:4));
numsubsectors /= sizeof(gl3_mapsubsector_t);
level.subsectors.Alloc(numsubsectors);
auto &subsectors = level.subsectors;
memset(&subsectors[0],0,numsubsectors * sizeof(subsector_t));
for (i=0; i<numsubsectors; i++)
{
subsectors[i].numlines = LittleLong(data[i].numsegs );
subsectors[i].firstline = &level.segs[LittleLong(data[i].firstseg)];
if (subsectors[i].numlines == 0)
{
delete [] datab;
return false;
}
}
}
for (auto &sub : level.subsectors)
{
for(unsigned j=0;j<sub.numlines;j++)
{
seg_t * seg = sub.firstline + j;
if (seg->linedef==NULL) seg->frontsector = seg->backsector = sub.firstline->frontsector;
}
seg_t *firstseg = sub.firstline;
seg_t *lastseg = sub.firstline + sub.numlines - 1;
// The subsector must be closed. If it isn't we can't use these nodes and have to do a rebuild.
if (lastseg->v2 != firstseg->v1)
{
delete [] datab;
return false;
}
}
delete [] datab;
return true;
}
//==========================================================================
//
// P_LoadNodes
//
//==========================================================================
static bool LoadNodes (FileReader &lump)
{
const int NF_SUBSECTOR = 0x8000;
const int GL5_NF_SUBSECTOR = (1 << 31);
int j;
int k;
node_t* no;
uint16_t* used;
if (!format5)
{
mapnode_t* mn, * basemn;
unsigned numnodes = unsigned(lump.GetLength() / sizeof(mapnode_t));
if (numnodes == 0) return false;
level.nodes.Alloc(numnodes);
lump.Seek(0, FileReader::SeekSet);
basemn = mn = new mapnode_t[numnodes];
lump.Read(mn, lump.GetLength());
used = (uint16_t *)alloca (sizeof(uint16_t)*numnodes);
memset (used, 0, sizeof(uint16_t)*numnodes);
no = &level.nodes[0];
for (unsigned i = 0; i < numnodes; i++, no++, mn++)
{
no->x = LittleShort(mn->x)<<FRACBITS;
no->y = LittleShort(mn->y)<<FRACBITS;
no->dx = LittleShort(mn->dx)<<FRACBITS;
no->dy = LittleShort(mn->dy)<<FRACBITS;
for (j = 0; j < 2; j++)
{
uint16_t child = LittleShort(mn->children[j]);
if (child & NF_SUBSECTOR)
{
child &= ~NF_SUBSECTOR;
if (child >= level.subsectors.Size())
{
delete [] basemn;
return false;
}
no->children[j] = (uint8_t *)&level.subsectors[child] + 1;
}
else if (child >= numnodes)
{
delete [] basemn;
return false;
}
else if (used[child])
{
delete [] basemn;
return false;
}
else
{
no->children[j] = &level.nodes[child];
used[child] = j + 1;
}
for (k = 0; k < 4; k++)
{
no->bbox[j][k] = (float)LittleShort(mn->bbox[j][k]);
}
}
}
delete [] basemn;
}
else
{
gl5_mapnode_t* mn, * basemn;
auto numnodes = unsigned(lump.GetLength() / sizeof(gl5_mapnode_t));
if (numnodes == 0) return false;
level.nodes.Alloc(numnodes);
lump.Seek(0, FileReader::SeekSet);
basemn = mn = new gl5_mapnode_t[numnodes];
lump.Read(mn, lump.GetLength());
used = (uint16_t *)alloca (sizeof(uint16_t)*numnodes);
memset (used, 0, sizeof(uint16_t)*numnodes);
no = &level.nodes[0];
for (unsigned i = 0; i < numnodes; i++, no++, mn++)
{
no->x = LittleShort(mn->x)<<FRACBITS;
no->y = LittleShort(mn->y)<<FRACBITS;
no->dx = LittleShort(mn->dx)<<FRACBITS;
no->dy = LittleShort(mn->dy)<<FRACBITS;
for (j = 0; j < 2; j++)
{
int32_t child = LittleLong(mn->children[j]);
if (child & GL5_NF_SUBSECTOR)
{
child &= ~GL5_NF_SUBSECTOR;
if ((unsigned)child >= level.subsectors.Size())
{
delete [] basemn;
return false;
}
no->children[j] = (uint8_t *)&level.subsectors[child] + 1;
}
else if ((unsigned)child >= numnodes)
{
delete [] basemn;
return false;
}
else if (used[child])
{
delete [] basemn;
return false;
}
else
{
no->children[j] = &level.nodes[child];
used[child] = j + 1;
}
for (k = 0; k < 4; k++)
{
no->bbox[j][k] = (float)LittleShort(mn->bbox[j][k]);
}
}
}
delete [] basemn;
}
return true;
}
//==========================================================================
//
// loads the GL node data
//
//==========================================================================
static bool DoLoadGLNodes(FileReader * lumps)
{
int missing = 0;
if (!LoadGLVertexes(lumps[0]) ||
!LoadGLSegs(lumps[1]) ||
!LoadGLSubsectors(lumps[2]) ||
!LoadNodes(lumps[3]))
{
goto fail;
}
// Quick check for the validity of the nodes
// For invalid nodes there is a high chance that this test will fail
for (auto &sub : level.subsectors)
{
seg_t * seg = sub.firstline;
if (!seg->sidedef)
{
Printf("GL nodes contain invalid data. The BSP has to be rebuilt.\n");
goto fail;
}
}
// check whether the BSP covers all sidedefs completely.
missing = CheckForMissingSegs();
if (missing > 0)
{
Printf("%d missing segs counted in GL nodes.\nThe BSP has to be rebuilt.\n", missing);
}
return missing == 0;
fail:
level.nodes.Clear();
level.subsectors.Clear();
level.segs.Clear();
return false;
}
//===========================================================================
//
// MatchHeader
//
// Checks whether a GL_LEVEL header belongs to this level
//
//===========================================================================
static bool MatchHeader(const char * label, const char * hdata)
{
if (memcmp(hdata, "LEVEL=", 6) == 0)
{
size_t labellen = strlen(label);
labellen = MIN(size_t(8), labellen);
if (strnicmp(hdata+6, label, labellen)==0 &&
(hdata[6+labellen]==0xa || hdata[6+labellen]==0xd))
{
return true;
}
}
return false;
}
//===========================================================================
//
// FindGLNodesInWAD
//
// Looks for GL nodes in the same WAD as the level itself
//
//===========================================================================
static int FindGLNodesInWAD(int labellump)
{
int wadfile = Wads.GetLumpFile(labellump);
FString glheader;
glheader.Format("GL_%s", Wads.GetLumpFullName(labellump));
if (glheader.Len()<=8)
{
int gllabel = Wads.CheckNumForName(glheader, ns_global, wadfile);
if (gllabel >= 0) return gllabel;
}
else
{
// Before scanning the entire WAD directory let's check first whether
// it is necessary.
int gllabel = Wads.CheckNumForName("GL_LEVEL", ns_global, wadfile);
if (gllabel >= 0)
{
int lastlump=0;
int lump;
while ((lump=Wads.FindLump("GL_LEVEL", &lastlump))>=0)
{
if (Wads.GetLumpFile(lump)==wadfile)
{
FMemLump mem = Wads.ReadLump(lump);
if (MatchHeader(Wads.GetLumpFullName(labellump), (const char *)mem.GetMem())) return lump;
}
}
}
}
return -1;
}
//===========================================================================
//
// FindGLNodesInFile
//
// Looks for GL nodes in the same WAD as the level itself
// Function returns the lump number within the file. Returns -1 if the input
// resource file is NULL.
//
//===========================================================================
static int FindGLNodesInFile(FResourceFile * f, const char * label)
{
// No file open? Probably shouldn't happen but assume no GL nodes
if(!f)
return -1;
FString glheader;
bool mustcheck=false;
uint32_t numentries = f->LumpCount();
glheader.Format("GL_%.8s", label);
if (glheader.Len()>8)
{
glheader="GL_LEVEL";
mustcheck=true;
}
if (numentries > 4)
{
for(uint32_t i=0;i<numentries-4;i++)
{
if (!strnicmp(f->GetLump(i)->Name, glheader, 8))
{
if (mustcheck)
{
char check[16]={0};
auto fr = f->GetLump(i)->GetReader();
fr->Read(check, 16);
if (MatchHeader(label, check)) return i;
}
else return i;
}
}
}
return -1;
}
//==========================================================================
//
// Checks for the presence of GL nodes in the loaded WADs or a .GWA file
// returns true if successful
//
//==========================================================================
bool P_LoadGLNodes(MapData * map)
{
if (map->Size(ML_GLZNODES) != 0)
{
const int idcheck1a = MAKE_ID('Z','G','L','N');
const int idcheck2a = MAKE_ID('Z','G','L','2');
const int idcheck3a = MAKE_ID('Z','G','L','3');
const int idcheck1b = MAKE_ID('X','G','L','N');
const int idcheck2b = MAKE_ID('X','G','L','2');
const int idcheck3b = MAKE_ID('X','G','L','3');
int id;
auto &file = map->Reader(ML_GLZNODES);
file.Read (&id, 4);
if (id == idcheck1a || id == idcheck2a || id == idcheck3a ||
id == idcheck1b || id == idcheck2b || id == idcheck3b)
{
try
{
level.subsectors.Clear();
level.segs.Clear();
level.nodes.Clear();
P_LoadZNodes (file, id);
return true;
}
catch (CRecoverableError &)
{
level.subsectors.Clear();
level.segs.Clear();
level.nodes.Clear();
}
}
}
if (!CheckCachedNodes(map))
{
FileReader gwalumps[4];
char path[256];
int li;
int lumpfile = Wads.GetLumpFile(map->lumpnum);
bool mapinwad = map->InWad;
FResourceFile * f_gwa = map->resource;
const char * name = Wads.GetWadFullName(lumpfile);
if (mapinwad)
{
li = FindGLNodesInWAD(map->lumpnum);
if (li>=0)
{
// GL nodes are loaded with a WAD
for(int i=0;i<4;i++)
{
gwalumps[i]=Wads.ReopenLumpReader(li+i+1);
}
return DoLoadGLNodes(gwalumps);
}
else
{
strcpy(path, name);
char * ext = strrchr(path, '.');
if (ext)
{
strcpy(ext, ".gwa");
// Todo: Compare file dates
f_gwa = FResourceFile::OpenResourceFile(path, true);
if (f_gwa==NULL) return false;
strncpy(map->MapLumps[0].Name, Wads.GetLumpFullName(map->lumpnum), 8);
}
}
}
bool result = false;
li = FindGLNodesInFile(f_gwa, map->MapLumps[0].Name);
if (li!=-1)
{
static const char check[][9]={"GL_VERT","GL_SEGS","GL_SSECT","GL_NODES"};
result=true;
for(unsigned i=0; i<4;i++)
{
if (strnicmp(f_gwa->GetLump(li+i+1)->Name, check[i], 8))
{
result=false;
break;
}
else
gwalumps[i] = f_gwa->GetLump(li+i+1)->NewReader();
}
if (result) result = DoLoadGLNodes(gwalumps);
}
if (f_gwa != map->resource)
delete f_gwa;
return result;
}
else return true;
}
//==========================================================================
//
// Checks whether nodes are GL friendly or not
//
//==========================================================================
bool P_CheckNodes(MapData * map, bool rebuilt, int buildtime)
{
bool ret = false;
bool loaded = false;
// If the map loading code has performed a node rebuild we don't need to check for it again.
if (!rebuilt && !P_CheckForGLNodes())
{
ret = true; // we are not using the level's original nodes if we get here.
for (auto &sub : level.subsectors)
{
sub.sector = sub.firstline->sidedef->sector;
}
// The nodes and subsectors need to be preserved for gameplay related purposes.
level.gamenodes = std::move(level.nodes);
level.gamesubsectors = std::move(level.subsectors);
level.segs.Clear();
// Try to load GL nodes (cached or GWA)
loaded = P_LoadGLNodes(map);
if (!loaded)
{
// none found - we have to build new ones!
uint64_t startTime, endTime;
startTime = I_msTime ();
TArray<FNodeBuilder::FPolyStart> polyspots, anchors;
P_GetPolySpots (map, polyspots, anchors);
FNodeBuilder::FLevel leveldata =
{
&level.vertexes[0], (int)level.vertexes.Size(),
&level.sides[0], (int)level.sides.Size(),
&level.lines[0], (int)level.lines.Size(),
0, 0, 0, 0
};
leveldata.FindMapBounds ();
FNodeBuilder builder (leveldata, polyspots, anchors, true);
builder.Extract (level);
endTime = I_msTime ();
DPrintf (DMSG_NOTIFY, "BSP generation took %.3f sec (%u segs)\n", (endTime - startTime) * 0.001, level.segs.Size());
buildtime = (int32_t)(endTime - startTime);
}
}
if (!loaded)
{
#ifdef DEBUG
// Building nodes in debug is much slower so let's cache them only if cachetime is 0
buildtime = 0;
#endif
if (level.maptype != MAPTYPE_BUILD && gl_cachenodes && buildtime/1000.f >= gl_cachetime)
{
DPrintf(DMSG_NOTIFY, "Caching nodes\n");
CreateCachedNodes(map);
}
else
{
DPrintf(DMSG_NOTIFY, "Not caching nodes (time = %f)\n", buildtime/1000.f);
}
}
return ret;
}
//==========================================================================
//
// Node caching
//
//==========================================================================
typedef TArray<uint8_t> MemFile;
static FString CreateCacheName(MapData *map, bool create)
{
FString path = M_GetCachePath(create);
FString lumpname = Wads.GetLumpFullPath(map->lumpnum);
int separator = lumpname.IndexOf(':');
path << '/' << lumpname.Left(separator);
if (create) CreatePath(path);
lumpname.ReplaceChars('/', '%');
lumpname.ReplaceChars(':', '$');
path << '/' << lumpname.Right(lumpname.Len() - separator - 1) << ".gzc";
return path;
}
static void WriteByte(MemFile &f, uint8_t b)
{
f.Push(b);
}
static void WriteWord(MemFile &f, uint16_t b)
{
int v = f.Reserve(2);
f[v] = (uint8_t)b;
f[v+1] = (uint8_t)(b>>8);
}
static void WriteLong(MemFile &f, uint32_t b)
{
int v = f.Reserve(4);
f[v] = (uint8_t)b;
f[v+1] = (uint8_t)(b>>8);
f[v+2] = (uint8_t)(b>>16);
f[v+3] = (uint8_t)(b>>24);
}
static void CreateCachedNodes(MapData *map)
{
MemFile ZNodes;
WriteLong(ZNodes, 0);
WriteLong(ZNodes, level.vertexes.Size());
for(auto &vert : level.vertexes)
{
WriteLong(ZNodes, vert.fixX());
WriteLong(ZNodes, vert.fixY());
}
WriteLong(ZNodes, level.subsectors.Size());
for (auto &sub : level.subsectors)
{
WriteLong(ZNodes, sub.numlines);
}
WriteLong(ZNodes, level.segs.Size());
for(auto &seg : level.segs)
{
WriteLong(ZNodes, seg.v1->Index());
WriteLong(ZNodes, seg.PartnerSeg == nullptr? 0xffffffffu : uint32_t(seg.PartnerSeg->Index()));
if (seg.linedef)
{
WriteLong(ZNodes, uint32_t(seg.linedef->Index()));
WriteByte(ZNodes, seg.sidedef == seg.linedef->sidedef[0]? 0:1);
}
else
{
WriteLong(ZNodes, 0xffffffffu);
WriteByte(ZNodes, 0);
}
}
WriteLong(ZNodes, level.nodes.Size());
for(auto &node : level.nodes)
{
WriteLong(ZNodes, node.x);
WriteLong(ZNodes, node.y);
WriteLong(ZNodes, node.dx);
WriteLong(ZNodes, node.dy);
for (int j = 0; j < 2; ++j)
{
for (int k = 0; k < 4; ++k)
{
WriteWord(ZNodes, (short)node.bbox[j][k]);
}
}
for (int j = 0; j < 2; ++j)
{
uint32_t child;
if ((size_t)node.children[j] & 1)
{
child = 0x80000000 | uint32_t(((subsector_t *)((uint8_t *)node.children[j] - 1))->Index());
}
else
{
child = ((node_t *)node.children[j])->Index();
}
WriteLong(ZNodes, child);
}
}
uLongf outlen = ZNodes.Size();
uint8_t *compressed;
int offset = level.lines.Size() * 8 + 12 + 16;
int r;
do
{
compressed = new Bytef[outlen + offset];
r = compress (compressed + offset, &outlen, &ZNodes[0], ZNodes.Size());
if (r == Z_BUF_ERROR)
{
delete[] compressed;
outlen += 1024;
}
}
while (r == Z_BUF_ERROR);
memcpy(compressed, "CACH", 4);
uint32_t len = LittleLong(level.lines.Size());
memcpy(compressed+4, &len, 4);
map->GetChecksum(compressed+8);
for (unsigned i = 0; i < level.lines.Size(); i++)
{
uint32_t ndx[2] = { LittleLong(uint32_t(level.lines[i].v1->Index())), LittleLong(uint32_t(level.lines[i].v2->Index())) };
memcpy(compressed + 8 + 16 + 8 * i, ndx, 8);
}
memcpy(compressed + offset - 4, "ZGL3", 4);
FString path = CreateCacheName(map, true);
FileWriter *fw = FileWriter::Open(path);
if (fw != nullptr)
{
const size_t length = outlen + offset;
if (fw->Write(compressed, length) != length)
{
Printf("Error saving nodes to file %s\n", path.GetChars());
}
delete fw;
}
else
{
Printf("Cannot open nodes file %s for writing\n", path.GetChars());
}
delete [] compressed;
}
static bool CheckCachedNodes(MapData *map)
{
char magic[4] = {0,0,0,0};
uint8_t md5[16];
uint8_t md5map[16];
uint32_t numlin;
uint32_t *verts = NULL;
FString path = CreateCacheName(map, false);
FileReader fr;
if (!fr.OpenFile(path)) return false;
if (fr.Read(magic, 4) != 4) goto errorout;
if (memcmp(magic, "CACH", 4)) goto errorout;
if (fr.Read(&numlin, 4) != 4) goto errorout;
numlin = LittleLong(numlin);
if (numlin != level.lines.Size()) goto errorout;
if (fr.Read(md5, 16) != 16) goto errorout;
map->GetChecksum(md5map);
if (memcmp(md5, md5map, 16)) goto errorout;
verts = new uint32_t[numlin * 8];
if (fr.Read(verts, 8 * numlin) != 8 * numlin) goto errorout;
if (fr.Read(magic, 4) != 4) goto errorout;
if (memcmp(magic, "ZGL2", 4) && memcmp(magic, "ZGL3", 4)) goto errorout;
try
{
P_LoadZNodes (fr, MAKE_ID(magic[0],magic[1],magic[2],magic[3]));
}
catch (CRecoverableError &error)
{
Printf ("Error loading nodes: %s\n", error.GetMessage());
level.subsectors.Clear();
level.segs.Clear();
level.nodes.Clear();
goto errorout;
}
for(auto &line : level.lines)
{
int i = line.Index();
line.v1 = &level.vertexes[LittleLong(verts[i*2])];
line.v2 = &level.vertexes[LittleLong(verts[i*2+1])];
}
delete [] verts;
return true;
errorout:
if (verts != NULL)
{
delete[] verts;
}
return false;
}
UNSAFE_CCMD(clearnodecache)
{
TArray<FFileList> list;
FString path = M_GetCachePath(false);
path += "/";
try
{
ScanDirectory(list, path);
}
catch (CRecoverableError &err)
{
Printf("%s\n", err.GetMessage());
return;
}
// Scan list backwards so that when we reach a directory
// all files within are already deleted.
for(int i = list.Size()-1; i >= 0; i--)
{
if (list[i].isDirectory)
{
rmdir(list[i].Filename);
}
else
{
remove(list[i].Filename);
}
}
}
//==========================================================================
//
// Keep both the original nodes from the WAD and the GL nodes created here.
// The original set is only being used to get the sector for in-game
// positioning of actors but not for rendering.
//
// This is necessary because ZDBSP is much more sensitive
// to sloppy mapping practices that produce overlapping sectors.
// The crane in P:AR E1M3 is a good example that would be broken if
// this wasn't done.
//
//==========================================================================
//==========================================================================
//
// PointOnLine
//
// Same as the one im the node builder, but not part of a specific class
//
//==========================================================================
static bool PointOnLine (int x, int y, int x1, int y1, int dx, int dy)
{
const double SIDE_EPSILON = 6.5536;
// For most cases, a simple dot product is enough.
double d_dx = double(dx);
double d_dy = double(dy);
double d_x = double(x);
double d_y = double(y);
double d_x1 = double(x1);
double d_y1 = double(y1);
double s_num = (d_y1-d_y)*d_dx - (d_x1-d_x)*d_dy;
if (fabs(s_num) < 17179869184.0) // 4<<32
{
// Either the point is very near the line, or the segment defining
// the line is very short: Do a more expensive test to determine
// just how far from the line the point is.
double l = g_sqrt(d_dx*d_dx+d_dy*d_dy);
double dist = fabs(s_num)/l;
if (dist < SIDE_EPSILON)
{
return true;
}
}
return false;
}
//==========================================================================
//
// SetRenderSector
//
// Sets the render sector for each GL subsector so that the proper flat
// information can be retrieved
//
//==========================================================================
void P_SetRenderSector()
{
int i;
uint32_t j;
TArray<subsector_t *> undetermined;
subsector_t * ss;
#if 0 // doesn't work as expected :(
// hide all sectors on textured automap that only have hidden lines.
bool *hidesec = new bool[numsectors];
for(i = 0; i < numsectors; i++)
{
hidesec[i] = true;
}
for(i = 0; i < numlines; i++)
{
if (!(lines[i].flags & ML_DONTDRAW))
{
hidesec[lines[i].frontsector - sectors] = false;
if (lines[i].backsector != NULL)
{
hidesec[lines[i].backsector - sectors] = false;
}
}
}
for(i = 0; i < numsectors; i++)
{
if (hidesec[i]) sectors[i].MoreFlags |= SECMF_HIDDEN;
}
delete [] hidesec;
#endif
// Check for incorrect partner seg info so that the following code does not crash.
for (auto &seg : level.segs)
{
auto p = seg.PartnerSeg;
if (p != nullptr)
{
int partner = p->Index();
if (partner < 0 || partner >= (int)level.segs.Size() || &level.segs[partner] != p)
{
seg.PartnerSeg = nullptr;
}
// glbsp creates such incorrect references for Strife.
if (seg.linedef && seg.PartnerSeg != nullptr && !seg.PartnerSeg->linedef)
{
seg.PartnerSeg = seg.PartnerSeg->PartnerSeg = nullptr;
}
}
}
for (auto &seg : level.segs)
{
if (seg.PartnerSeg != nullptr && seg.PartnerSeg->PartnerSeg != &seg)
{
seg.PartnerSeg = nullptr;
}
}
// look up sector number for each subsector
for (auto &ss : level.subsectors)
{
// For rendering pick the sector from the first seg that is a sector boundary
// this takes care of self-referencing sectors
seg_t *seg = ss.firstline;
// Check for one-dimensional subsectors. These should be ignored when
// being processed for automap drawing etc.
ss.flags |= SSECF_DEGENERATE;
for(j=2; j<ss.numlines; j++)
{
if (!PointOnLine(seg[j].v1->fixX(), seg[j].v1->fixY(), seg->v1->fixX(), seg->v1->fixY(), seg->v2->fixX() -seg->v1->fixX(), seg->v2->fixY() -seg->v1->fixY()))
{
// Not on the same line
ss.flags &= ~SSECF_DEGENERATE;
break;
}
}
seg = ss.firstline;
for(j=0; j<ss.numlines; j++)
{
if(seg->sidedef && (seg->PartnerSeg == nullptr || (seg->PartnerSeg->sidedef != nullptr && seg->sidedef->sector!=seg->PartnerSeg->sidedef->sector)))
{
ss.render_sector = seg->sidedef->sector;
break;
}
seg++;
}
if(ss.render_sector == NULL)
{
undetermined.Push(&ss);
}
}
// assign a vaild render sector to all subsectors which haven't been processed yet.
while (undetermined.Size())
{
bool deleted=false;
for(i=undetermined.Size()-1;i>=0;i--)
{
ss=undetermined[i];
seg_t * seg = ss->firstline;
for(j=0; j<ss->numlines; j++)
{
if (seg->PartnerSeg != nullptr && seg->PartnerSeg->Subsector)
{
sector_t * backsec = seg->PartnerSeg->Subsector->render_sector;
if (backsec)
{
ss->render_sector = backsec;
undetermined.Delete(i);
deleted = 1;
break;
}
}
seg++;
}
}
// We still got some left but the loop above was unable to assign them.
// This only happens when a subsector is off the map.
// Don't bother and just assign the real sector for rendering
if (!deleted && undetermined.Size())
{
for(i=undetermined.Size()-1;i>=0;i--)
{
ss=undetermined[i];
ss->render_sector=ss->sector;
}
break;
}
}
}