mirror of
https://github.com/DrBeef/Raze.git
synced 2024-11-16 09:21:12 +00:00
f7dd0ec4a2
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.
470 lines
12 KiB
C++
470 lines
12 KiB
C++
/*
|
|
** 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<vertex_t> vertexes;
|
|
TArray<subsector_t> subsectors;
|
|
TArray<node_t> nodes;
|
|
TArray<seg_t> 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<int> *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<node_t> Nodes;
|
|
TArray<subsector_t> Subsectors;
|
|
TArray<uint32_t> SubsectorSets;
|
|
TArray<FPrivSeg> Segs;
|
|
TArray<FPrivVert> Vertices;
|
|
TArray<USegPtr> SegList;
|
|
TArray<uint8_t> PlaneChecked;
|
|
TArray<FSimpleLine> Planes;
|
|
|
|
TArray<int> Touched; // Loops a splitter touches on a vertex
|
|
TArray<int> Colinear; // Loops with edges colinear to a splitter
|
|
FEventTree Events; // Vertices intersected by the current splitter
|
|
|
|
TArray<FSplitSharer> 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<glseg_t> &segs, int subsector, vertex_t *outVerts);
|
|
uint32_t PushGLSeg (TArray<glseg_t> &segs, const FPrivSeg *seg, vertex_t *outVerts);
|
|
void PushConnectingGLSeg (int subsector, TArray<glseg_t> &segs, vertex_t *v1, vertex_t *v2);
|
|
int OutputDegenerateSubsector (TArray<glseg_t> &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;
|