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.
428 lines
16 KiB
428 lines
16 KiB
3 months ago
|
// 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.11.16
|
||
|
|
||
|
#pragma once
|
||
|
|
||
|
#include <Mathematics/ExtremalQuery3.h>
|
||
|
#include <Mathematics/RangeIteration.h>
|
||
|
#include <Mathematics/VETManifoldMesh.h>
|
||
|
#include <stack>
|
||
|
#include <queue>
|
||
|
|
||
|
namespace gte
|
||
|
{
|
||
|
template <typename Real>
|
||
|
class ExtremalQuery3BSP : public ExtremalQuery3<Real>
|
||
|
{
|
||
|
public:
|
||
|
// Construction.
|
||
|
ExtremalQuery3BSP(Polyhedron3<Real> const& polytope)
|
||
|
:
|
||
|
ExtremalQuery3<Real>(polytope)
|
||
|
{
|
||
|
// Create the adjacency information for the polytope.
|
||
|
VETManifoldMesh mesh;
|
||
|
auto const& indices = this->mPolytope.GetIndices();
|
||
|
int const numTriangles = static_cast<int>(indices.size() / 3);
|
||
|
for (int t = 0; t < numTriangles; ++t)
|
||
|
{
|
||
|
std::array<int, 3> V = { 0, 0, 0 };
|
||
|
for (int j = 0; j < 3; ++j)
|
||
|
{
|
||
|
V[j] = indices[3 * t + j];
|
||
|
}
|
||
|
auto triangle = mesh.Insert(V[0], V[1], V[2]);
|
||
|
mTriToNormal.insert(std::make_pair(triangle, t));
|
||
|
}
|
||
|
|
||
|
// Create the set of unique arcs which are used to create the BSP
|
||
|
// tree.
|
||
|
std::multiset<SphericalArc> arcs;
|
||
|
CreateSphericalArcs(mesh, arcs);
|
||
|
|
||
|
// Create the BSP tree to be used in the extremal query.
|
||
|
CreateBSPTree(arcs);
|
||
|
}
|
||
|
|
||
|
// Disallow copying and assignment.
|
||
|
ExtremalQuery3BSP(ExtremalQuery3BSP const&) = delete;
|
||
|
ExtremalQuery3BSP& operator=(ExtremalQuery3BSP const&) = delete;
|
||
|
|
||
|
// Compute the extreme vertices in the specified direction and return
|
||
|
// the indices of the vertices in the polyhedron vertex array.
|
||
|
virtual void GetExtremeVertices(Vector3<Real> const& direction,
|
||
|
int& positiveDirection, int& negativeDirection) override
|
||
|
{
|
||
|
// Do a nonrecursive depth-first search of the BSP tree to
|
||
|
// determine spherical polygon contains the incoming direction D.
|
||
|
// Index 0 is the root of the BSP tree.
|
||
|
int current = 0;
|
||
|
while (current >= 0)
|
||
|
{
|
||
|
SphericalArc& node = mNodes[current];
|
||
|
int sign = gte::isign(Dot(direction, node.normal));
|
||
|
if (sign >= 0)
|
||
|
{
|
||
|
current = node.posChild;
|
||
|
if (current == -1)
|
||
|
{
|
||
|
// At a leaf node.
|
||
|
positiveDirection = node.posVertex;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
current = node.negChild;
|
||
|
if (current == -1)
|
||
|
{
|
||
|
// At a leaf node.
|
||
|
positiveDirection = node.negVertex;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Do a nonrecursive depth-first search of the BSP tree to
|
||
|
// determine spherical polygon contains the reverse incoming
|
||
|
// direction -D.
|
||
|
current = 0; // the root of the BSP tree
|
||
|
while (current >= 0)
|
||
|
{
|
||
|
SphericalArc& node = mNodes[current];
|
||
|
int sign = gte::isign(Dot(direction, node.normal));
|
||
|
if (sign <= 0)
|
||
|
{
|
||
|
current = node.posChild;
|
||
|
if (current == -1)
|
||
|
{
|
||
|
// At a leaf node.
|
||
|
negativeDirection = node.posVertex;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
current = node.negChild;
|
||
|
if (current == -1)
|
||
|
{
|
||
|
// At a leaf node.
|
||
|
negativeDirection = node.negVertex;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Tree statistics.
|
||
|
inline int GetNumNodes() const
|
||
|
{
|
||
|
return static_cast<int>(mNodes.size());
|
||
|
}
|
||
|
|
||
|
inline int GetTreeDepth() const
|
||
|
{
|
||
|
return mTreeDepth;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
class SphericalArc
|
||
|
{
|
||
|
public:
|
||
|
// Construction.
|
||
|
SphericalArc()
|
||
|
:
|
||
|
nIndex{ -1, -1 },
|
||
|
separation(0),
|
||
|
normal(Vector3<Real>::Zero()),
|
||
|
posVertex(-1),
|
||
|
negVertex(-1),
|
||
|
posChild(-1),
|
||
|
negChild(-1)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
// The arcs are stored in a multiset ordered by increasing
|
||
|
// separation. The multiset will be traversed in reverse order.
|
||
|
// This heuristic is designed to create BSP trees whose top-most
|
||
|
// nodes can eliminate as many arcs as possible during an extremal
|
||
|
// query.
|
||
|
bool operator<(SphericalArc const& arc) const
|
||
|
{
|
||
|
return separation < arc.separation;
|
||
|
}
|
||
|
|
||
|
// Indices N[] into the face normal array for the endpoints of the
|
||
|
// arc.
|
||
|
std::array<int, 2> nIndex;
|
||
|
|
||
|
// The number of arcs in the path from normal N[0] to normal N[1].
|
||
|
// For spherical polygon edges, the number is 1. The number is 2
|
||
|
// or larger for bisector arcs of the spherical polygon.
|
||
|
int separation;
|
||
|
|
||
|
// The normal is Cross(FaceNormal[N[0]],FaceNormal[N[1]]).
|
||
|
Vector3<Real> normal;
|
||
|
|
||
|
// Indices into the vertex array for the extremal points for the
|
||
|
// two regions sharing the arc. As the arc is traversed from
|
||
|
// normal N[0] to normal N[1], PosVertex is the index for the
|
||
|
// extreme vertex to the left of the arc and NegVertex is the
|
||
|
// index for the extreme vertex to the right of the arc.
|
||
|
int posVertex, negVertex;
|
||
|
|
||
|
// Support for BSP trees stored as contiguous nodes in an array.
|
||
|
int posChild, negChild;
|
||
|
};
|
||
|
|
||
|
typedef VETManifoldMesh::Triangle Triangle;
|
||
|
|
||
|
void SortAdjacentTriangles(int vIndex,
|
||
|
std::unordered_set<Triangle*> const& tAdj,
|
||
|
std::vector<Triangle*>& tAdjSorted)
|
||
|
{
|
||
|
// Copy the set of adjacent triangles into a vector container.
|
||
|
int const numTriangles = static_cast<int>(tAdj.size());
|
||
|
tAdjSorted.resize(tAdj.size());
|
||
|
|
||
|
// Traverse the triangles adjacent to vertex V using edge-triangle
|
||
|
// adjacency information to produce a sorted array of adjacent
|
||
|
// triangles.
|
||
|
Triangle* tri = *tAdj.begin();
|
||
|
for (int i = 0; i < numTriangles; ++i)
|
||
|
{
|
||
|
for (int prev = 2, curr = 0; curr < 3; prev = curr++)
|
||
|
{
|
||
|
if (tri->V[curr] == vIndex)
|
||
|
{
|
||
|
tAdjSorted[i] = tri;
|
||
|
tri = tri->T[prev];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CreateSphericalArcs(VETManifoldMesh& mesh, std::multiset<SphericalArc>& arcs)
|
||
|
{
|
||
|
int const prev[3] = { 2, 0, 1 };
|
||
|
int const next[3] = { 1, 2, 0 };
|
||
|
|
||
|
for (auto const& element : mesh.GetEdges())
|
||
|
{
|
||
|
auto const& edge = element.second;
|
||
|
|
||
|
// VS 2019 16.8.1 generates LNT1006 "Local variable is not
|
||
|
// initialized." Incorrect, because the default constructor
|
||
|
// initializes all the members.
|
||
|
SphericalArc arc;
|
||
|
arc.nIndex[0] = mTriToNormal[edge->T[0]];
|
||
|
arc.nIndex[1] = mTriToNormal[edge->T[1]];
|
||
|
arc.separation = 1;
|
||
|
arc.normal = Cross(this->mFaceNormals[arc.nIndex[0]], this->mFaceNormals[arc.nIndex[1]]);
|
||
|
|
||
|
Triangle* adj = edge->T[0];
|
||
|
int j;
|
||
|
for (j = 0; j < 3; ++j)
|
||
|
{
|
||
|
if (adj->V[j] != edge->V[0] && adj->V[j] != edge->V[1])
|
||
|
{
|
||
|
arc.posVertex = adj->V[prev[j]];
|
||
|
arc.negVertex = adj->V[next[j]];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
LogAssert(j < 3, "Unexpected condition.");
|
||
|
|
||
|
arcs.insert(arc);
|
||
|
}
|
||
|
|
||
|
CreateSphericalBisectors(mesh, arcs);
|
||
|
}
|
||
|
|
||
|
void CreateSphericalBisectors(VETManifoldMesh& mesh, std::multiset<SphericalArc>& arcs)
|
||
|
{
|
||
|
std::queue<std::pair<int, int>> queue;
|
||
|
for (auto const& element : mesh.GetVertices())
|
||
|
{
|
||
|
// Sort the normals into a counterclockwise spherical polygon
|
||
|
// when viewed from outside the sphere.
|
||
|
auto const& vertex = element.second;
|
||
|
int const vIndex = vertex->V;
|
||
|
std::vector<Triangle*> tAdjSorted;
|
||
|
SortAdjacentTriangles(vIndex, vertex->TAdjacent, tAdjSorted);
|
||
|
int const numTriangles = static_cast<int>(vertex->TAdjacent.size());
|
||
|
queue.push(std::make_pair(0, numTriangles));
|
||
|
while (!queue.empty())
|
||
|
{
|
||
|
std::pair<int, int> item = queue.front();
|
||
|
queue.pop();
|
||
|
int i0 = item.first, i1 = item.second;
|
||
|
int separation = i1 - i0;
|
||
|
if (separation > 1 && separation != numTriangles - 1)
|
||
|
{
|
||
|
if (i1 < numTriangles)
|
||
|
{
|
||
|
// VS 2019 16.8.1 generates LNT1006 "Local
|
||
|
// variable is not initialized." Incorrect,
|
||
|
// because the default constructor initializes
|
||
|
// all the members.
|
||
|
SphericalArc arc;
|
||
|
arc.nIndex[0] = mTriToNormal[tAdjSorted[i0]];
|
||
|
arc.nIndex[1] = mTriToNormal[tAdjSorted[i1]];
|
||
|
arc.separation = separation;
|
||
|
|
||
|
arc.normal = Cross(this->mFaceNormals[arc.nIndex[0]],
|
||
|
this->mFaceNormals[arc.nIndex[1]]);
|
||
|
|
||
|
arc.posVertex = vIndex;
|
||
|
arc.negVertex = vIndex;
|
||
|
arcs.insert(arc);
|
||
|
}
|
||
|
int imid = (i0 + i1 + 1) / 2;
|
||
|
if (imid != i1)
|
||
|
{
|
||
|
queue.push(std::make_pair(i0, imid));
|
||
|
queue.push(std::make_pair(imid, i1));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CreateBSPTree(std::multiset<SphericalArc>& arcs)
|
||
|
{
|
||
|
// The tree has at least a root.
|
||
|
mTreeDepth = 1;
|
||
|
|
||
|
for (auto const& arc : gte::reverse(arcs))
|
||
|
{
|
||
|
InsertArc(arc);
|
||
|
}
|
||
|
|
||
|
// The leaf nodes are not counted in the traversal of InsertArc.
|
||
|
// The depth must be incremented to account for leaves.
|
||
|
++mTreeDepth;
|
||
|
}
|
||
|
|
||
|
void InsertArc(SphericalArc const& arc)
|
||
|
{
|
||
|
// The incoming arc is stored at the end of the nodes array.
|
||
|
if (mNodes.size() > 0)
|
||
|
{
|
||
|
// Do a nonrecursive depth-first search of the current BSP
|
||
|
// tree to place the incoming arc. Index 0 is the root of the
|
||
|
// BSP tree.
|
||
|
std::stack<int> candidates;
|
||
|
candidates.push(0);
|
||
|
while (!candidates.empty())
|
||
|
{
|
||
|
int current = candidates.top();
|
||
|
candidates.pop();
|
||
|
SphericalArc* node = &mNodes[current];
|
||
|
|
||
|
int sign0;
|
||
|
if (arc.nIndex[0] == node->nIndex[0] || arc.nIndex[0] == node->nIndex[1])
|
||
|
{
|
||
|
sign0 = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Real dot = Dot(this->mFaceNormals[arc.nIndex[0]], node->normal);
|
||
|
sign0 = gte::isign(dot);
|
||
|
}
|
||
|
|
||
|
int sign1;
|
||
|
if (arc.nIndex[1] == node->nIndex[0] || arc.nIndex[1] == node->nIndex[1])
|
||
|
{
|
||
|
sign1 = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Real dot = Dot(this->mFaceNormals[arc.nIndex[1]], node->normal);
|
||
|
sign1 = gte::isign(dot);
|
||
|
}
|
||
|
|
||
|
int doTest = 0;
|
||
|
if (sign0 * sign1 < 0)
|
||
|
{
|
||
|
// The new arc straddles the current arc, so propagate
|
||
|
// it to both child nodes.
|
||
|
doTest = 3;
|
||
|
}
|
||
|
else if (sign0 > 0 || sign1 > 0)
|
||
|
{
|
||
|
// The new arc is on the positive side of the current
|
||
|
// arc.
|
||
|
doTest = 1;
|
||
|
}
|
||
|
else if (sign0 < 0 || sign1 < 0)
|
||
|
{
|
||
|
// The new arc is on the negative side of the current
|
||
|
// arc.
|
||
|
doTest = 2;
|
||
|
}
|
||
|
// else: sign0 = sign1 = 0, in which case no propagation
|
||
|
// is needed because the current BSP node will handle the
|
||
|
// correct partitioning of the arcs during extremal
|
||
|
// queries.
|
||
|
|
||
|
int depth;
|
||
|
|
||
|
if (doTest & 1)
|
||
|
{
|
||
|
if (node->posChild != -1)
|
||
|
{
|
||
|
candidates.push(node->posChild);
|
||
|
depth = static_cast<int>(candidates.size());
|
||
|
if (depth > mTreeDepth)
|
||
|
{
|
||
|
mTreeDepth = depth;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
node->posChild = static_cast<int>(mNodes.size());
|
||
|
mNodes.push_back(arc);
|
||
|
|
||
|
// The push_back can cause a reallocation, so the
|
||
|
// current pointer must be refreshed.
|
||
|
node = &mNodes[current];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (doTest & 2)
|
||
|
{
|
||
|
if (node->negChild != -1)
|
||
|
{
|
||
|
candidates.push(node->negChild);
|
||
|
depth = static_cast<int>(candidates.size());
|
||
|
if (depth > mTreeDepth)
|
||
|
{
|
||
|
mTreeDepth = depth;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
node->negChild = static_cast<int>(mNodes.size());
|
||
|
mNodes.push_back(arc);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// root node
|
||
|
mNodes.push_back(arc);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Lookup table for indexing into mFaceNormals.
|
||
|
std::map<Triangle*, int> mTriToNormal;
|
||
|
|
||
|
// Fixed-size storage for the BSP nodes.
|
||
|
std::vector<SphericalArc> mNodes;
|
||
|
int mTreeDepth;
|
||
|
};
|
||
|
}
|