From f7dd0ec4a2a5ebcef486b49d7373452c83b12c2e Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 3 Apr 2021 12:44:30 +0200 Subject: [PATCH] - use ZDoom's node builder for triangulating sectors that fail the simple approach. As it turned out, the triangulator only works fine for regular polygons, but creates incomplete meshes for sectors with multiple sections or some degenerate areas, which are quite common with swinging doors. The node builder is more costly and creates wall splits, of course, but it does not create broken output for degenerate sectors so it's a good fallback. --- source/CMakeLists.txt | 8 + source/core/nodebuilder/nodebuild.cpp | 1059 +++++++++++++++++ source/core/nodebuilder/nodebuild.h | 470 ++++++++ .../nodebuilder/nodebuild_classify_nosse2.cpp | 135 +++ source/core/nodebuilder/nodebuild_events.cpp | 226 ++++ source/core/nodebuilder/nodebuild_extract.cpp | 440 +++++++ source/core/nodebuilder/nodebuild_gl.cpp | 397 ++++++ source/core/nodebuilder/nodebuild_utility.cpp | 535 +++++++++ source/core/sectorgeometry.cpp | 109 +- source/core/sectorgeometry.h | 4 +- 10 files changed, 3378 insertions(+), 5 deletions(-) create mode 100644 source/core/nodebuilder/nodebuild.cpp create mode 100644 source/core/nodebuilder/nodebuild.h create mode 100644 source/core/nodebuilder/nodebuild_classify_nosse2.cpp create mode 100644 source/core/nodebuilder/nodebuild_events.cpp create mode 100644 source/core/nodebuilder/nodebuild_extract.cpp create mode 100644 source/core/nodebuilder/nodebuild_gl.cpp create mode 100644 source/core/nodebuilder/nodebuild_utility.cpp diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 99b5d0e26..f5149b96e 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -640,6 +640,7 @@ file( GLOB HEADER_FILES core/input/*.h core/rendering/*.h core/rendering/scene/*.h + core/nodebuilder/*.h common/audio/sound/thirdparty/*.h common/audio/sound/*.h @@ -1071,6 +1072,13 @@ set (PCH_SOURCES core/statusbar2.cpp core/gi.cpp + core/nodebuilder/nodebuild.cpp + core/nodebuilder/nodebuild_classify_nosse2.cpp + core/nodebuilder/nodebuild_events.cpp + core/nodebuilder/nodebuild_extract.cpp + core/nodebuilder/nodebuild_gl.cpp + core/nodebuilder/nodebuild_utility.cpp + core/rendering/hw_entrypoint.cpp core/rendering/hw_models.cpp core/rendering/scene/hw_clipper.cpp diff --git a/source/core/nodebuilder/nodebuild.cpp b/source/core/nodebuilder/nodebuild.cpp new file mode 100644 index 000000000..f355cba9c --- /dev/null +++ b/source/core/nodebuilder/nodebuild.cpp @@ -0,0 +1,1059 @@ +/* +** nodebuild.cpp +** +** The main logic for the internal node builder. +** +**--------------------------------------------------------------------------- +** 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 +#include +#include + +#include "nodebuild.h" +#include "m_bbox.h" +#include "printf.h" + +const int MaxSegs = 64; +const int SplitCost = 8; +const int AAPreference = 16; + +#if 0 +#define D(x) x +#else +#define D(x) do{}while(0) +#endif + +FNodeBuilder::FNodeBuilder (FLevel &lev) + : Level(lev), GLNodes(true), SegsStuffed(0) +{ + VertexMap = new FVertexMap (*this, Level.MinX, Level.MinY, Level.MaxX, Level.MaxY); + FindUsedVertices (Level.Vertices, Level.NumVertices); + MakeSegsFromSides (); + GroupSegPlanes (); + BuildTree (); +} + +FNodeBuilder::~FNodeBuilder() +{ + if (VertexMap != NULL) + { + delete VertexMap; + } + if (OldVertexTable != NULL) + { + delete[] OldVertexTable; + } +} + +void FNodeBuilder::BuildMini(bool makeGLNodes) +{ + GLNodes = makeGLNodes; + GroupSegPlanesSimple(); + BuildTree(); +} + +void FNodeBuilder::Clear() +{ + SegsStuffed = 0; + Nodes.Clear(); + Subsectors.Clear(); + SubsectorSets.Clear(); + Segs.Clear(); + Vertices.Clear(); + SegList.Clear(); + PlaneChecked.Clear(); + Planes.Clear(); + Touched.Clear(); + Colinear.Clear(); + SplitSharers.Clear(); + if (VertexMap == NULL) + { + VertexMap = new FVertexMapSimple(*this); + } +} + +void FNodeBuilder::BuildTree () +{ + fixed_t bbox[4]; + + HackSeg = UINT_MAX; + HackMate = UINT_MAX; + CreateNode (0, Segs.Size(), bbox); + CreateSubsectorsForReal (); +} + +int FNodeBuilder::CreateNode (uint32_t set, unsigned int count, fixed_t bbox[4]) +{ + node_t node; + int skip, selstat; + uint32_t splitseg; + + skip = int(count / MaxSegs); + + // When building GL nodes, count may not be an exact count of the number of segs + // in the set. That's okay, because we just use it to get a skip count, so an + // estimate is fine. + if ((selstat = SelectSplitter (set, node, splitseg, skip, true)) > 0 || + (skip > 0 && (selstat = SelectSplitter (set, node, splitseg, 1, true)) > 0) || + (selstat < 0 && (SelectSplitter (set, node, splitseg, skip, false) > 0 || + (skip > 0 && SelectSplitter (set, node, splitseg, 1, false)))) || + CheckSubsector (set, node, splitseg)) + { + // Create a normal node + uint32_t set1, set2; + unsigned int count1, count2; + + SplitSegs (set, node, splitseg, set1, set2, count1, count2); + D(PrintSet (1, set1)); + D(Printf (PRINT_LOG, "(%d,%d) delta (%d,%d) from seg %d\n", node.x>>16, node.y>>16, node.dx>>16, node.dy>>16, splitseg)); + D(PrintSet (2, set2)); + node.intchildren[0] = CreateNode (set1, count1, node.nb_bbox[0]); + node.intchildren[1] = CreateNode (set2, count2, node.nb_bbox[1]); + bbox[BOXTOP] = MAX (node.nb_bbox[0][BOXTOP], node.nb_bbox[1][BOXTOP]); + bbox[BOXBOTTOM] = MIN (node.nb_bbox[0][BOXBOTTOM], node.nb_bbox[1][BOXBOTTOM]); + bbox[BOXLEFT] = MIN (node.nb_bbox[0][BOXLEFT], node.nb_bbox[1][BOXLEFT]); + bbox[BOXRIGHT] = MAX (node.nb_bbox[0][BOXRIGHT], node.nb_bbox[1][BOXRIGHT]); + return (int)Nodes.Push (node); + } + else + { + return 0x80000000 | CreateSubsector (set, bbox); + } +} + +int FNodeBuilder::CreateSubsector (uint32_t set, fixed_t bbox[4]) +{ + int ssnum, count; + + bbox[BOXTOP] = bbox[BOXRIGHT] = INT_MIN; + bbox[BOXBOTTOM] = bbox[BOXLEFT] = INT_MAX; + + D(Printf (PRINT_LOG, "Subsector from set %d\n", set)); + + assert (set != UINT_MAX); + + // We cannot actually create the subsector now because the node building + // process might split a seg in this subsector (because all partner segs + // must use the same pair of vertices), adding a new seg that hasn't been + // created yet. After all the nodes are built, then we can create the + // actual subsectors using the CreateSubsectorsForReal function below. + ssnum = (int)SubsectorSets.Push (set); + + count = 0; + while (set != UINT_MAX) + { + AddSegToBBox (bbox, &Segs[set]); + set = Segs[set].next; + count++; + } + + SegsStuffed += count; + + D(Printf (PRINT_LOG, "bbox (%d,%d)-(%d,%d)\n", bbox[BOXLEFT]>>16, bbox[BOXBOTTOM]>>16, bbox[BOXRIGHT]>>16, bbox[BOXTOP]>>16)); + + return ssnum; +} + +void FNodeBuilder::CreateSubsectorsForReal () +{ + subsector_t sub; + unsigned int i; + + sub.sector = NULL; + + for (i = 0; i < SubsectorSets.Size(); ++i) + { + uint32_t set = SubsectorSets[i]; + uint32_t firstline = (uint32_t)SegList.Size(); + + while (set != UINT_MAX) + { + USegPtr ptr; + + ptr.SegPtr = &Segs[set]; + SegList.Push (ptr); + set = ptr.SegPtr->next; + } + sub.numlines = (uint32_t)(SegList.Size() - firstline); + sub.firstline = (seg_t *)(size_t)firstline; + + // Sort segs by linedef for special effects + qsort (&SegList[firstline], sub.numlines, sizeof(USegPtr), SortSegs); + + // Convert seg pointers into indices + D(Printf (PRINT_LOG, "Output subsector %d:\n", Subsectors.Size())); + for (unsigned int i = firstline; i < SegList.Size(); ++i) + { + D(Printf (PRINT_LOG, " Seg %5d%c%d(%5d,%5d)-%d(%5d,%5d) [%08x,%08x]-[%08x,%08x]\n", SegList[i].SegPtr - &Segs[0], + SegList[i].SegPtr->linedef == -1 ? '+' : ' ', + SegList[i].SegPtr->v1, + Vertices[SegList[i].SegPtr->v1].x>>16, + Vertices[SegList[i].SegPtr->v1].y>>16, + SegList[i].SegPtr->v2, + Vertices[SegList[i].SegPtr->v2].x>>16, + Vertices[SegList[i].SegPtr->v2].y>>16, + Vertices[SegList[i].SegPtr->v1].x, Vertices[SegList[i].SegPtr->v1].y, + Vertices[SegList[i].SegPtr->v2].x, Vertices[SegList[i].SegPtr->v2].y)); + SegList[i].SegNum = uint32_t(SegList[i].SegPtr - &Segs[0]); + } + Subsectors.Push (sub); + } +} + +int FNodeBuilder::SortSegs (const void *a, const void *b) +{ + const FPrivSeg *x = ((const USegPtr *)a)->SegPtr; + const FPrivSeg *y = ((const USegPtr *)b)->SegPtr; + + // Segs are grouped into three categories in this order: + // + // 1. Segs with different front and back sectors (or no back at all). + // 2. Segs with the same front and back sectors. + // 3. Minisegs. + // + // Within the first two sets, segs are also sorted by linedef. + // + // Note that when GL subsectors are written, the segs will be reordered + // so that they are in clockwise order, and extra minisegs will be added + // as needed to close the subsector. But the first seg used will still be + // the first seg chosen here. + + int xtype, ytype; + + if (x->linedef == -1) + { + xtype = 2; + } + else if (x->frontsector == x->backsector) + { + xtype = 1; + } + else + { + xtype = 0; + } + + if (y->linedef == -1) + { + ytype = 2; + } + else if (y->frontsector == y->backsector) + { + ytype = 1; + } + else + { + ytype = 0; + } + + if (xtype != ytype) + { + return xtype - ytype; + } + else if (xtype < 2) + { + return x->linedef - y->linedef; + } + else + { + return 0; + } +} + +// Given a set of segs, checks to make sure they all belong to a single +// sector. If so, false is returned, and they become a subsector. If not, +// a splitter is synthesized, and true is returned to continue processing +// down this branch of the tree. + +bool FNodeBuilder::CheckSubsector (uint32_t set, node_t &node, uint32_t &splitseg) +{ + sector_t *sec; + uint32_t seg; + + sec = NULL; + seg = set; + + do + { + D(Printf (PRINT_LOG, " - seg %d%c(%d,%d)-(%d,%d) line %d front %d back %d\n", seg, + Segs[seg].linedef == -1 ? '+' : ' ', + Vertices[Segs[seg].v1].x>>16, Vertices[Segs[seg].v1].y>>16, + Vertices[Segs[seg].v2].x>>16, Vertices[Segs[seg].v2].y>>16, + Segs[seg].linedef, + Segs[seg].frontsector == NULL ? -1 : Segs[seg].frontsector - sectors, + Segs[seg].backsector == NULL ? -1 : Segs[seg].backsector - sectors)); + if (Segs[seg].linedef != -1 && + Segs[seg].frontsector != sec + // Segs with the same front and back sectors are allowed to reside + // in a subsector with segs from a different sector, because the + // only effect they can have on the display is to place masked + // mid textures in the scene. Since minisegs only mark subsector + // boundaries, their sector information is unimportant. + // + // Update: Lines with the same front and back sectors *can* affect + // the display if their subsector does not match their front sector. + /*&& Segs[seg].frontsector != Segs[seg].backsector*/) + { + if (sec == NULL) + { + sec = Segs[seg].frontsector; + } + else + { + break; + } + } + seg = Segs[seg].next; + } while (seg != UINT_MAX); + + if (seg == UINT_MAX) + { // It's a valid non-GL subsector, and probably a valid GL subsector too. + if (GLNodes) + { + return CheckSubsectorOverlappingSegs (set, node, splitseg); + } + return false; + } + + D(Printf(PRINT_LOG, "Need to synthesize a splitter for set %d on seg %d\n", set, seg)); + splitseg = UINT_MAX; + + // This is a very simple and cheap "fix" for subsectors with segs + // from multiple sectors, and it seems ZenNode does something + // similar. It is the only technique I could find that makes the + // "transparent water" in nb_bmtrk.wad work properly. + return ShoveSegBehind (set, node, seg, UINT_MAX); +} + +// When creating GL nodes, we need to check for segs with the same start and +// end vertices and split them into two subsectors. + +bool FNodeBuilder::CheckSubsectorOverlappingSegs (uint32_t set, node_t &node, uint32_t &splitseg) +{ + int v1, v2; + uint32_t seg1, seg2; + + for (seg1 = set; seg1 != UINT_MAX; seg1 = Segs[seg1].next) + { + if (Segs[seg1].linedef == -1) + { // Do not check minisegs. + continue; + } + v1 = Segs[seg1].v1; + v2 = Segs[seg1].v2; + for (seg2 = Segs[seg1].next; seg2 != UINT_MAX; seg2 = Segs[seg2].next) + { + if (Segs[seg2].v1 == v1 && Segs[seg2].v2 == v2) + { + if (Segs[seg2].linedef == -1) + { // Do not put minisegs into a new subsector. + std::swap (seg1, seg2); + } + D(Printf(PRINT_LOG, "Need to synthesize a splitter for set %d on seg %d (ov)\n", set, seg2)); + splitseg = UINT_MAX; + + return ShoveSegBehind (set, node, seg2, seg1); + } + } + } + // It really is a good subsector. + return false; +} + +// The seg is marked to indicate that it should be forced to the +// back of the splitter. Because these segs already form a convex +// set, all the other segs will be in front of the splitter. Since +// the splitter is formed from this seg, the back of the splitter +// will have a one-dimensional subsector. SplitSegs() will add one +// or two new minisegs to close it: If mate is UINT_MAX, then a +// new seg is created to replace this one on the front of the +// splitter. Otherwise, mate takes its place. In either case, the +// seg in front of the splitter is partnered with a new miniseg on +// the back so that the back will have two segs. + +bool FNodeBuilder::ShoveSegBehind (uint32_t set, node_t &node, uint32_t seg, uint32_t mate) +{ + SetNodeFromSeg (node, &Segs[seg]); + HackSeg = seg; + HackMate = mate; + if (!Segs[seg].planefront) + { + node.x += node.dx; + node.y += node.dy; + node.dx = -node.dx; + node.dy = -node.dy; + } + return Heuristic (node, set, false) > 0; +} + +// Splitters are chosen to coincide with segs in the given set. To reduce the +// number of segs that need to be considered as splitters, segs are grouped into +// according to the planes that they lie on. Because one seg on the plane is just +// as good as any other seg on the plane at defining a split, only one seg from +// each unique plane needs to be considered as a splitter. A result of 0 means +// this set is a convex region. A result of -1 means that there were possible +// splitters, but they all split segs we want to keep intact. +int FNodeBuilder::SelectSplitter (uint32_t set, node_t &node, uint32_t &splitseg, int step, bool nosplit) +{ + int stepleft; + int bestvalue; + uint32_t bestseg; + uint32_t seg; + bool nosplitters = false; + + bestvalue = 0; + bestseg = UINT_MAX; + + seg = set; + stepleft = 0; + + memset (&PlaneChecked[0], 0, PlaneChecked.Size()); + + D(Printf (PRINT_LOG, "Processing set %d\n", set)); + + while (seg != UINT_MAX) + { + FPrivSeg *pseg = &Segs[seg]; + + if (--stepleft <= 0) + { + int l = pseg->planenum >> 3; + int r = 1 << (pseg->planenum & 7); + + if (l < 0 || (PlaneChecked[l] & r) == 0) + { + if (l >= 0) + { + PlaneChecked[l] |= r; + } + + stepleft = step; + SetNodeFromSeg (node, pseg); + + int value = Heuristic (node, set, nosplit); + + D(Printf (PRINT_LOG, "Seg %5d, ld %d (%5d,%5d)-(%5d,%5d) scores %d\n", seg, Segs[seg].linedef, node.x>>16, node.y>>16, + (node.x+node.dx)>>16, (node.y+node.dy)>>16, value)); + + if (value > bestvalue) + { + bestvalue = value; + bestseg = seg; + } + else if (value < 0) + { + nosplitters = true; + } + } + } + + seg = pseg->next; + } + + if (bestseg == UINT_MAX) + { // No lines split any others into two sets, so this is a convex region. + D(Printf (PRINT_LOG, "set %d, step %d, nosplit %d has no good splitter (%d)\n", set, step, nosplit, nosplitters)); + return nosplitters ? -1 : 0; + } + + D(Printf (PRINT_LOG, "split seg %u in set %d, score %d, step %d, nosplit %d\n", bestseg, set, bestvalue, step, nosplit)); + + splitseg = bestseg; + SetNodeFromSeg (node, &Segs[bestseg]); + return 1; +} + +// Given a splitter (node), returns a score based on how "good" the resulting +// split in a set of segs is. Higher scores are better. -1 means this splitter +// splits something it shouldn't and will only be returned if honorNoSplit is +// true. A score of 0 means that the splitter does not split any of the segs +// in the set. + +int FNodeBuilder::Heuristic (node_t &node, uint32_t set, bool honorNoSplit) +{ + // Set the initial score above 0 so that near vertex anti-weighting is less likely to produce a negative score. + int score = 1000000; + int segsInSet = 0; + int counts[2] = { 0, 0 }; + int realSegs[2] = { 0, 0 }; + int specialSegs[2] = { 0, 0 }; + uint32_t i = set; + int sidev[2]; + int side; + bool splitter = false; + unsigned int max, m2, p, q; + double frac; + + Touched.Clear (); + Colinear.Clear (); + + while (i != UINT_MAX) + { + const FPrivSeg *test = &Segs[i]; + + if (HackSeg == i) + { + side = 1; + } + else + { + side = ClassifyLine (node, &Vertices[test->v1], &Vertices[test->v2], sidev); + } + switch (side) + { + case 0: // Seg is on only one side of the partition + case 1: + // If we don't split this line, but it abuts the splitter, also reject it. + // The "right" thing to do in this case is to only reject it if there is + // another nosplit seg from the same sector at this vertex. Note that a line + // that lies exactly on top of the splitter is okay. + if (test->loopnum && honorNoSplit && (sidev[0] == 0 || sidev[1] == 0)) + { + if ((sidev[0] | sidev[1]) != 0) + { + max = Touched.Size(); + for (p = 0; p < max; ++p) + { + if (Touched[p] == test->loopnum) + { + break; + } + } + if (p == max) + { + Touched.Push (test->loopnum); + } + } + else + { + max = Colinear.Size(); + for (p = 0; p < max; ++p) + { + if (Colinear[p] == test->loopnum) + { + break; + } + } + if (p == max) + { + Colinear.Push (test->loopnum); + } + } + } + + counts[side]++; + if (test->linedef != -1) + { + realSegs[side]++; + if (test->frontsector == test->backsector) + { + specialSegs[side]++; + } + // Add some weight to the score for unsplit lines + score += SplitCost; + } + else + { + // Minisegs don't count quite as much for nosplitting + score += SplitCost / 4; + } + break; + + default: // Seg is cut by the partition + // If we are not allowed to split this seg, reject this splitter + if (test->loopnum) + { + if (honorNoSplit) + { + D(Printf (PRINT_LOG, "Splits seg %d\n", i)); + return -1; + } + else + { + splitter = true; + } + } + + // Splitters that are too close to a vertex are bad. + frac = InterceptVector (node, *test); + if (frac < 0.001 || frac > 0.999) + { + FPrivVert *v1 = &Vertices[test->v1]; + FPrivVert *v2 = &Vertices[test->v2]; + double x = v1->x, y = v1->y; + x += frac * (v2->x - x); + y += frac * (v2->y - y); + if (fabs(x - v1->x) < VERTEX_EPSILON+1 && fabs(y - v1->y) < VERTEX_EPSILON+1) + { + D(Printf("Splitter will produce same start vertex as seg %d\n", i)); + return -1; + } + if (fabs(x - v2->x) < VERTEX_EPSILON+1 && fabs(y - v2->y) < VERTEX_EPSILON+1) + { + D(Printf("Splitter will produce same end vertex as seg %d\n", i)); + return -1; + } + if (frac > 0.999) + { + frac = 1 - frac; + } + int penalty = int(1 / frac); + score = MAX(score - penalty, 1); + D(Printf ("Penalized splitter by %d for being near endpt of seg %d (%f).\n", penalty, i, frac)); + } + + counts[0]++; + counts[1]++; + if (test->linedef != -1) + { + realSegs[0]++; + realSegs[1]++; + if (test->frontsector == test->backsector) + { + specialSegs[0]++; + specialSegs[1]++; + } + } + break; + } + + segsInSet++; + i = test->next; + } + + // If this line is outside all the others, return a special score + if (counts[0] == 0 || counts[1] == 0) + { + return 0; + } + + // A splitter must have at least one real seg on each side. + // Otherwise, a subsector could be left without any way to easily + // determine which sector it lies inside. + if (realSegs[0] == 0 || realSegs[1] == 0) + { + D(Printf (PRINT_LOG, "Leaves a side with only mini segs\n")); + return -1; + } + + // Try to avoid splits that leave only "special" segs, so that the generated + // subsectors have a better chance of choosing the correct sector. This situation + // is not neccesarily bad, just undesirable. + if (honorNoSplit && (specialSegs[0] == realSegs[0] || specialSegs[1] == realSegs[1])) + { + D(Printf (PRINT_LOG, "Leaves a side with only special segs\n")); + return -1; + } + + // If this splitter intersects any vertices of segs that should not be split, + // check if it is also colinear with another seg from the same sector. If it + // is, the splitter is okay. If not, it should be rejected. Why? Assuming that + // polyobject containers are convex (which they should be), a splitter that + // is colinear with one of the sector's segs and crosses the vertex of another + // seg of that sector must be crossing the container's corner and does not + // actually split the container. + + max = Touched.Size (); + m2 = Colinear.Size (); + + // If honorNoSplit is false, then both these lists will be empty. + + // If the splitter touches some vertices without being colinear to any, we + // can skip further checks and reject this right away. + if (m2 == 0 && max > 0) + { + return -1; + } + + for (p = 0; p < max; ++p) + { + int look = Touched[p]; + for (q = 0; q < m2; ++q) + { + if (look == Colinear[q]) + { + break; + } + } + if (q == m2) + { // Not a good one + return -1; + } + } + + // Doom maps are primarily axis-aligned lines, so it's usually a good + // idea to prefer axis-aligned splitters over diagonal ones. Doom originally + // had special-casing for orthogonal lines, so they performed better. ZDoom + // does not care about the line's direction, so this is merely a choice to + // try and improve the final tree. + + if ((node.dx == 0) || (node.dy == 0)) + { + // If we have to split a seg we would prefer to keep unsplit, give + // extra precedence to orthogonal lines so that the polyobjects + // outside the entrance to MAP06 in Hexen MAP02 display properly. + if (splitter) + { + score += segsInSet*8; + } + else + { + score += segsInSet/AAPreference; + } + } + + score += (counts[0] + counts[1]) - abs(counts[0] - counts[1]); + + return score; +} + +void FNodeBuilder::SplitSegs (uint32_t set, node_t &node, uint32_t splitseg, uint32_t &outset0, uint32_t &outset1, unsigned int &count0, unsigned int &count1) +{ + unsigned int _count0 = 0; + unsigned int _count1 = 0; + outset0 = UINT_MAX; + outset1 = UINT_MAX; + + Events.DeleteAll (); + SplitSharers.Clear (); + + while (set != UINT_MAX) + { + bool hack; + FPrivSeg *seg = &Segs[set]; + int next = seg->next; + + int sidev[2], side; + + if (HackSeg == set) + { + HackSeg = UINT_MAX; + side = 1; + sidev[0] = sidev[1] = 0; + hack = true; + } + else + { + side = ClassifyLine (node, &Vertices[seg->v1], &Vertices[seg->v2], sidev); + hack = false; + } + + switch (side) + { + case 0: // seg is entirely in front + seg->next = outset0; + outset0 = set; + _count0++; + break; + + case 1: // seg is entirely in back + seg->next = outset1; + outset1 = set; + _count1++; + break; + + default: // seg needs to be split + double frac; + FPrivVert newvert; + unsigned int vertnum; + int seg2; + + frac = InterceptVector (node, *seg); + newvert.x = Vertices[seg->v1].x; + newvert.y = Vertices[seg->v1].y; + newvert.x += fixed_t(frac * (double(Vertices[seg->v2].x) - newvert.x)); + newvert.y += fixed_t(frac * (double(Vertices[seg->v2].y) - newvert.y)); + vertnum = VertexMap->SelectVertexClose (newvert); + + if (vertnum != (unsigned int)seg->v1 && vertnum != (unsigned int)seg->v2) + { + seg2 = SplitSeg(set, vertnum, sidev[0]); + + Segs[seg2].next = outset0; + outset0 = seg2; + Segs[set].next = outset1; + outset1 = set; + _count0++; + _count1++; + + // Also split the seg on the back side + if (Segs[set].partner != UINT_MAX) + { + int partner1 = Segs[set].partner; + int partner2 = SplitSeg(partner1, vertnum, sidev[1]); + // The newly created seg stays in the same set as the + // back seg because it has not been considered for splitting + // yet. If it had been, then the front seg would have already + // been split, and we would not be in this default case. + // Moreover, the back seg may not even be in the set being + // split, so we must not move its pieces into the out sets. + Segs[partner1].next = partner2; + Segs[partner2].partner = seg2; + Segs[seg2].partner = partner2; + } + + if (GLNodes) + { + AddIntersection(node, vertnum); + } + } + else + { + // all that matters here is to prevent a crash so we must make sure that we do not end up with all segs being sorted to the same side - even if this may not be correct. + // But if we do not do that this code would not be able to move on. Just discarding the seg is also not an option because it won't guarantee that we achieve an actual split. + if (_count0 == 0) + { + side = 0; + seg->next = outset0; + outset0 = set; + _count0++; + } + else + { + side = 1; + seg->next = outset1; + outset1 = set; + _count1++; + } + } + break; + } + if (side >= 0 && GLNodes) + { + if (sidev[0] == 0) + { + double dist1 = AddIntersection (node, seg->v1); + if (sidev[1] == 0) + { + double dist2 = AddIntersection (node, seg->v2); + FSplitSharer share = { dist1, set, dist2 > dist1 }; + SplitSharers.Push (share); + } + } + else if (sidev[1] == 0) + { + AddIntersection (node, seg->v2); + } + } + if (hack && GLNodes) + { + uint32_t newback, newfront; + + newback = AddMiniseg (seg->v2, seg->v1, UINT_MAX, set, splitseg); + if (HackMate == UINT_MAX) + { + newfront = AddMiniseg (Segs[set].v1, Segs[set].v2, newback, set, splitseg); + Segs[newfront].next = outset0; + outset0 = newfront; + } + else + { + newfront = HackMate; + Segs[newfront].partner = newback; + Segs[newback].partner = newfront; + } + Segs[newback].frontsector = Segs[newback].backsector = + Segs[newfront].frontsector = Segs[newfront].backsector = + Segs[set].frontsector; + + Segs[newback].next = outset1; + outset1 = newback; + } + set = next; + } + FixSplitSharers (node); + if (GLNodes) + { + AddMinisegs (node, splitseg, outset0, outset1); + } + count0 = _count0; + count1 = _count1; +} + +void FNodeBuilder::SetNodeFromSeg (node_t &node, const FPrivSeg *pseg) const +{ + if (pseg->planenum >= 0) + { + FSimpleLine *pline = &Planes[pseg->planenum]; + node.x = pline->x; + node.y = pline->y; + node.dx = pline->dx; + node.dy = pline->dy; + } + else + { + node.x = Vertices[pseg->v1].x; + node.y = Vertices[pseg->v1].y; + node.dx = Vertices[pseg->v2].x - node.x; + node.dy = Vertices[pseg->v2].y - node.y; + } +} + +uint32_t FNodeBuilder::SplitSeg (uint32_t segnum, int splitvert, int v1InFront) +{ + double dx, dy; + FPrivSeg newseg; + int newnum = (int)Segs.Size(); + + newseg = Segs[segnum]; + dx = double(Vertices[splitvert].x - Vertices[newseg.v1].x); + dy = double(Vertices[splitvert].y - Vertices[newseg.v1].y); + if (v1InFront > 0) + { + newseg.v1 = splitvert; + Segs[segnum].v2 = splitvert; + + RemoveSegFromVert2 (segnum, newseg.v2); + + newseg.nextforvert = Vertices[splitvert].segs; + Vertices[splitvert].segs = newnum; + + newseg.nextforvert2 = Vertices[newseg.v2].segs2; + Vertices[newseg.v2].segs2 = newnum; + + Segs[segnum].nextforvert2 = Vertices[splitvert].segs2; + Vertices[splitvert].segs2 = segnum; + } + else + { + Segs[segnum].v1 = splitvert; + newseg.v2 = splitvert; + + RemoveSegFromVert1 (segnum, newseg.v1); + + newseg.nextforvert = Vertices[newseg.v1].segs; + Vertices[newseg.v1].segs = newnum; + + newseg.nextforvert2 = Vertices[splitvert].segs2; + Vertices[splitvert].segs2 = newnum; + + Segs[segnum].nextforvert = Vertices[splitvert].segs; + Vertices[splitvert].segs = segnum; + } + + Segs.Push (newseg); + + D(Printf (PRINT_LOG, "Split seg %d to get seg %d\n", segnum, newnum)); + + return newnum; +} + +void FNodeBuilder::RemoveSegFromVert1 (uint32_t segnum, int vertnum) +{ + FPrivVert *v = &Vertices[vertnum]; + + if (v->segs == segnum) + { + v->segs = Segs[segnum].nextforvert; + } + else + { + uint32_t prev, curr; + prev = 0; + curr = v->segs; + while (curr != UINT_MAX && curr != segnum) + { + prev = curr; + curr = Segs[curr].nextforvert; + } + if (curr == segnum) + { + Segs[prev].nextforvert = Segs[curr].nextforvert; + } + } +} + +void FNodeBuilder::RemoveSegFromVert2 (uint32_t segnum, int vertnum) +{ + FPrivVert *v = &Vertices[vertnum]; + + if (v->segs2 == segnum) + { + v->segs2 = Segs[segnum].nextforvert2; + } + else + { + uint32_t prev, curr; + prev = 0; + curr = v->segs2; + while (curr != UINT_MAX && curr != segnum) + { + prev = curr; + curr = Segs[curr].nextforvert2; + } + if (curr == segnum) + { + Segs[prev].nextforvert2 = Segs[curr].nextforvert2; + } + } +} + +double FNodeBuilder::InterceptVector (const node_t &splitter, const FPrivSeg &seg) +{ + double v2x = (double)Vertices[seg.v1].x; + double v2y = (double)Vertices[seg.v1].y; + double v2dx = (double)Vertices[seg.v2].x - v2x; + double v2dy = (double)Vertices[seg.v2].y - v2y; + double v1dx = (double)splitter.dx; + double v1dy = (double)splitter.dy; + + double den = v1dy*v2dx - v1dx*v2dy; + + if (den == 0.0) + return 0; // parallel + + double v1x = (double)splitter.x; + double v1y = (double)splitter.y; + + double num = (v1x - v2x)*v1dy + (v2y - v1y)*v1dx; + double frac = num / den; + + return frac; +} + +void FNodeBuilder::PrintSet (int l, uint32_t set) +{ + Printf (PRINT_LOG, "set %d:\n", l); + for (; set != UINT_MAX; set = Segs[set].next) + { + Printf (PRINT_LOG, "\t%u(%d)%c%d(%d,%d)-%d(%d,%d)\n", set,0/*Segs[set].frontsector->sectornum*/, + Segs[set].linedef == -1 ? '+' : ':', + Segs[set].v1, + Vertices[Segs[set].v1].x>>16, Vertices[Segs[set].v1].y>>16, + Segs[set].v2, + Vertices[Segs[set].v2].x>>16, Vertices[Segs[set].v2].y>>16); + } + Printf (PRINT_LOG, "*\n"); +} diff --git a/source/core/nodebuilder/nodebuild.h b/source/core/nodebuilder/nodebuild.h new file mode 100644 index 000000000..0b212ad2f --- /dev/null +++ b/source/core/nodebuilder/nodebuild.h @@ -0,0 +1,470 @@ +/* +** nodebuild.cpp +** +**--------------------------------------------------------------------------- +** Copyright 2002-2016 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. +**--------------------------------------------------------------------------- +** +*/ +#pragma once + +#include "tarray.h" +#include "x86.h" +#include "build.h" + +struct FPolySeg; +struct FMiniBSP; +struct FLevelLocals; + +struct FEventInfo +{ + int Vertex; + uint32_t FrontSeg; +}; + +struct FEvent +{ + FEvent *Parent, *Left, *Right; + double Distance; + FEventInfo Info; +}; + +class FEventTree +{ +public: + FEventTree (); + ~FEventTree (); + + FEvent *GetMinimum (); + FEvent *GetSuccessor (FEvent *event) const { FEvent *node = Successor(event); return node == &Nil ? NULL : node; } + FEvent *GetPredecessor (FEvent *event) const { FEvent *node = Predecessor(event); return node == &Nil ? NULL : node; } + + FEvent *GetNewNode (); + void Insert (FEvent *event); + FEvent *FindEvent (double distance) const; + void DeleteAll (); + + void PrintTree () const { PrintTree (Root); } + +private: + FEvent Nil; + FEvent *Root; + FEvent *Spare; + + void DeletionTraverser (FEvent *event); + FEvent *Successor (FEvent *event) const; + FEvent *Predecessor (FEvent *event) const; + + void PrintTree (const FEvent *event) const; +}; + +struct FSimpleVert +{ + fixed_t x, y; +}; + +typedef int64_t fixed64_t; + +struct vertex_t +{ + DVector2 p; + + void set(fixed_t x, fixed_t y) + { + p.X = x / 65536.; + p.Y = y / 65536.; + } + + double fX() const + { + return p.X; + } + + double fY() const + { + return p.Y; + } + + fixed_t fixX() const + { + return FLOAT2FIXED(p.X); + } + + fixed_t fixY() const + { + return FLOAT2FIXED(p.Y); + } +}; + +struct side_t; + +struct line_t +{ + vertex_t* v1, * v2; // vertices, from v1 to v2 + side_t* sidedef[2]; + sectortype* frontsector, * backsector; + int linenum; + + int Index() const { return linenum; } + +}; + +struct side_t +{ + sectortype* sector; // Sector the SideDef is facing. + int sidenum; + int Index() const { return sidenum; } +}; + +struct subsector_t; +struct seg_t +{ + vertex_t* v1; + vertex_t* v2; + + side_t* sidedef; + line_t* linedef; + + // Sector references. Could be retrieved from linedef, too. + sectortype* frontsector; + sectortype* backsector; // NULL for one-sided lines + + seg_t* PartnerSeg; + subsector_t* Subsector; + + int segnum; + + int Index() const { return segnum; } +}; + +struct glseg_t : public seg_t +{ + uint32_t Partner; +}; + +struct subsector_t +{ + sectortype* sector; + seg_t* firstline; + uint32_t numlines; +}; + +struct node_t +{ + // Partition line. + fixed_t x; + fixed_t y; + fixed_t dx; + fixed_t dy; + union + { + float bbox[2][4]; // Bounding box for each child. + fixed_t nb_bbox[2][4]; // Used by nodebuilder. + }; + float len; + int nodenum; + union + { + void* children[2]; // If bit 0 is set, it's a subsector. + int intchildren[2]; // Used by nodebuilder. + }; + + int Index() const { return nodenum; } +}; + +struct FLevelLocals +{ + TArray vertexes; + TArray subsectors; + TArray nodes; + TArray segs; +}; + +enum { NO_SIDE = 0x7fffffff }; + + +class FNodeBuilder +{ + struct FPrivSeg + { + int v1, v2; + int sidedef; + int linedef; + sectortype *frontsector; + sectortype *backsector; + uint32_t next; + uint32_t nextforvert; + uint32_t nextforvert2; + int loopnum; // loop number for split avoidance (0 means splitting is okay) + uint32_t partner; // seg on back side + uint32_t storedseg; // seg # in the GL_SEGS lump + + int planenum; + bool planefront; + FPrivSeg *hashnext; + }; + struct FPrivVert : FSimpleVert + { + uint32_t segs; // segs that use this vertex as v1 + uint32_t segs2; // segs that use this vertex as v2 + + bool operator== (const FPrivVert &other) + { + return x == other.x && y == other.y; + } + }; + struct FSimpleLine + { + fixed_t x, y, dx, dy; + }; + union USegPtr + { + uint32_t SegNum; + FPrivSeg *SegPtr; + }; + struct FSplitSharer + { + double Distance; + uint32_t Seg; + bool Forward; + }; + + // Like a blockmap, but for vertices instead of lines + class IVertexMap + { + public: + virtual ~IVertexMap(); + virtual int SelectVertexExact(FPrivVert &vert) = 0; + virtual int SelectVertexClose(FPrivVert &vert) = 0; + private: + IVertexMap &operator=(const IVertexMap &); + }; + + class FVertexMap : public IVertexMap + { + public: + FVertexMap (FNodeBuilder &builder, fixed_t minx, fixed_t miny, fixed_t maxx, fixed_t maxy); + ~FVertexMap (); + + int SelectVertexExact (FPrivVert &vert); + int SelectVertexClose (FPrivVert &vert); + + private: + FNodeBuilder &MyBuilder; + TArray *VertexGrid; + + fixed64_t MinX, MinY, MaxX, MaxY; + int BlocksWide, BlocksTall; + + enum { BLOCK_SHIFT = 8 + FRACBITS }; + enum { BLOCK_SIZE = 1 << BLOCK_SHIFT }; + + int InsertVertex (FPrivVert &vert); + inline int GetBlock (fixed64_t x, fixed64_t y) + { + assert (x >= MinX); + assert (y >= MinY); + assert (x <= MaxX); + assert (y <= MaxY); + return (unsigned(x - MinX) >> BLOCK_SHIFT) + (unsigned(y - MinY) >> BLOCK_SHIFT) * BlocksWide; + } + }; + + class FVertexMapSimple : public IVertexMap + { + public: + FVertexMapSimple(FNodeBuilder &builder); + + int SelectVertexExact(FPrivVert &vert); + int SelectVertexClose(FPrivVert &vert); + private: + int InsertVertex(FPrivVert &vert); + + FNodeBuilder &MyBuilder; + }; + + friend class FVertexMap; + friend class FVertexMapSimple; + +public: + struct FLevel + { + vertex_t *Vertices; int NumVertices; + side_t *Sides; int NumSides; + line_t *Lines; int NumLines; + + fixed_t MinX, MinY, MaxX, MaxY; + + void FindMapBounds(); + void ResetMapBounds() + { + MinX = FIXED_MAX; + MinY = FIXED_MAX; + MaxX = FIXED_MIN; + MaxY = FIXED_MIN; + } + }; + + struct FPolyStart + { + int polynum; + fixed_t x, y; + }; + + FNodeBuilder (FLevel &lev); + ~FNodeBuilder (); + + void Extract(FLevelLocals &lev); + const int *GetOldVertexTable(); + + // These are used for building sub-BSP trees for polyobjects. + void Clear(); + void AddSegs(seg_t *segs, int numsegs); + void BuildMini(bool makeGLNodes); + + static angle_t PointToAngle (fixed_t dx, fixed_t dy); + + // < 0 : in front of line + // == 0 : on line + // > 0 : behind line + + static inline int PointOnSide (int x, int y, int x1, int y1, int dx, int dy); + +private: + IVertexMap *VertexMap; + int *OldVertexTable; + + TArray Nodes; + TArray Subsectors; + TArray SubsectorSets; + TArray Segs; + TArray Vertices; + TArray SegList; + TArray PlaneChecked; + TArray Planes; + + TArray Touched; // Loops a splitter touches on a vertex + TArray Colinear; // Loops with edges colinear to a splitter + FEventTree Events; // Vertices intersected by the current splitter + + TArray SplitSharers; // Segs colinear with the current splitter + + uint32_t HackSeg; // Seg to force to back of splitter + uint32_t HackMate; // Seg to use in front of hack seg + FLevel &Level; + bool GLNodes; // Add minisegs to make GL nodes? + + // Progress meter stuff + int SegsStuffed; + + void FindUsedVertices (vertex_t *vertices, int max); + void BuildTree (); + void MakeSegsFromSides (); + int CreateSeg (int linenum, int sidenum); + void GroupSegPlanes (); + void GroupSegPlanesSimple (); + void AddSegToBBox (fixed_t bbox[4], const FPrivSeg *seg); + int CreateNode (uint32_t set, unsigned int count, fixed_t bbox[4]); + int CreateSubsector (uint32_t set, fixed_t bbox[4]); + void CreateSubsectorsForReal (); + bool CheckSubsector (uint32_t set, node_t &node, uint32_t &splitseg); + bool CheckSubsectorOverlappingSegs (uint32_t set, node_t &node, uint32_t &splitseg); + bool ShoveSegBehind (uint32_t set, node_t &node, uint32_t seg, uint32_t mate); int SelectSplitter (uint32_t set, node_t &node, uint32_t &splitseg, int step, bool nosplit); + void SplitSegs (uint32_t set, node_t &node, uint32_t splitseg, uint32_t &outset0, uint32_t &outset1, unsigned int &count0, unsigned int &count1); + uint32_t SplitSeg (uint32_t segnum, int splitvert, int v1InFront); + int Heuristic (node_t &node, uint32_t set, bool honorNoSplit); + + // Returns: + // 0 = seg is in front + // 1 = seg is in back + // -1 = seg cuts the node + + int ClassifyLine (node_t &node, const FPrivVert *v1, const FPrivVert *v2, int sidev[2]); + + void FixSplitSharers (const node_t &node); + double AddIntersection (const node_t &node, int vertex); + void AddMinisegs (const node_t &node, uint32_t splitseg, uint32_t &fset, uint32_t &rset); + uint32_t CheckLoopStart (fixed_t dx, fixed_t dy, int vertex1, int vertex2); + uint32_t CheckLoopEnd (fixed_t dx, fixed_t dy, int vertex2); + void RemoveSegFromVert1 (uint32_t segnum, int vertnum); + void RemoveSegFromVert2 (uint32_t segnum, int vertnum); + uint32_t AddMiniseg (int v1, int v2, uint32_t partner, uint32_t seg1, uint32_t splitseg); + void SetNodeFromSeg (node_t &node, const FPrivSeg *pseg) const; + + int CloseSubsector (TArray &segs, int subsector, vertex_t *outVerts); + uint32_t PushGLSeg (TArray &segs, const FPrivSeg *seg, vertex_t *outVerts); + void PushConnectingGLSeg (int subsector, TArray &segs, vertex_t *v1, vertex_t *v2); + int OutputDegenerateSubsector (TArray &segs, int subsector, bool bForward, double lastdot, FPrivSeg *&prev, vertex_t *outVerts); + + static int SortSegs (const void *a, const void *b); + + double InterceptVector (const node_t &splitter, const FPrivSeg &seg); + + void PrintSet (int l, uint32_t set); + + FNodeBuilder &operator= (const FNodeBuilder &) { return *this; } +}; + +// Points within this distance of a line will be considered on the line. +// Units are in fixed_ts. +const double SIDE_EPSILON = 6.5536; + +// Vertices within this distance of each other will be considered as the same vertex. +#define VERTEX_EPSILON 6 // This is a fixed_t value + +inline int FNodeBuilder::PointOnSide (int x, int y, int x1, int y1, int dx, int dy) +{ + // 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.f) // 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 = d_dx*d_dx + d_dy*d_dy; // double l = sqrt(d_dx*d_dx+d_dy*d_dy); + double dist = s_num * s_num / l; // double dist = fabs(s_num)/l; + if (dist < SIDE_EPSILON*SIDE_EPSILON) // if (dist < SIDE_EPSILON) + { + return 0; + } + } + return s_num > 0.0 ? -1 : 1; +} + +using sector_t = sectortype; diff --git a/source/core/nodebuilder/nodebuild_classify_nosse2.cpp b/source/core/nodebuilder/nodebuild_classify_nosse2.cpp new file mode 100644 index 000000000..90a2d02d9 --- /dev/null +++ b/source/core/nodebuilder/nodebuild_classify_nosse2.cpp @@ -0,0 +1,135 @@ +#include "nodebuild.h" + +#define FAR_ENOUGH 17179869184.f // 4<<32 + +int FNodeBuilder::ClassifyLine(node_t &node, const FPrivVert *v1, const FPrivVert *v2, int sidev[2]) +{ + double d_x1 = double(node.x); + double d_y1 = double(node.y); + double d_dx = double(node.dx); + double d_dy = double(node.dy); + double d_xv1 = double(v1->x); + double d_xv2 = double(v2->x); + double d_yv1 = double(v1->y); + double d_yv2 = double(v2->y); + + double s_num1 = (d_y1 - d_yv1) * d_dx - (d_x1 - d_xv1) * d_dy; + double s_num2 = (d_y1 - d_yv2) * d_dx - (d_x1 - d_xv2) * d_dy; + + int nears = 0; + + if (s_num1 <= -FAR_ENOUGH) + { + if (s_num2 <= -FAR_ENOUGH) + { + sidev[0] = sidev[1] = 1; + return 1; + } + if (s_num2 >= FAR_ENOUGH) + { + sidev[0] = 1; + sidev[1] = -1; + return -1; + } + nears = 1; + } + else if (s_num1 >= FAR_ENOUGH) + { + if (s_num2 >= FAR_ENOUGH) + { + sidev[0] = sidev[1] = -1; + return 0; + } + if (s_num2 <= -FAR_ENOUGH) + { + sidev[0] = -1; + sidev[1] = 1; + return -1; + } + nears = 1; + } + else + { + nears = 2 | int(fabs(s_num2) < FAR_ENOUGH); + } + + if (nears) + { + double l = 1.f / (d_dx*d_dx + d_dy*d_dy); + if (nears & 2) + { + double dist = s_num1 * s_num1 * l; + if (dist < SIDE_EPSILON*SIDE_EPSILON) + { + sidev[0] = 0; + } + else + { + sidev[0] = s_num1 > 0.0 ? -1 : 1; + } + } + else + { + sidev[0] = s_num1 > 0.0 ? -1 : 1; + } + if (nears & 1) + { + double dist = s_num2 * s_num2 * l; + if (dist < SIDE_EPSILON*SIDE_EPSILON) + { + sidev[1] = 0; + } + else + { + sidev[1] = s_num2 > 0.0 ? -1 : 1; + } + } + else + { + sidev[1] = s_num2 > 0.0 ? -1 : 1; + } + } + else + { + sidev[0] = s_num1 > 0.0 ? -1 : 1; + sidev[1] = s_num2 > 0.0 ? -1 : 1; + } + + if ((sidev[0] | sidev[1]) == 0) + { // seg is coplanar with the splitter, so use its orientation to determine + // which child it ends up in. If it faces the same direction as the splitter, + // it goes in front. Otherwise, it goes in back. + + if (node.dx != 0) + { + if ((node.dx > 0 && v2->x > v1->x) || (node.dx < 0 && v2->x < v1->x)) + { + return 0; + } + else + { + return 1; + } + } + else + { + if ((node.dy > 0 && v2->y > v1->y) || (node.dy < 0 && v2->y < v1->y)) + { + return 0; + } + else + { + return 1; + } + } + } + else if (sidev[0] <= 0 && sidev[1] <= 0) + { + return 0; + } + else if (sidev[0] >= 0 && sidev[1] >= 0) + { + return 1; + } + return -1; +} diff --git a/source/core/nodebuilder/nodebuild_events.cpp b/source/core/nodebuilder/nodebuild_events.cpp new file mode 100644 index 000000000..e615382cc --- /dev/null +++ b/source/core/nodebuilder/nodebuild_events.cpp @@ -0,0 +1,226 @@ +/* +** nodebuild_events.cpp +** +** A red-black tree for keeping track of segs that get touched by a splitter. +** +**--------------------------------------------------------------------------- +** 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 +#include "nodebuild.h" +#include "printf.h" + +FEventTree::FEventTree () +: Root (&Nil), Spare (NULL) +{ + memset (&Nil, 0, sizeof(Nil)); +} + +FEventTree::~FEventTree () +{ + FEvent *probe; + + DeleteAll (); + probe = Spare; + while (probe != NULL) + { + FEvent *next = probe->Left; + delete probe; + probe = next; + } +} + +void FEventTree::DeleteAll () +{ + DeletionTraverser (Root); + Root = &Nil; +} + +void FEventTree::DeletionTraverser (FEvent *node) +{ + if (node != &Nil && node != NULL) + { + DeletionTraverser (node->Left); + DeletionTraverser (node->Right); + node->Left = Spare; + Spare = node; + } +} + +FEvent *FEventTree::GetNewNode () +{ + FEvent *node; + + if (Spare != NULL) + { + node = Spare; + Spare = node->Left; + } + else + { + node = new FEvent; + } + return node; +} + +void FEventTree::Insert (FEvent *z) +{ + FEvent *y = &Nil; + FEvent *x = Root; + + while (x != &Nil) + { + y = x; + if (z->Distance < x->Distance) + { + x = x->Left; + } + else + { + x = x->Right; + } + } + z->Parent = y; + if (y == &Nil) + { + Root = z; + } + else if (z->Distance < y->Distance) + { + y->Left = z; + } + else + { + y->Right = z; + } + z->Left = &Nil; + z->Right = &Nil; +} + +FEvent *FEventTree::Successor (FEvent *event) const +{ + if (event->Right != &Nil) + { + event = event->Right; + while (event->Left != &Nil) + { + event = event->Left; + } + return event; + } + else + { + FEvent *y = event->Parent; + while (y != &Nil && event == y->Right) + { + event = y; + y = y->Parent; + } + return y; + } +} + +FEvent *FEventTree::Predecessor (FEvent *event) const +{ + if (event->Left != &Nil) + { + event = event->Left; + while (event->Right != &Nil) + { + event = event->Right; + } + return event; + } + else + { + FEvent *y = event->Parent; + while (y != &Nil && event == y->Left) + { + event = y; + y = y->Parent; + } + return y; + } +} + +FEvent *FEventTree::FindEvent (double key) const +{ + FEvent *node = Root; + + while (node != &Nil) + { + if (node->Distance == key) + { + return node; + } + else if (node->Distance > key) + { + node = node->Left; + } + else + { + node = node->Right; + } + } + return NULL; +} + +FEvent *FEventTree::GetMinimum () +{ + FEvent *node = Root; + + if (node == &Nil) + { + return NULL; + } + while (node->Left != &Nil) + { + node = node->Left; + } + return node; +} + +void FEventTree::PrintTree (const FEvent *event) const +{ + // Use the CRT's sprintf so that it shares the same formatting as ZDBSP's output. + char buff[100]; + if (event != &Nil) + { + PrintTree(event->Left); + sprintf(buff, " Distance %g, vertex %d, seg %u\n", + g_sqrt(event->Distance/4294967296.0), event->Info.Vertex, (unsigned)event->Info.FrontSeg); + Printf(PRINT_LOG, "%s", buff); + PrintTree(event->Right); + } +} diff --git a/source/core/nodebuilder/nodebuild_extract.cpp b/source/core/nodebuilder/nodebuild_extract.cpp new file mode 100644 index 000000000..31449e83e --- /dev/null +++ b/source/core/nodebuilder/nodebuild_extract.cpp @@ -0,0 +1,440 @@ +/* +** 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 +#include + +#include "nodebuild.h" + +#if 0 +#define D(x) x +#define DD 1 +#else +#define D(x) do{}while(0) +#undef DD +#endif + + +void FNodeBuilder::Extract (FLevelLocals &theLevel) +{ + int i; + + auto &outVerts = theLevel.vertexes; + int vertCount = Vertices.Size (); + outVerts.Alloc(vertCount); + + for (i = 0; i < vertCount; ++i) + { + outVerts[i].set(Vertices[i].x, Vertices[i].y); + } + + auto &outSubs = theLevel.subsectors; + auto subCount = Subsectors.Size(); + outSubs.Alloc(subCount); + memset(&outSubs[0], 0, subCount * sizeof(subsector_t)); + + auto &outNodes = theLevel.nodes; + auto nodeCount = Nodes.Size (); + outNodes.Alloc(nodeCount); + + memcpy (&outNodes[0], &Nodes[0], nodeCount*sizeof(node_t)); + for (unsigned 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] = (uint8_t *)(&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]]; + } + } + for (int j = 0; j < 2; ++j) + { + for (int k = 0; k < 4; ++k) + { + outNodes[i].bbox[j][k] = FIXED2FLOAT(outNodes[i].nb_bbox[j][k]); + } + } + } + + auto &outSegs = theLevel.segs; + if (GLNodes) + { + TArray segs (Segs.Size()*5/4); + + for (unsigned i = 0; i < subCount; ++i) + { + uint32_t numsegs = CloseSubsector (segs, i, &outVerts[0]); + outSubs[i].numlines = numsegs; + outSubs[i].firstline = (seg_t *)(size_t)(segs.Size() - numsegs); + } + + auto segCount = segs.Size (); + outSegs.Alloc(segCount); + + for (unsigned i = 0; i < segCount; ++i) + { + outSegs[i] = *(seg_t *)&segs[i]; + + if (segs[i].Partner != UINT_MAX) + { + const uint32_t storedseg = Segs[segs[i].Partner].storedseg; + outSegs[i].PartnerSeg = UINT_MAX == storedseg ? nullptr : &outSegs[storedseg]; + } + else + { + outSegs[i].PartnerSeg = nullptr; + } + } + } + else + { + memcpy (&outSubs[0], &Subsectors[0], subCount*sizeof(subsector_t)); + auto segCount = Segs.Size (); + outSegs.Alloc(segCount); + for (unsigned 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; + out->PartnerSeg = nullptr; + } + } + for (unsigned 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]; + } +} + +int FNodeBuilder::CloseSubsector (TArray &segs, int subsector, vertex_t *outVerts) +{ + FPrivSeg *seg, *prev; + angle_t prevAngle; + double accumx, accumy; + fixed_t midx, midy; + int firstVert; + uint32_t first, max, count, i, j; + bool diffplanes; + int firstplane; + + first = (uint32_t)(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; + uint32_t bestj = UINT_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->fixX()>>16, + segs[i].v1->fixY()>>16, + segs[i].v2->fixX()>>16, + segs[i].v2->fixY()>>16, + segs[i].v1->fixX(), + segs[i].v1->fixY(), + segs[i].v2->fixX(), + segs[i].v2->fixY()); + } +#endif + + return count; +} + +int FNodeBuilder::OutputDegenerateSubsector (TArray &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 = (uint32_t)(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; +} + +uint32_t FNodeBuilder::PushGLSeg (TArray &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 (uint32_t)segs.Push (newseg); +} + +void FNodeBuilder::PushConnectingGLSeg (int subsector, TArray &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 = UINT_MAX; + segs.Push (newseg); +} diff --git a/source/core/nodebuilder/nodebuild_gl.cpp b/source/core/nodebuilder/nodebuild_gl.cpp new file mode 100644 index 000000000..279fa3fb0 --- /dev/null +++ b/source/core/nodebuilder/nodebuild_gl.cpp @@ -0,0 +1,397 @@ +/* +** nodebuild_gl.cpp +** +** Extra functions for the node builder to create minisegs. +** +**--------------------------------------------------------------------------- +** 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 "nodebuild.h" + +static inline void Warn (const char *format, ...) +{ +} + +static const angle_t ANGLE_EPSILON = 5000; + +#if 0 +#define D(x) x +#else +#define D(x) do{}while(0) +#endif + +double FNodeBuilder::AddIntersection (const node_t &node, int vertex) +{ + static const FEventInfo defaultInfo = + { + -1, UINT_MAX + }; + + // Calculate signed distance of intersection vertex from start of splitter. + // Only ordering is important, so we don't need a sqrt. + FPrivVert *v = &Vertices[vertex]; + double dist = (double(v->x) - node.x)*(node.dx) + (double(v->y) - node.y)*(node.dy); + + FEvent *event = Events.FindEvent (dist); + if (event == NULL) + { + event = Events.GetNewNode (); + event->Distance = dist; + event->Info = defaultInfo; + event->Info.Vertex = vertex; + Events.Insert (event); + } + return dist; +} + +// If there are any segs on the splitter that span more than two events, they +// must be split. Alien Vendetta is one example wad that is quite bad about +// having overlapping lines. If we skip this step, these segs will still be +// split later, but minisegs will erroneously be added for them, and partner +// seg information will be messed up in the generated tree. +void FNodeBuilder::FixSplitSharers (const node_t &node) +{ + D(Printf(PRINT_LOG, "events:\n")); + D(Events.PrintTree()); + for (unsigned int i = 0; i < SplitSharers.Size(); ++i) + { + uint32_t seg = SplitSharers[i].Seg; + int v2 = Segs[seg].v2; + FEvent *event = Events.FindEvent (SplitSharers[i].Distance); + FEvent *next; + + if (event == NULL) + { // Should not happen + continue; + } + + // Use the CRT's printf so the formatting matches ZDBSP's + D(char buff[200]); + D(sprintf(buff, "Considering events on seg %d(%d[%d,%d]->%d[%d,%d]) [%g:%g]\n", seg, + Segs[seg].v1, + Vertices[Segs[seg].v1].x>>16, + Vertices[Segs[seg].v1].y>>16, + Segs[seg].v2, + Vertices[Segs[seg].v2].x>>16, + Vertices[Segs[seg].v2].y>>16, + SplitSharers[i].Distance, event->Distance)); + D(Printf(PRINT_LOG, "%s", buff)); + + if (SplitSharers[i].Forward) + { + event = Events.GetSuccessor (event); + if (event == NULL) + { + continue; + } + next = Events.GetSuccessor (event); + } + else + { + event = Events.GetPredecessor (event); + if (event == NULL) + { + continue; + } + next = Events.GetPredecessor (event); + } + + while (event != NULL && next != NULL && event->Info.Vertex != v2) + { + D(Printf(PRINT_LOG, "Forced split of seg %d(%d->%d) at %d(%d,%d)\n", seg, + Segs[seg].v1, Segs[seg].v2, + event->Info.Vertex, + Vertices[event->Info.Vertex].x>>16, + Vertices[event->Info.Vertex].y>>16)); + + uint32_t newseg = SplitSeg (seg, event->Info.Vertex, 1); + + Segs[newseg].next = Segs[seg].next; + Segs[seg].next = newseg; + + uint32_t partner = Segs[seg].partner; + if (partner != UINT_MAX) + { + int endpartner = SplitSeg (partner, event->Info.Vertex, 1); + + Segs[endpartner].next = Segs[partner].next; + Segs[partner].next = endpartner; + + Segs[seg].partner = endpartner; + Segs[partner].partner = newseg; + } + + seg = newseg; + if (SplitSharers[i].Forward) + { + event = next; + next = Events.GetSuccessor (next); + } + else + { + event = next; + next = Events.GetPredecessor (next); + } + } + } +} + +void FNodeBuilder::AddMinisegs (const node_t &node, uint32_t splitseg, uint32_t &fset, uint32_t &bset) +{ + FEvent *event = Events.GetMinimum (), *prev = NULL; + + while (event != NULL) + { + if (prev != NULL) + { + uint32_t fseg1, bseg1, fseg2, bseg2; + uint32_t fnseg, bnseg; + + // Minisegs should only be added when they can create valid loops on both the front and + // back of the splitter. This means some subsectors could be unclosed if their sectors + // are unclosed, but at least we won't be needlessly creating subsectors in void space. + // Unclosed subsectors can be closed trivially once the BSP tree is complete. + + if ((fseg1 = CheckLoopStart (node.dx, node.dy, prev->Info.Vertex, event->Info.Vertex)) != UINT_MAX && + (bseg1 = CheckLoopStart (-node.dx, -node.dy, event->Info.Vertex, prev->Info.Vertex)) != UINT_MAX && + (fseg2 = CheckLoopEnd (node.dx, node.dy, event->Info.Vertex)) != UINT_MAX && + (bseg2 = CheckLoopEnd (-node.dx, -node.dy, prev->Info.Vertex)) != UINT_MAX) + { + // Add miniseg on the front side + fnseg = AddMiniseg (prev->Info.Vertex, event->Info.Vertex, UINT_MAX, fseg1, splitseg); + Segs[fnseg].next = fset; + fset = fnseg; + + // Add miniseg on the back side + bnseg = AddMiniseg (event->Info.Vertex, prev->Info.Vertex, fnseg, bseg1, splitseg); + Segs[bnseg].next = bset; + bset = bnseg; + + sector_t *fsector, *bsector; + + fsector = Segs[fseg1].frontsector; + bsector = Segs[bseg1].frontsector; + + Segs[fnseg].frontsector = fsector; + Segs[fnseg].backsector = bsector; + Segs[bnseg].frontsector = bsector; + Segs[bnseg].backsector = fsector; + + // Only print the warning if this might be bad. + if (fsector != bsector && + fsector != Segs[fseg1].backsector && + bsector != Segs[bseg1].backsector) + { + Warn ("Sectors %d at (%d,%d) and %d at (%d,%d) don't match.\n", + Segs[fseg1].frontsector, + Vertices[prev->Info.Vertex].x>>FRACBITS, Vertices[prev->Info.Vertex].y>>FRACBITS, + Segs[bseg1].frontsector, + Vertices[event->Info.Vertex].x>>FRACBITS, Vertices[event->Info.Vertex].y>>FRACBITS + ); + } + + D(Printf (PRINT_LOG, "**Minisegs** %d/%d added %d(%d,%d)->%d(%d,%d)\n", fnseg, bnseg, + prev->Info.Vertex, + Vertices[prev->Info.Vertex].x>>16, Vertices[prev->Info.Vertex].y>>16, + event->Info.Vertex, + Vertices[event->Info.Vertex].x>>16, Vertices[event->Info.Vertex].y>>16)); + } + } + prev = event; + event = Events.GetSuccessor (event); + } +} + +uint32_t FNodeBuilder::AddMiniseg (int v1, int v2, uint32_t partner, uint32_t seg1, uint32_t splitseg) +{ + uint32_t nseg; + FPrivSeg *seg = &Segs[seg1]; + FPrivSeg newseg; + + newseg.sidedef = NO_SIDE; + newseg.linedef = -1; + newseg.loopnum = 0; + newseg.next = UINT_MAX; + newseg.planefront = true; + newseg.hashnext = NULL; + newseg.storedseg = UINT_MAX; + newseg.frontsector = NULL; + newseg.backsector = NULL; + + if (splitseg != UINT_MAX) + { + newseg.planenum = Segs[splitseg].planenum; + } + else + { + newseg.planenum = -1; + } + + newseg.v1 = v1; + newseg.v2 = v2; + newseg.nextforvert = Vertices[v1].segs; + newseg.nextforvert2 = Vertices[v2].segs2; + newseg.next = seg->next; + if (partner != UINT_MAX) + { + newseg.partner = partner; + } + else + { + newseg.partner = UINT_MAX; + } + nseg = Segs.Push (newseg); + if (newseg.partner != UINT_MAX) + { + Segs[partner].partner = nseg; + } + Vertices[v1].segs = nseg; + Vertices[v2].segs2 = nseg; + //Printf ("Between %d and %d::::\n", seg1, seg2); + return nseg; +} + +uint32_t FNodeBuilder::CheckLoopStart (fixed_t dx, fixed_t dy, int vertex, int vertex2) +{ + FPrivVert *v = &Vertices[vertex]; + angle_t splitAngle = PointToAngle (dx, dy); + uint32_t segnum; + angle_t bestang; + uint32_t bestseg; + + // Find the seg ending at this vertex that forms the smallest angle + // to the splitter. + segnum = v->segs2; + bestang = ANGLE_MAX; + bestseg = UINT_MAX; + while (segnum != UINT_MAX) + { + FPrivSeg *seg = &Segs[segnum]; + angle_t segAngle = PointToAngle (Vertices[seg->v1].x - v->x, Vertices[seg->v1].y - v->y); + angle_t diff = splitAngle - segAngle; + + if (diff < ANGLE_EPSILON && + PointOnSide (Vertices[seg->v1].x, Vertices[seg->v1].y, v->x, v->y, dx, dy) == 0) + { + // If a seg lies right on the splitter, don't count it + } + else + { + if (diff <= bestang) + { + bestang = diff; + bestseg = segnum; + } + } + segnum = seg->nextforvert2; + } + if (bestseg == UINT_MAX) + { + return UINT_MAX; + } + // Now make sure there are no segs starting at this vertex that form + // an even smaller angle to the splitter. + segnum = v->segs; + while (segnum != UINT_MAX) + { + FPrivSeg *seg = &Segs[segnum]; + if (seg->v2 == vertex2) + { + return UINT_MAX; + } + angle_t segAngle = PointToAngle (Vertices[seg->v2].x - v->x, Vertices[seg->v2].y - v->y); + angle_t diff = splitAngle - segAngle; + if (diff < bestang && seg->partner != bestseg) + { + return UINT_MAX; + } + segnum = seg->nextforvert; + } + return bestseg; +} + +uint32_t FNodeBuilder::CheckLoopEnd (fixed_t dx, fixed_t dy, int vertex) +{ + FPrivVert *v = &Vertices[vertex]; + angle_t splitAngle = PointToAngle (dx, dy) + ANGLE_180; + uint32_t segnum; + angle_t bestang; + uint32_t bestseg; + + // Find the seg starting at this vertex that forms the smallest angle + // to the splitter. + segnum = v->segs; + bestang = ANGLE_MAX; + bestseg = UINT_MAX; + while (segnum != UINT_MAX) + { + FPrivSeg *seg = &Segs[segnum]; + angle_t segAngle = PointToAngle (Vertices[seg->v2].x - v->x, Vertices[seg->v2].y - v->y); + angle_t diff = segAngle - splitAngle; + + if (diff < ANGLE_EPSILON && + PointOnSide (Vertices[seg->v1].x, Vertices[seg->v1].y, v->x, v->y, dx, dy) == 0) + { + // If a seg lies right on the splitter, don't count it + } + else + { + if (diff <= bestang) + { + bestang = diff; + bestseg = segnum; + } + } + segnum = seg->nextforvert; + } + if (bestseg == UINT_MAX) + { + return UINT_MAX; + } + // Now make sure there are no segs ending at this vertex that form + // an even smaller angle to the splitter. + segnum = v->segs2; + while (segnum != UINT_MAX) + { + FPrivSeg *seg = &Segs[segnum]; + angle_t segAngle = PointToAngle (Vertices[seg->v1].x - v->x, Vertices[seg->v1].y - v->y); + angle_t diff = segAngle - splitAngle; + if (diff < bestang && seg->partner != bestseg) + { + return UINT_MAX; + } + segnum = seg->nextforvert2; + } + return bestseg; +} diff --git a/source/core/nodebuilder/nodebuild_utility.cpp b/source/core/nodebuilder/nodebuild_utility.cpp new file mode 100644 index 000000000..afc321668 --- /dev/null +++ b/source/core/nodebuilder/nodebuild_utility.cpp @@ -0,0 +1,535 @@ +/* +** nodebuild_utility.cpp +** +** Miscellaneous node builder utility functions. +** +**--------------------------------------------------------------------------- +** 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 +#ifdef _MSC_VER +#include +#endif +#include +#include "nodebuild.h" +#include "printf.h" +#include "m_fixed.h" +#include "m_bbox.h" + +#if 0 +#define D(x) x +#else +#define D(x) do{}while(0) +#endif + +#if 0 +#define P(x) x +#else +#define P(x) do{}while(0) +#endif + +angle_t FNodeBuilder::PointToAngle (fixed_t x, fixed_t y) +{ + const double rad2bam = double(1<<30) / M_PI; + double ang = g_atan2 (double(y), double(x)); + // Convert to signed first since negative double to unsigned is undefined. + return angle_t(int(ang * rad2bam)) << 1; +} + +void FNodeBuilder::FindUsedVertices (vertex_t *oldverts, int max) +{ + int *map = new int[max]; + int i; + FPrivVert newvert; + + memset (&map[0], -1, sizeof(int)*max); + + for (i = 0; i < Level.NumLines; ++i) + { + ptrdiff_t v1 = Level.Lines[i].v1 - oldverts; + ptrdiff_t v2 = Level.Lines[i].v2 - oldverts; + + if (map[v1] == -1) + { + newvert.x = oldverts[v1].fixX(); + newvert.y = oldverts[v1].fixY(); + map[v1] = VertexMap->SelectVertexExact (newvert); + } + if (map[v2] == -1) + { + newvert.x = oldverts[v2].fixX(); + newvert.y = oldverts[v2].fixY(); + map[v2] = VertexMap->SelectVertexExact (newvert); + } + + Level.Lines[i].v1 = (vertex_t *)(size_t)map[v1]; + Level.Lines[i].v2 = (vertex_t *)(size_t)map[v2]; + } + OldVertexTable = map; +} + +// Retrieves the original vertex -> current vertex table. +// Doing so prevents the node builder from freeing it. + +const int *FNodeBuilder::GetOldVertexTable() +{ + int *table = OldVertexTable; + OldVertexTable = NULL; + return table; +} + +// For every sidedef in the map, create a corresponding seg. + +void FNodeBuilder::MakeSegsFromSides () +{ + int i, j; + + if (Level.NumLines == 0) + { + I_Error ("Map is empty.\n"); + } + + for (i = 0; i < Level.NumLines; ++i) + { + if (Level.Lines[i].sidedef[0] != NULL) + { + CreateSeg (i, 0); + } + else + { + Printf ("Linedef %d does not have a front side.\n", i); + } + + if (Level.Lines[i].sidedef[1] != NULL) + { + j = CreateSeg (i, 1); + if (Level.Lines[i].sidedef[0] != NULL) + { + Segs[j-1].partner = j; + Segs[j].partner = j-1; + } + } + } +} + +int FNodeBuilder::CreateSeg (int linenum, int sidenum) +{ + FPrivSeg seg; + int segnum; + + seg.next = UINT_MAX; + seg.loopnum = 0; + seg.partner = UINT_MAX; + seg.hashnext = NULL; + seg.planefront = false; + seg.planenum = UINT_MAX; + seg.storedseg = UINT_MAX; + + if (sidenum == 0) + { // front + seg.frontsector = Level.Lines[linenum].frontsector; + seg.backsector = Level.Lines[linenum].backsector; + seg.v1 = (int)(size_t)Level.Lines[linenum].v1; + seg.v2 = (int)(size_t)Level.Lines[linenum].v2; + } + else + { // back + seg.frontsector = Level.Lines[linenum].backsector; + seg.backsector = Level.Lines[linenum].frontsector; + seg.v2 = (int)(size_t)Level.Lines[linenum].v1; + seg.v1 = (int)(size_t)Level.Lines[linenum].v2; + } + seg.linedef = linenum; + side_t *sd = Level.Lines[linenum].sidedef[sidenum]; + seg.sidedef = sd != NULL? sd->Index() : int(NO_SIDE); + seg.nextforvert = Vertices[seg.v1].segs; + seg.nextforvert2 = Vertices[seg.v2].segs2; + + segnum = (int)Segs.Push (seg); + Vertices[seg.v1].segs = segnum; + Vertices[seg.v2].segs2 = segnum; + D(Printf(PRINT_LOG, "Seg %4d: From line %d, side %s (%5d,%5d)-(%5d,%5d) [%08x,%08x]-[%08x,%08x]\n", segnum, linenum, sidenum ? "back " : "front", + Vertices[seg.v1].x>>16, Vertices[seg.v1].y>>16, Vertices[seg.v2].x>>16, Vertices[seg.v2].y>>16, + Vertices[seg.v1].x, Vertices[seg.v1].y, Vertices[seg.v2].x, Vertices[seg.v2].y)); + + return segnum; +} + +// For every seg, create FPrivSegs and FPrivVerts. + +void FNodeBuilder::AddSegs(seg_t *segs, int numsegs) +{ + assert(numsegs > 0); + + for (int i = 0; i < numsegs; ++i) + { + FPrivSeg seg; + FPrivVert vert; + int segnum; + + seg.next = UINT_MAX; + seg.loopnum = 0; + seg.partner = UINT_MAX; + seg.hashnext = NULL; + seg.planefront = false; + seg.planenum = UINT_MAX; + seg.storedseg = UINT_MAX; + + seg.frontsector = segs[i].frontsector; + seg.backsector = segs[i].backsector; + vert.x = segs[i].v1->fixX(); + vert.y = segs[i].v1->fixY(); + seg.v1 = VertexMap->SelectVertexExact(vert); + vert.x = segs[i].v2->fixX(); + vert.y = segs[i].v2->fixY(); + seg.v2 = VertexMap->SelectVertexExact(vert); + seg.linedef = segs[i].linedef->Index(); + seg.sidedef = segs[i].sidedef != NULL ? segs[i].sidedef->Index() : int(NO_SIDE); + seg.nextforvert = Vertices[seg.v1].segs; + seg.nextforvert2 = Vertices[seg.v2].segs2; + + segnum = (int)Segs.Push(seg); + Vertices[seg.v1].segs = segnum; + Vertices[seg.v2].segs2 = segnum; + } +} + + + +// Group colinear segs together so that only one seg per line needs to be checked +// by SelectSplitter(). + +void FNodeBuilder::GroupSegPlanes () +{ + const int bucketbits = 12; + FPrivSeg *buckets[1<next = i+1; + seg->hashnext = NULL; + } + + Segs[Segs.Size()-1].next = UINT_MAX; + + for (i = planenum = 0; i < (int)Segs.Size(); ++i) + { + FPrivSeg *seg = &Segs[i]; + fixed_t x1 = Vertices[seg->v1].x; + fixed_t y1 = Vertices[seg->v1].y; + fixed_t x2 = Vertices[seg->v2].x; + fixed_t y2 = Vertices[seg->v2].y; + angle_t ang = PointToAngle (x2 - x1, y2 - y1); + + if (ang >= 1u<<31) + ang += 1u<<31; + + FPrivSeg *check = buckets[ang >>= 31-bucketbits]; + + while (check != NULL) + { + fixed_t cx1 = Vertices[check->v1].x; + fixed_t cy1 = Vertices[check->v1].y; + fixed_t cdx = Vertices[check->v2].x - cx1; + fixed_t cdy = Vertices[check->v2].y - cy1; + if (PointOnSide (x1, y1, cx1, cy1, cdx, cdy) == 0 && + PointOnSide (x2, y2, cx1, cy1, cdx, cdy) == 0) + { + break; + } + check = check->hashnext; + } + if (check != NULL) + { + seg->planenum = check->planenum; + const FSimpleLine *line = &Planes[seg->planenum]; + if (line->dx != 0) + { + if ((line->dx > 0 && x2 > x1) || (line->dx < 0 && x2 < x1)) + { + seg->planefront = true; + } + else + { + seg->planefront = false; + } + } + else + { + if ((line->dy > 0 && y2 > y1) || (line->dy < 0 && y2 < y1)) + { + seg->planefront = true; + } + else + { + seg->planefront = false; + } + } + } + else + { + seg->hashnext = buckets[ang]; + buckets[ang] = seg; + seg->planenum = planenum++; + seg->planefront = true; + + FSimpleLine pline = { Vertices[seg->v1].x, + Vertices[seg->v1].y, + Vertices[seg->v2].x - Vertices[seg->v1].x, + Vertices[seg->v2].y - Vertices[seg->v1].y }; + Planes.Push (pline); + } + } + + D(Printf ("%d planes from %d segs\n", planenum, Segs.Size())); + + PlaneChecked.Reserve ((planenum + 7) / 8); +} + +// Just create one plane per seg. Should be good enough for mini BSPs. +void FNodeBuilder::GroupSegPlanesSimple() +{ + Planes.Resize(Segs.Size()); + for (int i = 0; i < (int)Segs.Size(); ++i) + { + FPrivSeg *seg = &Segs[i]; + FSimpleLine *pline = &Planes[i]; + seg->next = i+1; + seg->hashnext = NULL; + seg->planenum = i; + seg->planefront = true; + pline->x = Vertices[seg->v1].x; + pline->y = Vertices[seg->v1].y; + pline->dx = Vertices[seg->v2].x - Vertices[seg->v1].x; + pline->dy = Vertices[seg->v2].y - Vertices[seg->v1].y; + } + Segs.Last().next = UINT_MAX; + PlaneChecked.Reserve((Segs.Size() + 7) / 8); +} + +void FNodeBuilder::AddSegToBBox (fixed_t bbox[4], const FPrivSeg *seg) +{ + FPrivVert *v1 = &Vertices[seg->v1]; + FPrivVert *v2 = &Vertices[seg->v2]; + + if (v1->x < bbox[BOXLEFT]) bbox[BOXLEFT] = v1->x; + if (v1->x > bbox[BOXRIGHT]) bbox[BOXRIGHT] = v1->x; + if (v1->y < bbox[BOXBOTTOM]) bbox[BOXBOTTOM] = v1->y; + if (v1->y > bbox[BOXTOP]) bbox[BOXTOP] = v1->y; + + if (v2->x < bbox[BOXLEFT]) bbox[BOXLEFT] = v2->x; + if (v2->x > bbox[BOXRIGHT]) bbox[BOXRIGHT] = v2->x; + if (v2->y < bbox[BOXBOTTOM]) bbox[BOXBOTTOM] = v2->y; + if (v2->y > bbox[BOXTOP]) bbox[BOXTOP] = v2->y; +} + +void FNodeBuilder::FLevel::FindMapBounds() +{ + double minx, maxx, miny, maxy; + + minx = maxx = Vertices[0].fX(); + miny = maxy = Vertices[0].fY(); + + for (int i = 1; i < NumLines; ++i) + { + for (int j = 0; j < 2; j++) + { + vertex_t *v = (j == 0 ? Lines[i].v1 : Lines[i].v2); + if (v->fX() < minx) minx = v->fX(); + else if (v->fX() > maxx) maxx = v->fX(); + if (v->fY() < miny) miny = v->fY(); + else if (v->fY() > maxy) maxy = v->fY(); + } + } + + MinX = FLOAT2FIXED(minx); + MinY = FLOAT2FIXED(miny); + MaxX = FLOAT2FIXED(maxx); + MaxY = FLOAT2FIXED(maxy); +} + +FNodeBuilder::IVertexMap::~IVertexMap() +{ +} + +FNodeBuilder::FVertexMap::FVertexMap (FNodeBuilder &builder, + fixed_t minx, fixed_t miny, fixed_t maxx, fixed_t maxy) + : MyBuilder(builder) +{ + MinX = minx; + MinY = miny; + BlocksWide = int(((double(maxx) - minx + 1) + (BLOCK_SIZE - 1)) / BLOCK_SIZE); + BlocksTall = int(((double(maxy) - miny + 1) + (BLOCK_SIZE - 1)) / BLOCK_SIZE); + MaxX = MinX + fixed64_t(BlocksWide) * BLOCK_SIZE - 1; + MaxY = MinY + fixed64_t(BlocksTall) * BLOCK_SIZE - 1; + VertexGrid = new TArray[BlocksWide * BlocksTall]; +} + +FNodeBuilder::FVertexMap::~FVertexMap () +{ + delete[] VertexGrid; +} + +int FNodeBuilder::FVertexMap::SelectVertexExact (FNodeBuilder::FPrivVert &vert) +{ + TArray &block = VertexGrid[GetBlock (vert.x, vert.y)]; + FPrivVert *vertices = &MyBuilder.Vertices[0]; + unsigned int i; + + for (i = 0; i < block.Size(); ++i) + { + if (vertices[block[i]].x == vert.x && vertices[block[i]].y == vert.y) + { + return block[i]; + } + } + + // Not present: add it! + return InsertVertex (vert); +} + +int FNodeBuilder::FVertexMap::SelectVertexClose (FNodeBuilder::FPrivVert &vert) +{ + TArray &block = VertexGrid[GetBlock (vert.x, vert.y)]; + FPrivVert *vertices = &MyBuilder.Vertices[0]; + unsigned int i; + + for (i = 0; i < block.Size(); ++i) + { +#if VERTEX_EPSILON <= 1 + if (vertices[block[i]].x == vert.x && vertices[block[i]].y == vert.y) +#else + if (abs(vertices[block[i]].x - vert.x) < VERTEX_EPSILON && + abs(vertices[block[i]].y - vert.y) < VERTEX_EPSILON) +#endif + { + return block[i]; + } + } + + // Not present: add it! + return InsertVertex (vert); +} + +int FNodeBuilder::FVertexMap::InsertVertex (FNodeBuilder::FPrivVert &vert) +{ + int vertnum; + + vert.segs = UINT_MAX; + vert.segs2 = UINT_MAX; + vertnum = (int)MyBuilder.Vertices.Push (vert); + + // If a vertex is near a block boundary, then it will be inserted on + // both sides of the boundary so that SelectVertexClose can find + // it by checking in only one block. + fixed64_t minx = MAX (MinX, fixed64_t(vert.x) - VERTEX_EPSILON); + fixed64_t maxx = MIN (MaxX, fixed64_t(vert.x) + VERTEX_EPSILON); + fixed64_t miny = MAX (MinY, fixed64_t(vert.y) - VERTEX_EPSILON); + fixed64_t maxy = MIN (MaxY, fixed64_t(vert.y) + VERTEX_EPSILON); + + int blk[4] = + { + GetBlock (minx, miny), + GetBlock (maxx, miny), + GetBlock (minx, maxy), + GetBlock (maxx, maxy) + }; + unsigned int blkcount[4] = + { + VertexGrid[blk[0]].Size(), + VertexGrid[blk[1]].Size(), + VertexGrid[blk[2]].Size(), + VertexGrid[blk[3]].Size() + }; + for (int i = 0; i < 4; ++i) + { + if (VertexGrid[blk[i]].Size() == blkcount[i]) + { + VertexGrid[blk[i]].Push (vertnum); + } + } + + return vertnum; +} + +FNodeBuilder::FVertexMapSimple::FVertexMapSimple(FNodeBuilder &builder) + : MyBuilder(builder) +{ +} + +int FNodeBuilder::FVertexMapSimple::SelectVertexExact(FNodeBuilder::FPrivVert &vert) +{ + FPrivVert *verts = &MyBuilder.Vertices[0]; + unsigned int stop = MyBuilder.Vertices.Size(); + + for (unsigned int i = 0; i < stop; ++i) + { + if (verts[i].x == vert.x && verts[i].y == vert.y) + { + return i; + } + } + // Not present: add it! + return InsertVertex(vert); +} + +int FNodeBuilder::FVertexMapSimple::SelectVertexClose(FNodeBuilder::FPrivVert &vert) +{ + FPrivVert *verts = &MyBuilder.Vertices[0]; + unsigned int stop = MyBuilder.Vertices.Size(); + + for (unsigned int i = 0; i < stop; ++i) + { +#if VERTEX_EPSILON <= 1 + if (verts[i].x == vert.x && verts[i].y == y) +#else + if (abs(verts[i].x - vert.x) < VERTEX_EPSILON && + abs(verts[i].y - vert.y) < VERTEX_EPSILON) +#endif + { + return i; + } + } + // Not present: add it! + return InsertVertex (vert); +} + +int FNodeBuilder::FVertexMapSimple::InsertVertex (FNodeBuilder::FPrivVert &vert) +{ + vert.segs = UINT_MAX; + vert.segs2 = UINT_MAX; + return (int)MyBuilder.Vertices.Push (vert); +} diff --git a/source/core/sectorgeometry.cpp b/source/core/sectorgeometry.cpp index c39e11042..423b94630 100644 --- a/source/core/sectorgeometry.cpp +++ b/source/core/sectorgeometry.cpp @@ -39,6 +39,7 @@ #include "gamefuncs.h" #include "texturemanager.h" #include "earcut.hpp" +#include "nodebuilder/nodebuild.h" SectorGeometry sectorGeometry; @@ -214,7 +215,7 @@ public: // //========================================================================== -void SectorGeometry::MakeVertices(unsigned int secnum, int plane, const FVector2& offset) +bool SectorGeometry::MakeVertices(unsigned int secnum, int plane, const FVector2& offset) { auto sec = §or[secnum]; int numvertices = sec->wallnum; @@ -229,7 +230,6 @@ void SectorGeometry::MakeVertices(unsigned int secnum, int plane, const FVector2 FixedBitArray done; int fz = sec->floorz, cz = sec->ceilingz; - sec->floorz = sec->ceilingz = 0; int vertstoadd = numvertices; @@ -249,7 +249,8 @@ void SectorGeometry::MakeVertices(unsigned int secnum, int plane, const FVector2 if (fabs(X) > 32768. || fabs(Y) > 32768.) { // If we get here there's some fuckery going around with the coordinates. Let's better abort and wait for things to realign. - return; + // Do not try alternative methods if this happens. + return true; } curPoly->push_back(std::make_pair(X, Y)); done.Set(start); @@ -279,6 +280,12 @@ void SectorGeometry::MakeVertices(unsigned int secnum, int plane, const FVector2 } if (outer != 0) std::swap(polygon[0], polygon[outer]); auto indices = mapbox::earcut(polygon); + if (indices.size() < 3 * (sec->wallnum - 2)) + { + // this means that full triangulation failed. + return false; + } + sec->floorz = sec->ceilingz = 0; int p = 0; for (size_t a = 0; a < polygon.size(); a++) @@ -310,7 +317,96 @@ void SectorGeometry::MakeVertices(unsigned int secnum, int plane, const FVector2 sec->floorz = fz; sec->ceilingz = cz; + return true; +} +//========================================================================== +// +// Use ZDoom's node builder if the simple approach fails. +// This will create something usable in the vast majority of cases, +// even if the result is less efficient. +// +//========================================================================== + +bool SectorGeometry::MakeVertices2(unsigned int secnum, int plane, const FVector2& offset) +{ + + // Convert our sector into something the node builder understands + auto sect = §or[secnum]; + TArray vertexes(sect->wallnum, true); + TArray lines(sect->wallnum, true); + TArray sides(sect->wallnum, true); + for (int i = 0; i < sect->wallnum; i++) + { + auto wal = &wall[sect->wallptr + i]; + vertexes[i].p = { wal->x * (1 / 16.), wal->y * (1 / -16.) }; + lines[i].backsector = nullptr; + lines[i].frontsector = sect; + lines[i].linenum = i; + lines[i].sidedef[0] = &sides[i]; + lines[i].sidedef[1] = nullptr; + lines[i].v1 = &vertexes[i]; + lines[i].v2 = &vertexes[wal->point2 - sect->wallptr]; + + sides[i].sidenum = i; + sides[i].sector = sect; + } + + + FNodeBuilder::FLevel leveldata = + { + &vertexes[0], (int)vertexes.Size(), + &sides[0], (int)sides.Size(), + &lines[0], (int)lines.Size(), + 0, 0, 0, 0 + }; + leveldata.FindMapBounds(); + FNodeBuilder builder(leveldata); + + FLevelLocals Level; + builder.Extract(Level); + + // Now turn the generated subsectors into triangle meshes + + auto& entry = data[secnum].planes[plane]; + + int fz = sect->floorz, cz = sect->ceilingz; + sect->floorz = sect->ceilingz = 0; + + for (auto& sub : Level.subsectors) + { + auto v0 = sub.firstline->v1; + for (unsigned i = 1; i < sub.numlines-1; i++) + { + auto v1 = sub.firstline[i].v1; + auto v2 = sub.firstline[i].v2; + + entry.vertices.Push({ (float)v0->fX(), (float)v0->fY(), 0 }); + entry.vertices.Push({ (float)v1->fX(), (float)v1->fY(), 0 }); + entry.vertices.Push({ (float)v2->fX(), (float)v2->fY(), 0 }); + + } + } + + // calculate the rest. + auto texture = tileGetTexture(plane ? sect->ceilingpicnum : sect->floorpicnum); + + UVCalculator uvcalc(sect, plane, texture, offset); + + entry.texcoords.Resize(entry.vertices.Size()); + for (unsigned i = 0; i < entry.vertices.Size(); i++) + { + auto& pt = entry.vertices[i]; + + float planez; + PlanesAtPoint(sect, (pt.X * 16), (pt.Y * -16), plane ? &planez : nullptr, !plane ? &planez : nullptr); + entry.vertices[i].Z = planez; + entry.texcoords[i] = uvcalc.GetUV(int(pt.X * 16.), int(pt.Y * -16.), pt.Z); + } + entry.normal = CalcNormal(sect, plane); + sect->floorz = fz; + sect->ceilingz = cz; + return true; } //========================================================================== @@ -352,5 +448,10 @@ void SectorGeometry::ValidateSector(unsigned int secnum, int plane, const FVecto *compare = *sec; data[secnum].poscompare[plane] = wall[sec->wallptr].pos; data[secnum].poscompare2[plane] = wall[wall[sec->wallptr].point2].pos; - MakeVertices(secnum, plane, offset); + if (data[secnum].degenerate || !MakeVertices(secnum, plane, offset)) + { + data[secnum].degenerate = true; + //Printf(TEXTCOLOR_YELLOW "Normal triangulation failed for sector %d. Retrying with alternative approach\n", secnum); + MakeVertices2(secnum, plane, offset); + } } diff --git a/source/core/sectorgeometry.h b/source/core/sectorgeometry.h index 75026d028..32ce750f8 100644 --- a/source/core/sectorgeometry.h +++ b/source/core/sectorgeometry.h @@ -17,6 +17,7 @@ struct SectorGeometryData sectortype compare[2] = {}; vec2_t poscompare[2] = {}; vec2_t poscompare2[2] = {}; + bool degenerate = false; }; class SectorGeometry @@ -24,7 +25,8 @@ class SectorGeometry TArray data; void ValidateSector(unsigned sectnum, int plane, const FVector2& offset); - void MakeVertices(unsigned sectnum, int plane, const FVector2& offset); + bool MakeVertices(unsigned sectnum, int plane, const FVector2& offset); + bool MakeVertices2(unsigned sectnum, int plane, const FVector2& offset); public: SectorGeometryPlane* get(unsigned sectnum, int plane, const FVector2& offset)