You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
873 lines
29 KiB
873 lines
29 KiB
// David Eberly, Geometric Tools, Redmond WA 98052
|
|
// Copyright (c) 1998-2021
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
// https://www.boost.org/LICENSE_1_0.txt
|
|
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
|
|
// Version: 4.0.2020.09.17
|
|
|
|
#pragma once
|
|
|
|
#include <Mathematics/Logger.h>
|
|
#include <Mathematics/Vector2.h>
|
|
#include <Mathematics/EdgeKey.h>
|
|
#include <list>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#define GTE_BSPPOLYGON2_ENABLE_DEBUG_PRINT
|
|
#if defined(GTE_BSPPOLYGON2_ENABLE_DEBUG_PRINT)
|
|
#include <fstream>
|
|
#endif
|
|
|
|
namespace gte
|
|
{
|
|
template <typename Real>
|
|
class BSPPolygon2
|
|
{
|
|
public:
|
|
// vertices
|
|
typedef Vector2<Real> Vertex;
|
|
typedef std::map<Vertex, int> VMap;
|
|
typedef std::vector<Vertex> VArray;
|
|
|
|
// edges
|
|
typedef EdgeKey<true> Edge;
|
|
typedef std::map<Edge, int> EMap;
|
|
typedef std::vector<Edge> EArray;
|
|
|
|
// Construction and destruction.
|
|
BSPPolygon2(Real epsilon)
|
|
:
|
|
mEpsilon(epsilon >= (Real)0 ? epsilon : (Real)0)
|
|
{
|
|
}
|
|
|
|
~BSPPolygon2()
|
|
{
|
|
}
|
|
|
|
// Copy semantics.
|
|
BSPPolygon2(BSPPolygon2 const& polygon)
|
|
{
|
|
*this = polygon;
|
|
}
|
|
|
|
BSPPolygon2& operator=(BSPPolygon2 const& polygon)
|
|
{
|
|
mEpsilon = polygon.mEpsilon;
|
|
mVMap = polygon.mVMap;
|
|
mVArray = polygon.mVArray;
|
|
mEMap = polygon.mEMap;
|
|
mEArray = polygon.mEArray;
|
|
mTree = (polygon.mTree ? polygon.mTree->GetCopy() : nullptr);
|
|
return *this;
|
|
}
|
|
|
|
// Move semantics.
|
|
BSPPolygon2(BSPPolygon2&& polygon)
|
|
{
|
|
*this = std::move(polygon);
|
|
}
|
|
|
|
BSPPolygon2& operator=(BSPPolygon2&& polygon)
|
|
{
|
|
mEpsilon = polygon.mEpsilon;
|
|
mVMap = std::move(polygon.mVMap);
|
|
mVArray = std::move(polygon.mVArray);
|
|
mEMap = std::move(polygon.mEMap);
|
|
mEArray = std::move(polygon.mEArray);
|
|
mTree = polygon.mTree;
|
|
polygon.mTree = nullptr;
|
|
return *this;
|
|
}
|
|
|
|
// Support for deferred construction.
|
|
int InsertVertex(Vertex const& vertex)
|
|
{
|
|
auto iter = mVMap.find(vertex);
|
|
if (iter != mVMap.end())
|
|
{
|
|
// Vertex already in map, just return its unique index.
|
|
return iter->second;
|
|
}
|
|
|
|
// Vertex not in map, insert it and assign it a unique index.
|
|
int i = static_cast<int>(mVArray.size());
|
|
mVMap.insert(std::make_pair(vertex, i));
|
|
mVArray.push_back(vertex);
|
|
return i;
|
|
}
|
|
|
|
int InsertEdge(Edge const& edge)
|
|
{
|
|
LogAssert(edge.V[0] != edge.V[1], "Degenerate edges not allowed.");
|
|
|
|
auto iter = mEMap.find(edge);
|
|
if (iter != mEMap.end())
|
|
{
|
|
// Edge already in map, just return its unique index.
|
|
return iter->second;
|
|
}
|
|
|
|
// Edge not in map, insert it and assign it a unique index.
|
|
int i = static_cast<int>(mEArray.size());
|
|
mEMap.insert(std::make_pair(edge, i));
|
|
mEArray.push_back(edge);
|
|
return i;
|
|
}
|
|
|
|
void Finalize()
|
|
{
|
|
// The BSPTree2 constructor is badly designed. The '*this'
|
|
// is passed as non-const but the previous code in this function
|
|
// passed 'this->mEArray' as const. The mEArray can be modified
|
|
// via '*this' in the BSPTree2 class, which led to an infinite
|
|
// loop in a test data set for a bug report. For now, make a
|
|
// copy of mEArray and pass it.
|
|
EArray eArray = mEArray;
|
|
mTree = std::make_shared<BSPTree2>(*this, eArray, mEpsilon);
|
|
}
|
|
|
|
// Member access.
|
|
inline int GetNumVertices() const
|
|
{
|
|
return static_cast<int>(mVMap.size());
|
|
}
|
|
|
|
inline Vertex const& GetVertex(int i) const
|
|
{
|
|
return mVArray[i];
|
|
}
|
|
|
|
inline int GetNumEdges() const
|
|
{
|
|
return static_cast<int>(mEMap.size());
|
|
}
|
|
|
|
inline Edge const& GetEdge(int i) const
|
|
{
|
|
return mEArray[i];
|
|
}
|
|
|
|
// negation
|
|
BSPPolygon2 operator~() const
|
|
{
|
|
LogAssert(mTree != nullptr, "Tree must exist.");
|
|
|
|
BSPPolygon2 neg(mEpsilon);
|
|
neg.mVMap = mVMap;
|
|
neg.mVArray = mVArray;
|
|
|
|
for (auto const& element : mEMap)
|
|
{
|
|
auto const& edge = element.first;
|
|
neg.InsertEdge(Edge(edge.V[1], edge.V[0]));
|
|
}
|
|
|
|
neg.mTree = mTree->GetCopy();
|
|
neg.mTree->Negate();
|
|
return neg;
|
|
}
|
|
|
|
// intersection
|
|
BSPPolygon2 operator&(BSPPolygon2 const& polygon) const
|
|
{
|
|
LogAssert(mTree != nullptr, "Tree must exist.");
|
|
|
|
BSPPolygon2 intersect(mEpsilon);
|
|
GetInsideEdgesFrom(polygon, intersect);
|
|
polygon.GetInsideEdgesFrom(*this, intersect);
|
|
intersect.Finalize();
|
|
return intersect;
|
|
}
|
|
|
|
// union
|
|
BSPPolygon2 operator|(BSPPolygon2 const& polygon) const
|
|
{
|
|
return ~(~*this & ~polygon);
|
|
}
|
|
|
|
// difference
|
|
BSPPolygon2 operator-(BSPPolygon2 const& polygon) const
|
|
{
|
|
return *this & ~polygon;
|
|
}
|
|
|
|
// exclusive or
|
|
BSPPolygon2 operator^(BSPPolygon2 const& polygon) const
|
|
{
|
|
return (*this - polygon) | (polygon - *this);
|
|
}
|
|
|
|
// point location (-1 inside, 0 on polygon, 1 outside)
|
|
int PointLocation(Vertex const& vertex) const
|
|
{
|
|
LogAssert(mTree != nullptr, "Tree must exist.");
|
|
return mTree->PointLocation(*this, vertex);
|
|
}
|
|
|
|
#if defined(GTE_BSPPOLYGON2_ENABLE_DEBUG_PRINT)
|
|
// debugging support
|
|
void Print(std::string const& filename) const
|
|
{
|
|
std::ofstream output(filename);
|
|
|
|
int const numVertices = GetNumVertices();
|
|
output << "vquantity = " << numVertices << std::endl;
|
|
for (int i = 0; i < numVertices; ++i)
|
|
{
|
|
output << i << " (" << mVArray[i][0] << ',' << mVArray[i][1] << ')' << std::endl;
|
|
}
|
|
output << std::endl;
|
|
|
|
int const numEdges = GetNumEdges();
|
|
output << "equantity = " << numEdges << std::endl;
|
|
for (int i = 0; i < numEdges; ++i)
|
|
{
|
|
output << " <" << mEArray[i].V[0] << ',' << mEArray[i].V[1] << '>' << std::endl;
|
|
}
|
|
output << std::endl;
|
|
|
|
output << "bsp tree" << std::endl;
|
|
if (mTree)
|
|
{
|
|
mTree->Print(output, 0, 'r');
|
|
}
|
|
output << std::endl;
|
|
output.close();
|
|
}
|
|
#endif
|
|
|
|
private:
|
|
// Binary space partitioning tree support for polygon Boolean
|
|
// operations.
|
|
class BSPTree2
|
|
{
|
|
public:
|
|
// Construction and destruction.
|
|
BSPTree2(Real epsilon)
|
|
:
|
|
mEpsilon(epsilon >= (Real)0 ? epsilon : (Real)0)
|
|
{
|
|
}
|
|
|
|
BSPTree2(BSPPolygon2& polygon, EArray const& edges, Real epsilon)
|
|
:
|
|
mEpsilon(epsilon >= (Real)0 ? epsilon : (Real)0)
|
|
{
|
|
LogAssert(edges.size() > 0, "Invalid input.");
|
|
|
|
// Construct splitting line from first edge.
|
|
Vertex end0 = polygon.GetVertex(edges[0].V[0]);
|
|
Vertex end1 = polygon.GetVertex(edges[0].V[1]);
|
|
|
|
// Add edge to coincident list.
|
|
mCoincident.push_back(edges[0]);
|
|
|
|
// Split remaining edges.
|
|
EArray posArray, negArray;
|
|
for (size_t i = 1; i < edges.size(); ++i)
|
|
{
|
|
int v0 = edges[i].V[0];
|
|
int v1 = edges[i].V[1];
|
|
Vertex vertex0 = polygon.GetVertex(v0);
|
|
Vertex vertex1 = polygon.GetVertex(v1);
|
|
|
|
Vertex intr;
|
|
int vmid;
|
|
|
|
switch (Classify(end0, end1, vertex0, vertex1, intr))
|
|
{
|
|
case TRANSVERSE_POSITIVE:
|
|
// modify edge <V0,V1> to <V0,I>, add new edge <I,V1>
|
|
vmid = polygon.InsertVertex(intr);
|
|
if (vmid == v0 || vmid == v1)
|
|
{
|
|
// The intersection point is within epsilon of an
|
|
// endpoint.
|
|
mCoincident.push_back(edges[i]);
|
|
}
|
|
else
|
|
{
|
|
polygon.SplitEdge(v0, v1, vmid);
|
|
posArray.push_back(Edge(vmid, v1));
|
|
negArray.push_back(Edge(v0, vmid));
|
|
}
|
|
break;
|
|
case TRANSVERSE_NEGATIVE:
|
|
// modify edge <V0,V1> to <V0,I>, add new edge <I,V1>
|
|
vmid = polygon.InsertVertex(intr);
|
|
if (vmid == v0 || vmid == v1)
|
|
{
|
|
// The intersection point is within epsilon of an
|
|
// endpoint.
|
|
mCoincident.push_back(edges[i]);
|
|
}
|
|
else
|
|
{
|
|
polygon.SplitEdge(v0, v1, vmid);
|
|
posArray.push_back(Edge(v0, vmid));
|
|
negArray.push_back(Edge(vmid, v1));
|
|
}
|
|
break;
|
|
case ALL_POSITIVE:
|
|
posArray.push_back(edges[i]);
|
|
break;
|
|
case ALL_NEGATIVE:
|
|
negArray.push_back(edges[i]);
|
|
break;
|
|
default: // COINCIDENT
|
|
mCoincident.push_back(edges[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (posArray.size() > 0)
|
|
{
|
|
mPosChild = std::make_shared<BSPTree2>(polygon, posArray, mEpsilon);
|
|
}
|
|
|
|
if (negArray.size() > 0)
|
|
{
|
|
mNegChild = std::make_shared<BSPTree2>(polygon, negArray, mEpsilon);
|
|
}
|
|
}
|
|
|
|
~BSPTree2()
|
|
{
|
|
}
|
|
|
|
// Disallow copying and assignment. Use GetCopy() instead to
|
|
// obtain a deep copy of the BSP tree.
|
|
BSPTree2(const BSPTree2&) = delete;
|
|
BSPTree2& operator= (const BSPTree2&) = delete;
|
|
|
|
std::shared_ptr<BSPTree2> GetCopy() const
|
|
{
|
|
auto tree = std::make_shared<BSPTree2>(mEpsilon);
|
|
|
|
tree->mCoincident = mCoincident;
|
|
|
|
if (mPosChild)
|
|
{
|
|
tree->mPosChild = mPosChild->GetCopy();
|
|
}
|
|
|
|
if (mNegChild)
|
|
{
|
|
tree->mNegChild = mNegChild->GetCopy();
|
|
}
|
|
|
|
return tree;
|
|
}
|
|
|
|
// Polygon Boolean operation support.
|
|
void Negate()
|
|
{
|
|
// Reverse coincident edge directions.
|
|
for (auto& edge : mCoincident)
|
|
{
|
|
std::swap(edge.V[0], edge.V[1]);
|
|
}
|
|
|
|
// Swap positive and negative subtrees.
|
|
std::swap(mPosChild, mNegChild);
|
|
|
|
if (mPosChild)
|
|
{
|
|
mPosChild->Negate();
|
|
}
|
|
|
|
if (mNegChild)
|
|
{
|
|
mNegChild->Negate();
|
|
}
|
|
}
|
|
|
|
void GetPartition(BSPPolygon2 const& polygon, Vertex const& v0,
|
|
Vertex const& v1, BSPPolygon2& pos, BSPPolygon2& neg,
|
|
BSPPolygon2& coSame, BSPPolygon2& coDiff) const
|
|
{
|
|
// Construct splitting line from first coincident edge.
|
|
Vertex end0 = polygon.GetVertex(mCoincident[0].V[0]);
|
|
Vertex end1 = polygon.GetVertex(mCoincident[0].V[1]);
|
|
|
|
Vertex intr;
|
|
|
|
switch (Classify(end0, end1, v0, v1, intr))
|
|
{
|
|
case TRANSVERSE_POSITIVE:
|
|
GetPosPartition(polygon, intr, v1, pos, neg, coSame, coDiff);
|
|
GetNegPartition(polygon, v0, intr, pos, neg, coSame, coDiff);
|
|
break;
|
|
case TRANSVERSE_NEGATIVE:
|
|
GetPosPartition(polygon, v0, intr, pos, neg, coSame, coDiff);
|
|
GetNegPartition(polygon, intr, v1, pos, neg, coSame, coDiff);
|
|
break;
|
|
case ALL_POSITIVE:
|
|
GetPosPartition(polygon, v0, v1, pos, neg, coSame, coDiff);
|
|
break;
|
|
case ALL_NEGATIVE:
|
|
GetNegPartition(polygon, v0, v1, pos, neg, coSame, coDiff);
|
|
break;
|
|
default: // COINCIDENT
|
|
GetCoPartition(polygon, v0, v1, pos, neg, coSame, coDiff);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Point-in-polygon support (-1 outside, 0 on polygon, +1 inside).
|
|
int PointLocation(BSPPolygon2 const& polygon, Vertex const& vertex) const
|
|
{
|
|
// Construct splitting line from first coincident edge.
|
|
Vertex end0 = polygon.GetVertex(mCoincident[0].V[0]);
|
|
Vertex end1 = polygon.GetVertex(mCoincident[0].V[1]);
|
|
|
|
switch (Classify(end0, end1, vertex))
|
|
{
|
|
case ALL_POSITIVE:
|
|
if (mPosChild)
|
|
{
|
|
return mPosChild->PointLocation(polygon, vertex);
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
case ALL_NEGATIVE:
|
|
if (mNegChild)
|
|
{
|
|
return mNegChild->PointLocation(polygon, vertex);
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
default: // COINCIDENT
|
|
return CoPointLocation(polygon, vertex);
|
|
}
|
|
}
|
|
|
|
#if defined(GTE_BSPPOLYGON2_ENABLE_DEBUG_PRINT)
|
|
void Print(std::ofstream& outFile, int level, char type) const
|
|
{
|
|
for (size_t i = 0; i < mCoincident.size(); ++i)
|
|
{
|
|
for (int j = 0; j < 4 * level; ++j)
|
|
{
|
|
outFile << ' ';
|
|
}
|
|
|
|
outFile << type << " <" << mCoincident[i].V[0] << ',' <<
|
|
mCoincident[i].V[1] << ">" << std::endl;
|
|
}
|
|
|
|
if (mPosChild)
|
|
{
|
|
mPosChild->Print(outFile, level + 1, 'p');
|
|
}
|
|
|
|
if (mNegChild)
|
|
{
|
|
mNegChild->Print(outFile, level + 1, 'n');
|
|
}
|
|
}
|
|
#endif
|
|
|
|
private:
|
|
enum
|
|
{
|
|
TRANSVERSE_POSITIVE,
|
|
TRANSVERSE_NEGATIVE,
|
|
ALL_POSITIVE,
|
|
ALL_NEGATIVE,
|
|
COINCIDENT
|
|
};
|
|
|
|
int Classify(Vertex const& end0, Vertex const& end1,
|
|
Vertex const& v0, Vertex const& v1, Vertex& intr) const
|
|
{
|
|
Vertex dir = end1 - end0;
|
|
Vertex nor = Perp(dir);
|
|
Vertex diff0 = v0 - end0;
|
|
Vertex diff1 = v1 - end0;
|
|
|
|
Real d0 = Dot(nor, diff0);
|
|
Real d1 = Dot(nor, diff1);
|
|
|
|
if (d0 * d1 < (Real)0)
|
|
{
|
|
// Edge <V0,V1> transversely crosses line. Compute point
|
|
// of intersection I = V0 + t*(V1 - V0).
|
|
Real t = d0 / (d0 - d1);
|
|
if (t > mEpsilon)
|
|
{
|
|
if (t < (Real)1 - mEpsilon)
|
|
{
|
|
intr = v0 + t * (v1 - v0);
|
|
if (d1 > (Real)0)
|
|
{
|
|
return TRANSVERSE_POSITIVE;
|
|
}
|
|
else
|
|
{
|
|
return TRANSVERSE_NEGATIVE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// T is effectively 1 (numerical round-off issue),
|
|
// so set d1 = 0 and go on to other cases.
|
|
d1 = (Real)0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// T is effectively 0 (numerical round-off issue), so
|
|
// set d0 = 0 and go on to other cases.
|
|
d0 = (Real)0;
|
|
}
|
|
}
|
|
|
|
if (d0 > (Real)0 || d1 > (Real)0)
|
|
{
|
|
// edge on positive side of line
|
|
return ALL_POSITIVE;
|
|
}
|
|
|
|
if (d0 < (Real)0 || d1 < (Real)0)
|
|
{
|
|
// edge on negative side of line
|
|
return ALL_NEGATIVE;
|
|
}
|
|
|
|
return COINCIDENT;
|
|
}
|
|
|
|
void GetPosPartition(BSPPolygon2 const& polygon, Vertex const& v0,
|
|
Vertex const& v1, BSPPolygon2& pos, BSPPolygon2& neg,
|
|
BSPPolygon2& coSame, BSPPolygon2& coDiff) const
|
|
{
|
|
if (mPosChild)
|
|
{
|
|
mPosChild->GetPartition(polygon, v0, v1, pos, neg, coSame, coDiff);
|
|
}
|
|
else
|
|
{
|
|
int i0 = pos.InsertVertex(v0);
|
|
int i1 = pos.InsertVertex(v1);
|
|
pos.InsertEdge(Edge(i0, i1));
|
|
}
|
|
}
|
|
|
|
void GetNegPartition(BSPPolygon2 const& polygon, Vertex const& v0,
|
|
Vertex const& v1, BSPPolygon2& pos, BSPPolygon2& neg,
|
|
BSPPolygon2& coSame, BSPPolygon2& coDiff) const
|
|
{
|
|
if (mNegChild)
|
|
{
|
|
mNegChild->GetPartition(polygon, v0, v1, pos, neg, coSame, coDiff);
|
|
}
|
|
else
|
|
{
|
|
int i0 = neg.InsertVertex(v0);
|
|
int i1 = neg.InsertVertex(v1);
|
|
neg.InsertEdge(Edge(i0, i1));
|
|
}
|
|
}
|
|
|
|
class Interval
|
|
{
|
|
public:
|
|
Interval(Real inT0, Real inT1, bool inSameDir, bool inTouching)
|
|
:
|
|
t0(inT0),
|
|
t1(inT1),
|
|
sameDir(inSameDir),
|
|
touching(inTouching)
|
|
{
|
|
}
|
|
|
|
Real t0, t1;
|
|
bool sameDir, touching;
|
|
};
|
|
|
|
void GetCoPartition(BSPPolygon2 const& polygon, Vertex const& v0,
|
|
Vertex const& v1, BSPPolygon2& pos, BSPPolygon2& neg,
|
|
BSPPolygon2& coSame, BSPPolygon2& coDiff) const
|
|
{
|
|
// Segment the line containing V0 and V1 by the coincident
|
|
// intervals that intersect <V0,V1>.
|
|
Vertex dir = v1 - v0;
|
|
Real tmax = Dot(dir, dir);
|
|
|
|
Vertex end0, end1;
|
|
Real t0, t1;
|
|
bool sameDir;
|
|
|
|
std::list<Interval> intervals;
|
|
typename std::list<Interval>::iterator iter;
|
|
|
|
for (auto const& edge : mCoincident)
|
|
{
|
|
end0 = polygon.GetVertex(edge.V[0]);
|
|
end1 = polygon.GetVertex(edge.V[1]);
|
|
|
|
t0 = Dot(dir, end0 - v0);
|
|
if (std::fabs(t0) <= mEpsilon)
|
|
{
|
|
t0 = (Real)0;
|
|
}
|
|
else if (std::fabs(t0 - tmax) <= mEpsilon)
|
|
{
|
|
t0 = tmax;
|
|
}
|
|
|
|
t1 = Dot(dir, end1 - v0);
|
|
if (std::fabs(t1) <= mEpsilon)
|
|
{
|
|
t1 = (Real)0;
|
|
}
|
|
else if (std::fabs(t1 - tmax) <= mEpsilon)
|
|
{
|
|
t1 = tmax;
|
|
}
|
|
|
|
sameDir = (t1 > t0);
|
|
if (!sameDir)
|
|
{
|
|
Real save = t0;
|
|
t0 = t1;
|
|
t1 = save;
|
|
}
|
|
|
|
if (t1 > (Real)0 && t0 < tmax)
|
|
{
|
|
if (intervals.empty())
|
|
{
|
|
intervals.push_front(Interval(t0, t1, sameDir, true));
|
|
}
|
|
else
|
|
{
|
|
for (iter = intervals.begin(); iter != intervals.end(); ++iter)
|
|
{
|
|
if (std::fabs(t1 - iter->t0) <= mEpsilon)
|
|
{
|
|
t1 = iter->t0;
|
|
}
|
|
|
|
if (t1 <= iter->t0)
|
|
{
|
|
// [t0,t1] is on the left of [I.t0,I.t1]
|
|
intervals.insert(iter, Interval(t0, t1, sameDir, true));
|
|
break;
|
|
}
|
|
|
|
// Theoretically, the intervals are disjoint
|
|
// or intersect only at an end point. The
|
|
// assert makes sure that [t0,t1] is to the
|
|
// right of [I.t0,I.t1].
|
|
if (std::fabs(t0 - iter->t1) <= mEpsilon)
|
|
{
|
|
t0 = iter->t1;
|
|
}
|
|
|
|
LogAssert(t0 >= iter->t1, "Invalid ordering in BSPTree2::GetCoPartition.");
|
|
|
|
auto last = std::prev(intervals.end());
|
|
if (iter == last)
|
|
{
|
|
intervals.push_back(Interval(t0, t1, sameDir, true));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (intervals.empty())
|
|
{
|
|
GetPosPartition(polygon, v0, v1, pos, neg, coSame, coDiff);
|
|
GetNegPartition(polygon, v0, v1, pos, neg, coSame, coDiff);
|
|
return;
|
|
}
|
|
|
|
// Insert outside intervals between the touching intervals.
|
|
// It is possible that two touching intervals are adjacent,
|
|
// so this is not just a simple alternation of touching and
|
|
// outside intervals.
|
|
Interval& front = intervals.front();
|
|
if (front.t0 > (Real)0)
|
|
{
|
|
intervals.push_front(Interval((Real)0, front.t0, front.sameDir, false));
|
|
}
|
|
else
|
|
{
|
|
front.t0 = (Real)0;
|
|
}
|
|
|
|
Interval& back = intervals.back();
|
|
if (back.t1 < tmax)
|
|
{
|
|
intervals.push_back(Interval(back.t1, tmax, back.sameDir, false));
|
|
}
|
|
else
|
|
{
|
|
back.t1 = tmax;
|
|
}
|
|
|
|
typename std::list<Interval>::iterator iter0 = intervals.begin();
|
|
typename std::list<Interval>::iterator iter1 = intervals.begin();
|
|
for (++iter1; iter1 != intervals.end(); ++iter0, ++iter1)
|
|
{
|
|
t0 = iter0->t1;
|
|
t1 = iter1->t0;
|
|
if (t1 - t0 > mEpsilon)
|
|
{
|
|
iter0 = intervals.insert(iter1, Interval(t0, t1, true, false));
|
|
}
|
|
}
|
|
|
|
// Process the segmentation.
|
|
Real invTMax = (Real)1 / tmax;
|
|
t0 = intervals.front().t0 * invTMax;
|
|
end1 = v0 + (intervals.front().t0 * invTMax) * dir;
|
|
for (iter = intervals.begin(); iter != intervals.end(); ++iter)
|
|
{
|
|
end0 = end1;
|
|
t1 = iter->t1 * invTMax;
|
|
end1 = v0 + (iter->t1 * invTMax) * dir;
|
|
|
|
if (iter->touching)
|
|
{
|
|
Edge edge;
|
|
if (iter->sameDir)
|
|
{
|
|
edge.V[0] = coSame.InsertVertex(end0);
|
|
edge.V[1] = coSame.InsertVertex(end1);
|
|
if (edge.V[0] != edge.V[1])
|
|
{
|
|
coSame.InsertEdge(edge);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
edge.V[0] = coDiff.InsertVertex(end1);
|
|
edge.V[1] = coDiff.InsertVertex(end0);
|
|
if (edge.V[0] != edge.V[1])
|
|
{
|
|
coDiff.InsertEdge(edge);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GetPosPartition(polygon, end0, end1, pos, neg, coSame, coDiff);
|
|
GetNegPartition(polygon, end0, end1, pos, neg, coSame, coDiff);
|
|
}
|
|
}
|
|
}
|
|
|
|
// point-in-polygon support
|
|
int Classify(Vertex const& end0, Vertex const& end1, Vertex const& vertex) const
|
|
{
|
|
Vertex dir = end1 - end0;
|
|
Vertex nor = Perp(dir);
|
|
Vertex diff = vertex - end0;
|
|
Real c = Dot(nor, diff);
|
|
|
|
if (c > mEpsilon)
|
|
{
|
|
return ALL_POSITIVE;
|
|
}
|
|
|
|
if (c < -mEpsilon)
|
|
{
|
|
return ALL_NEGATIVE;
|
|
}
|
|
|
|
return COINCIDENT;
|
|
}
|
|
|
|
int CoPointLocation(BSPPolygon2 const& polygon, Vertex const& vertex) const
|
|
{
|
|
for (auto const& edge : mCoincident)
|
|
{
|
|
Vertex end0 = polygon.GetVertex(edge.V[0]);
|
|
Vertex end1 = polygon.GetVertex(edge.V[1]);
|
|
Vertex dir = end1 - end0;
|
|
Vertex diff = vertex - end0;
|
|
Real tmax = Dot(dir, dir);
|
|
Real t = Dot(dir, diff);
|
|
|
|
if (-mEpsilon <= t && t <= tmax + mEpsilon)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// It does not matter which subtree you use.
|
|
if (mPosChild)
|
|
{
|
|
return mPosChild->PointLocation(polygon, vertex);
|
|
}
|
|
|
|
if (mNegChild)
|
|
{
|
|
return mNegChild->PointLocation(polygon, vertex);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
Real mEpsilon;
|
|
EArray mCoincident;
|
|
std::shared_ptr<BSPTree2> mPosChild;
|
|
std::shared_ptr<BSPTree2> mNegChild;
|
|
};
|
|
|
|
private:
|
|
void SplitEdge(int v0, int v1, int vmid)
|
|
{
|
|
// Find the edge in the map to get the edge-array index.
|
|
auto iter = mEMap.find(Edge(v0, v1));
|
|
LogAssert(iter != mEMap.end(), "Edge does not exist.");
|
|
|
|
int eIndex = iter->second;
|
|
|
|
// Delete edge <V0,V1>.
|
|
mEMap.erase(iter);
|
|
|
|
// Insert edge <V0,VM>.
|
|
mEArray[eIndex].V[1] = vmid;
|
|
mEMap.insert(std::make_pair(mEArray[eIndex], eIndex));
|
|
|
|
// Insert edge <VM,V1>.
|
|
InsertEdge(Edge(vmid, v1));
|
|
}
|
|
|
|
void GetInsideEdgesFrom(BSPPolygon2 const& polygon, BSPPolygon2& inside) const
|
|
{
|
|
LogAssert(mTree != nullptr, "Tree must exist.");
|
|
|
|
BSPPolygon2 ignore(mEpsilon);
|
|
const int numEdges = polygon.GetNumEdges();
|
|
for (int i = 0; i < numEdges; ++i)
|
|
{
|
|
int v0 = polygon.mEArray[i].V[0];
|
|
int v1 = polygon.mEArray[i].V[1];
|
|
Vertex vertex0 = polygon.mVArray[v0];
|
|
Vertex vertex1 = polygon.mVArray[v1];
|
|
mTree->GetPartition(*this, vertex0, vertex1, ignore, inside, inside, ignore);
|
|
}
|
|
}
|
|
|
|
Real mEpsilon;
|
|
VMap mVMap;
|
|
VArray mVArray;
|
|
EMap mEMap;
|
|
EArray mEArray;
|
|
std::shared_ptr<BSPTree2> mTree;
|
|
};
|
|
}
|
|
|