mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-15 00:41:57 +00:00
2d7592c2c3
- For easy comparison, changes between the built-in nodebuilder and ZDBSP must be kept to a minimum.
518 lines
15 KiB
C++
518 lines
15 KiB
C++
/*
|
|
** nodebuild_extract.cpp
|
|
**
|
|
** Converts the nodes, segs, and subsectors from the node builder's
|
|
** internal format to the format used by the rest of the game.
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** Copyright 2002-2006 Randy Heit
|
|
** 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 ZDoom or a ZDoom derivative, this code will be
|
|
** covered by 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 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 <string.h>
|
|
#include <float.h>
|
|
|
|
#include "nodebuild.h"
|
|
#include "templates.h"
|
|
|
|
#if 0
|
|
#define D(x) x
|
|
#define DD 1
|
|
#else
|
|
#define D(x) do{}while(0)
|
|
#undef DD
|
|
#endif
|
|
|
|
void FNodeBuilder::Extract (node_t *&outNodes, int &nodeCount,
|
|
seg_t *&outSegs, glsegextra_t *&outSegExtras, int &segCount,
|
|
subsector_t *&outSubs, int &subCount,
|
|
vertex_t *&outVerts, int &vertCount)
|
|
{
|
|
int i;
|
|
|
|
vertCount = Vertices.Size ();
|
|
outVerts = new vertex_t[vertCount];
|
|
|
|
for (i = 0; i < vertCount; ++i)
|
|
{
|
|
outVerts[i].x = Vertices[i].x;
|
|
outVerts[i].y = Vertices[i].y;
|
|
}
|
|
|
|
subCount = Subsectors.Size();
|
|
outSubs = new subsector_t[subCount];
|
|
memset(outSubs, 0, subCount * sizeof(subsector_t));
|
|
|
|
nodeCount = Nodes.Size ();
|
|
outNodes = new node_t[nodeCount];
|
|
|
|
memcpy (outNodes, &Nodes[0], nodeCount*sizeof(node_t));
|
|
for (i = 0; i < nodeCount; ++i)
|
|
{
|
|
D(Printf(PRINT_LOG, "Node %d: Splitter[%08x,%08x] [%08x,%08x]\n", i,
|
|
outNodes[i].x, outNodes[i].y, outNodes[i].dx, outNodes[i].dy));
|
|
// Go backwards because on 64-bit systems, both of the intchildren are
|
|
// inside the first in-game child.
|
|
for (int j = 1; j >= 0; --j)
|
|
{
|
|
if (outNodes[i].intchildren[j] & 0x80000000)
|
|
{
|
|
D(Printf(PRINT_LOG, " subsector %d\n", outNodes[i].intchildren[j] & 0x7FFFFFFF));
|
|
outNodes[i].children[j] = (BYTE *)(outSubs + (outNodes[i].intchildren[j] & 0x7fffffff)) + 1;
|
|
}
|
|
else
|
|
{
|
|
D(Printf(PRINT_LOG, " node %d\n", outNodes[i].intchildren[j]));
|
|
outNodes[i].children[j] = outNodes + outNodes[i].intchildren[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GLNodes)
|
|
{
|
|
TArray<glseg_t> segs (Segs.Size()*5/4);
|
|
|
|
for (i = 0; i < subCount; ++i)
|
|
{
|
|
DWORD numsegs = CloseSubsector (segs, i, outVerts);
|
|
outSubs[i].numlines = numsegs;
|
|
outSubs[i].firstline = (seg_t *)(size_t)(segs.Size() - numsegs);
|
|
}
|
|
|
|
segCount = segs.Size ();
|
|
outSegs = new seg_t[segCount];
|
|
outSegExtras = new glsegextra_t[segCount];
|
|
|
|
for (i = 0; i < segCount; ++i)
|
|
{
|
|
outSegs[i] = *(seg_t *)&segs[i];
|
|
|
|
if (segs[i].Partner != DWORD_MAX)
|
|
{
|
|
outSegExtras[i].PartnerSeg = Segs[segs[i].Partner].storedseg;
|
|
}
|
|
else
|
|
{
|
|
outSegExtras[i].PartnerSeg = DWORD_MAX;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
memcpy (outSubs, &Subsectors[0], subCount*sizeof(subsector_t));
|
|
segCount = Segs.Size ();
|
|
outSegs = new seg_t[segCount];
|
|
outSegExtras = NULL;
|
|
for (i = 0; i < segCount; ++i)
|
|
{
|
|
const FPrivSeg *org = &Segs[SegList[i].SegNum];
|
|
seg_t *out = &outSegs[i];
|
|
|
|
D(Printf(PRINT_LOG, "Seg %d: v1(%d) -> v2(%d)\n", i, org->v1, org->v2));
|
|
|
|
out->v1 = outVerts + org->v1;
|
|
out->v2 = outVerts + org->v2;
|
|
out->backsector = org->backsector;
|
|
out->frontsector = org->frontsector;
|
|
out->linedef = Level.Lines + org->linedef;
|
|
out->sidedef = Level.Sides + org->sidedef;
|
|
}
|
|
}
|
|
for (i = 0; i < subCount; ++i)
|
|
{
|
|
outSubs[i].firstline = &outSegs[(size_t)outSubs[i].firstline];
|
|
}
|
|
|
|
D(Printf("%i segs, %i nodes, %i subsectors\n", segCount, nodeCount, subCount));
|
|
|
|
for (i = 0; i < Level.NumLines; ++i)
|
|
{
|
|
Level.Lines[i].v1 = outVerts + (size_t)Level.Lines[i].v1;
|
|
Level.Lines[i].v2 = outVerts + (size_t)Level.Lines[i].v2;
|
|
}
|
|
}
|
|
|
|
void FNodeBuilder::ExtractMini (FMiniBSP *bsp)
|
|
{
|
|
unsigned int i;
|
|
|
|
bsp->bDirty = false;
|
|
bsp->Verts.Resize(Vertices.Size());
|
|
for (i = 0; i < Vertices.Size(); ++i)
|
|
{
|
|
bsp->Verts[i].x = Vertices[i].x;
|
|
bsp->Verts[i].y = Vertices[i].y;
|
|
}
|
|
|
|
bsp->Subsectors.Resize(Subsectors.Size());
|
|
memset(&bsp->Subsectors[0], 0, Subsectors.Size() * sizeof(subsector_t));
|
|
|
|
bsp->Nodes.Resize(Nodes.Size());
|
|
memcpy(&bsp->Nodes[0], &Nodes[0], Nodes.Size()*sizeof(node_t));
|
|
for (i = 0; i < Nodes.Size(); ++i)
|
|
{
|
|
D(Printf(PRINT_LOG, "Node %d:\n", i));
|
|
// Go backwards because on 64-bit systems, both of the intchildren are
|
|
// inside the first in-game child.
|
|
for (int j = 1; j >= 0; --j)
|
|
{
|
|
if (bsp->Nodes[i].intchildren[j] & 0x80000000)
|
|
{
|
|
D(Printf(PRINT_LOG, " subsector %d\n", bsp->Nodes[i].intchildren[j] & 0x7FFFFFFF));
|
|
bsp->Nodes[i].children[j] = (BYTE *)&bsp->Subsectors[bsp->Nodes[i].intchildren[j] & 0x7fffffff] + 1;
|
|
}
|
|
else
|
|
{
|
|
D(Printf(PRINT_LOG, " node %d\n", bsp->Nodes[i].intchildren[j]));
|
|
bsp->Nodes[i].children[j] = &bsp->Nodes[bsp->Nodes[i].intchildren[j]];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GLNodes)
|
|
{
|
|
TArray<glseg_t> glsegs;
|
|
for (i = 0; i < Subsectors.Size(); ++i)
|
|
{
|
|
DWORD numsegs = CloseSubsector (glsegs, i, &bsp->Verts[0]);
|
|
bsp->Subsectors[i].numlines = numsegs;
|
|
bsp->Subsectors[i].firstline = &bsp->Segs[bsp->Segs.Size() - numsegs];
|
|
}
|
|
bsp->Segs.Resize(glsegs.Size());
|
|
for (i = 0; i < glsegs.Size(); ++i)
|
|
{
|
|
bsp->Segs[i] = *(seg_t *)&glsegs[i];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
memcpy(&bsp->Subsectors[0], &Subsectors[0], Subsectors.Size()*sizeof(subsector_t));
|
|
bsp->Segs.Resize(Segs.Size());
|
|
for (i = 0; i < Segs.Size(); ++i)
|
|
{
|
|
const FPrivSeg *org = &Segs[SegList[i].SegNum];
|
|
seg_t *out = &bsp->Segs[i];
|
|
|
|
D(Printf(PRINT_LOG, "Seg %d: v1(%d) -> v2(%d)\n", i, org->v1, org->v2));
|
|
|
|
out->v1 = &bsp->Verts[org->v1];
|
|
out->v2 = &bsp->Verts[org->v2];
|
|
out->backsector = org->backsector;
|
|
out->frontsector = org->frontsector;
|
|
if (org->sidedef != int(NO_SIDE))
|
|
{
|
|
out->linedef = Level.Lines + org->linedef;
|
|
out->sidedef = Level.Sides + org->sidedef;
|
|
}
|
|
else // part of a miniseg
|
|
{
|
|
out->linedef = NULL;
|
|
out->sidedef = NULL;
|
|
}
|
|
}
|
|
for (i = 0; i < bsp->Subsectors.Size(); ++i)
|
|
{
|
|
bsp->Subsectors[i].firstline = &bsp->Segs[(size_t)bsp->Subsectors[i].firstline];
|
|
}
|
|
}
|
|
}
|
|
|
|
int FNodeBuilder::CloseSubsector (TArray<glseg_t> &segs, int subsector, vertex_t *outVerts)
|
|
{
|
|
FPrivSeg *seg, *prev;
|
|
angle_t prevAngle;
|
|
double accumx, accumy;
|
|
fixed_t midx, midy;
|
|
int firstVert;
|
|
DWORD first, max, count, i, j;
|
|
bool diffplanes;
|
|
int firstplane;
|
|
|
|
first = (DWORD)(size_t)Subsectors[subsector].firstline;
|
|
max = first + Subsectors[subsector].numlines;
|
|
count = 0;
|
|
|
|
accumx = accumy = 0.0;
|
|
diffplanes = false;
|
|
firstplane = Segs[SegList[first].SegNum].planenum;
|
|
|
|
// Calculate the midpoint of the subsector and also check for degenerate subsectors.
|
|
// A subsector is degenerate if it exists in only one dimension, which can be
|
|
// detected when all the segs lie in the same plane. This can happen if you have
|
|
// outward-facing lines in the void that don't point toward any sector. (Some of the
|
|
// polyobjects in Hexen are constructed like this.)
|
|
for (i = first; i < max; ++i)
|
|
{
|
|
seg = &Segs[SegList[i].SegNum];
|
|
accumx += double(Vertices[seg->v1].x) + double(Vertices[seg->v2].x);
|
|
accumy += double(Vertices[seg->v1].y) + double(Vertices[seg->v2].y);
|
|
if (firstplane != seg->planenum)
|
|
{
|
|
diffplanes = true;
|
|
}
|
|
}
|
|
|
|
midx = fixed_t(accumx / (max - first) / 2);
|
|
midy = fixed_t(accumy / (max - first) / 2);
|
|
|
|
seg = &Segs[SegList[first].SegNum];
|
|
prevAngle = PointToAngle (Vertices[seg->v1].x - midx, Vertices[seg->v1].y - midy);
|
|
seg->storedseg = PushGLSeg (segs, seg, outVerts);
|
|
count = 1;
|
|
prev = seg;
|
|
firstVert = seg->v1;
|
|
|
|
#ifdef DD
|
|
Printf(PRINT_LOG, "--%d--\n", subsector);
|
|
for (j = first; j < max; ++j)
|
|
{
|
|
seg = &Segs[SegList[j].SegNum];
|
|
angle_t ang = PointToAngle (Vertices[seg->v1].x - midx, Vertices[seg->v1].y - midy);
|
|
Printf(PRINT_LOG, "%d%c %5d(%5d,%5d)->%5d(%5d,%5d) - %3.5f %d,%d [%08x,%08x]-[%08x,%08x]\n", j,
|
|
seg->linedef == -1 ? '+' : ':',
|
|
seg->v1, Vertices[seg->v1].x>>16, Vertices[seg->v1].y>>16,
|
|
seg->v2, Vertices[seg->v2].x>>16, Vertices[seg->v2].y>>16,
|
|
double(ang/2)*180/(1<<30),
|
|
seg->planenum, seg->planefront,
|
|
Vertices[seg->v1].x, Vertices[seg->v1].y,
|
|
Vertices[seg->v2].x, Vertices[seg->v2].y);
|
|
}
|
|
#endif
|
|
|
|
if (diffplanes)
|
|
{ // A well-behaved subsector. Output the segs sorted by the angle formed by connecting
|
|
// the subsector's center to their first vertex.
|
|
|
|
D(Printf(PRINT_LOG, "Well behaved subsector\n"));
|
|
for (i = first + 1; i < max; ++i)
|
|
{
|
|
angle_t bestdiff = ANGLE_MAX;
|
|
FPrivSeg *bestseg = NULL;
|
|
DWORD bestj = DWORD_MAX;
|
|
j = first;
|
|
do
|
|
{
|
|
seg = &Segs[SegList[j].SegNum];
|
|
angle_t ang = PointToAngle (Vertices[seg->v1].x - midx, Vertices[seg->v1].y - midy);
|
|
angle_t diff = prevAngle - ang;
|
|
if (seg->v1 == prev->v2)
|
|
{
|
|
bestdiff = diff;
|
|
bestseg = seg;
|
|
bestj = j;
|
|
break;
|
|
}
|
|
if (diff < bestdiff && diff > 0)
|
|
{
|
|
bestdiff = diff;
|
|
bestseg = seg;
|
|
bestj = j;
|
|
}
|
|
}
|
|
while (++j < max);
|
|
// Is a NULL bestseg actually okay?
|
|
if (bestseg != NULL)
|
|
{
|
|
seg = bestseg;
|
|
}
|
|
if (prev->v2 != seg->v1)
|
|
{
|
|
// Add a new miniseg to connect the two segs
|
|
PushConnectingGLSeg (subsector, segs, &outVerts[prev->v2], &outVerts[seg->v1]);
|
|
count++;
|
|
}
|
|
#ifdef DD
|
|
Printf(PRINT_LOG, "+%d\n", bestj);
|
|
#endif
|
|
prevAngle -= bestdiff;
|
|
seg->storedseg = PushGLSeg (segs, seg, outVerts);
|
|
count++;
|
|
prev = seg;
|
|
if (seg->v2 == firstVert)
|
|
{
|
|
prev = seg;
|
|
break;
|
|
}
|
|
}
|
|
#ifdef DD
|
|
Printf(PRINT_LOG, "\n");
|
|
#endif
|
|
}
|
|
else
|
|
{ // A degenerate subsector. These are handled in three stages:
|
|
// Stage 1. Proceed in the same direction as the start seg until we
|
|
// hit the seg furthest from it.
|
|
// Stage 2. Reverse direction and proceed until we hit the seg
|
|
// furthest from the start seg.
|
|
// Stage 3. Reverse direction again and insert segs until we get
|
|
// to the start seg.
|
|
// A dot product serves to determine distance from the start seg.
|
|
|
|
D(Printf(PRINT_LOG, "degenerate subsector\n"));
|
|
|
|
// Stage 1. Go forward.
|
|
count += OutputDegenerateSubsector (segs, subsector, true, 0, prev, outVerts);
|
|
|
|
// Stage 2. Go backward.
|
|
count += OutputDegenerateSubsector (segs, subsector, false, DBL_MAX, prev, outVerts);
|
|
|
|
// Stage 3. Go forward again.
|
|
count += OutputDegenerateSubsector (segs, subsector, true, -DBL_MAX, prev, outVerts);
|
|
}
|
|
|
|
if (prev->v2 != firstVert)
|
|
{
|
|
PushConnectingGLSeg (subsector, segs, &outVerts[prev->v2], &outVerts[firstVert]);
|
|
count++;
|
|
}
|
|
#ifdef DD
|
|
Printf(PRINT_LOG, "Output GL subsector %d:\n", subsector);
|
|
for (i = segs.Size() - count; i < (int)segs.Size(); ++i)
|
|
{
|
|
Printf(PRINT_LOG, " Seg %5d%c(%5d,%5d)-(%5d,%5d) [%08x,%08x]-[%08x,%08x]\n", i,
|
|
segs[i].linedef == NULL ? '+' : ' ',
|
|
segs[i].v1->x>>16,
|
|
segs[i].v1->y>>16,
|
|
segs[i].v2->x>>16,
|
|
segs[i].v2->y>>16,
|
|
segs[i].v1->x,
|
|
segs[i].v1->y,
|
|
segs[i].v2->x,
|
|
segs[i].v2->y);
|
|
}
|
|
#endif
|
|
|
|
return count;
|
|
}
|
|
|
|
int FNodeBuilder::OutputDegenerateSubsector (TArray<glseg_t> &segs, int subsector, bool bForward, double lastdot, FPrivSeg *&prev, vertex_t *outVerts)
|
|
{
|
|
static const double bestinit[2] = { -DBL_MAX, DBL_MAX };
|
|
FPrivSeg *seg;
|
|
int i, j, first, max, count;
|
|
double dot, x1, y1, dx, dy, dx2, dy2;
|
|
bool wantside;
|
|
|
|
first = (DWORD)(size_t)Subsectors[subsector].firstline;
|
|
max = first + Subsectors[subsector].numlines;
|
|
count = 0;
|
|
|
|
seg = &Segs[SegList[first].SegNum];
|
|
x1 = Vertices[seg->v1].x;
|
|
y1 = Vertices[seg->v1].y;
|
|
dx = Vertices[seg->v2].x - x1;
|
|
dy = Vertices[seg->v2].y - y1;
|
|
wantside = seg->planefront ^ !bForward;
|
|
|
|
for (i = first + 1; i < max; ++i)
|
|
{
|
|
double bestdot = bestinit[bForward];
|
|
FPrivSeg *bestseg = NULL;
|
|
for (j = first + 1; j < max; ++j)
|
|
{
|
|
seg = &Segs[SegList[j].SegNum];
|
|
if (seg->planefront != wantside)
|
|
{
|
|
continue;
|
|
}
|
|
dx2 = Vertices[seg->v1].x - x1;
|
|
dy2 = Vertices[seg->v1].y - y1;
|
|
dot = dx*dx2 + dy*dy2;
|
|
|
|
if (bForward)
|
|
{
|
|
if (dot < bestdot && dot > lastdot)
|
|
{
|
|
bestdot = dot;
|
|
bestseg = seg;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (dot > bestdot && dot < lastdot)
|
|
{
|
|
bestdot = dot;
|
|
bestseg = seg;
|
|
}
|
|
}
|
|
}
|
|
if (bestseg != NULL)
|
|
{
|
|
if (prev->v2 != bestseg->v1)
|
|
{
|
|
PushConnectingGLSeg (subsector, segs, &outVerts[prev->v2], &outVerts[bestseg->v1]);
|
|
count++;
|
|
}
|
|
seg->storedseg = PushGLSeg (segs, bestseg, outVerts);
|
|
count++;
|
|
prev = bestseg;
|
|
lastdot = bestdot;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
DWORD FNodeBuilder::PushGLSeg (TArray<glseg_t> &segs, const FPrivSeg *seg, vertex_t *outVerts)
|
|
{
|
|
glseg_t newseg;
|
|
|
|
newseg.v1 = outVerts + seg->v1;
|
|
newseg.v2 = outVerts + seg->v2;
|
|
newseg.backsector = seg->backsector;
|
|
newseg.frontsector = seg->frontsector;
|
|
if (seg->linedef != -1)
|
|
{
|
|
newseg.linedef = Level.Lines + seg->linedef;
|
|
newseg.sidedef = Level.Sides + seg->sidedef;
|
|
}
|
|
else
|
|
{
|
|
newseg.linedef = NULL;
|
|
newseg.sidedef = NULL;
|
|
}
|
|
newseg.Partner = seg->partner;
|
|
return (DWORD)segs.Push (newseg);
|
|
}
|
|
|
|
void FNodeBuilder::PushConnectingGLSeg (int subsector, TArray<glseg_t> &segs, vertex_t *v1, vertex_t *v2)
|
|
{
|
|
glseg_t newseg;
|
|
|
|
newseg.v1 = v1;
|
|
newseg.v2 = v2;
|
|
newseg.backsector = NULL;
|
|
newseg.frontsector = NULL;
|
|
newseg.linedef = NULL;
|
|
newseg.sidedef = NULL;
|
|
newseg.Partner = DWORD_MAX;
|
|
segs.Push (newseg);
|
|
}
|