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.

682 lines
26 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.2019.08.13
#pragma once
#include <Mathematics/MinHeap.h>
#include <array>
#include <map>
#include <memory>
#include <set>
#include <stack>
// Extract the minimal cycle basis for a planar graph. The input vertices and
// edges must form a graph for which edges intersect only at vertices; that is,
// no two edges must intersect at an interior point of one of the edges. The
// algorithm is described in
// https://www.geometrictools.com/Documentation/MinimalCycleBasis.pdf
// The graph might have filaments, which are polylines in the graph that are
// not shared by a cycle. These are also extracted by the implementation.
// Because the inputs to the constructor are vertices and edges of the graph,
// isolated vertices are ignored.
//
// The computations that determine which adjacent vertex to visit next during
// a filament or cycle traversal do not require division, so the exact
// arithmetic type BSNumber<UIntegerAP32> suffices for ComputeType when you
// want to ensure a correct output. (Floating-point rounding errors
// potentially can lead to an incorrect output.)
namespace gte
{
template <typename Real>
class MinimalCycleBasis
{
public:
struct Tree
{
std::vector<int> cycle;
std::vector<std::shared_ptr<Tree>> children;
};
// The input positions and edges must form a planar graph for which
// edges intersect only at vertices; that is, no two edges must
// intersect at an interior point of one of the edges.
MinimalCycleBasis(
std::vector<std::array<Real, 2>> const& positions,
std::vector<std::array<int, 2>> const& edges,
std::vector<std::shared_ptr<Tree>>& forest)
{
forest.clear();
if (positions.size() == 0 || edges.size() == 0)
{
// The graph is empty, so there are no filaments or cycles.
return;
}
// Determine the unique positions referenced by the edges.
std::map<int, std::shared_ptr<Vertex>> unique;
for (auto const& edge : edges)
{
for (int i = 0; i < 2; ++i)
{
int name = edge[i];
if (unique.find(name) == unique.end())
{
auto vertex = std::make_shared<Vertex>(name, &positions[name]);
unique.insert(std::make_pair(name, vertex));
}
}
}
// Assign responsibility for ownership of the Vertex objects.
std::vector<Vertex*> vertices;
mVertexStorage.reserve(unique.size());
vertices.reserve(unique.size());
for (auto const& element : unique)
{
mVertexStorage.push_back(element.second);
vertices.push_back(element.second.get());
}
// Determine the adjacencies from the edge information.
for (auto const& edge : edges)
{
auto iter0 = unique.find(edge[0]);
auto iter1 = unique.find(edge[1]);
iter0->second->adjacent.insert(iter1->second.get());
iter1->second->adjacent.insert(iter0->second.get());
}
// Get the connected components of the graph. The 'visited' flags
// are 0 (unvisited), 1 (discovered), 2 (finished). The Vertex
// constructor sets all 'visited' flags to 0.
std::vector<std::vector<Vertex*>> components;
for (auto vInitial : mVertexStorage)
{
if (vInitial->visited == 0)
{
components.push_back(std::vector<Vertex*>());
DepthFirstSearch(vInitial.get(), components.back());
}
}
// The depth-first search is used later for collecting vertices
// for subgraphs that are detached from the main graph, so the
// 'visited' flags must be reset to zero after component finding.
for (auto vertex : mVertexStorage)
{
vertex->visited = 0;
}
// Get the primitives for the components.
for (auto& component : components)
{
forest.push_back(ExtractBasis(component));
}
}
// No copy or assignment allowed.
MinimalCycleBasis(MinimalCycleBasis const&) = delete;
MinimalCycleBasis& operator=(MinimalCycleBasis const&) = delete;
private:
struct Vertex
{
Vertex(int inName, std::array<Real, 2> const* inPosition)
:
name(inName),
position(inPosition),
visited(0)
{
}
bool operator< (Vertex const& vertex) const
{
return name < vertex.name;
}
// The index into the 'positions' input provided to the call to
// operator(). The index is used when reporting cycles to the
// caller of the constructor for MinimalCycleBasis.
int name;
// Multiple vertices can share a position during processing of
// graph components.
std::array<Real, 2> const* position;
// The mVertexStorage member owns the Vertex objects and maintains
// the reference counts on those objects. The adjacent pointers
// are considered to be weak pointers; neither object ownership
// nor reference counting is required by 'adjacent'.
std::set<Vertex*> adjacent;
// Support for depth-first traversal of a graph.
int visited;
};
// The constructor uses GetComponents(...) and DepthFirstSearch(...)
// to get the connected components of the graph implied by the input
// 'edges'. Recursive processing uses only DepthFirstSearch(...) to
// collect vertices of the subgraphs of the original graph.
static void DepthFirstSearch(Vertex* vInitial, std::vector<Vertex*>& component)
{
std::stack<Vertex*> vStack;
vStack.push(vInitial);
while (vStack.size() > 0)
{
Vertex* vertex = vStack.top();
vertex->visited = 1;
size_t i = 0;
for (auto adjacent : vertex->adjacent)
{
if (adjacent && adjacent->visited == 0)
{
vStack.push(adjacent);
break;
}
++i;
}
if (i == vertex->adjacent.size())
{
vertex->visited = 2;
component.push_back(vertex);
vStack.pop();
}
}
}
// Support for traversing a simply connected component of the graph.
std::shared_ptr<Tree> ExtractBasis(std::vector<Vertex*>& component)
{
// The root will not have its 'cycle' member set. The children
// are the cycle trees extracted from the component.
auto tree = std::make_shared<Tree>();
while (component.size() > 0)
{
RemoveFilaments(component);
if (component.size() > 0)
{
tree->children.push_back(ExtractCycleFromComponent(component));
}
}
if (tree->cycle.size() == 0 && tree->children.size() == 1)
{
// Replace the parent by the child to avoid having two empty
// cycles in parent/child.
auto child = tree->children.back();
tree->cycle = std::move(child->cycle);
tree->children = std::move(child->children);
}
return tree;
}
void RemoveFilaments(std::vector<Vertex*>& component)
{
// Locate all filament endpoints, which are vertices, each having
// exactly one adjacent vertex.
std::vector<Vertex*> endpoints;
for (auto vertex : component)
{
if (vertex->adjacent.size() == 1)
{
endpoints.push_back(vertex);
}
}
if (endpoints.size() > 0)
{
// Remove the filaments from the component. If a filament has
// two endpoints, each having one adjacent vertex, the
// adjacency set of the final visited vertex become empty.
// We must test for that condition before starting a new
// filament removal.
for (auto vertex : endpoints)
{
if (vertex->adjacent.size() == 1)
{
// Traverse the filament and remove the vertices.
while (vertex->adjacent.size() == 1)
{
// Break the connection between the two vertices.
Vertex* adjacent = *vertex->adjacent.begin();
adjacent->adjacent.erase(vertex);
vertex->adjacent.erase(adjacent);
// Traverse to the adjacent vertex.
vertex = adjacent;
}
}
}
// At this time the component is either empty (it was a union
// of polylines) or it has no filaments and at least one
// cycle. Remove the isolated vertices generated by filament
// extraction.
std::vector<Vertex*> remaining;
remaining.reserve(component.size());
for (auto vertex : component)
{
if (vertex->adjacent.size() > 0)
{
remaining.push_back(vertex);
}
}
component = std::move(remaining);
}
}
std::shared_ptr<Tree> ExtractCycleFromComponent(std::vector<Vertex*>& component)
{
// Search for the left-most vertex of the component. If two or
// more vertices attain minimum x-value, select the one that has
// minimum y-value.
Vertex* minVertex = component[0];
for (auto vertex : component)
{
if (*vertex->position < *minVertex->position)
{
minVertex = vertex;
}
}
// Traverse the closed walk, duplicating the starting vertex as
// the last vertex.
std::vector<Vertex*> closedWalk;
Vertex* vCurr = minVertex;
Vertex* vStart = vCurr;
closedWalk.push_back(vStart);
Vertex* vAdj = GetClockwiseMost(nullptr, vStart);
while (vAdj != vStart)
{
closedWalk.push_back(vAdj);
Vertex* vNext = GetCounterclockwiseMost(vCurr, vAdj);
vCurr = vAdj;
vAdj = vNext;
}
closedWalk.push_back(vStart);
// Recursively process the closed walk to extract cycles.
auto tree = ExtractCycleFromClosedWalk(closedWalk);
// The isolated vertices generated by cycle removal are also
// removed from the component.
std::vector<Vertex*> remaining;
remaining.reserve(component.size());
for (auto vertex : component)
{
if (vertex->adjacent.size() > 0)
{
remaining.push_back(vertex);
}
}
component = std::move(remaining);
return tree;
}
std::shared_ptr<Tree> ExtractCycleFromClosedWalk(std::vector<Vertex*>& closedWalk)
{
auto tree = std::make_shared<Tree>();
std::map<Vertex*, int> duplicates;
std::set<int> detachments;
int numClosedWalk = static_cast<int>(closedWalk.size());
for (int i = 1; i < numClosedWalk - 1; ++i)
{
auto diter = duplicates.find(closedWalk[i]);
if (diter == duplicates.end())
{
// We have not yet visited this vertex.
duplicates.insert(std::make_pair(closedWalk[i], i));
continue;
}
// The vertex has been visited previously. Collapse the
// closed walk by removing the subwalk sharing this vertex.
// Note that the vertex is pointed to by
// closedWalk[diter->second] and closedWalk[i].
int iMin = diter->second, iMax = i;
detachments.insert(iMin);
for (int j = iMin + 1; j < iMax; ++j)
{
Vertex* vertex = closedWalk[j];
duplicates.erase(vertex);
detachments.erase(j);
}
closedWalk.erase(closedWalk.begin() + iMin + 1, closedWalk.begin() + iMax + 1);
numClosedWalk = static_cast<int>(closedWalk.size());
i = iMin;
}
if (numClosedWalk > 3)
{
// We do not know whether closedWalk[0] is a detachment point.
// To determine this, we must test for any edges strictly
// contained by the wedge formed by the edges
// <closedWalk[0],closedWalk[N-1]> and
// <closedWalk[0],closedWalk[1]>. However, we must execute
// this test even for the known detachment points. The
// ensuing logic is designed to handle this and reduce the
// amount of code, so we insert closedWalk[0] into the
// detachment set and will ignore it later if it actually
// is not.
detachments.insert(0);
// Detach subgraphs from the vertices of the cycle.
for (auto i : detachments)
{
Vertex* original = closedWalk[i];
Vertex* maxVertex = closedWalk[i + 1];
Vertex* minVertex = (i > 0 ? closedWalk[i - 1] : closedWalk[numClosedWalk - 2]);
std::array<Real, 2> dMin, dMax;
for (int j = 0; j < 2; ++j)
{
dMin[j] = (*minVertex->position)[j] - (*original->position)[j];
dMax[j] = (*maxVertex->position)[j] - (*original->position)[j];
}
// For debugging.
bool isConvex = (dMax[0] * dMin[1] >= dMax[1] * dMin[0]);
(void)isConvex;
std::set<Vertex*> inWedge;
std::set<Vertex*> adjacent = original->adjacent;
for (auto vertex : adjacent)
{
if (vertex->name == minVertex->name || vertex->name == maxVertex->name)
{
continue;
}
std::array<Real, 2> dVer;
for (int j = 0; j < 2; ++j)
{
dVer[j] = (*vertex->position)[j] - (*original->position)[j];
}
bool containsVertex;
if (isConvex)
{
containsVertex =
dVer[0] * dMin[1] > dVer[1] * dMin[0] &&
dVer[0] * dMax[1] < dVer[1] * dMax[0];
}
else
{
containsVertex =
(dVer[0] * dMin[1] > dVer[1] * dMin[0]) ||
(dVer[0] * dMax[1] < dVer[1] * dMax[0]);
}
if (containsVertex)
{
inWedge.insert(vertex);
}
}
if (inWedge.size() > 0)
{
// The clone will manage the adjacents for 'original'
// that lie inside the wedge defined by the first and
// last edges of the subgraph rooted at 'original'.
// The sorting is in the clockwise direction.
auto clone = std::make_shared<Vertex>(original->name, original->position);
mVertexStorage.push_back(clone);
// Detach the edges inside the wedge.
for (auto vertex : inWedge)
{
original->adjacent.erase(vertex);
vertex->adjacent.erase(original);
clone->adjacent.insert(vertex);
vertex->adjacent.insert(clone.get());
}
// Get the subgraph (it is a single connected
// component).
std::vector<Vertex*> component;
DepthFirstSearch(clone.get(), component);
// Extract the cycles of the subgraph.
tree->children.push_back(ExtractBasis(component));
}
// else the candidate was closedWalk[0] and it has no
// subgraph to detach.
}
tree->cycle = std::move(ExtractCycle(closedWalk));
}
else
{
// Detach the subgraph from vertex closedWalk[0]; the subgraph
// is attached via a filament.
Vertex* original = closedWalk[0];
Vertex* adjacent = closedWalk[1];
auto clone = std::make_shared<Vertex>(original->name, original->position);
mVertexStorage.push_back(clone);
original->adjacent.erase(adjacent);
adjacent->adjacent.erase(original);
clone->adjacent.insert(adjacent);
adjacent->adjacent.insert(clone.get());
// Get the subgraph (it is a single connected component).
std::vector<Vertex*> component;
DepthFirstSearch(clone.get(), component);
// Extract the cycles of the subgraph.
tree->children.push_back(ExtractBasis(component));
if (tree->cycle.size() == 0 && tree->children.size() == 1)
{
// Replace the parent by the child to avoid having two
// empty cycles in parent/child.
auto child = tree->children.back();
tree->cycle = std::move(child->cycle);
tree->children = std::move(child->children);
}
}
return tree;
}
std::vector<int> ExtractCycle(std::vector<Vertex*>& closedWalk)
{
// TODO: This logic was designed not to remove filaments after
// the cycle deletion is complete. Modify this to allow filament
// removal.
// The closed walk is a cycle.
int const numVertices = static_cast<int>(closedWalk.size());
std::vector<int> cycle(numVertices);
for (int i = 0; i < numVertices; ++i)
{
cycle[i] = closedWalk[i]->name;
}
// The clockwise-most edge is always removable.
Vertex* v0 = closedWalk[0];
Vertex* v1 = closedWalk[1];
Vertex* vBranch = (v0->adjacent.size() > 2 ? v0 : nullptr);
v0->adjacent.erase(v1);
v1->adjacent.erase(v0);
// Remove edges while traversing counterclockwise.
while (v1 != vBranch && v1->adjacent.size() == 1)
{
Vertex* adj = *v1->adjacent.begin();
v1->adjacent.erase(adj);
adj->adjacent.erase(v1);
v1 = adj;
}
if (v1 != v0)
{
// If v1 had exactly 3 adjacent vertices, removal of the CCW
// edge that shared v1 leads to v1 having 2 adjacent vertices.
// When the CW removal occurs and we reach v1, the edge
// deletion will lead to v1 having 1 adjacent vertex, making
// it a filament endpoint. We must ensure we do not delete v1
// in this case, allowing the recursive algorithm to handle
// the filament later.
vBranch = v1;
// Remove edges while traversing clockwise.
while (v0 != vBranch && v0->adjacent.size() == 1)
{
v1 = *v0->adjacent.begin();
v0->adjacent.erase(v1);
v1->adjacent.erase(v0);
v0 = v1;
}
}
// else the cycle is its own connected component.
return cycle;
}
Vertex* GetClockwiseMost(Vertex* vPrev, Vertex* vCurr) const
{
Vertex* vNext = nullptr;
bool vCurrConvex = false;
std::array<Real, 2> dCurr, dNext;
if (vPrev)
{
dCurr[0] = (*vCurr->position)[0] - (*vPrev->position)[0];
dCurr[1] = (*vCurr->position)[1] - (*vPrev->position)[1];
}
else
{
dCurr[0] = static_cast<Real>(0);
dCurr[1] = static_cast<Real>(-1);
}
for (auto vAdj : vCurr->adjacent)
{
// vAdj is a vertex adjacent to vCurr. No backtracking is
// allowed.
if (vAdj == vPrev)
{
continue;
}
// Compute the potential direction to move in.
std::array<Real, 2> dAdj;
dAdj[0] = (*vAdj->position)[0] - (*vCurr->position)[0];
dAdj[1] = (*vAdj->position)[1] - (*vCurr->position)[1];
// Select the first candidate.
if (!vNext)
{
vNext = vAdj;
dNext = dAdj;
vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]);
continue;
}
// Update if the next candidate is clockwise of the current
// clockwise-most vertex.
if (vCurrConvex)
{
if (dCurr[0] * dAdj[1] < dCurr[1] * dAdj[0]
|| dNext[0] * dAdj[1] < dNext[1] * dAdj[0])
{
vNext = vAdj;
dNext = dAdj;
vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]);
}
}
else
{
if (dCurr[0] * dAdj[1] < dCurr[1] * dAdj[0]
&& dNext[0] * dAdj[1] < dNext[1] * dAdj[0])
{
vNext = vAdj;
dNext = dAdj;
vCurrConvex = (dNext[0] * dCurr[1] < dNext[1] * dCurr[0]);
}
}
}
return vNext;
}
Vertex* GetCounterclockwiseMost(Vertex* vPrev, Vertex* vCurr) const
{
Vertex* vNext = nullptr;
bool vCurrConvex = false;
std::array<Real, 2> dCurr, dNext;
if (vPrev)
{
dCurr[0] = (*vCurr->position)[0] - (*vPrev->position)[0];
dCurr[1] = (*vCurr->position)[1] - (*vPrev->position)[1];
}
else
{
dCurr[0] = static_cast<Real>(0);
dCurr[1] = static_cast<Real>(-1);
}
for (auto vAdj : vCurr->adjacent)
{
// vAdj is a vertex adjacent to vCurr. No backtracking is
// allowed.
if (vAdj == vPrev)
{
continue;
}
// Compute the potential direction to move in.
std::array<Real, 2> dAdj;
dAdj[0] = (*vAdj->position)[0] - (*vCurr->position)[0];
dAdj[1] = (*vAdj->position)[1] - (*vCurr->position)[1];
// Select the first candidate.
if (!vNext)
{
vNext = vAdj;
dNext = dAdj;
vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]);
continue;
}
// Select the next candidate if it is counterclockwise of the
// current counterclockwise-most vertex.
if (vCurrConvex)
{
if (dCurr[0] * dAdj[1] > dCurr[1] * dAdj[0]
&& dNext[0] * dAdj[1] > dNext[1] * dAdj[0])
{
vNext = vAdj;
dNext = dAdj;
vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]);
}
}
else
{
if (dCurr[0] * dAdj[1] > dCurr[1] * dAdj[0]
|| dNext[0] * dAdj[1] > dNext[1] * dAdj[0])
{
vNext = vAdj;
dNext = dAdj;
vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]);
}
}
}
return vNext;
}
// Storage for referenced vertices of the original graph and for new
// vertices added during graph traversal.
std::vector<std::shared_ptr<Vertex>> mVertexStorage;
};
}