Finding all non-comparable nodes in DAG - boost

I am interested in finding sets of vertices that are not ordered in a directed acyclic graph (in the sense of a topological order).
That is, for example: two vertices in non-connected subgraphs, or the pairs (B,C), (B,D) in cases such as :
The naive possibility I thought of was to enumerate all the topological sorts (in this case [ A, B, C, D ] and [ A, C, D, B ] & find all pairs whose order ends up being different in at least two sorts, but this would be pretty expensive computationally.
Are there other, faster possibilities for what I want to achieve ? I am using boost.graph.

Basically what you want is the pair of nodes (u,v) such that there is no path from u to v, and no path from v to u. You can find for each node, all nodes that are reachable from that node using DFS. Total Complexity O(n(n+m)).
Now all you have to do is for each pair check if neither of the 2 nodes are reachable by the other.

You can start with a simple topological sort. Boost's implementation conveniently returns a reverse ordered list of vertices.
You can iterate that list, marking each initial leaf node with a new branch id until a shared node is encountered.
Demo Time
Let's start with the simplests of graph models:
#include <boost/graph/adjacency_list.hpp>
using Graph = boost::adjacency_list<>;
We wish to map branches:
using BranchID = int;
using BranchMap = std::vector<BranchID>; // maps vertex id -> branch id
We want to build, map and visualize the mappings:
Graph build();
BranchMap map_branches(Graph const&);
void visualize(Graph const&, BranchMap const& branch_map);
int main() {
// sample data
Graph g = build();
// do the topo sort and distinguish branches
BranchMap mappings = map_branches(g);
// output
visualize(g, mappings);
}
Building Graph
Just the sample data from the question:
Graph build() {
Graph g(4);
enum {A,B,C,D};
add_edge(A, B, g);
add_edge(A, C, g);
add_edge(C, D, g);
return g;
}
Mapping The Branches
As described in the introduction:
#include <boost/graph/topological_sort.hpp>
std::vector<BranchID> map_branches(Graph const& g) {
std::vector<Vertex> reverse_topo;
boost::topological_sort(g, back_inserter(reverse_topo));
// traverse the output to map to unique branch ids
std::vector<BranchID> branch_map(num_vertices(g));
BranchID branch_id = 0;
for (auto v : reverse_topo) {
auto degree = out_degree(v, g);
if (0 == degree) // is leaf?
++branch_id;
if (degree < 2) // "unique" path
branch_map[v] = branch_id;
}
return branch_map;
}
Visualizing
Let's write a graph-viz representation with each branch colored:
#include <boost/graph/graphviz.hpp>
#include <iostream>
void visualize(Graph const& g, BranchMap const& branch_map) {
// display helpers
std::vector<std::string> const colors { "gray", "red", "green", "blue" };
auto name = [](Vertex v) -> char { return 'A'+v; };
auto color = [&](Vertex v) -> std::string { return colors[branch_map.at(v) % colors.size()]; };
// write graphviz:
boost::dynamic_properties dp;
dp.property("node_id", transform(name));
dp.property("color", transform(color));
write_graphviz_dp(std::cout, g, dp);
}
This uses a tiny shorthand helper to create the transforming property maps:
// convenience short-hand to write transformed property maps
template <typename F>
static auto transform(F f) { return boost::make_transform_value_property_map(f, boost::identity_property_map{}); };
To compile this on a non-c++14 compiler you can replace the call to transform with the expanded body
Full Listing
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
using Graph = boost::adjacency_list<>;
using BranchID = int;
using BranchMap = std::vector<BranchID>; // maps vertex id -> branch id
Graph build();
BranchMap map_branches(Graph const&);
void visualize(Graph const&, BranchMap const& branch_map);
int main() {
// sample data
Graph g = build();
// do the topo sort and distinguish branches
BranchMap mappings = map_branches(g);
// output
visualize(g, mappings);
}
using Vertex = Graph::vertex_descriptor;
Graph build() {
Graph g(4);
enum {A,B,C,D};
add_edge(A, B, g);
add_edge(A, C, g);
add_edge(C, D, g);
return g;
}
#include <boost/graph/topological_sort.hpp>
std::vector<BranchID> map_branches(Graph const& g) {
std::vector<Vertex> reverse_topo;
boost::topological_sort(g, back_inserter(reverse_topo));
// traverse the output to map to unique branch ids
std::vector<BranchID> branch_map(num_vertices(g));
BranchID branch_id = 0;
for (auto v : reverse_topo) {
auto degree = out_degree(v, g);
if (0 == degree) // is leaf?
++branch_id;
if (degree < 2) // "unique" path
branch_map[v] = branch_id;
}
return branch_map;
}
#include <boost/property_map/transform_value_property_map.hpp>
// convenience short-hand to write transformed property maps
template <typename F>
static auto transform(F f) { return boost::make_transform_value_property_map(f, boost::identity_property_map{}); };
#include <boost/graph/graphviz.hpp>
#include <iostream>
void visualize(Graph const& g, BranchMap const& branch_map) {
// display helpers
std::vector<std::string> const colors { "gray", "red", "green", "blue" };
auto name = [](Vertex v) -> char { return 'A'+v; };
auto color = [&](Vertex v) -> std::string { return colors[branch_map.at(v) % colors.size()]; };
// write graphviz:
boost::dynamic_properties dp;
dp.property("node_id", transform(name));
dp.property("color", transform(color));
write_graphviz_dp(std::cout, g, dp);
}
Printing
digraph G {
A [color=gray];
B [color=red];
C [color=green];
D [color=green];
A->B ;
A->C ;
C->D ;
}
And the rendered graph:
Summary
Nodes in branches with different colors cannot be compared.

Related

Boost A* throwing segmentation fault

this is the continuation of another question I asked regarding boost graphs. (GraphML in Boost).
After successfully reading the graph, I am trying to apply boost A* on some random start and goal nodes but its throwing segmentation fault.
Following are the details of my graph.
using Graph = boost::adjacency_list<boost::setS, boost::vecS, boost::undirectedS, VertexProperties, EdgeProperties>;
struct VertexProperties {
std::vector<double> joint_angles;
VertexProperties() : joint_angles(3){}
};
struct EdgeProperties {
double weight;
};
I used A* cities file from Boost as reference (A* Cities) to code my distance heuristic and astar_goal_visitor.
struct found_goal {}; // exception for termination
// visitor that terminates when we find the goal
template <typename Vertex>
class astar_goal_visitor : public boost::default_astar_visitor
{
public:
astar_goal_visitor(Vertex goal) : m_goal(goal) {}
template <class Graph>
void examine_vertex(Vertex u, Graph& g) {
if(u == m_goal)
throw found_goal();
}
private:
Vertex m_goal;
};
// euclidean distance heuristic
template <class Graph>
class distance_heuristic : public boost::astar_heuristic<typename Graph::Graph, double>
{
public:
typedef typename boost::graph_traits<typename Graph::Graph>::vertex_descriptor Vertex;
distance_heuristic(Vertex goal, Graph &graph)
: m_goal(goal), m_graph(graph) {}
double operator()(Vertex u)
{
double dx = m_graph.getGraph()[m_goal].joint_angles[0] - m_graph.getGraph()[u].joint_angles[0];
double dy = m_graph.getGraph()[m_goal].joint_angles[1] - m_graph.getGraph()[u].joint_angles[1];
double dz = m_graph.getGraph()[m_goal].joint_angles[2] - m_graph.getGraph()[u].joint_angles[2];
return ::sqrt(dx * dx + dy * dy + dz * dz);
}
private:
Graph m_graph;
Vertex m_goal;
};
As for astar_search parameters, the predecessor map is defined as below.
typedef boost::property_map < Graph, boost::vertex_index_t >::type IndexMap;
typedef boost::iterator_property_map < Vertex*, IndexMap, Vertex, Vertex& > PredecessorMap;
BoostGraph::PredecessorMap BoostGraph::getPredecessorMap(){
IndexMap indexMap = boost::get(boost::vertex_index, graph);
std::vector<Vertex> p(boost::num_vertices(graph));
PredecessorMap predecessorMap(&p[0], indexMap);
return predecessorMap;
}
The final code for search is
std::vector<double> d(boost::num_vertices(graph.getGraph()));
std::mt19937 gen(time(0));
BoostGraph::Vertex start = boost::random_vertex(graph.getGraph(), gen);
BoostGraph::Vertex goal = boost::random_vertex(graph.getGraph(), gen);
auto weightmap = boost::get(&EdgeProperties::weight, graph.getGraph());
try {
// call astar named parameter interface
boost::astar_search
(graph.getGraph(), start,
distance_heuristic<BoostGraph>
(goal, graph),
boost::predecessor_map(graph.getPredecessorMap()).distance_map(boost::make_iterator_property_map(d.begin(), boost::get(boost::vertex_index, graph.getGraph()))).
weight_map(weightmap).
visitor(astar_goal_visitor<BoostGraph::Vertex>(goal)));
} catch(found_goal fg) { // found a path to the goal
std::list<BoostGraph::Vertex> shortest_path;
for(BoostGraph::Vertex v = goal;; v = p[v]) {
shortest_path.push_front(v);
if(p[v] == v)
break;
}
}
The getGraph function of Class BoostGraph is defined below where graph is the protected member of class BoostGraph.
protected:
Graph graph;
const BoostGraph::Graph& BoostGraph::getGraph() const{
return graph;
}
The segmentation fault is coming in stl_tree.h and I have no idea what has gone wrong. Any help would be appreciated. Thanks
Your heuristic holds a copy of the graph. You're indexing using vertex descriptors belonging to different graphs.
Your predecessor map is a local variable (the vector), and the map is a dangling reference to it after getPredecessorMap returns. Just make the vector a member, and then getPredecessorMap can be eliminated, because it doesn't really add much.
Also, you're indexing into joint_angles without bounds checking. Prefer .at(n) over [n] if you want to be safer. In fact, consider using std::array<double, 3> instead of std::vector<double>.
All in all I get the impression that you've been trying to hide complexity in a class and member functions, however it leads to the code becoming fragmented and inviting lots of unnecessary lifetime issues.
There are also parts of the code you do not show. They likely hold more problems (e.g. getGraph() is crucial).
Here's my simplified take:
Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/astar_search.hpp>
#include <boost/graph/random.hpp>
using JointAngles = std::vector<double>;
struct VertexProperties {
JointAngles joint_angles{0, 0, 0};
};
struct EdgeProperties {
double weight = 0;
};
using Graph = boost::adjacency_list<boost::setS, boost::vecS, boost::undirectedS,
VertexProperties, EdgeProperties>;
using Vertex = Graph::vertex_descriptor;
// visitor that terminates when we find the goal
struct goal_visitor : boost::default_astar_visitor {
struct found {}; // exception for termination
Vertex m_goal;
goal_visitor(Vertex g) : m_goal(g) {}
template <class Graph> void examine_vertex(Vertex u, Graph&) {
if (u == m_goal)
throw found{};
}
};
// euclidean distance heuristic
struct distance_heuristic : boost::astar_heuristic<Graph, double> {
distance_heuristic(Vertex goal, Graph& graph)
: m_graph(graph)
, m_goal(graph[goal].joint_angles) {}
double operator()(Vertex u) const {
auto& c = m_graph[u].joint_angles;
auto //
dx = m_goal.at(0) - c.at(0), //
dy = m_goal.at(1) - c.at(1), //
dz = m_goal.at(2) - c.at(2);
using std::sqrt; // allow ADL, good practice
return sqrt(dx * dx + dy * dy + dz * dz);
}
private:
Graph& m_graph; // reference!
JointAngles m_goal;
};
#include <random>
#include <fmt/ranges.h>
int main() {
Graph graph(4);
graph[0] = VertexProperties{{0, 0, 0}};
graph[1] = VertexProperties{{1, 1, 1}};
graph[2] = VertexProperties{{2, 2, 2}};
add_edge(0, 1, graph);
add_edge(1, 2, graph);
std::vector<Vertex> predecessors(num_vertices(graph));
std::vector<double> distances(num_vertices(graph));
auto index = get(boost::vertex_index, graph);
auto pmap = make_safe_iterator_property_map(predecessors.begin(), predecessors.size(), index);
auto dmap = make_safe_iterator_property_map(distances.begin(), distances.size(), index);
auto weightmap = get(&EdgeProperties::weight, graph);
std::mt19937 gen(std::random_device{}());
Vertex start = random_vertex(graph, gen);
Vertex goal = random_vertex(graph, gen);
try {
// call astar named parameter interface
astar_search( //
graph, start, distance_heuristic{goal, graph},
boost::predecessor_map(pmap) //
.distance_map(dmap)
.weight_map(weightmap)
.visitor(goal_visitor{goal}));
fmt::print("{} -> {}: No path\n", start, goal);
} catch (goal_visitor::found) {
std::list<Vertex> path;
for (auto cursor = goal;;) {
path.push_front(cursor);
auto previous = std::exchange(cursor, predecessors.at(cursor));
if (cursor == previous)
break;
}
fmt::print("{} -> {}: {}\n", start, goal, path);
}
}
Which prints e.g.
2 -> 1: [2, 1]
Encapsulation?
If you want to encapsulate, do it along the functional boundaries, instead of artificial boundaries that gave you the lifetime headaches you didn't need. If performance is no concern, you can reduce code with a facility like vector_property_map. For example:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/astar_search.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/random.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <boost/property_map/vector_property_map.hpp>
#include <fmt/ranges.h>
#include <random>
class BoostGraph {
private:
using JointAngles = std::vector<double>;
struct VertexProperties {
JointAngles joint_angles{0, 0, 0};
};
struct EdgeProperties {
double weight = 0;
};
using Graph = boost::adjacency_list<boost::setS, boost::vecS, boost::undirectedS,
VertexProperties, EdgeProperties>;
using Vertex = Graph::vertex_descriptor;
public:
BoostGraph() : m_graph(4) {
// TODO read graph
m_graph[0] = VertexProperties{{0, 0, 0}};
m_graph[1] = VertexProperties{{1, 1, 1}};
m_graph[2] = VertexProperties{{2, 2, 2}};
add_edge(0, 1, m_graph);
add_edge(1, 2, m_graph);
}
friend std::ostream& operator<<(std::ostream& os, BoostGraph const& bg) {
auto name = boost::make_transform_value_property_map(
[ja = get(&VertexProperties::joint_angles, bg.m_graph)](Vertex v) {
return fmt::format("Vertex #{} ({})", v, ja[v]);
},
get(boost::vertex_index, bg.m_graph));
print_graph(bg.m_graph, name, os);
return os;
}
Vertex random_vertex() { return boost::random_vertex(m_graph, m_prng); }
std::list<Vertex> find_path(Vertex start, Vertex goal) const {
std::list<Vertex> path;
auto pmap = make_vector_property_map<Vertex>(get(boost::vertex_index, m_graph));
try {
astar_search( //
m_graph, start, distance_heuristic{goal, m_graph},
boost::predecessor_map(pmap) //
.weight_map(get(&EdgeProperties::weight, m_graph))
.visitor(finder{goal}));
} catch (finder::found) {
for (auto cursor = goal;;) {
path.push_front(cursor);
auto previous = std::exchange(cursor, pmap[cursor]);
if (cursor == previous)
break;
}
}
return path;
}
private:
// visitor that terminates when we find the goal
struct finder : boost::default_astar_visitor {
struct found {}; // exception for termination
Vertex m_goal;
finder(Vertex g) : m_goal(g) {}
void examine_vertex(Vertex u, Graph const&) const {
if (u == m_goal)
throw found{};
}
};
// euclidean distance heuristic
struct distance_heuristic : boost::astar_heuristic<Graph, double> {
distance_heuristic(Vertex goal, Graph const& graph)
: m_graph(graph)
, m_goal(graph[goal].joint_angles) {}
double operator()(Vertex u) const {
auto& c = m_graph[u].joint_angles;
auto //
dx = m_goal.at(0) - c.at(0), //
dy = m_goal.at(1) - c.at(1), //
dz = m_goal.at(2) - c.at(2);
using std::sqrt; // allow ADL, good practice
return sqrt(dx * dx + dy * dy + dz * dz);
}
private:
Graph const& m_graph; // reference!
JointAngles m_goal;
};
Graph m_graph;
std::mt19937 m_prng{std::random_device{}()};
};
int main() {
BoostGraph bg;
std::cout << "Graph: " << bg << "\n";
for (auto i = 0; i < 10; ++i) {
auto start = bg.random_vertex(), goal = bg.random_vertex();
auto path = bg.find_path(start, goal);
fmt::print("{} -> {}: {}\n", start, goal, path);
}
}
Printing e.g.
Graph: Vertex #0 ([0, 0, 0]) <--> Vertex #1 ([1, 1, 1])
Vertex #1 ([1, 1, 1]) <--> Vertex #0 ([0, 0, 0]) Vertex #2 ([2, 2, 2])
Vertex #2 ([2, 2, 2]) <--> Vertex #1 ([1, 1, 1])
Vertex #3 ([0, 0, 0]) <-->
2 -> 2: [2]
1 -> 0: [1, 0]
0 -> 1: [0, 1]
2 -> 0: [2, 1, 0]
3 -> 0: []
0 -> 3: []
0 -> 3: []
1 -> 0: [1, 0]
1 -> 0: [1, 0]
3 -> 1: []

Populate a tree from vectors with BGL

I have two vectors of objects that I need to make a tree structure from them. Let's assume we have vector <obj> parents and vector <obj> leaves. Therefore, each element of vector <obj> parents has several leaves that sits at the end of the tree. What I am doing is defining Vertex properties and Edges properties as below, and then define a bidirectional graph:
struct VertexData
{
std::string obj_name; // concatenation of labels
std::string obj_class_num;
int num;
vector <int> segments_list;
bool is_leaf=false;
};
struct EdgeData
{
std::string edge_name;
double confidence;
};
typedef boost::adjacency_list<boost::vecS, boost::vecS,
boost::bidirectionalS,
VertexData,
boost::property<boost::edge_weight_t, double, EdgeData> > Graph;
Graph graph;
First approach: looping through the vector <obj> leaves, for each member, I find the parent and make an edge. Then assign properties to the edge and vertices. But then for next leaf, I should check if already it has a parent in the tree or I should add a new vertex for its parent.
Second approach: another thing that I tried, was looping through the vector <obj> parents, and for each element try to make its leaves. But I am not sure what is the correct way to do this.
Here is a link:
adding custom vertices to a boost graph that I try to do the same but with iterations.
Code added for 1st approach:
vector <class1> parents; // this has some objects of type class1
vector <class2> leaves; // this has some objects of type class2
/// declare the graph
typedef boost::adjacency_list<boost::vecS, boost::vecS,
boost::bidirectionalS,
VertexData,
boost::property<boost::edge_weight_t, double, EdgeData> > Graph;
/// instantiate the graph
Graph graph;
typedef boost::graph_traits<Graph>::vertex_descriptor vertex_t;
typedef boost::graph_traits<Graph>::edge_descriptor edge_t;
vector<vertex_t> obj_vertices;
vector<string> parents_labels_v;
bool parent_exist=false;
/// loop through leaves and make edges with associated parent
for (auto leaf: leaves) {
int leaf_nr = leaf.Number;
vertex_t v = boost::add_vertex(graph); // this is the leaf vertex
graph[v].num = leaf_nr; // leaf number
graph[v].is_leaf = true;
/// access the parent label by leaf number
string label1 = parents[leaf_nr].label;
/// check if the parent already exist, using its label
if(std::find(parents_labels_v.begin(), parents_labels_v.end(), label1)
!= parents_labels_v.end()){
parent_exist = true;
}else{
parents_labels_v.push_back(label1);
}
if(parent_exist) {
// find already_exist parent vertex to make the edge
vertex_t u = ??? here i have problem
// Create an edge connecting those two vertices
edge_t e; bool b;
boost::tie(e,b) = boost::add_edge(u,v,graph);
} else{
// if parent-vertex there is not, add it to the graph
vertex_t u = boost::add_vertex(graph); // this is the parent vertex
graph[u].obj_name = label1;
graph[u].segments_list.push_back(leaf_nr);
obj_vertices.push_back(u);
// Create an edge connecting those two vertices
edge_t e; bool b;
boost::tie(e,b) = boost::add_edge(u,v,graph);
}
}

Unique set of undirected edges

I want to create a set (mathematically speaking, not std::set) of unique elements in C++. My elements are std::pair<int, int> and they represent an edge. Because those edges are not directed, I don't want to have duplicates like (3,4) and (4,3). How can I achieve this in C++ ?
Something along these lines, perhaps:
using Edge = std::pair<int, int>;
struct CompareEdges {
bool operator()(const Edge& a, const Edge& b) const {
return Normalize(a) < Normalize(b);
}
private:
Edge Normalize(const Edge& e) {
if (e.first <= e.second) return e;
return {e.second, e.first};
}
};
std::set<Edge, CompareEdges> SetOfEdges;
This is another solution, with the compare function as lambda expression.
using Edge = pair<int, int>;
std::set<Edge, std::function<bool(const Edge &, const Edge &)>> edges(
[](const Edge &a, const Edge &b)
{
const int x = min(a.first, a.second);
const int y = min(b.first, b.second);
if (x < y)
return true;
else if (y > x)
return false;
else
return max(a.first, a.second) < max(b.first, b.second);
}
);

Output BGL Edge Weights

I am trying to iterate through the edges of a graph and output their edge weights. I am confused though. I know how to output the "edges", but this is actually just a (vertex, vertex) which defines the edge. So do I index *edgePair.first into the EdgeWeightMap to get the weight of the edge starting from vertex *edgePair.first? This doesn't compile : "no match for operator<<".
#include <iostream>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
typedef boost::property<boost::edge_weight_t, double> EdgeWeightProperty;
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, EdgeWeightProperty> Graph;
int main(int,char*[])
{
// Create a graph object
Graph g(2);
EdgeWeightProperty e = 5;
add_edge(0, 1, e, g);
boost::property_map<Graph, boost::edge_weight_t>::type EdgeWeightMap = get(boost::edge_weight_t(), g);
typedef boost::graph_traits<Graph>::edge_iterator edge_iter;
std::pair<edge_iter, edge_iter> edgePair;
for(edgePair = edges(g); edgePair.first != edgePair.second; ++edgePair.first)
{
std::cout << EdgeWeightMap[*edgePair.first] << " ";
}
return 0;
}
Any thoughts?
Thanks,
David
In this code, EdgeWeightProperty is declared as a vertex property rather than an edge property, and so it doesn't make sense to insert edges with that property. Try adding boost::no_property before EdgeWeightProperty in your adjacency_list typedef. Also, you might want to use get(EdgeWeightMap, *edgePair.first) rather than operator[] because that will work with more property map types.

Constructing the contour of a polygon (in particular a triangulation)

How would I go about constructing the contour of 2d polygon which is formed of only triangles and it can have holes and the external contour can be concave/convex and the holes can also be concave/convex.
From what I'm reading over here it seems that It's exactly the inverse of the triangulation problem.
Do you know any articles treating this type of problem?
Are octrees/quadtrees relevant to this?
I guess that you have data in the form of sets of three points, which constitute a "filled" triangle, that these triangles are adjoined along edges, and that all vertices that will be corners of the complete shape are also vertices of all the triangles that touch this point. You would then just have to find all edges that are not doubled, i.e. do not belong to two adjoined triangles.
I think you can solve your problem by creating a topological data structure to represent your set of triangles, and then using that structure to iterate in order over the triangle edges that lie on the boundary.
For example: you can create a halfedge data structure. Assuming you insert halfedges even on the boundary (correctly), iterating over the boundary contour is as simple as locating one halfedge on the boundary and then iterating over it's "next" pointer until you get back to the halfedge you started from.
Similarly to halfedges, you can use other topological structures like winged-edge, etc., but the concept is the same.
Here is an implementation operating on a triangle-mesh, finding and connecting all non-doubled edges as explained also in this answer.
#include <list>
#include <map>
#include <set>
#include <vector>
#include <iostream>
typedef int Vertex;
class Triangle {
public:
const Vertex& operator [] (size_t i) const {
return p[i];
}
Vertex p[3];
};
std::list<std::list<Vertex>> find_contours(const std::vector<Triangle>& triangles) {
std::set<std::pair<Vertex, Vertex>> edges;
std::map<Vertex, Vertex> neighbors;
for(const auto& t : triangles) {
edges.insert(std::make_pair(t[0], t[1]));
edges.insert(std::make_pair(t[1], t[2]));
edges.insert(std::make_pair(t[2], t[0]));
}
for(const auto& t : triangles) {
edges.erase(std::make_pair(t[1], t[0]));
edges.erase(std::make_pair(t[2], t[1]));
edges.erase(std::make_pair(t[0], t[2]));
}
for(const auto& t : triangles) {
if (edges.find(std::make_pair(t[0], t[1])) != edges.end()) {
neighbors[t[0]] = t[1];
}
if (edges.find(std::make_pair(t[1], t[2])) != edges.end()) {
neighbors[t[1]] = t[2];
}
if (edges.find(std::make_pair(t[2], t[0])) != edges.end()) {
neighbors[t[2]] = t[0];
}
}
std::list<std::list<Vertex>> result;
while(!neighbors.empty()) {
std::list<Vertex> contour;
auto v0 = neighbors.begin()->first;
auto v = v0;
while(neighbors.find(v) != neighbors.end()) {
contour.push_back(v);
auto old_v = v;
v = neighbors.at(v);
neighbors.erase(old_v);
}
if (v != v0) {
throw std::runtime_error("Contour is not closed");
}
neighbors.erase(v);
result.push_back(contour);
}
return result;
}
int main() {
int v00 = 0;
int v10 = 1;
int v01 = 2;
int v11 = 3;
std::vector<Triangle> v{
{v00, v10, v11},
{v00, v11, v01}};
for(const auto& c : find_contours(v)) {
for(const auto& v : c) {
std::cerr << v << " | ";
}
std::cerr << std::endl;
}
std::cerr << std::endl;
return 0;
}

Resources