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

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;
}

Related

Create Edges in Boost Graph using Multi Threading

I am trying to create a boost graph with more than 50K nodes (It will map the configuration space of a robot) and I want to create edges between the node using multi threading as it has become a bottleneck for my program.
I store all the vertices' index in a hash map so that they are easy for lookup while adding edges. For each vertex I find 5 nearest neighbors that are to be connected.
Also I have disabled parallel edges in the graph and the graph definition is
using Graph = boost::adjacency_list<boost::setS, boost::vecS, boost::undirectedS, VertexProperties, EdgeProperties>;
For finding the nearest neighbours, I use Local Senstivity Hashing (github_repo).
model* myModel;
myModel = new lshEuclidean();
myModel->fit(datapoints, status); /// training on all leaf nodes that are not in collision
Also before connecting the edges, I insert all the vertices in the graph and also make a hash map so that it is easy to recover the vertex index for adding an edge. (For quick testing, I convert the vector to a string to store in the hashmap, I know this is inefficient and need to make my own hash function)
BoostGraph::VertexProperties vp1;
BoostGraph graph(5);
std::unordered_map<std::string, int> map;
for(int center = 0; center < finalLeafNodes.size(); center++){
Vec origin = finalLeafNodes[center]->getOrigin();
std::vector<double> joint_angle = {origin.at(0)*toRadians, origin.at(1)*toRadians, origin.at(2)*toRadians,
origin.at(3)*toRadians, origin.at(4)*toRadians};
Eigen::VectorXd joint_angle_center;
joint_angle_center.resize(5);
joint_angle_center << joint_angle[0], joint_angle[1], joint_angle[2], joint_angle[3], joint_angle[4];
vp1.joint_angles = joint_angle;
BoostGraph::Vertex v_center = graph.AddVertex(vp1);
int vertex_index_center = graph.getVertexIndex(v_center);
Vec joint_angle_in_vector_degrees = origin;
std::stringstream output;
std::copy(joint_angle_in_vector_degrees.begin(), joint_angle_in_vector_degrees.end(), std::ostream_iterator<double>(output, " "));
map[output.str()] = vertex_index_center;
}
Then for each vertex node, I find neighbours in a given radius, sort them to nearest neighbour and take top 3/5 and add an edge by finding those neighbours vertex index through the hashmap mentioned above. I also have a local planner that checks if the path between two points will also be collision free or not. If its collision free, edge is added.
neighbors.sort([&query](Item &a, Item &b) -> bool {compare(a, b, query);});
auto edge = graph.AddEdge(center_iterator->second, neighbour_iterator->second, BoostGraph::EdgeProperties{(double)recursion_index + 1.});
Also I am now trying on a five degree of freedom robot, so the dimension has also increased.
I have tried multi threading with mutex_lock() but its not giving much of a speedup.
Is there a way to create a shared memory object where I can store the all the edges in multi threading and just loop over it to add the edges in the graph so that I don't have parallel edges.
I want to create edges between the node using multi threading as it has become a bottleneck for my program
Frequently the solution will be to change the choice of datastructure or algorithm. And it is quite likely that the time is actually spent doing other things than actually inserting edges.
In some cases you will even want to have a graph model that is just an edge list.
Here's a implementation of your description (using parts of the code from previous questions). In some sense it is straight-forward. In some sense it might show you some advanced algorithm/datastructure ideas. I think it doesn't have the performance bottleneck you are talking about?
Generating Input Data
Let's read vertices from CSV data. Generating 50k input lines:
Live On Coliru: gen.cpp
./a.out > input.txt; wc -l input.txt; tail input.txt
50000 input.txt
-0.54953,0.309816,1.49314
-1.38758,1.10754,1.12841
0.468204,-1.38628,1.29798
1.44672,-0.600287,-1.1688
1.28432,-1.40215,0.701882
1.4669,-0.215648,-0.404705
-0.701017,-0.130071,-0.62072
1.3742,-0.639261,1.44033
-1.17127,-1.48499,-1.03837
-1.16458,-1.19539,-0.946286
Parsing Vertices From Input Data
Note I included the optimization I suggested in an earlier question:
using JointAngles = std::array<double, 3>;
This also makes it easier later on to use geometry algorithms.
The parsing is not really related to the question, so posted as-is:
template <typename F> size_t read_vertices(std::string_view input, F callback) {
using namespace boost::spirit::x3;
using boost::fusion::at_c;
Vertex n = 0;
auto action = [&](auto& ctx) {
auto& vv = _attr(ctx);
callback(JointAngles{at_c<0>(vv), at_c<1>(vv), at_c<2>(vv)});
n += 1;
};
static auto const line = (double_ >> ',' >> double_ >> ',' >> double_)[action];
parse(begin(input), end(input), skip(blank)[line % (eol | eoi) > (*eol >> eoi)]);
return n;
}
Note how it is a whitespace tolerant where possible and supports ±inf/nan.
A Spatial Index
Instead of brute-forcing our way, let's use a Spatial Index from Boost Geometry. What this will allow us to do is find the nearest-k points much cheaper than bruteforce.
Firstly, include the relevant headers:
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/adapted/std_array.hpp>
#include <boost/geometry/index/adaptors/query.hpp>
#include <boost/geometry/index/rtree.hpp>
Now, let's tell Boost Geometry about our point type, and define a Tree type:
BOOST_GEOMETRY_REGISTER_STD_ARRAY_CS(bg::cs::cartesian)
namespace bg = boost::geometry;
namespace bgi = bg::index;
using Tree = bgi::rtree<std::pair<JointAngles, Vertex>, bgi::rstar<16>>;
We choose R* packing algorithm, which should usually give us best nearest() performance at the cost of higher insertion cost:
Actually Read The Graph
Using the parsing function above, let's build the graph and the spatial index tree at once:
int main() {
// read and index vertices
Tree tree;
Graph graph;
std::ifstream ifs("input.txt", std::ios::binary);
std::string const input(std::istreambuf_iterator<char>(ifs), {});
graph.m_vertices.reserve(50'000);
auto const n = read_vertices(input, [&](JointAngles ja) {
tree.insert({ja, add_vertex(VertexProperties{ja}, graph)});
});
std::cout << "Parsed " << n << " vertices, indexed: " << tree.size()
<< " graph: " << num_vertices(graph) << "\n";
That's all. Note how each inserted point in the tree carries the vertex descriptor as meta data, so we can correlate vertices with tree nodes.
This code will print, as expected, for our generated input.txt:
Parsed 50000 vertices, indexed: 50000 graph: 50000
Adding 5-nearest edges
Using a bgi query this is pretty simple. Likely this can be optimized, but let's do the naive thing first, just to see whether the performance is reasonable:
// connect 5-degree nearest vertices
size_t added = 0, dups =0;
for (auto& [vja, v] : tree) {
for (auto& [uja, u] : tree | queried(bgi::nearest(vja, 6))) {
if (v == u)
continue;
auto w = bg::distance(vja, uja);
auto [e, ok] = add_edge(v, u, EdgeProperties{w}, graph);
//std::cout << (ok ? "Added " : "Duplicate ") << e << " weight " << w << "\n";
(ok? added:dups)++;
}
}
std::cout << "Total edges added:" << added << " dups:" << dups << "\n";
Note that we omit self-edges, and rely on setS and undirectedS to detect duplicates - which are obviously expected. This prints, for our test data:
Total edges added:150778 dups:99222
BONUS: A* search
Like in your previous question, let's perform an A* search between arbitrary vertices:
// do A* search
std::vector<Vertex> predecessors(n);
std::vector<double> distances(n);
auto vidx = get(boost::vertex_index, graph); // redundant with vecS
auto pmap = make_iterator_property_map(predecessors.data(), vidx);
auto dmap = make_iterator_property_map(distances.data(), vidx);
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
auto heuristic = [&, gja = graph[goal].joint_angles](Vertex u) {
return bg::distance(graph[u].joint_angles, gja);
};
astar_search( //
graph, start, heuristic,
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);
}
As you can see everything is basically unchanged, except the distance_heuristic class has been replaced by the much simpler lambda:
auto heuristic = [&, gja = graph[goal].joint_angles](Vertex u) {
return bg::distance(graph[u].joint_angles, gja);
};
This effectively does the same as your manual heuristic, except potentially smarter - who knows :).
Possible outputs. Doing 1000 random searches took ~1.8s:
Parsed 50000 vertices, indexed: 50000 graph: 50000 0.161082s
Total edges added:150778 dups:99222 0.190395s
7489 -> 8408: [7489, 23635, 34645, 41337, 1725, 46184, 25161, 33297, 30471, 37500, 4073, 30763, 4488, 30949, 9505, 48543, 33639, 35640, 19525, 34765, 18439, 21830, 4170, 27552, 22621, 6327, 8277, 8082, 15932, 23390, 8408]
6968 -> 49906: [6968, 43210, 9331, 36641, 15088, 45635, 47530, 9136, 18177, 30781, 46243, 21125, 12868, 42416, 46187, 24824, 39841, 39095, 13494, 27104, 34973, 49906]
39242 -> 46236: [39242, 34365, 14041, 30310, 8757, 35459, 41035, 32883, 1552, 24120, 43646, 38812, 17835, 14082, 46568, 37492, 17564, 4934, 28288, 20393, 924, 14615, 15993, 39413, 10407, 46236]
--
31949 -> 38708: [31949, 16473, 18328, 20099, 22828, 42868, 46176, 22766, 49370, 17479, 636, 6173, 36367, 32040, 16961, 48438, 18883, 44611, 19468, 4095, 18156, 33083, 12925, 41017, 17514, 17765, 19710, 25790, 46668, 28202, 12010, 39520, 17796, 45443, 9474, 17370, 5071, 27279, 17083, 3503, 11401, 11209, 32403, 23265, 38708]
9895 -> 41286: [9895, 7793, 34802, 28190, 24889, 578, 49750, 20217, 41057, 2637, 24109, 4262, 38363, 11680, 7513, 39893, 21158, 15747, 33531, 11051, 7893, 31583, 45825, 18988, 38405, 13631, 31016, 45820, 9078, 37368, 28401, 14573, 9294, 6214, 28330, 22949, 10575, 41286]
42176 -> 37875: [42176, 12091, 19799, 41080, 47399, 30041, 41714, 10766, 8904, 41305, 4973, 21270, 18139, 29246, 34739, 35599, 11807, 36557, 48764, 9641, 3619, 11747, 34201, 33629, 20414, 24646, 43402, 36831, 7384, 29363, 24768, 33415, 41325, 17709, 32108, 42284, 28683, 5310, 1506, 14339, 27331, 14861, 7152, 37211, 22754, 7602, 48398, 27378, 39577, 37875]
Total search time: 1.79371s
real 0m2,209s
user 0m2,160s
sys 0m0,044s
Complete Benchmark
Live On Coliru
#include <boost/fusion/adapted/std_array.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/astar_search.hpp>
#include <boost/graph/random.hpp>
#include <chrono>
#include <fmt/ranges.h>
#include <fstream>
#include <random>
static auto now = &std::chrono::steady_clock::now;
using namespace std::chrono_literals;
using JointAngles = std::array<double, 3>;
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;
template <typename F> size_t read_vertices(std::string_view input, F callback) {
using namespace boost::spirit::x3;
using boost::fusion::at_c;
Vertex n = 0;
auto action = [&](auto& ctx) {
auto& vv = _attr(ctx);
callback(JointAngles{at_c<0>(vv), at_c<1>(vv), at_c<2>(vv)});
n += 1;
};
static auto const line = (double_ >> ',' >> double_ >> ',' >> double_)[action];
parse(begin(input), end(input), skip(blank)[line % (eol | eoi) > (*eol >> eoi)]);
return n;
}
// 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{};
}
};
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/adapted/std_array.hpp>
#include <boost/geometry/index/adaptors/query.hpp>
#include <boost/geometry/index/rtree.hpp>
namespace bg = boost::geometry;
namespace bgi = bg::index;
using bgi::adaptors::queried;
BOOST_GEOMETRY_REGISTER_STD_ARRAY_CS(bg::cs::cartesian)
using Tree = bgi::rtree<std::pair<JointAngles, Vertex>, bgi::rstar<16>>;
int main() {
auto elapsed = [start = now()]() mutable {
auto n = now();
return (n - std::exchange(start, n)) / 1.0s;
};
// read and index vertices
Tree tree;
Graph graph;
std::ifstream ifs("input.txt", std::ios::binary);
std::string const input(std::istreambuf_iterator<char>(ifs), {});
graph.m_vertices.reserve(50'000);
auto const n = read_vertices(input, [&](JointAngles ja) {
tree.insert({ja, add_vertex(VertexProperties{ja}, graph)});
});
std::cout << "Parsed " << n << " vertices, indexed: " << tree.size()
<< " graph: " << num_vertices(graph) << " " << elapsed() << "s\n";
assert(n == tree.size());
assert(n == num_vertices(graph));
// connect 5-degree nearest vertices
size_t added = 0, dups =0;
for (auto& [vja, v] : tree) {
for (auto& [uja, u] : tree | queried(bgi::nearest(vja, 6))) {
if (v == u)
continue;
auto w = bg::distance(vja, uja);
auto [e, ok] = add_edge(v, u, EdgeProperties{w}, graph);
//std::cout << (ok ? "Added " : "Duplicate ") << e << " weight " << w << "\n";
(ok? added:dups)++;
}
}
std::cout << "Total edges added:" << added << " dups:" << dups << " " << elapsed() << "s\n";
// do A* search
std::vector<Vertex> predecessors(n);
std::vector<double> distances(n);
for (auto i = 0; i < 1'000; ++i) {
auto vidx = get(boost::vertex_index, graph); // redundant with vecS
auto pmap = make_iterator_property_map(predecessors.data(), vidx);
auto dmap = make_iterator_property_map(distances.data(), vidx);
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
auto heuristic = [&, gja = graph[goal].joint_angles](Vertex u) {
return bg::distance(graph[u].joint_angles, gja);
};
astar_search( //
graph, start, heuristic,
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);
}
}
std::cout << "Total search time: " << elapsed() << "s\n";
}
On Coliru, takes a little longer:
Parsed 50000 vertices, indexed: 50000 graph: 50000 0.252916s
Total edges added:150778 dups:99222 0.38979s
43176 -> 2998: [43176, 8919, 27234, 38221, 8714, 2907, 45819, 32924, 33376, 14539, 9174, 19001, 30909, 3923, 36332, 4521, 43005, 31867, 7326, 46231, 20699, 24026, 44641, 21918, 43012, 37366, 2800, 14239, 21197, 26989, 38269, 16522, 25964, 18224, 47148, 21553, 19350, 37546, 41390, 1247, 2998]
19955 -> 30654: [19955, 18833, 24521, 9310, 29015, 5746, 46264, 7706, 4929, 11078, 41910, 30676, 26759, 16638, 3075, 23001, 9322, 38446, 20634, 1120, 30761, 47535, 15750, 10039, 34123, 42874, 22325, 24136, 30285, 34230, 23926, 9978, 4427, 23805, 10436, 41678, 46936, 37189, 30654]
45710 -> 21757: [45710, 45416, 1375, 16480, 21730, 22843, 15897, 33652, 12561, 46834, 23178, 44302, 21027, 15457, 38383, 14716, 26787, 20697, 41752, 42153, 44194, 21757]
--
16543 -> 43355: [16543, 44982, 27516, 6578, 27706, 39013, 35842, 33455, 30460, 22955, 579, 46537, 43224, 6811, 1651, 41054, 21637, 9496, 36577, 21896, 49329, 43355]
2856 -> 24431: [2856, 21766, 1449, 2525, 15156, 6325, 23773, 25733, 48449, 24269, 49865, 34213, 47119, 48167, 12609, 46284, 33395, 10107, 26726, 14078, 28431, 33884, 468, 39873, 42529, 32395, 49457, 44554, 2207, 47678, 4783, 14247, 39638, 8510, 9439, 20570, 18018, 34614, 37184, 17579, 49921, 8755, 44316, 24431]
17195 -> 21888: [17195, 38851, 28287, 18829, 14051, 28305, 32206, 11044, 6989, 30201, 49002, 19410, 6456, 47912, 35145, 9286, 17782, 10294, 14344, 49966, 49634, 5262, 12496, 45270, 20093, 11298, 7202, 15409, 41313, 35934, 14510, 17221, 23121, 49522, 38138, 45948, 43564, 7840, 4456, 32016, 16660, 5832, 7578, 380, 9925, 18908, 38131, 36929, 28073, 21888]
Total search time: 3.41871s

Algorithm that finds a simple cycle in the graph and prints it

Let G=(V,E) be a simple undirected graph. Suggest an algorithm that finds some simple cycle in the graph and prints it (the sequence of nodes composing it). If there is no such cycle, the algorithm will not print anything.
Algorithm:
Initiate an array of size n, and a parent variable for each vertex.
Start DFS on a random vertex, and for each visited vertex, mark "1" in the array, and assign its parent node.
If in the DFS run, the next vertex is an already marked vertex which is not its parent - there is a cycle in the graph, and print backwards all of the nodes using their parent variable.
Is the algorithm correct? Or do I need to change things?
Thanks!
From the graph theory we know that:
if the quantity of vertices of a graph is more than the quantity of edges, therefore, cycles (closed contours) are absent.
if the quantity of vertices of a graph is equal to the quantity of edges, therefore, a graph has only one cycle.
if the quantity of vertices of a graph is less than the quantity of edges, therefore, a graph has more than one closed contour.
I offer the algorithm Depth first search, that finds a simple cycles in the graph and prints them:
#include <iostream>
#include <vector>
#include <set>
#include <algorithm>
using namespace std;
const int maximumSize=40;
vector<vector<int>> visited(maximumSize, vector<int>(maximumSize, 0));
vector<int> graph[maximumSize], closedContour, temporary;
int vertices, edges;
set<vector<int>> contours;
void showContentSetVector(set<vector<int>> input)
{
for(auto iterator=input.begin(); iterator!=input.end(); ++iterator)
{
for(auto item : *iterator)
{
cout<<item<<", ";
}
cout<<endl;
}
return;
}
bool compare(int i,int j)
{
return (i<j);
}
void createGraph()
{
cin>>vertices>>edges;
int vertex0, vertex1;
for(int i=1; i<=edges; ++i)
{
cin>>vertex0>>vertex1;
graph[vertex0].push_back(vertex1);
graph[vertex1].push_back(vertex0);
}
return;
}
void depthFirstSearch(int initial, int current, int previous)
{
if(visited[initial][current]==1)
{
for(int i=0; i<temporary.size(); ++i)
{
if(temporary[i]==current)
{
for(int j=i; j<temporary.size(); ++j)
{
closedContour.push_back(temporary[j]);
}
}
}
sort(closedContour.begin(), closedContour.end(), compare);
contours.insert(closedContour);
closedContour.clear();
return;
}
visited[initial][current]=1;
temporary.push_back(current);
for(int next : graph[current])
{
if(next==previous)
{
continue;
}
depthFirstSearch(initial, next, current);
}
temporary.pop_back();
return;
}
void solve()
{
createGraph();
for(int vertex=1; vertex<=vertices; ++vertex)
{
temporary.clear();
depthFirstSearch(vertex, vertex, -1);
}
cout<<"contours <- ";
showContentSetVector(contours);
return;
}
int main()
{
solve();
return 0;
}
Here is the result:
contours <-
1, 2, 3, 4,
6, 7, 8,

How to fill a fixed rectangle with square pieces entirely?

Is this knapsack algorithm or bin packing? I couldn't find an exact solution but basically I have a fixed rectangle area that I want to fill with perfect squares that represents my items where each have a different weight which will influence their size relative to others.
They will be sorted from large to smaller from top left to bottom right.
Also even though I need perfect squares, in the end some non-uniform scaling is allowed to fill the entire space as long as they still retain their relative area, and the non-uniform scaling is done with the least possible amount.
What algorithm I can use to achieve this?
There's a fast approximation algorithm due to Hiroshi Nagamochi and Yuusuke Abe. I implemented it in C++, taking care to obtain a worst-case O(n log n)-time implementation with worst-case recursive depth O(log n). If n ≤ 100, these precautions are probably unnecessary.
#include <algorithm>
#include <iostream>
#include <random>
#include <vector>
namespace {
struct Rectangle {
double x;
double y;
double width;
double height;
};
Rectangle Slice(Rectangle &r, const double beta) {
const double alpha = 1 - beta;
if (r.width > r.height) {
const double alpha_width = alpha * r.width;
const double beta_width = beta * r.width;
r.width = alpha_width;
return {r.x + alpha_width, r.y, beta_width, r.height};
}
const double alpha_height = alpha * r.height;
const double beta_height = beta * r.height;
r.height = alpha_height;
return {r.x, r.y + alpha_height, r.width, beta_height};
}
void LayoutRecursive(const std::vector<double> &reals, const std::size_t begin,
std::size_t end, double sum, Rectangle rectangle,
std::vector<Rectangle> &dissection) {
while (end - begin > 1) {
double suffix_sum = reals[end - 1];
std::size_t mid = end - 1;
while (mid > begin + 1 && suffix_sum + reals[mid - 1] <= 2 * sum / 3) {
suffix_sum += reals[mid - 1];
mid -= 1;
}
LayoutRecursive(reals, mid, end, suffix_sum,
Slice(rectangle, suffix_sum / sum), dissection);
end = mid;
sum -= suffix_sum;
}
dissection.push_back(rectangle);
}
std::vector<Rectangle> Layout(std::vector<double> reals,
const Rectangle rectangle) {
std::sort(reals.begin(), reals.end());
std::vector<Rectangle> dissection;
dissection.reserve(reals.size());
LayoutRecursive(reals, 0, reals.size(),
std::accumulate(reals.begin(), reals.end(), double{0}),
rectangle, dissection);
return dissection;
}
std::vector<double> RandomReals(const std::size_t n) {
std::vector<double> reals(n);
std::exponential_distribution<> dist;
std::default_random_engine gen;
for (double &real : reals) {
real = dist(gen);
}
return reals;
}
} // namespace
int main() {
const std::vector<Rectangle> dissection =
Layout(RandomReals(100), {72, 72, 6.5 * 72, 9 * 72});
std::cout << "%!PS\n";
for (const Rectangle &r : dissection) {
std::cout << r.x << " " << r.y << " " << r.width << " " << r.height
<< " rectstroke\n";
}
std::cout << "showpage\n";
}
Ok so lets assume integer positions and sizes (no float operations). To evenly divide rectangle into regular square grid (as big squares as possible) the size of the cells will be greatest common divisor GCD of the rectangle sizes.
However you want to have much less squares than that so I would try something like this:
try all square sizes a from 1 to smaller size of rectangle
for each a compute the naive square grid size of the rest of rectangle once a*a square is cut of it
so its simply GCD again on the 2 rectangles that will be created once a*a square is cut of. If the min of all 3 sizes a and GCD for the 2 rectangles is bigger than 1 (ignoring zero area rectangles) then consider a as valid solution so remember it.
after the for loop use last found valida
so simply add a*a square to your output and recursively do this whole again for the 2 rectangles that will remain from your original rectangle after a*a square was cut off.
Here simple C++/VCL/OpenGL example for this:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "gl_simple.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
class square // simple square
{
public:
int x,y,a; // corner 2D position and size
square(){ x=y=a=0.0; }
square(int _x,int _y,int _a){ x=_x; y=_y; a=_a; }
~square(){}
void draw()
{
glBegin(GL_LINE_LOOP);
glVertex2i(x ,y);
glVertex2i(x+a,y);
glVertex2i(x+a,y+a);
glVertex2i(x ,y+a);
glEnd();
}
};
int rec[4]={20,20,760,560}; // x,y,a,b
const int N=1024; // max square number
int n=0; // number of squares
square s[N]; // squares
//---------------------------------------------------------------------------
int gcd(int a,int b) // slow euclid GCD
{
if(!b) return a;
return gcd(b, a % b);
}
//---------------------------------------------------------------------------
void compute(int x0,int y0,int xs,int ys)
{
if ((xs==0)||(ys==0)) return;
const int x1=x0+xs;
const int y1=y0+ys;
int a,b,i,x,y;
square t;
// try to find biggest square first
for (a=1,b=0;(a<=xs)&&(a<=ys);a++)
{
// sizes for the rest of the rectangle once a*a square is cut of
if (xs==a) x=0; else x=gcd(a,xs-a);
if (ys==a) y=0; else y=gcd(a,ys-a);
// min of all sizes
i=a;
if ((x>0)&&(i>x)) i=x;
if ((y>0)&&(i>y)) i=y;
// if divisible better than by 1 remember it as better solution
if (i>1) b=a;
} a=b;
// bigger square not found so use naive square grid division
if (a<=1)
{
t.a=gcd(xs,ys);
for (t.y=y0;t.y<y1;t.y+=t.a)
for (t.x=x0;t.x<x1;t.x+=t.a)
if (n<N){ s[n]=t; n++; }
}
// bigest square found so add it to result and recursively process the rest
else{
t=square(x0,y0,a);
if (n<N){ s[n]=t; n++; }
compute(x0+a,y0,xs-a,a);
compute(x0,y0+a,xs,ys-a);
}
}
//---------------------------------------------------------------------------
void gl_draw()
{
glClear(GL_COLOR_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
glDisable(GL_TEXTURE_2D);
// set view to 2D [pixel] units
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(-1.0,-1.0,0.0);
glScalef(2.0/float(xs),2.0/float(ys),1.0);
// render input rectangle
glColor3f(0.2,0.2,0.2);
glBegin(GL_QUADS);
glVertex2i(rec[0] ,rec[1]);
glVertex2i(rec[0]+rec[2],rec[1]);
glVertex2i(rec[0]+rec[2],rec[1]+rec[3]);
glVertex2i(rec[0] ,rec[1]+rec[3]);
glEnd();
// render output squares
glColor3f(0.2,0.5,0.9);
for (int i=0;i<n;i++) s[i].draw();
glFinish();
SwapBuffers(hdc);
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{
// Init of program
gl_init(Handle); // init OpenGL
n=0; compute(rec[0],rec[1],rec[2],rec[3]);
Caption=n;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
// Exit of program
gl_exit();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
// repaint
gl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
{
// resize
gl_resize(ClientWidth,ClientHeight);
}
//---------------------------------------------------------------------------
And preview for the actually hardcoded rectangle:
The number 8 in Caption of the window is the number of squares produced.
Beware this is just very simple startup example for this problem. I Have not tested it extensively so there is possibility once prime sizes or just unlucky aspect ratios are involved then this might result in really high number of squares... for example if GCD of the rectangle size is 1 (primes) ... In such case you should tweak the initial rectangle size (+/-1 or whatever)
The important thing in the code is just the compute() function and the global variables holding the output squares s[n]... beware the compute() does not clear the list (in order to allow recursion) so you need to set n=0 before its non recursive call.
To keep this simple I avoided to use any libs or dynamic allocations or containers for the compute itself...

Finding all non-comparable nodes in DAG

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.

How to traverse a connected node network in a connected manner?

I have data structure that can be visualized as a connected network such as this:
I believe (without proof) that it should be possible to traverse all nodes, always moving from one node to a connected node (backtracking is of course required and allowed - as you would have done with a tree structure). How to do it?
The data structure may be written in pseudo-code as:
node[N] nodes; // the network as an array of N nodes
class node {
List<pt_to_node> friend_nodes; // a list of pointers to connected nodes
}
You can simply implement the graph with a stack for Depth First Search or a queue for Breadth First Search
e.g.
Assume that we're going to start with Node 1,
#include <vector>
#include <list>
#include <vector>
using namespace std;
class Graph
{
public:
int vert; //number of vertices
vector<list<int>> adj;
Graph(int _v)
{
adj.resize(_v);
vert = _v;
}
void addEdge(int v, int key)
{
adj[v].push_back(key);
adj[key].push_back(v); // comment out if undirected
}
void bfs(int start);
void dfs(int start);
};
void Graph::bfs(int start)
{
vector<bool> visited(vert,false);
list<int> queue;
visited[start] = true; // visit the first node
queue.push_back(start);
while (!queue.empty())
{
start = queue.front();
cout << start << " ";
queue.pop_front();
for (auto i = adj[start].begin(); i != adj[start].end(); ++i)
if (!visited[*i])
{
visited[*i] = true;
queue.push_back(*i);
}
}
cout << endl;
}
void Graph::dfs(int start)
{
vector<bool> visited(vert,false);
vector<int> stack;
visited[start] = true;
stack.push_back(start);
while (!stack.empty())
{
start = stack.back();
cout << start << " ";
stack.pop_back();
for (auto i = adj[start].begin(); i != adj[start].end(); ++i)
if (!visited[*i])
{
visited[*i] = true;
stack.push_back(*i);
}
}
cout << endl;
}
int main()
{
Graph g(6); // number of vertices we need
g.addEdge(1, 4);
g.addEdge(4, 2);
g.addEdge(4, 5);
g.addEdge(2, 5);
g.addEdge(5, 3);
g.bfs(1); // start with 1
g.dfs(1);
}
The results are DFS : 1 4 2 5 3 and BFS 1 4 5 3 2.
However, in the real network, each edge has a weight value that means distance or speed of the edge.

Resources