GteMinimalCycleBasis.h
Go to the documentation of this file.
1 // David Eberly, Geometric Tools, Redmond WA 98052
2 // Copyright (c) 1998-2017
3 // Distributed under the Boost Software License, Version 1.0.
4 // http://www.boost.org/LICENSE_1_0.txt
5 // http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
6 // File Version: 3.0.0 (2016/06/19)
7 
8 #pragma once
9 
10 #include <LowLevel/GteLogger.h>
11 #include <LowLevel/GteMinHeap.h>
12 #include <array>
13 #include <map>
14 #include <memory>
15 #include <set>
16 #include <stack>
17 
18 // Extract the minimal cycle basis for a planar graph. The input vertices and
19 // edges must form a graph for which edges intersect only at vertices; that is,
20 // no two edges must intersect at an interior point of one of the edges. The
21 // algorithm is described in
22 // http://www.geometrictools.com/Documentation/MinimalCycleBasis.pdf
23 // The graph might have filaments, which are polylines in the graph that are
24 // not shared by a cycle. These are also extracted by the implementation.
25 // Because the inputs to the constructor are vertices and edges of the graph,
26 // isolated vertices are ignored.
27 //
28 // The computations that determine which adjacent vertex to visit next during
29 // a filament or cycle traversal do not require division, so the exact
30 // arithmetic type BSNumber<UIntegerAP32> suffices for ComputeType when you
31 // want to ensure a correct output. (Floating-point rounding errors
32 // potentially can lead to an incorrect output.)
33 
34 namespace gte
35 {
36 
37 template <typename Real>
39 {
40 public:
41  struct Tree
42  {
43  std::vector<int> cycle;
44  std::vector<std::shared_ptr<Tree>> children;
45  };
46 
47  // The input positions and edges must form a planar graph for which edges
48  // intersect only at vertices; that is, no two edges must intersect at an
49  // interior point of one of the edges.
51  std::vector<std::array<Real, 2>> const& positions,
52  std::vector<std::array<int, 2>> const& edges,
53  std::vector<std::shared_ptr<Tree>>& forest);
54 
55 private:
56  // No copy or assignment allowed.
57  MinimalCycleBasis(MinimalCycleBasis const&) = delete;
59 
60  struct Vertex
61  {
62  Vertex(int inName, std::array<Real, 2> const* inPosition);
63  bool operator< (Vertex const& vertex) const;
64 
65  // The index into the 'positions' input provided to the call to
66  // operator(). The index is used when reporting cycles to the
67  // caller of the constructor for MinimalCycleBasis.
68  int name;
69 
70  // Multiple vertices can share a position during processing of
71  // graph components.
72  std::array<Real, 2> const* position;
73 
74  // The mVertexStorage member owns the Vertex objects and maintains
75  // the reference counts on those objects. The adjacent pointers
76  // are considered to be weak pointers; neither object ownership nor
77  // reference counting is required by 'adjacent'.
78  std::set<Vertex*> adjacent;
79 
80  // Support for depth-first traversal of a graph.
81  int visited;
82  };
83 
84  // The constructor uses GetComponents(...) and DepthFirstSearch(...) to
85  // get the connected components of the graph implied by the input 'edges'.
86  // Recursive processing uses only DepthFirstSearch(...) to collect
87  // vertices of the subgraphs of the original graph.
88  static void DepthFirstSearch(Vertex* vInitial, std::vector<Vertex*>& component);
89 
90  // Support for traversing a simply connected component of the graph.
91  std::shared_ptr<Tree> ExtractBasis(std::vector<Vertex*>& component);
92  void RemoveFilaments(std::vector<Vertex*>& component);
93  std::shared_ptr<Tree> ExtractCycleFromComponent(std::vector<Vertex*>& component);
94  std::shared_ptr<Tree> ExtractCycleFromClosedWalk(std::vector<Vertex*>& closedWalk);
95  std::vector<int> ExtractCycle(std::vector<Vertex*>& closedWalk);
96 
97  Vertex* GetClockwiseMost(Vertex* vPrev, Vertex* vCurr) const;
98  Vertex* GetCounterclockwiseMost(Vertex* vPrev, Vertex* vCurr) const;
99 
100  // Storage for referenced vertices of the original graph and for new
101  // vertices added during graph traversal.
102  std::vector<std::shared_ptr<Vertex>> mVertexStorage;
103 };
104 
105 
106 template <typename Real>
107 MinimalCycleBasis<Real>::MinimalCycleBasis(std::vector<std::array<Real, 2>> const& positions,
108  std::vector<std::array<int, 2>> const& edges, std::vector<std::shared_ptr<Tree>>& forest)
109 {
110  forest.clear();
111  if (positions.size() == 0 || edges.size() == 0)
112  {
113  // The graph is empty, so there are no filaments or cycles.
114  return;
115  }
116 
117  // Determine the unique positions referenced by the edges.
118  std::map<int, std::shared_ptr<Vertex>> unique;
119  for (auto const& edge : edges)
120  {
121  for (int i = 0; i < 2; ++i)
122  {
123  int name = edge[i];
124  if (unique.find(name) == unique.end())
125  {
126  std::shared_ptr<Vertex> vertex =
127  std::make_shared<Vertex>(name, &positions[name]);
128  unique.insert(std::make_pair(name, vertex));
129 
130  }
131  }
132  }
133 
134  // Assign responsibility for ownership of the Vertex objects.
135  std::vector<Vertex*> vertices;
136  mVertexStorage.reserve(unique.size());
137  vertices.reserve(unique.size());
138  for (auto const& element : unique)
139  {
140  mVertexStorage.push_back(element.second);
141  vertices.push_back(element.second.get());
142  }
143 
144  // Determine the adjacencies from the edge information.
145  for (auto const& edge : edges)
146  {
147  auto iter0 = unique.find(edge[0]);
148  auto iter1 = unique.find(edge[1]);
149  iter0->second->adjacent.insert(iter1->second.get());
150  iter1->second->adjacent.insert(iter0->second.get());
151  }
152 
153  // Get the connected components of the graph. The 'visited' flags are
154  // 0 (unvisited), 1 (discovered), 2 (finished). The Vertex constructor
155  // sets all 'visited' flags to 0.
156  std::vector<std::vector<Vertex*>> components;
157  for (auto vInitial : mVertexStorage)
158  {
159  if (vInitial->visited == 0)
160  {
161  components.push_back(std::vector<Vertex*>());
162  DepthFirstSearch(vInitial.get(), components.back());
163  }
164  }
165 
166  // The depth-first search is used later for collecting vertices for
167  // subgraphs that are detached from the main graph, so the 'visited'
168  // flags must be reset to zero after component finding.
169  for (auto vertex : mVertexStorage)
170  {
171  vertex->visited = 0;
172  }
173 
174  // Get the primitives for the components.
175  for (auto& component : components)
176  {
177  forest.push_back(ExtractBasis(component));
178  }
179 }
180 
181 template <typename Real>
182 void MinimalCycleBasis<Real>::DepthFirstSearch(Vertex* vInitial, std::vector<Vertex*>& component)
183 {
184  std::stack<Vertex*> vStack;
185  vStack.push(vInitial);
186  while (vStack.size() > 0)
187  {
188  Vertex* vertex = vStack.top();
189  vertex->visited = 1;
190  size_t i = 0;
191  for (auto adjacent : vertex->adjacent)
192  {
193  if (adjacent && adjacent->visited == 0)
194  {
195  vStack.push(adjacent);
196  break;
197  }
198  ++i;
199  }
200 
201  if (i == vertex->adjacent.size())
202  {
203  vertex->visited = 2;
204  component.push_back(vertex);
205  vStack.pop();
206  }
207  }
208 }
209 
210 template <typename Real>
211 std::shared_ptr<typename MinimalCycleBasis<Real>::Tree>
212  MinimalCycleBasis<Real>::ExtractBasis(std::vector<Vertex*>& component)
213 {
214  // The root will not have its 'cycle' member set. The children are
215  // the cycle trees extracted from the component.
216  std::shared_ptr<Tree> tree = std::make_shared<Tree>();
217  while (component.size() > 0)
218  {
219  RemoveFilaments(component);
220  if (component.size() > 0)
221  {
222  tree->children.push_back(ExtractCycleFromComponent(component));
223  }
224  }
225 
226  if (tree->cycle.size() == 0 && tree->children.size() == 1)
227  {
228  // Replace the parent by the child to avoid having two empty
229  // cycles in parent/child.
230  std::shared_ptr<Tree> child = tree->children.back();
231  tree->cycle = std::move(child->cycle);
232  tree->children = std::move(child->children);
233  }
234  return tree;
235 }
236 
237 template <typename Real>
238 void MinimalCycleBasis<Real>::RemoveFilaments(std::vector<Vertex*>& component)
239 {
240  // Locate all filament endpoints, which are vertices, each having exactly
241  // one adjacent vertex.
242  std::vector<Vertex*> endpoints;
243  for (auto vertex : component)
244  {
245  if (vertex->adjacent.size() == 1)
246  {
247  endpoints.push_back(vertex);
248  }
249  }
250 
251  if (endpoints.size() > 0)
252  {
253  // Remove the filaments from the component. If a filament has two
254  // endpoints, each having one adjacent vertex, the adjacency set of
255  // the final visited vertex become empty. We must test for that
256  // condition before starting a new filament removal.
257  for (auto vertex : endpoints)
258  {
259  if (vertex->adjacent.size() == 1)
260  {
261  // Traverse the filament and remove the vertices.
262  while (vertex->adjacent.size() == 1)
263  {
264  // Break the connection between the two vertices.
265  Vertex* adjacent = *vertex->adjacent.begin();
266  adjacent->adjacent.erase(vertex);
267  vertex->adjacent.erase(adjacent);
268 
269  // Traverse to the adjacent vertex.
270  vertex = adjacent;
271  }
272  }
273  }
274 
275  // At this time the component is either empty (it was a union of
276  // polylines) or it has no filaments and at least one cycle. Remove
277  // the isolated vertices generated by filament extraction.
278  std::vector<Vertex*> remaining;
279  remaining.reserve(component.size());
280  for (auto vertex : component)
281  {
282  if (vertex->adjacent.size() > 0)
283  {
284  remaining.push_back(vertex);
285  }
286  }
287  component = std::move(remaining);
288  }
289 }
290 
291 template <typename Real>
292 std::shared_ptr<typename MinimalCycleBasis<Real>::Tree>
293  MinimalCycleBasis<Real>::ExtractCycleFromComponent(std::vector<Vertex*>& component)
294 {
295  // Search for the left-most vertex of the component. If two or more
296  // vertices attain minimum x-value, select the one that has minimum
297  // y-value.
298  Vertex* minVertex = component[0];
299  for (auto vertex : component)
300  {
301  if (*vertex->position < *minVertex->position)
302  {
303  minVertex = vertex;
304  }
305  }
306 
307  // Traverse the closed walk, duplicating the starting vertex as the
308  // last vertex.
309  std::vector<Vertex*> closedWalk;
310  Vertex* vCurr = minVertex;
311  Vertex* vStart = vCurr;
312  closedWalk.push_back(vStart);
313  Vertex* vAdj = GetClockwiseMost(nullptr, vStart);
314  while (vAdj != vStart)
315  {
316  closedWalk.push_back(vAdj);
317  Vertex* vNext = GetCounterclockwiseMost(vCurr, vAdj);
318  vCurr = vAdj;
319  vAdj = vNext;
320  }
321  closedWalk.push_back(vStart);
322 
323  // Recursively process the closed walk to extract cycles.
324  std::shared_ptr<Tree> tree = ExtractCycleFromClosedWalk(closedWalk);
325 
326  // The isolated vertices generated by cycle removal are also removed from
327  // the component.
328  std::vector<Vertex*> remaining;
329  remaining.reserve(component.size());
330  for (auto vertex : component)
331  {
332  if (vertex->adjacent.size() > 0)
333  {
334  remaining.push_back(vertex);
335  }
336  }
337  component = std::move(remaining);
338 
339  return tree;
340 }
341 
342 template <typename Real>
343 std::shared_ptr<typename MinimalCycleBasis<Real>::Tree>
344  MinimalCycleBasis<Real>::ExtractCycleFromClosedWalk(std::vector<Vertex*>& closedWalk)
345 {
346  std::shared_ptr<Tree> tree = std::make_shared<Tree>();
347 
348  std::map<Vertex*, int> duplicates;
349  std::set<int> detachments;
350  int numClosedWalk = static_cast<int>(closedWalk.size());
351  for (int i = 1; i < numClosedWalk - 1; ++i)
352  {
353  auto diter = duplicates.find(closedWalk[i]);
354  if (diter == duplicates.end())
355  {
356  // We have not yet visited this vertex.
357  duplicates.insert(std::make_pair(closedWalk[i], i));
358  continue;
359  }
360 
361  // The vertex has been visited previously. Collapse the closed walk
362  // by removing the subwalk sharing this vertex. Note that the vertex
363  // is pointed to by closedWalk[diter->second] and closedWalk[i].
364  int iMin = diter->second, iMax = i;
365  detachments.insert(iMin);
366  for (int j = iMin + 1; j < iMax; ++j)
367  {
368  Vertex* vertex = closedWalk[j];
369  duplicates.erase(vertex);
370  detachments.erase(j);
371  }
372  closedWalk.erase(closedWalk.begin() + iMin + 1, closedWalk.begin() + iMax + 1);
373  numClosedWalk = static_cast<int>(closedWalk.size());
374  i = iMin;
375  }
376 
377  if (numClosedWalk > 3)
378  {
379  // We do not know whether closedWalk[0] is a detachment point. To
380  // determine this, we must test for any edges strictly contained by
381  // the wedge formed by the edges <closedWalk[0],closedWalk[N-1]> and
382  // <closedWalk[0],closedWalk[1]>. However, we must execute this test
383  // even for the known detachment points. The ensuing logic is designed
384  // to handle this and reduce the amount of code, so we insert
385  // closedWalk[0] into the detachment set and will ignore it later if
386  // it actually is not.
387  detachments.insert(0);
388 
389  // Detach subgraphs from the vertices of the cycle.
390  for (auto i : detachments)
391  {
392  Vertex* original = closedWalk[i];
393  Vertex* maxVertex = closedWalk[i + 1];
394  Vertex* minVertex = (i > 0 ? closedWalk[i - 1] : closedWalk[numClosedWalk - 2]);
395 
396  std::array<Real, 2> dMin, dMax;
397  for (int j = 0; j < 2; ++j)
398  {
399  dMin[j] = (*minVertex->position)[j] - (*original->position)[j];
400  dMax[j] = (*maxVertex->position)[j] - (*original->position)[j];
401  }
402 
403  bool isConvex = (dMax[0] * dMin[1] >= dMax[1] * dMin[0]); (void)isConvex;
404 
405  std::set<Vertex*> inWedge;
406  std::set<Vertex*> adjacent = original->adjacent;
407  for (auto vertex : adjacent)
408  {
409  if (vertex->name == minVertex->name || vertex->name == maxVertex->name)
410  {
411  continue;
412  }
413 
414  std::array<Real, 2> dVer;
415  for (int j = 0; j < 2; ++j)
416  {
417  dVer[j] = (*vertex->position)[j] - (*original->position)[j];
418  }
419 
420  bool containsVertex;
421  if (isConvex)
422  {
423  containsVertex =
424  dVer[0] * dMin[1] > dVer[1] * dMin[0] &&
425  dVer[0] * dMax[1] < dVer[1] * dMax[0];
426  }
427  else
428  {
429  containsVertex =
430  (dVer[0] * dMin[1] > dVer[1] * dMin[0]) ||
431  (dVer[0] * dMax[1] < dVer[1] * dMax[0]);
432  }
433  if (containsVertex)
434  {
435  inWedge.insert(vertex);
436  }
437  }
438 
439  if (inWedge.size() > 0)
440  {
441  // The clone will manage the adjacents for 'original' that lie
442  // inside the wedge defined by the first and last edges of the
443  // subgraph rooted at 'original'. The sorting is in the
444  // clockwise direction.
445  std::shared_ptr<Vertex> clone =
446  std::make_shared<Vertex>(original->name, original->position);
447  mVertexStorage.push_back(clone);
448 
449  // Detach the edges inside the wedge.
450  for (auto vertex : inWedge)
451  {
452  original->adjacent.erase(vertex);
453  vertex->adjacent.erase(original);
454  clone->adjacent.insert(vertex);
455  vertex->adjacent.insert(clone.get());
456  }
457 
458  // Get the subgraph (it is a single connected component).
459  std::vector<Vertex*> component;
460  DepthFirstSearch(clone.get(), component);
461 
462  // Extract the cycles of the subgraph.
463  tree->children.push_back(ExtractBasis(component));
464  }
465  // else the candidate was closedWalk[0] and it has no subgraph
466  // to detach.
467  }
468 
469  tree->cycle = std::move(ExtractCycle(closedWalk));
470  }
471  else
472  {
473  // Detach the subgraph from vertex closedWalk[0]; the subgraph
474  // is attached via a filament.
475  Vertex* original = closedWalk[0];
476  Vertex* adjacent = closedWalk[1];
477 
478  std::shared_ptr<Vertex> clone =
479  std::make_shared<Vertex>(original->name, original->position);
480  mVertexStorage.push_back(clone);
481 
482  original->adjacent.erase(adjacent);
483  adjacent->adjacent.erase(original);
484  clone->adjacent.insert(adjacent);
485  adjacent->adjacent.insert(clone.get());
486 
487  // Get the subgraph (it is a single connected component).
488  std::vector<Vertex*> component;
489  DepthFirstSearch(clone.get(), component);
490 
491  // Extract the cycles of the subgraph.
492  tree->children.push_back(ExtractBasis(component));
493  if (tree->cycle.size() == 0 && tree->children.size() == 1)
494  {
495  // Replace the parent by the child to avoid having two empty
496  // cycles in parent/child.
497  std::shared_ptr<Tree> child = tree->children.back();
498  tree->cycle = std::move(child->cycle);
499  tree->children = std::move(child->children);
500  }
501  }
502 
503  return tree;
504 }
505 
506 template <typename Real>
507 std::vector<int> MinimalCycleBasis<Real>::ExtractCycle(std::vector<Vertex*>& closedWalk)
508 {
509  // TODO: This logic was designed not to remove filaments after the
510  // cycle deletion is complete. Modify this to allow filament removal.
511 
512  // The closed walk is a cycle.
513  int const numVertices = static_cast<int>(closedWalk.size());
514  std::vector<int> cycle(numVertices);
515  for (int i = 0; i < numVertices; ++i)
516  {
517  cycle[i] = closedWalk[i]->name;
518  }
519 
520  // The clockwise-most edge is always removable.
521  Vertex* v0 = closedWalk[0];
522  Vertex* v1 = closedWalk[1];
523  Vertex* vBranch = (v0->adjacent.size() > 2 ? v0 : nullptr);
524  v0->adjacent.erase(v1);
525  v1->adjacent.erase(v0);
526 
527  // Remove edges while traversing counterclockwise.
528  while (v1 != vBranch && v1->adjacent.size() == 1)
529  {
530  Vertex* adj = *v1->adjacent.begin();
531  v1->adjacent.erase(adj);
532  adj->adjacent.erase(v1);
533  v1 = adj;
534  }
535 
536  if (v1 != v0)
537  {
538  // If v1 had exactly 3 adjacent vertices, removal of the CCW edge
539  // that shared v1 leads to v1 having 2 adjacent vertices. When
540  // the CW removal occurs and we reach v1, the edge deletion will
541  // lead to v1 having 1 adjacent vertex, making it a filament
542  // endpoints. We must ensure we do not delete v1 in this case,
543  // allowing the recursive algorithm to handle the filament later.
544  vBranch = v1;
545 
546  // Remove edges while traversing clockwise.
547  while (v0 != vBranch && v0->adjacent.size() == 1)
548  {
549  v1 = *v0->adjacent.begin();
550  v0->adjacent.erase(v1);
551  v1->adjacent.erase(v0);
552  v0 = v1;
553  }
554  }
555  // else the cycle is its own connected component.
556 
557  return cycle;
558 }
559 
560 template <typename Real>
563 {
564  Vertex* vNext = nullptr;
565  bool vCurrConvex = false;
566  std::array<Real, 2> dCurr, dNext;
567  if (vPrev)
568  {
569  dCurr[0] = (*vCurr->position)[0] - (*vPrev->position)[0];
570  dCurr[1] = (*vCurr->position)[1] - (*vPrev->position)[1];
571  }
572  else
573  {
574  dCurr[0] = static_cast<Real>(0);
575  dCurr[1] = static_cast<Real>(-1);
576  }
577 
578  for (auto vAdj : vCurr->adjacent)
579  {
580  // vAdj is a vertex adjacent to vCurr. No backtracking is allowed.
581  if (vAdj == vPrev)
582  {
583  continue;
584  }
585 
586  // Compute the potential direction to move in.
587  std::array<Real, 2> dAdj;
588  dAdj[0] = (*vAdj->position)[0] - (*vCurr->position)[0];
589  dAdj[1] = (*vAdj->position)[1] - (*vCurr->position)[1];
590 
591  // Select the first candidate.
592  if (!vNext)
593  {
594  vNext = vAdj;
595  dNext = dAdj;
596  vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]);
597  continue;
598  }
599 
600  // Update if the next candidate is clockwise of the current
601  // clockwise-most vertex.
602  if (vCurrConvex)
603  {
604  if (dCurr[0] * dAdj[1] < dCurr[1] * dAdj[0]
605  || dNext[0] * dAdj[1] < dNext[1] * dAdj[0])
606  {
607  vNext = vAdj;
608  dNext = dAdj;
609  vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]);
610  }
611  }
612  else
613  {
614  if (dCurr[0] * dAdj[1] < dCurr[1] * dAdj[0]
615  && dNext[0] * dAdj[1] < dNext[1] * dAdj[0])
616  {
617  vNext = vAdj;
618  dNext = dAdj;
619  vCurrConvex = (dNext[0] * dCurr[1] < dNext[1] * dCurr[0]);
620  }
621  }
622  }
623 
624  return vNext;
625 }
626 
627 template <typename Real>
630 {
631  Vertex* vNext = nullptr;
632  bool vCurrConvex = false;
633  std::array<Real, 2> dCurr, dNext;
634  if (vPrev)
635  {
636  dCurr[0] = (*vCurr->position)[0] - (*vPrev->position)[0];
637  dCurr[1] = (*vCurr->position)[1] - (*vPrev->position)[1];
638  }
639  else
640  {
641  dCurr[0] = static_cast<Real>(0);
642  dCurr[1] = static_cast<Real>(-1);
643  }
644 
645  for (auto vAdj : vCurr->adjacent)
646  {
647  // vAdj is a vertex adjacent to vCurr. No backtracking is allowed.
648  if (vAdj == vPrev)
649  {
650  continue;
651  }
652 
653  // Compute the potential direction to move in.
654  std::array<Real, 2> dAdj;
655  dAdj[0] = (*vAdj->position)[0] - (*vCurr->position)[0];
656  dAdj[1] = (*vAdj->position)[1] - (*vCurr->position)[1];
657 
658  // Select the first candidate.
659  if (!vNext)
660  {
661  vNext = vAdj;
662  dNext = dAdj;
663  vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]);
664  continue;
665  }
666 
667  // Select the next candidate if it is counterclockwise of the current
668  // counterclockwise-most vertex.
669  if (vCurrConvex)
670  {
671  if (dCurr[0] * dAdj[1] > dCurr[1] * dAdj[0]
672  && dNext[0] * dAdj[1] > dNext[1] * dAdj[0])
673  {
674  vNext = vAdj;
675  dNext = dAdj;
676  vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]);
677  }
678  }
679  else
680  {
681  if (dCurr[0] * dAdj[1] > dCurr[1] * dAdj[0]
682  || dNext[0] * dAdj[1] > dNext[1] * dAdj[0])
683  {
684  vNext = vAdj;
685  dNext = dAdj;
686  vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]);
687  }
688  }
689  }
690 
691  return vNext;
692 }
693 
694 template <typename Real>
695 MinimalCycleBasis<Real>::Vertex::Vertex(int inName, std::array<Real, 2> const* inPosition)
696  :
697  name(inName),
698  position(inPosition),
699  visited(0)
700 {
701 }
702 
703 template <typename Real>
705 {
706  return name < vertex.name;
707 }
708 
709 }
Vertex(int inName, std::array< Real, 2 > const *inPosition)
std::shared_ptr< Tree > ExtractBasis(std::vector< Vertex * > &component)
MinimalCycleBasis & operator=(MinimalCycleBasis const &)=delete
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
std::vector< int > ExtractCycle(std::vector< Vertex * > &closedWalk)
bool operator<(Vertex const &vertex) const
GLfloat GLfloat v1
Definition: glcorearb.h:812
GLuint const GLchar * name
Definition: glcorearb.h:781
std::vector< std::shared_ptr< Vertex > > mVertexStorage
static void DepthFirstSearch(Vertex *vInitial, std::vector< Vertex * > &component)
std::shared_ptr< Tree > ExtractCycleFromClosedWalk(std::vector< Vertex * > &closedWalk)
void RemoveFilaments(std::vector< Vertex * > &component)
Vertex * GetClockwiseMost(Vertex *vPrev, Vertex *vCurr) const
GLfloat v0
Definition: glcorearb.h:811
GLenum GLenum GLuint components
Definition: glext.h:8352
MinimalCycleBasis(std::vector< std::array< Real, 2 >> const &positions, std::vector< std::array< int, 2 >> const &edges, std::vector< std::shared_ptr< Tree >> &forest)
Vertex * GetCounterclockwiseMost(Vertex *vPrev, Vertex *vCurr) const
std::shared_ptr< Tree > ExtractCycleFromComponent(std::vector< Vertex * > &component)
std::vector< std::shared_ptr< Tree > > children
std::array< Real, 2 > const * position


geometric_tools_engine
Author(s): Yijiang Huang
autogenerated on Thu Jul 18 2019 04:00:01