123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681 |
- // David Eberly, Geometric Tools, Redmond WA 98052
- // Copyright (c) 1998-2020
- // 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 WwiseGTE
- {
- 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;
- };
- }
|