zdbsp/nodebuild_extract.cpp

561 lines
15 KiB
C++
Raw Normal View History

/*
Routines for extracting usable data from the new BSP tree.
Copyright (C) 2002-2006 Randy Heit
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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <string.h>
#include <stdio.h>
#include <float.h>
#include "zdbsp.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::GetGLNodes (MapNodeEx *&outNodes, int &nodeCount,
MapSegGLEx *&outSegs, int &segCount,
MapSubsectorEx *&outSubs, int &subCount)
{
TArray<MapSegGLEx> segs (Segs.Size()*5/4);
int i, j, k;
nodeCount = Nodes.Size ();
outNodes = new MapNodeEx[nodeCount];
for (i = 0; i < nodeCount; ++i)
{
const node_t *orgnode = &Nodes[i];
MapNodeEx *newnode = &outNodes[i];
newnode->x = short(orgnode->x >> FRACBITS);
newnode->y = short(orgnode->y >> FRACBITS);
newnode->dx = short(orgnode->dx >> FRACBITS);
newnode->dy = short(orgnode->dy >> FRACBITS);
for (j = 0; j < 2; ++j)
{
for (k = 0; k < 4; ++k)
{
newnode->bbox[j][k] = orgnode->bbox[j][k] >> FRACBITS;
}
newnode->children[j] = orgnode->intchildren[j];
}
}
subCount = Subsectors.Size();
outSubs = new MapSubsectorEx[subCount];
for (i = 0; i < subCount; ++i)
{
int numsegs = CloseSubsector (segs, i);
outSubs[i].numlines = numsegs;
outSubs[i].firstline = segs.Size() - numsegs;
}
segCount = segs.Size ();
outSegs = new MapSegGLEx[segCount];
memcpy (outSegs, &segs[0], segCount*sizeof(MapSegGLEx));
for (i = 0; i < segCount; ++i)
{
if (outSegs[i].partner != DWORD_MAX)
{
outSegs[i].partner = Segs[outSegs[i].partner].storedseg;
}
}
}
int FNodeBuilder::CloseSubsector (TArray<MapSegGLEx> &segs, int subsector)
{
FPrivSeg *seg, *prev;
angle_t prevAngle;
double accumx, accumy;
fixed_t midx, midy;
int i, j, first, max, count, firstVert;
bool diffplanes;
int firstplane;
first = 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);
count = 1;
prev = seg;
firstVert = seg->v1;
#ifdef DD
printf("--%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 ("%d%c %5d(%5d,%5d)->%5d(%5d,%5d) - %3.3f %d,%d\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);
}
#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("Well behaved subsector\n"));
for (i = first + 1; i < max; ++i)
{
angle_t bestdiff = ANGLE_MAX;
FPrivSeg *bestseg = NULL;
int bestj = -1;
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);
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;
}
}
if (bestseg != NULL)
{
seg = bestseg;
}
if (prev->v2 != seg->v1)
{
// Add a new miniseg to connect the two segs
PushConnectingGLSeg (subsector, segs, prev->v2, seg->v1);
count++;
}
#ifdef DD
printf ("+%d\n", bestj);
#endif
prevAngle -= bestdiff;
seg->storedseg = PushGLSeg (segs, seg);
count++;
prev = seg;
if (seg->v2 == firstVert)
{
prev = seg;
break;
}
}
#ifdef DD
printf ("\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("degenerate subsector\n"));
// Stage 1. Go forward.
count += OutputDegenerateSubsector (segs, subsector, true, 0, prev);
// Stage 2. Go backward.
count += OutputDegenerateSubsector (segs, subsector, false, DBL_MAX, prev);
// Stage 3. Go forward again.
count += OutputDegenerateSubsector (segs, subsector, true, -DBL_MAX, prev);
}
if (prev->v2 != firstVert)
{
PushConnectingGLSeg (subsector, segs, prev->v2, firstVert);
count++;
}
#ifdef DD
printf ("Output GL subsector %d:\n", subsector);
for (i = segs.Size() - count; i < (int)segs.Size(); ++i)
{
printf (" Seg %5d%c(%5d,%5d)-(%5d,%5d)\n", i,
segs[i].linedef == NO_INDEX ? '+' : ' ',
Vertices[segs[i].v1].x>>16,
Vertices[segs[i].v1].y>>16,
Vertices[segs[i].v2].x>>16,
Vertices[segs[i].v2].y>>16);
}
#endif
return count;
}
int FNodeBuilder::OutputDegenerateSubsector (TArray<MapSegGLEx> &segs, int subsector, bool bForward, double lastdot, FPrivSeg *&prev)
{
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 = 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, prev->v2, bestseg->v1);
count++;
}
seg->storedseg = PushGLSeg (segs, bestseg);
count++;
prev = bestseg;
lastdot = bestdot;
}
}
return count;
}
DWORD FNodeBuilder::PushGLSeg (TArray<MapSegGLEx> &segs, const FPrivSeg *seg)
{
MapSegGLEx newseg;
newseg.v1 = seg->v1;
newseg.v2 = seg->v2;
newseg.linedef = seg->linedef;
// Just checking the sidedef to determine the side is insufficient.
// When a level is sidedef compressed both sides may well have the same sidedef.
if (newseg.linedef != NO_INDEX)
{
IntLineDef *ld = &Level.Lines[newseg.linedef];
if (ld->sidenum[0] == ld->sidenum[1])
{
// When both sidedefs are the same a quick check doesn't work so this
// has to be done by comparing the distances of the seg's end point to
// the line's start.
WideVertex *lv1 = &Level.Vertices[ld->v1];
WideVertex *sv1 = &Level.Vertices[seg->v1];
WideVertex *sv2 = &Level.Vertices[seg->v2];
double dist1sq = double(sv1->x-lv1->x)*(sv1->x-lv1->x) + double(sv1->y-lv1->y)*(sv1->y-lv1->y);
double dist2sq = double(sv2->x-lv1->x)*(sv2->x-lv1->x) + double(sv2->y-lv1->y)*(sv2->y-lv1->y);
newseg.side = dist1sq < dist2sq ? 0 : 1;
}
else
{
newseg.side = ld->sidenum[1] == seg->sidedef ? 1 : 0;
}
}
else
{
newseg.side = 0;
}
newseg.partner = seg->partner;
return segs.Push (newseg);
}
void FNodeBuilder::PushConnectingGLSeg (int subsector, TArray<MapSegGLEx> &segs, int v1, int v2)
{
MapSegGLEx newseg;
Warn ("Unclosed subsector %d, from (%d,%d) to (%d,%d)\n", subsector,
Vertices[v1].x >> FRACBITS, Vertices[v1].y >> FRACBITS,
Vertices[v2].x >> FRACBITS, Vertices[v2].y >> FRACBITS);
newseg.v1 = v1;
newseg.v2 = v2;
newseg.linedef = NO_MAP_INDEX;
newseg.side = 0;
newseg.partner = DWORD_MAX;
segs.Push (newseg);
}
void FNodeBuilder::GetVertices (WideVertex *&verts, int &count)
{
count = Vertices.Size ();
verts = new WideVertex[count];
for (int i = 0; i < count; ++i)
{
verts[i].x = Vertices[i].x;
verts[i].y = Vertices[i].y;
verts[i].index = Vertices[i].index;
}
}
void FNodeBuilder::GetNodes (MapNodeEx *&outNodes, int &nodeCount,
MapSegEx *&outSegs, int &segCount,
MapSubsectorEx *&outSubs, int &subCount)
{
short bbox[4];
TArray<MapSegEx> segs (Segs.Size());
// Walk the BSP and create a new BSP with only the information
// suitable for a standard tree. At a minimum, this means removing
// all minisegs. As an optional step, I also recompute all the
// nodes' bounding boxes so that they only bound the real segs and
// not the minisegs.
nodeCount = Nodes.Size ();
outNodes = new MapNodeEx[nodeCount];
subCount = Subsectors.Size ();
outSubs = new MapSubsectorEx[subCount];
RemoveMinisegs (outNodes, segs, outSubs, Nodes.Size() - 1, bbox);
segCount = segs.Size ();
outSegs = new MapSegEx[segCount];
memcpy (outSegs, &segs[0], segCount*sizeof(MapSegEx));
#ifdef DD
int i, j;
for (i = 0; i < nodeCount; ++i)
{
printf("Node %d:\n", i);
for (j = 1; j >= 0; --j)
{
if (outNodes[i].children[j] & NFX_SUBSECTOR)
{
printf(" subsector %d\n", outNodes[i].children[j] & ~NFX_SUBSECTOR);
}
else
{
printf(" node %d\n", outNodes[i].children[j]);
}
}
}
for (i = 0; i < segCount; ++i)
{
printf("Seg %d: v1(%d) -> v2(%d)\n", i, outSegs[i].v1, outSegs[i].v2);
}
#endif
}
int FNodeBuilder::RemoveMinisegs (MapNodeEx *nodes,
TArray<MapSegEx> &segs, MapSubsectorEx *subs, int node, short bbox[4])
{
if (node & NFX_SUBSECTOR)
{
int subnum = node == -1 ? 0 : node & ~NFX_SUBSECTOR;
int numsegs = StripMinisegs (segs, subnum, bbox);
subs[subnum].numlines = numsegs;
subs[subnum].firstline = segs.Size() - numsegs;
return NFX_SUBSECTOR | subnum;
}
else
{
const node_t *orgnode = &Nodes[node];
MapNodeEx *newnode = &nodes[node];
int child0 = RemoveMinisegs (nodes, segs, subs, orgnode->intchildren[0], newnode->bbox[0]);
int child1 = RemoveMinisegs (nodes, segs, subs, orgnode->intchildren[1], newnode->bbox[1]);
newnode->x = orgnode->x >> FRACBITS;
newnode->y = orgnode->y >> FRACBITS;
newnode->dx = orgnode->dx >> FRACBITS;
newnode->dy = orgnode->dy >> FRACBITS;
newnode->children[0] = child0;
newnode->children[1] = child1;
bbox[BOXTOP] = MAX(newnode->bbox[0][BOXTOP], newnode->bbox[1][BOXTOP]);
bbox[BOXBOTTOM] = MIN(newnode->bbox[0][BOXBOTTOM], newnode->bbox[1][BOXBOTTOM]);
bbox[BOXLEFT] = MIN(newnode->bbox[0][BOXLEFT], newnode->bbox[1][BOXLEFT]);
bbox[BOXRIGHT] = MAX(newnode->bbox[0][BOXRIGHT], newnode->bbox[1][BOXRIGHT]);
return node;
}
}
int FNodeBuilder::StripMinisegs (TArray<MapSegEx> &segs, int subsector, short bbox[4])
{
int count, i, max;
// The bounding box is recomputed to only cover the real segs and not the
// minisegs in the subsector.
bbox[BOXTOP] = -32768;
bbox[BOXBOTTOM] = 32767;
bbox[BOXLEFT] = 32767;
bbox[BOXRIGHT] = -32768;
i = Subsectors[subsector].firstline;
max = Subsectors[subsector].numlines + i;
for (count = 0; i < max; ++i)
{
const FPrivSeg *org = &Segs[SegList[i].SegNum];
// Because of the ordering guaranteed by SortSegs(), all mini segs will
// be at the end of the subsector, so once one is encountered, we can
// stop right away.
if (org->linedef == -1)
{
break;
}
else
{
MapSegEx newseg;
AddSegToShortBBox (bbox, org);
newseg.v1 = org->v1;
newseg.v2 = org->v2;
newseg.angle = org->angle >> 16;
newseg.offset = org->offset >> FRACBITS;
newseg.linedef = org->linedef;
// Just checking the sidedef to determine the side is insufficient.
// When a level is sidedef compressed both sides may well have the same sidedef.
IntLineDef * ld = &Level.Lines[newseg.linedef];
if (ld->sidenum[0]==ld->sidenum[1])
{
// When both sidedefs are the same a quick check doesn't work so this
// has to be done by comparing the distances of the seg's end point to
// the line's start.
WideVertex * lv1 = &Level.Vertices[ld->v1];
WideVertex * sv1 = &Level.Vertices[org->v1];
WideVertex * sv2 = &Level.Vertices[org->v2];
double dist1sq = double(sv1->x-lv1->x)*(sv1->x-lv1->x) + double(sv1->y-lv1->y)*(sv1->y-lv1->y);
double dist2sq = double(sv2->x-lv1->x)*(sv2->x-lv1->x) + double(sv2->y-lv1->y)*(sv2->y-lv1->y);
newseg.side = dist1sq<dist2sq? 0:1;
}
else
{
newseg.side = ld->sidenum[1] == org->sidedef ? 1 : 0;
}
newseg.side = Level.Lines[org->linedef].sidenum[1] == org->sidedef ? 1 : 0;
segs.Push (newseg);
++count;
}
}
return count;
}
void FNodeBuilder::AddSegToShortBBox (short bbox[4], const FPrivSeg *seg)
{
const FPrivVert *v1 = &Vertices[seg->v1];
const FPrivVert *v2 = &Vertices[seg->v2];
short v1x = v1->x >> FRACBITS;
short v1y = v1->y >> FRACBITS;
short v2x = v2->x >> FRACBITS;
short v2y = v2->y >> FRACBITS;
if (v1x < bbox[BOXLEFT]) bbox[BOXLEFT] = v1x;
if (v1x > bbox[BOXRIGHT]) bbox[BOXRIGHT] = v1x;
if (v1y < bbox[BOXBOTTOM]) bbox[BOXBOTTOM] = v1y;
if (v1y > bbox[BOXTOP]) bbox[BOXTOP] = v1y;
if (v2x < bbox[BOXLEFT]) bbox[BOXLEFT] = v2x;
if (v2x > bbox[BOXRIGHT]) bbox[BOXRIGHT] = v2x;
if (v2y < bbox[BOXBOTTOM]) bbox[BOXBOTTOM] = v2y;
if (v2y > bbox[BOXTOP]) bbox[BOXTOP] = v2y;
}