Comparar commits
93 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 6c931e2935 | |||
| 295a5a9fc4 | |||
| cc8dadf571 | |||
| 118e71c7af | |||
| b5d29a66b2 | |||
| e937e98b27 | |||
| c3705604cd | |||
| e1993a8ccd | |||
| b819f696eb | |||
| b1b37cf19b | |||
| 9808be9dfa | |||
| 06e8848ee2 | |||
| c43d5ccbf3 | |||
| e3e4ccdba8 | |||
| e0eacf0675 | |||
| ad1489e0ee | |||
| 3efe717154 | |||
| 17bd906318 | |||
| b8cdf0da24 | |||
| 801641823c | |||
| ac25e429b8 | |||
| 54e4b2d44e | |||
| 573699fd07 | |||
| 304a84a25c | |||
| 0e6412289c | |||
| e5901df8e1 | |||
| e2f96fb1ad | |||
| 190908ab27 | |||
| ff775d12f2 | |||
| a2fd4c0bf3 | |||
| 404edc5cdc | |||
| de4edc36bb | |||
| 1623e58063 | |||
| bb1c00f255 | |||
| 773e152454 | |||
| 9c59eff999 | |||
| b21b7e3115 | |||
| 36b09753af | |||
| 71b466a933 | |||
| f0141230b2 | |||
| d4631e3f69 | |||
| 64aa1bcd97 | |||
| d34e168562 | |||
| 8f7afd75da | |||
| 0380fd183d | |||
| c114904828 | |||
| 9c08d3dd82 | |||
| f3dbde8e7f | |||
| 7b0f4d204b | |||
| 79a2dc0a72 | |||
| c97bcdf005 | |||
| 03677b8ac7 | |||
| 9db6352281 | |||
| 3da8607afd | |||
| 4f248088ce | |||
| d08580f969 | |||
| a3652a4fc4 | |||
| 49426a0417 | |||
| 01163bf581 | |||
| b96bdf41b5 | |||
| 943c41ed7b | |||
| a6cd192156 | |||
| 06c82f3426 | |||
| eb06f19f11 | |||
| 4fc471ecf9 | |||
| ff018d40c0 | |||
| cc9199ee50 | |||
| 7bcef0ad97 | |||
| f5f9595627 | |||
| d2f2417012 | |||
| 5299c76521 | |||
| 80f976d1f3 | |||
| b117e64288 | |||
| ca41665c3e | |||
| b94c95149a | |||
| 5b95b7a557 | |||
| bdc31425ba | |||
| f001f31e78 | |||
| 6d56e2c622 | |||
| c983a7e83b | |||
| 86d0ce4e6b | |||
| f79933b140 | |||
| a47716161a | |||
| 831f5ea16e | |||
| 219d400e62 | |||
| 2b8c92cba9 | |||
| 44c5c11160 | |||
| 1f5cdf5d6b | |||
| 7607992eb6 | |||
| 2461073d64 | |||
| 3a43fe2885 | |||
| 9e2d5217d0 | |||
| 353e151c6b |
@@ -86,6 +86,7 @@ set(engine_SRCS # Except main.cpp.
|
||||
set(engine_TEST
|
||||
GCodePlannerTest
|
||||
LinearAlg2DTest
|
||||
TravellingSalesmanTest
|
||||
)
|
||||
|
||||
# Generating ProtoBuf protocol.
|
||||
|
||||
@@ -298,7 +298,7 @@ void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* c
|
||||
}
|
||||
void GCodePlanner::addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config, SpaceFillType space_fill_type, int wipe_dist)
|
||||
{
|
||||
LineOrderOptimizer orderOptimizer(lastPosition);
|
||||
LineOrderOptimizer orderOptimizer(lastPosition, storage.getSettingInMicrons("infill_line_distance")); //Use infill line distance to make adjacent infill lines fall in the same cluster.
|
||||
for(unsigned int i=0;i<polygons.size();i++)
|
||||
orderOptimizer.addPolygon(polygons[i]);
|
||||
orderOptimizer.optimize();
|
||||
|
||||
+162
-123
@@ -2,25 +2,27 @@
|
||||
#include "pathOrderOptimizer.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "utils/BucketGrid2D.h"
|
||||
#include "utils/TravellingSalesman.h"
|
||||
|
||||
#define INLINE static inline
|
||||
|
||||
namespace cura {
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
*
|
||||
*/
|
||||
void PathOrderOptimizer::optimize()
|
||||
{
|
||||
bool picked[polygons.size()];
|
||||
memset(picked, false, sizeof(bool) * polygons.size());/// initialized as falses
|
||||
|
||||
for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++) /// find closest point to initial starting point within each polygon +initialize picked
|
||||
memset(picked, false, sizeof (bool) * polygons.size()); /// initialized as falses
|
||||
|
||||
for (unsigned int i_polygon = 0; i_polygon < polygons.size(); i_polygon++) /// find closest point to initial starting point within each polygon +initialize picked
|
||||
{
|
||||
int best = -1;
|
||||
float bestDist = std::numeric_limits<float>::infinity();
|
||||
PolygonRef poly = polygons[i_polygon];
|
||||
for(unsigned int i_point=0; i_point<poly.size(); i_point++) /// get closest point in polygon
|
||||
for (unsigned int i_point = 0; i_point < poly.size(); i_point++) /// get closest point in polygon
|
||||
{
|
||||
float dist = vSize2f(poly[i_point] - startPoint);
|
||||
if (dist < bestDist)
|
||||
@@ -37,23 +39,23 @@ void PathOrderOptimizer::optimize()
|
||||
|
||||
|
||||
Point prev_point = startPoint;
|
||||
for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++) /// actual path order optimizer
|
||||
for (unsigned int polygon_idx = 0; polygon_idx < polygons.size(); polygon_idx++) /// actual path order optimizer
|
||||
{
|
||||
int best = -1;
|
||||
float bestDist = std::numeric_limits<float>::infinity();
|
||||
|
||||
|
||||
for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++)
|
||||
for (unsigned int polygon2_idx = 0; polygon2_idx < polygons.size(); polygon2_idx++)
|
||||
{
|
||||
if (picked[i_polygon] || polygons[i_polygon].size() < 1) /// skip single-point-polygons
|
||||
if (picked[polygon2_idx] || polygons[polygon2_idx].size() < 1) /// skip single-point-polygons
|
||||
continue;
|
||||
|
||||
assert (polygons[i_polygon].size() != 2);
|
||||
assert(polygons[polygon2_idx].size() != 2);
|
||||
|
||||
float dist = vSize2f(polygons[i_polygon][polyStart[i_polygon]] - prev_point);
|
||||
float dist = vSize2f(polygons[polygon2_idx][polyStart[polygon2_idx]] - prev_point);
|
||||
if (dist < bestDist)
|
||||
{
|
||||
best = i_polygon;
|
||||
best = polygon2_idx;
|
||||
bestDist = dist;
|
||||
}
|
||||
|
||||
@@ -74,7 +76,7 @@ void PathOrderOptimizer::optimize()
|
||||
}
|
||||
|
||||
prev_point = startPoint;
|
||||
for(unsigned int n=0; n<polyOrder.size(); n++) /// decide final starting points in each polygon
|
||||
for (unsigned int n = 0; n < polyOrder.size(); n++) /// decide final starting points in each polygon
|
||||
{
|
||||
int poly_idx = polyOrder[n];
|
||||
int point_idx = getPolyStart(prev_point, poly_idx);
|
||||
@@ -88,24 +90,23 @@ int PathOrderOptimizer::getPolyStart(Point prev_point, int poly_idx)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case EZSeamType::BACK: return getFarthestPointInPolygon(poly_idx);
|
||||
case EZSeamType::RANDOM: return getRandomPointInPolygon(poly_idx);
|
||||
case EZSeamType::BACK: return getFarthestPointInPolygon(poly_idx);
|
||||
case EZSeamType::RANDOM: return getRandomPointInPolygon(poly_idx);
|
||||
case EZSeamType::SHORTEST: return getClosestPointInPolygon(prev_point, poly_idx);
|
||||
default: return getClosestPointInPolygon(prev_point, poly_idx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int PathOrderOptimizer::getClosestPointInPolygon(Point prev_point, int poly_idx)
|
||||
{
|
||||
PolygonRef poly = polygons[poly_idx];
|
||||
int best_point_idx = -1;
|
||||
float bestDist = std::numeric_limits<float>::infinity();
|
||||
bool orientation = poly.orientation();
|
||||
for(unsigned int i_point=0 ; i_point<poly.size() ; i_point++)
|
||||
for (unsigned int i_point = 0; i_point < poly.size(); i_point++)
|
||||
{
|
||||
float dist = vSize2f(poly[i_point] - prev_point);
|
||||
Point n0 = normal(poly[(i_point-1+poly.size())%poly.size()] - poly[i_point], 2000);
|
||||
Point n0 = normal(poly[(i_point - 1 + poly.size()) % poly.size()] - poly[i_point], 2000);
|
||||
Point n1 = normal(poly[i_point] - poly[(i_point + 1) % poly.size()], 2000);
|
||||
float dot_score = dot(n0, n1) - dot(crossZ(n0), n1); /// prefer binnenbocht
|
||||
if (orientation)
|
||||
@@ -124,13 +125,12 @@ int PathOrderOptimizer::getRandomPointInPolygon(int poly_idx)
|
||||
return rand() % polygons[poly_idx].size();
|
||||
}
|
||||
|
||||
|
||||
int PathOrderOptimizer::getFarthestPointInPolygon(int poly_idx)
|
||||
{
|
||||
PolygonRef poly = polygons[poly_idx];
|
||||
int best_point_idx = -1;
|
||||
float best_y = std::numeric_limits<float>::min();
|
||||
for(unsigned int point_idx=0 ; point_idx<poly.size() ; point_idx++)
|
||||
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
|
||||
{
|
||||
if (poly[point_idx].Y > best_y)
|
||||
{
|
||||
@@ -141,136 +141,175 @@ int PathOrderOptimizer::getFarthestPointInPolygon(int poly_idx)
|
||||
return best_point_idx;
|
||||
}
|
||||
|
||||
LineOrderOptimizer::LineOrderOptimizer(const Point& start_point, unsigned long long cluster_grid_size)
|
||||
: cluster_grid_size(cluster_grid_size == 0 ? 2000 : cluster_grid_size) //Initialise cluster_grid_size to 2000 if the input grid size is invalid (e.g. no infill).
|
||||
{
|
||||
this->startPoint = start_point;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
*
|
||||
*/
|
||||
void LineOrderOptimizer::optimize()
|
||||
{
|
||||
int gridSize = 5000; // the size of the cells in the hash grid.
|
||||
BucketGrid2D<unsigned int> line_bucket_grid(gridSize);
|
||||
bool picked[polygons.size()];
|
||||
memset(picked, false, sizeof(bool) * polygons.size());/// initialized as falses
|
||||
|
||||
for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++) /// find closest point to initial starting point within each polygon +initialize picked
|
||||
if (lines.empty()) //Nothing to do. Terminate early.
|
||||
{
|
||||
int best = -1;
|
||||
float bestDist = std::numeric_limits<float>::infinity();
|
||||
PolygonRef poly = polygons[i_polygon];
|
||||
for(unsigned int i_point=0; i_point<poly.size(); i_point++) /// get closest point from polygon
|
||||
{
|
||||
float dist = vSize2f(poly[i_point] - startPoint);
|
||||
if (dist < bestDist)
|
||||
{
|
||||
best = i_point;
|
||||
bestDist = dist;
|
||||
}
|
||||
}
|
||||
polyStart.push_back(best);
|
||||
|
||||
assert(poly.size() == 2);
|
||||
|
||||
line_bucket_grid.insert(poly[0], i_polygon);
|
||||
line_bucket_grid.insert(poly[1], i_polygon);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//Since polyOrder must be filled with indices, an index in the polygons vector represents each line.
|
||||
std::vector<Cluster> line_clusters = cluster();
|
||||
|
||||
Point incommingPerpundicularNormal(0, 0);
|
||||
Point prev_point = startPoint;
|
||||
for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++) /// actual path order optimizer
|
||||
//Define how the TSP solver should use its elements.
|
||||
std::function<std::vector<std::pair<Point, Point>> (size_t)> get_orientations = [&](size_t cluster_index)->std::vector<std::pair<Point, Point>> //How to get the possible orientations of a cluster.
|
||||
{
|
||||
int best = -1;
|
||||
float bestDist = std::numeric_limits<float>::infinity();
|
||||
|
||||
for(unsigned int i_close_line_polygon : line_bucket_grid.findNearbyObjects(prev_point)) /// check if single-line-polygon is close to last point
|
||||
std::vector<std::pair<Point, Point>> result;
|
||||
const size_t first_line_index = line_clusters[cluster_index][0]; //The first line in the current cluster.
|
||||
const size_t last_line_index = line_clusters[cluster_index].back(); //The last line in the current cluster.
|
||||
const PolygonRef first_line = lines[first_line_index];
|
||||
const PolygonRef last_line = lines[last_line_index];
|
||||
const Point start_normal = first_line[polyStart[first_line_index]]; //Start of the path, not mirrored.
|
||||
const Point end_normal = last_line[(polyStart[last_line_index] + last_line.size() - 1) % last_line.size()]; //End of the path, not mirrored.
|
||||
result.push_back(std::pair<Point, Point>(start_normal, end_normal));
|
||||
result.push_back(std::pair<Point, Point>(end_normal, start_normal)); //Can also insert in reverse!
|
||||
if (line_clusters[cluster_index].size() > 1u) //If the cluster has one line, mirroring the line is equal to reversing the path. Otherwise, we must also include mirrored options.
|
||||
{
|
||||
if (picked[i_close_line_polygon] || polygons[i_close_line_polygon].size() < 1)
|
||||
continue;
|
||||
|
||||
|
||||
checkIfLineIsBest(i_close_line_polygon, best, bestDist, prev_point, incommingPerpundicularNormal);
|
||||
|
||||
const Point start_mirrored = first_line[(polyStart[first_line_index] + first_line.size() - 1) % first_line.size()]; //Start of the path, mirrored.
|
||||
const Point end_mirrored = first_line[(polyStart[first_line_index] + first_line.size() - 1) % first_line.size()]; //End of the path, mirrored.
|
||||
result.push_back(std::pair<Point, Point>(start_mirrored, end_mirrored));
|
||||
result.push_back(std::pair<Point, Point>(end_mirrored, start_mirrored));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
TravellingSalesman<size_t> tspsolver(get_orientations); //Solves the macro TSP problem of ordering the clusters.
|
||||
std::vector<size_t> cluster_orientations;
|
||||
std::vector<size_t> unoptimised(line_clusters.size());
|
||||
std::iota(unoptimised.begin(), unoptimised.end(), 0);
|
||||
std::vector<size_t> optimised = tspsolver.findPath(unoptimised, cluster_orientations, &startPoint); //Approximate the shortest path with the TSP solver.
|
||||
|
||||
if (best == -1) /// if single-line-polygon hasn't been found yet
|
||||
//Actually put the paths in their correct order for the output.
|
||||
polyOrder.reserve(lines.size());
|
||||
for (size_t cluster_index = 0; cluster_index < optimised.size(); cluster_index++)
|
||||
{
|
||||
Cluster cluster = line_clusters[optimised[cluster_index]];
|
||||
//Determine in what orientation we should place the cluster depending on cluster_orientations[cluster_index].
|
||||
size_t orientation = cluster_orientations[cluster_index];
|
||||
if (cluster.size() == 1) //Singleton clusters have only 2 possible orientations.
|
||||
{
|
||||
for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++)
|
||||
polyOrder.push_back(static_cast<int>(cluster[0]));
|
||||
if (orientation >= 1)
|
||||
{
|
||||
if (picked[i_polygon] || polygons[i_polygon].size() < 1) /// skip single-point-polygons
|
||||
continue;
|
||||
assert(polygons[i_polygon].size() == 2);
|
||||
|
||||
checkIfLineIsBest(i_polygon, best, bestDist, prev_point, incommingPerpundicularNormal);
|
||||
|
||||
polyStart[cluster[0]] = 1 - polyStart[cluster[0]];
|
||||
}
|
||||
}
|
||||
|
||||
if (best > -1) /// should always be true; we should have been able to identify the best next polygon
|
||||
else //Larger clusters have 4 possible orientations.
|
||||
{
|
||||
assert(polygons[best].size() == 2);
|
||||
|
||||
int endIdx = polyStart[best] * -1 + 1; /// 1 -> 0 , 0 -> 1
|
||||
prev_point = polygons[best][endIdx];
|
||||
incommingPerpundicularNormal = crossZ(normal(polygons[best][endIdx] - polygons[best][polyStart[best]], 1000));
|
||||
|
||||
picked[best] = true;
|
||||
polyOrder.push_back(best);
|
||||
}
|
||||
else
|
||||
logError("Failed to find next closest line.\n");
|
||||
}
|
||||
|
||||
prev_point = startPoint;
|
||||
for(unsigned int n=0; n<polyOrder.size(); n++) /// decide final starting points in each polygon
|
||||
{
|
||||
int nr = polyOrder[n];
|
||||
PolygonRef poly = polygons[nr];
|
||||
int best = -1;
|
||||
float bestDist = std::numeric_limits<float>::infinity();
|
||||
bool orientation = poly.orientation();
|
||||
for(unsigned int i=0;i<poly.size(); i++)
|
||||
{
|
||||
float dist = vSize2f(polygons[nr][i] - prev_point);
|
||||
Point n0 = normal(poly[(i+poly.size()-1)%poly.size()] - poly[i], 2000);
|
||||
Point n1 = normal(poly[i] - poly[(i + 1) % poly.size()], 2000);
|
||||
float dot_score = dot(n0, n1) - dot(crossZ(n0), n1);
|
||||
if (orientation)
|
||||
dot_score = -dot_score;
|
||||
if (dist + dot_score < bestDist)
|
||||
if ((orientation & 1) == 0) //Not reversed.
|
||||
{
|
||||
best = i;
|
||||
bestDist = dist + dot_score;
|
||||
for (size_t polygon_index = 0; polygon_index < cluster.size(); polygon_index++)
|
||||
{
|
||||
polyOrder.push_back(static_cast<int>(cluster[polygon_index]));
|
||||
}
|
||||
}
|
||||
else //Reversed.
|
||||
{
|
||||
for (size_t polygon_index = cluster.size(); polygon_index-- > 0; ) //Insert the lines in backward direction.
|
||||
{
|
||||
polyOrder.push_back(static_cast<int>(cluster[polygon_index]));
|
||||
}
|
||||
}
|
||||
if (orientation >= 2u) //Mirrored.
|
||||
{
|
||||
for (size_t polygon_index : cluster) //Mirror each line in the cluster.
|
||||
{
|
||||
polyStart[polygon_index] = 1 - polyStart[polygon_index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
polyStart[nr] = best;
|
||||
assert(poly.size() == 2);
|
||||
prev_point = poly[best *-1 + 1]; /// 1 -> 0 , 0 -> 1
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
inline void LineOrderOptimizer::checkIfLineIsBest(unsigned int i_line_polygon, int& best, float& bestDist, Point& prev_point, Point& incommingPerpundicularNormal)
|
||||
std::vector<std::vector<size_t>> LineOrderOptimizer::cluster()
|
||||
{
|
||||
{ /// check distance to first point on line (0)
|
||||
float dist = vSize2f(polygons[i_line_polygon][0] - prev_point);
|
||||
dist += abs(dot(incommingPerpundicularNormal, normal(polygons[i_line_polygon][1] - polygons[i_line_polygon][0], 1000))) * 0.0001f; /// penalize sharp corners
|
||||
if (dist < bestDist)
|
||||
polyStart.resize(lines.size()); //Polystart should always contain an entry for all polygons.
|
||||
BucketGrid2D<size_t> grid(cluster_grid_size);
|
||||
for (size_t polygon_index = 0; polygon_index < lines.size(); polygon_index++) //First put every endpoint of all lines in the grid.
|
||||
{
|
||||
grid.insert(lines[polygon_index][0], polygon_index);
|
||||
grid.insert(lines[polygon_index].back(), polygon_index);
|
||||
}
|
||||
|
||||
std::vector<Cluster> clusters;
|
||||
bool picked[lines.size()]; //For each polygon, whether it is already in a cluster.
|
||||
memset(picked, 0, lines.size()); //Initialise to false.
|
||||
for (size_t polygon_index = 0; polygon_index < lines.size(); polygon_index++) //Find clusters with nearest neighbour-ish search.
|
||||
{
|
||||
if (picked[polygon_index]) //Already in a cluster.
|
||||
{
|
||||
best = i_line_polygon;
|
||||
bestDist = dist;
|
||||
polyStart[i_line_polygon] = 0;
|
||||
continue;
|
||||
}
|
||||
clusters.push_back(Cluster()); //Make a new cluster for this line.
|
||||
clusters.back().push_back(polygon_index);
|
||||
polyStart[polygon_index] = 0; //Choose one possible mirroring of the lines. This determines the start point of each line. The mirror of a cluster is also checked by the TSP solver (but the combination of directions of each individual line is determined here below).
|
||||
picked[polygon_index] = true;
|
||||
size_t current_polygon = polygon_index; //We'll do a walk to the nearest valid neighbour. A neighbour is valid if it is not picked yet and if both its endpoints are near.
|
||||
size_t best_polygon = current_polygon;
|
||||
while (best_polygon != static_cast<size_t>(-1)) //Keep going until there is no valid neighbour.
|
||||
{
|
||||
const PolygonRef current_line = lines[current_polygon];
|
||||
Point current_start = current_line[polyStart[current_polygon]]; //Start and end point of the current polygon. These are used to find the distance to the next polygon.
|
||||
Point current_end = current_line[(polyStart[current_polygon] + current_line.size() - 1) % current_line.size()];
|
||||
best_polygon = static_cast<size_t>(-1);
|
||||
unsigned long long best_distance = cluster_grid_size * cluster_grid_size + 1; //grid_size squared since vSize2 gives squared distance.
|
||||
size_t best_start;
|
||||
for (size_t neighbour : grid.findNearbyObjects(current_line[0]))
|
||||
{
|
||||
if (picked[neighbour]) //Don't use neighbours that are already in another cluster.
|
||||
{
|
||||
continue;
|
||||
}
|
||||
tryCluster(neighbour, current_start, current_end, &best_polygon, &best_distance, &best_start);
|
||||
}
|
||||
if (best_polygon != static_cast<size_t>(-1)) //We found one.
|
||||
{
|
||||
current_polygon = best_polygon;
|
||||
clusters.back().push_back(best_polygon);
|
||||
polyStart[best_polygon] = best_start;
|
||||
picked[best_polygon] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
{ /// check distance to second point on line (1)
|
||||
float dist = vSize2f(polygons[i_line_polygon][1] - prev_point);
|
||||
dist += abs(dot(incommingPerpundicularNormal, normal(polygons[i_line_polygon][0] - polygons[i_line_polygon][1], 1000) )) * 0.0001f; /// penalize sharp corners
|
||||
if (dist < bestDist)
|
||||
return clusters;
|
||||
}
|
||||
|
||||
void LineOrderOptimizer::tryCluster(size_t line, const Point current_start, const Point current_end, size_t* best_polygon, unsigned long long* best_distance, size_t* best_start)
|
||||
{
|
||||
//Input checking.
|
||||
if (!best_polygon || !best_distance || !best_start) //Output parameter missing.
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const unsigned long long distance_start_start = vSize2(current_start - lines[line][0]);
|
||||
const unsigned long long distance_start_end = vSize2(current_start - lines[line].back());
|
||||
const unsigned long long distance_end_start = vSize2(current_end - lines[line][0]);
|
||||
const unsigned long long distance_end_end = vSize2(current_end - lines[line].back());
|
||||
if (distance_start_start < cluster_grid_size * cluster_grid_size && distance_end_end < cluster_grid_size * cluster_grid_size) //Two lines are alongside each other.
|
||||
{
|
||||
if (distance_end_end < *best_distance) //Best neighbour and orientation so far.
|
||||
{
|
||||
best = i_line_polygon;
|
||||
bestDist = dist;
|
||||
polyStart[i_line_polygon] = 1;
|
||||
*best_polygon = line;
|
||||
*best_distance = distance_end_end;
|
||||
*best_start = lines[line].size() - 1;
|
||||
}
|
||||
}
|
||||
if (distance_start_end < cluster_grid_size * cluster_grid_size && distance_end_start < cluster_grid_size * cluster_grid_size) //Two lines are alongside each other, but in reverse direction.
|
||||
{
|
||||
if (distance_end_start < *best_distance)
|
||||
{
|
||||
*best_polygon = line;
|
||||
*best_distance = distance_end_start;
|
||||
*best_start = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,33 +56,89 @@ private:
|
||||
*/
|
||||
class LineOrderOptimizer
|
||||
{
|
||||
typedef typename std::vector<size_t> Cluster; //To make it more clear what a cluster is.
|
||||
|
||||
public:
|
||||
/*!
|
||||
* \brief The size of the grid cells used to cluster lines.
|
||||
*
|
||||
* Increase this value to make the optimisation algorithm fall back to
|
||||
* nearest neighbour more often. Reduce this value to make the optimisation
|
||||
* algorithm use random insertion on smaller pieces of the input.
|
||||
*/
|
||||
const unsigned long long cluster_grid_size;
|
||||
|
||||
Point startPoint; //!< The location of the nozzle before starting to print the current layer
|
||||
std::vector<PolygonRef> polygons; //!< the parts of the layer (in arbitrary order)
|
||||
std::vector<PolygonRef> lines; //!< the parts of the layer (in arbitrary order)
|
||||
std::vector<int> polyStart; //!< polygons[i][polyStart[i]] = point of polygon i which is to be the starting point in printing the polygon
|
||||
std::vector<int> polyOrder; //!< the optimized order as indices in #polygons
|
||||
|
||||
LineOrderOptimizer(Point startPoint)
|
||||
{
|
||||
this->startPoint = startPoint;
|
||||
}
|
||||
/*!
|
||||
* \brief Constructs the line order optimiser with the specified settings.
|
||||
*
|
||||
* \param start_point The starting point from where the paths generated by
|
||||
* this optimiser must start.
|
||||
* \param cluster_grid_size The size of the grid cells used to cluster
|
||||
* lines. Make this bigger and the optimiser will fall back to nearest
|
||||
* neighbour search more often. Make this smaller and the optimiser will
|
||||
* use random insertion more often.
|
||||
*/
|
||||
LineOrderOptimizer(const Point& start_point, unsigned long long cluster_grid_size);
|
||||
|
||||
void addPolygon(PolygonRef polygon)
|
||||
{
|
||||
this->polygons.push_back(polygon);
|
||||
this->lines.push_back(polygon);
|
||||
}
|
||||
|
||||
void addPolygons(Polygons& polygons)
|
||||
{
|
||||
for(unsigned int i=0;i<polygons.size(); i++)
|
||||
this->polygons.push_back(polygons[i]);
|
||||
for(unsigned int i = 0; i < polygons.size(); i++)
|
||||
{
|
||||
this->lines.push_back(polygons[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void optimize(); //!< sets #polyStart and #polyOrder
|
||||
|
||||
private:
|
||||
void checkIfLineIsBest(unsigned int i_line_polygon, int& best, float& bestDist, Point& prev_point, Point& incommingPerpundicularNormal);
|
||||
/*!
|
||||
* \brief Clusters the polygons in groups such that the start and end of the
|
||||
* polygons in each group are close together.
|
||||
*
|
||||
* This performs a simple nearest-neighbour traversal through all lines. An
|
||||
* arbitrary line is chosen as starting point for a cluster, and iteratively
|
||||
* the nearest neighbouring line will get added to that cluster. A line is
|
||||
* only neighbouring if both of its endpoints are nearby the endpoints of
|
||||
* the previous line. This way you get logical groups of lines that should
|
||||
* always be in sequence, with fairly low computational cost.
|
||||
*
|
||||
* \return Clusters of polygons, where each cluster is represented with a
|
||||
* vector of indices pointing to positions in \link polygons.
|
||||
*/
|
||||
std::vector<Cluster> cluster();
|
||||
|
||||
/*!
|
||||
* \brief Tries to insert the specified line in the currently processed
|
||||
* cluster.
|
||||
*
|
||||
* The distance of the line to the current line (represented by
|
||||
* \p current_start and \p current_end) is measured, and compared to the
|
||||
* distance of the best candidate to insert so far. If it is shorter, then
|
||||
* the new line is stored via \p best_polygon as the best line to add to the
|
||||
* cluster.
|
||||
*
|
||||
* \param line The index of the new line to try to add to the cluster.
|
||||
* \param current_start The start point of the previous line that was added.
|
||||
* \param current_end The end point of the previous line that was added.
|
||||
* \param best_polygon The line that is the best candidate to insert so far.
|
||||
* This parameter may be changed by this function if the new line is better.
|
||||
* \param best_distance The distance of the best candidate to insert so far.
|
||||
* This parameter may be changed by this function if the new line is better.
|
||||
* \param best_start The starting vertex index of the best candidate to
|
||||
* insert so far. This parameter may be changed by this function if the new
|
||||
* line is better.
|
||||
*/
|
||||
void tryCluster(size_t line, const Point current_start, const Point current_end, size_t* best_polygon, unsigned long long* best_distance, size_t* best_start);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
@@ -30,6 +30,43 @@ public:
|
||||
{
|
||||
calculate(polys);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Creates an axis aligned bounding box around the specified polygon.
|
||||
*
|
||||
* The bounding box will fit snugly around the polygon.
|
||||
*
|
||||
* \param polygon The polygon to create a bounding box for.
|
||||
*/
|
||||
AABB(PolygonRef& polygon)
|
||||
: min(POINT_MAX, POINT_MAX), max(POINT_MIN, POINT_MIN)
|
||||
{
|
||||
for(Point point : polygon)
|
||||
{
|
||||
include(point);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Creates an axis aligned bounding box around the specified set of
|
||||
* polygons.
|
||||
*
|
||||
* The bounding box will fit around all polygons.
|
||||
*
|
||||
* \param polygons A vector of polygons that should be included in the
|
||||
* bounding box.
|
||||
*/
|
||||
AABB(std::vector<PolygonRef>& polygons)
|
||||
: min(POINT_MAX, POINT_MAX), max(POINT_MIN, POINT_MIN)
|
||||
{
|
||||
for(PolygonRef polygon : polygons)
|
||||
{
|
||||
for(Point point : polygon)
|
||||
{
|
||||
include(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void calculate(Polygons& polys)
|
||||
{
|
||||
|
||||
@@ -205,11 +205,6 @@ public:
|
||||
// if (! emplaced.second)
|
||||
// logError("Error! BucketGrid2D couldn't insert object!");
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+39
-1
@@ -140,6 +140,44 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Draws the specified list of polygons on the canvas.
|
||||
*
|
||||
* Each polygon is drawn in sequence.
|
||||
*
|
||||
* \param polygons A vector of polygons to draw.
|
||||
* \param colour The colour of the fill of the polygons.
|
||||
* \param outline_colour The colour of the outline of the polygons.
|
||||
*/
|
||||
void writeAreas(const std::vector<PolygonRef>& polygons, Color colour = Color::GRAY, Color outline_colour = Color::BLACK)
|
||||
{
|
||||
for(PolygonRef polygon : polygons)
|
||||
{
|
||||
writeAreas(polygon,colour,outline_colour);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Draws the specified polygon on the canvas.
|
||||
*
|
||||
* The polygon is always closed and has an outline and fill colour that you
|
||||
* can specify.
|
||||
*
|
||||
* \param polygon The polygon to draw on the canvas.
|
||||
* \param colour The colour of the polygon.
|
||||
* \param outline_colour The colour of the outline of the polygon.
|
||||
*/
|
||||
void writeAreas(PolygonRef polygon, Color colour = Color::GRAY, Color outline_colour = Color::BLACK)
|
||||
{
|
||||
fprintf(out, "<polygon points=\"");
|
||||
for(Point point : polygon)
|
||||
{
|
||||
Point transformed = transform(point);
|
||||
fprintf(out, "%lli,%lli ", transformed.X, transformed.Y);
|
||||
}
|
||||
fprintf(out, "\" fill=\"%s\" stroke=\"%s\" stroke-width=\"4\" />\n", toString(colour).c_str(),toString(outline_colour).c_str());
|
||||
}
|
||||
|
||||
void writeAreas(std::vector<Point> polygon,Color color = Color::GRAY,Color outline_color = Color::BLACK)
|
||||
{
|
||||
fprintf(out,"<polygon fill=\"%s\" stroke=\"%s\" stroke-width=\"1\" points=\"",toString(color).c_str(),toString(outline_color).c_str()); //The beginning of the polygon tag.
|
||||
@@ -209,7 +247,7 @@ public:
|
||||
{
|
||||
Point fa = transform(a);
|
||||
Point fb = transform(b);
|
||||
fprintf(out, "<line x1=\"%lli\" y1=\"%lli\" x2=\"%lli\" y2=\"%lli\" style=\"stroke:%s;stroke-width:1\" />\n", fa.X, fa.Y, fb.X, fb.Y, toString(color).c_str());
|
||||
fprintf(out, "<line x1=\"%lli\" y1=\"%lli\" x2=\"%lli\" y2=\"%lli\" stroke=\"%s\" />\n", fa.X, fa.Y, fb.X, fb.Y, toString(color).c_str());
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
//Copyright (c) 2015 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef TRAVELLINGSALESMAN_H
|
||||
#define TRAVELLINGSALESMAN_H
|
||||
|
||||
#include "intpoint.h" //For the Point type.
|
||||
|
||||
#include <algorithm> //For std::shuffle to randomly shuffle the array.
|
||||
#include <list> //For std::list, which is used to insert stuff in the result in constant time.
|
||||
#include <random> //For the RNG. Note: CuraEngine should be deterministic! Always use a FIXED SEED!
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* \brief Struct that holds all information of one element of the path.
|
||||
*
|
||||
* It needs to know the actual element in the path, but also where the element's
|
||||
* own path starts and ends.
|
||||
*
|
||||
* \tparam E The type of element data stored in this waypoint. Note that these
|
||||
* are copied into the waypoint on construction and out of the waypoint right
|
||||
* before deletion.
|
||||
*/
|
||||
template<class E> struct Waypoint
|
||||
{
|
||||
/*!
|
||||
* \brief Constructs a new waypoint with the specified possible start and
|
||||
* end points and the specified element.
|
||||
*
|
||||
* \param orientations The possible start and end points of the waypoints
|
||||
* for each orientation the element could be placed in.
|
||||
* \param element The element that's to be bound to this waypoint.
|
||||
*/
|
||||
Waypoint(std::vector<std::pair<Point, Point>> orientations, E element) : orientation_indices(orientations), element(element)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief The possible orientations in which the waypoint could be placed in
|
||||
* the path.
|
||||
*
|
||||
* This defines in what direction or way the element in this waypoint should
|
||||
* be traversed in the final path. The Travelling Salesman solution only
|
||||
* requires the start and end point of this traversal in order to piece the
|
||||
* waypoint into the path.
|
||||
*/
|
||||
std::vector<std::pair<Point, Point>> orientation_indices;
|
||||
|
||||
/*!
|
||||
* \brief The actual element this waypoint holds.
|
||||
*/
|
||||
E element;
|
||||
|
||||
/*!
|
||||
* \brief The optimal orientation of this waypoint in the final path.
|
||||
*
|
||||
* This is computed during the <em>TravellingSalesman::findPath</em>
|
||||
* function. It indicates an index in \link orientations that provides the
|
||||
* shortest path.
|
||||
*/
|
||||
size_t best_orientation_index;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief A class of functions implementing solutions of Travelling Salesman.
|
||||
*
|
||||
* Various variants can be implemented here, such as the shortest path past a
|
||||
* set of points or of lines.
|
||||
*
|
||||
* \tparam E The type of elements that must be ordered by this instance of
|
||||
* <em>TravellingSalesman</em>. Note that each element is copied twice in a run
|
||||
* of \link findPath, so if this type is difficult to copy, provide pointers to
|
||||
* the elements instead.
|
||||
*/
|
||||
template<class E> class TravellingSalesman
|
||||
{
|
||||
typedef typename std::list<Waypoint<E>*>::iterator WaypointListIterator; //To help the compiler with templates in templates.
|
||||
|
||||
public:
|
||||
/*!
|
||||
* \brief Constructs an instance of Travelling Salesman.
|
||||
*
|
||||
* \param get_orientations A function to get possible orientations for
|
||||
* elements in the path. Each orientation defines a possible way that the
|
||||
* element could be inserted in the path. To do that it must provide a
|
||||
* start point and an end point for each orientation.
|
||||
*/
|
||||
TravellingSalesman(std::function<std::vector<std::pair<Point, Point>>(E)> get_orientations) : get_orientations(get_orientations)
|
||||
{
|
||||
//Do nothing. All parameters are already copied to fields.
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Destroys the instance, releasing all memory used by it.
|
||||
*/
|
||||
virtual ~TravellingSalesman();
|
||||
|
||||
/*!
|
||||
* \brief Computes a short path along all specified elements.
|
||||
*
|
||||
* A short path is computed that includes all specified elements, but not
|
||||
* always the shortest path. Finding the shortest path is known as the
|
||||
* Travelling Salesman Problem, and this is an NP-complete problem. The
|
||||
* solution returned by this function is just a heuristic approximation.
|
||||
*
|
||||
* The approximation will try to insert random elements at the best location
|
||||
* in the current path, thereby incrementally constructing a good path. Each
|
||||
* element can be inserted in multiple possible orientations, defined by the
|
||||
* <em>get_orientations</em> function.
|
||||
*
|
||||
* \param elements The elements past which the path must run.
|
||||
* \param element_orientations Output parameter to indicate for each element
|
||||
* in which orientation it must be placed to minimise the travel time. The
|
||||
* resulting integers correspond to the index of the options given by the
|
||||
* <em>get_orientations</em> constructor parameter.
|
||||
* \param starting_point A fixed starting point of the path, if any. If this
|
||||
* is <em>nullptr</em>, the path may start at the start or end point of any
|
||||
* element, depending on which the heuristic deems shortest.
|
||||
* \return A vector of elements, in an order that would make a short path.
|
||||
*/
|
||||
std::vector<E> findPath(std::vector<E> elements, std::vector<size_t>& element_orientations, Point* starting_point = nullptr);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* \brief Function to use to get the possible orientations of an element.
|
||||
*
|
||||
* Each orientation has a start point and an end point, in that order.
|
||||
*/
|
||||
std::function<std::vector<std::pair<Point, Point>>(E)> get_orientations;
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief Puts all elements in waypoints, caching their endpoints.
|
||||
*
|
||||
* The <em>get_start</em> and <em>get_end</em> functions are called on each
|
||||
* element. The results are stored along with the element in a waypoint and
|
||||
* a pointer to the waypoint is added to the resulting vector.
|
||||
*
|
||||
* Note that this creates a waypoint for each element on the heap. These
|
||||
* waypoints need to be deleted when the algorithm is done. This is why this
|
||||
* function must always stay private.
|
||||
*
|
||||
* \param elements The elements to put into waypoints.
|
||||
* \return A vector of waypoints with the specified elements in them.
|
||||
*/
|
||||
std::vector<Waypoint<E>*> fillWaypoints(std::vector<E> elements);
|
||||
|
||||
/*!
|
||||
* \brief Tries to insert a waypoint at the first position in the list.
|
||||
*
|
||||
* It will try to insert the waypoint at all possible orientations. If it
|
||||
* finds an orientation with an insertion distance that is less than the
|
||||
* best distance, it will update the \p best_distance, \p best_orientation
|
||||
* and \p best_insert variables with the parameters of this insertion.
|
||||
*
|
||||
* \param waypoint The waypoint to insert in the list.
|
||||
* \param starting_point The starting point to insert the waypoint after, if
|
||||
* any.
|
||||
* \param first_element The first element of the list to insert the waypoint
|
||||
* before.
|
||||
* \param best_distance The current best distance of inserting the waypoint.
|
||||
* This parameter is changed if a better insertion is found.
|
||||
* \param best_orientation The current best orientation of inserting the
|
||||
* waypoint. This parameter is changed if a better insertion is found.
|
||||
* \param best_insert The current best place to insert the waypoint. This
|
||||
* parameter is changed if a better insertion is found.
|
||||
*/
|
||||
inline void tryInsertFirst(Waypoint<E>* waypoint, Point* starting_point, WaypointListIterator first_element, int64_t* best_distance, size_t* best_orientation_index, WaypointListIterator* best_insert);
|
||||
|
||||
/*!
|
||||
* \brief Tries to insert a waypoint at the last position in the list.
|
||||
*
|
||||
* It will try to insert the waypoint at all possible orientations. If it
|
||||
* finds an orientation with an insertion distance that is less than the
|
||||
* best distance, it will update the \p best_distance, \p best_orientation
|
||||
* and \p best_insert variables with the parameters of this insertion.
|
||||
*
|
||||
* \param waypoint The waypoint to insert in the list.
|
||||
* \param last_element The last element of the list to insert the waypoint
|
||||
* after.
|
||||
* \param The WaypointListIterator to use to indicate that it should insert
|
||||
* the new waypoint after the last element of the list, if it should find
|
||||
* that as a new best position.
|
||||
* \param best_distance The current best distance of inserting the waypoint.
|
||||
* This parameter is changed if a better insertion is found.
|
||||
* \param best_orientation The current best orientation of inserting the
|
||||
* waypoint. This parameter is changed if a better insertion is found.
|
||||
* \param best_insert The current best place to insert the waypoint. This
|
||||
* parameter is changed if a better insertion is found.
|
||||
*/
|
||||
inline void tryInsertLast(Waypoint<E>* waypoint, WaypointListIterator last_element, WaypointListIterator after_insert, int64_t* best_distance, size_t* best_orientation_index, WaypointListIterator* best_insert);
|
||||
|
||||
/*!
|
||||
* \brief Tries to insert a waypoint at a position in the centre of the
|
||||
* list.
|
||||
*
|
||||
* It will try to insert the waypoint at all possible orientations. If it
|
||||
* finds an orientation with an insertion distance that is less than the
|
||||
* best distance, it will update the \p best_distance, \p best_orientation
|
||||
* and \p best_insert variables with the parameters of this insertion.
|
||||
*
|
||||
* \param waypoint The waypoint to insert in the list.
|
||||
* \param before_insert The element after which to insert the new waypoint.
|
||||
* \param after_insert The element before which to insert the new waypoint.
|
||||
* \param best_distance The current best distance of inserting the waypoint.
|
||||
* This parameter is changed if a better insertion is found.
|
||||
* \param best_orientation The current best orientation of inserting the
|
||||
* waypoint. This parameter is changed if a better insertion is found.
|
||||
* \param best_insert The current best place to insert the waypoint. This
|
||||
* parameter is changed if a better insertion is found.
|
||||
*/
|
||||
inline void tryInsertMiddle(Waypoint<E>* waypoint, WaypointListIterator before_insert, WaypointListIterator after_insert, int64_t* best_distance, size_t* best_orientation_index, WaypointListIterator* best_insert);
|
||||
};
|
||||
|
||||
////BELOW FOLLOWS THE IMPLEMENTATION.////
|
||||
|
||||
template<class E> TravellingSalesman<E>::~TravellingSalesman()
|
||||
{
|
||||
//Do nothing.
|
||||
}
|
||||
|
||||
template<class E> std::vector<E> TravellingSalesman<E>::findPath(std::vector<E> elements, std::vector<size_t>& element_orientations, Point* starting_point)
|
||||
{
|
||||
/* This approximation algorithm of TSP implements the random insertion
|
||||
* heuristic. Random insertion has in tests proven to be almost as good as
|
||||
* Christofides (111% of the optimal path length rather than 110% on random
|
||||
* graphs) but is much faster to compute. */
|
||||
if (elements.empty())
|
||||
{
|
||||
return std::vector<E>();
|
||||
}
|
||||
|
||||
auto rng = std::default_random_engine(0xDECAFF); //Always use a fixed seed! Wouldn't want it to be nondeterministic.
|
||||
std::vector<Waypoint<E>*> shuffled = fillWaypoints(elements);
|
||||
std::shuffle(shuffled.begin(), shuffled.end(), rng); //"Randomly" shuffles the waypoints.
|
||||
|
||||
std::list<Waypoint<E>*> result;
|
||||
|
||||
if (!starting_point) //If there is no starting point, just insert the initial element.
|
||||
{
|
||||
shuffled[0]->best_orientation_index = 0; //Choose an arbitrary orientation for the first element.
|
||||
result.push_back(shuffled[0]); //Due to the check at the start, we know that shuffled always contains at least 1 element.
|
||||
}
|
||||
else //If there is a starting point, insert the initial element after it.
|
||||
{
|
||||
int64_t best_distance = std::numeric_limits<int64_t>::max(); //Change in travel distance to insert the waypoint. Minimise this distance by varying the orientation.
|
||||
size_t best_orientation_index; //In what orientation to insert the element.
|
||||
for (size_t orientation_index = 0; orientation_index < shuffled[0]->orientation_indices.size(); orientation_index++)
|
||||
{
|
||||
int64_t distance = vSize(*starting_point - shuffled[0]->orientation_indices[orientation_index].first); //Distance from the starting point to the start point of this element.
|
||||
if (distance < best_distance)
|
||||
{
|
||||
best_distance = distance;
|
||||
best_orientation_index = orientation_index;
|
||||
}
|
||||
}
|
||||
shuffled[0]->best_orientation_index = best_orientation_index;
|
||||
result.push_back(shuffled[0]);
|
||||
}
|
||||
|
||||
//Now randomly insert the rest of the points.
|
||||
for (size_t next_to_insert = 1; next_to_insert < shuffled.size(); next_to_insert++)
|
||||
{
|
||||
Waypoint<E>* waypoint = shuffled[next_to_insert];
|
||||
int64_t best_distance = std::numeric_limits<int64_t>::max(); //Change in travel distance to insert the waypoint. Minimise this distance by varying the insertion point and orientation.
|
||||
WaypointListIterator best_insert; //Where to insert the element. It will be inserted before this element. If it's nullptr, insert at the very front.
|
||||
size_t best_orientation_index; //In what orientation to insert the element.
|
||||
|
||||
//First try inserting before the first element.
|
||||
tryInsertFirst(waypoint, starting_point, result.begin(), &best_distance, &best_orientation_index, &best_insert);
|
||||
|
||||
//Try inserting at the other positions.
|
||||
for (WaypointListIterator before_insert = result.begin(); before_insert != result.end(); before_insert++)
|
||||
{
|
||||
WaypointListIterator after_insert = before_insert;
|
||||
after_insert++; //Get the element after the current element.
|
||||
if (after_insert == result.end()) //There is no next element. We're inserting at the end of the path.
|
||||
{
|
||||
tryInsertLast(waypoint, before_insert, after_insert, &best_distance, &best_orientation_index, &best_insert);
|
||||
}
|
||||
else //There is a next element. We're inserting somewhere in the middle.
|
||||
{
|
||||
tryInsertMiddle(waypoint, before_insert, after_insert, &best_distance, &best_orientation_index, &best_insert);
|
||||
}
|
||||
}
|
||||
|
||||
//Actually insert the waypoint at the best position we found.
|
||||
waypoint->best_orientation_index = best_orientation_index;
|
||||
if (best_insert == result.end()) //We must insert at the very end.
|
||||
{
|
||||
result.push_back(waypoint);
|
||||
}
|
||||
else //We must insert before best_insert.
|
||||
{
|
||||
result.insert(best_insert, waypoint);
|
||||
}
|
||||
}
|
||||
|
||||
//Now that we've inserted all points, linearise them into one vector.
|
||||
std::vector<E> result_vector;
|
||||
result_vector.reserve(elements.size());
|
||||
element_orientations.clear(); //Prepare the element_orientations vector for storing in which orientation each element should be placed.
|
||||
element_orientations.reserve(elements.size());
|
||||
for (Waypoint<E>* waypoint : result)
|
||||
{
|
||||
result_vector.push_back(waypoint->element);
|
||||
element_orientations.push_back(waypoint->best_orientation_index);
|
||||
delete waypoint; //Free the waypoint from memory. It is no longer needed from here on, since we copied the element in it to the output.
|
||||
}
|
||||
return result_vector;
|
||||
}
|
||||
|
||||
template<class E> std::vector<Waypoint<E>*> TravellingSalesman<E>::fillWaypoints(std::vector<E> elements)
|
||||
{
|
||||
std::vector<Waypoint<E>*> result;
|
||||
result.reserve(elements.size());
|
||||
|
||||
for (E element : elements) //Put every element in a waypoint.
|
||||
{
|
||||
Waypoint<E>* waypoint = new Waypoint<E>(get_orientations(element), element); //Yes, this must be deleted when the algorithm is done!
|
||||
result.push_back(waypoint);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template<class E> inline void TravellingSalesman<E>::tryInsertFirst(Waypoint<E>* waypoint, Point* starting_point, WaypointListIterator first_element, int64_t* best_distance, size_t* best_orientation_index, WaypointListIterator* best_insert)
|
||||
{
|
||||
if (!waypoint or !best_distance or !best_orientation_index or !best_insert) //Input checking.
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t orientation = 0; orientation < waypoint->orientation_indices.size(); orientation++)
|
||||
{
|
||||
int64_t before_distance = 0;
|
||||
if (starting_point) //If there is a starting point, we're inserting between the first point and the starting point.
|
||||
{
|
||||
before_distance = vSize(*starting_point - waypoint->orientation_indices[orientation].first);
|
||||
}
|
||||
const Point end_of_this = waypoint->orientation_indices[orientation].second;
|
||||
const Point start_of_first = (*first_element)->orientation_indices[(*first_element)->best_orientation_index].first;
|
||||
const int64_t after_distance = vSize(end_of_this - start_of_first); //From the end of this element to the start of the first element.
|
||||
const int64_t distance = before_distance + after_distance;
|
||||
if (distance < *best_distance)
|
||||
{
|
||||
*best_distance = distance;
|
||||
*best_insert = first_element;
|
||||
*best_orientation_index = orientation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class E> inline void TravellingSalesman<E>::tryInsertLast(Waypoint<E>* waypoint, WaypointListIterator last_element, WaypointListIterator after_insert, int64_t* best_distance, size_t* best_orientation_index, WaypointListIterator* best_insert)
|
||||
{
|
||||
if (!waypoint or !best_distance or !best_orientation_index or !best_insert) //Input checking.
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t orientation = 0; orientation < waypoint->orientation_indices.size(); orientation++)
|
||||
{
|
||||
const Point end_of_last = (*last_element)->orientation_indices[(*last_element)->best_orientation_index].second;
|
||||
const Point start_of_this = waypoint->orientation_indices[orientation].first;
|
||||
const int64_t distance = vSize(end_of_last - start_of_this); //From the end of the last element to the start of this element.
|
||||
if (distance < *best_distance)
|
||||
{
|
||||
*best_distance = distance;
|
||||
*best_insert = after_insert;
|
||||
*best_orientation_index = orientation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class E> inline void TravellingSalesman<E>::tryInsertMiddle(Waypoint<E>* waypoint, WaypointListIterator before_insert, WaypointListIterator after_insert, int64_t* best_distance, size_t* best_orientation_index, WaypointListIterator* best_insert)
|
||||
{
|
||||
if (!waypoint or !best_distance or !best_orientation_index or !best_insert) //Input checking.
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t orientation = 0; orientation < waypoint->orientation_indices.size(); orientation++)
|
||||
{
|
||||
const Point end_of_before = (*before_insert)->orientation_indices[(*before_insert)->best_orientation_index].second;
|
||||
const Point start_of_after = (*after_insert)->orientation_indices[(*after_insert)->best_orientation_index].first;
|
||||
const Point start_of_this = waypoint->orientation_indices[orientation].first;
|
||||
const Point end_of_this = waypoint->orientation_indices[orientation].second;
|
||||
const int64_t removed_distance = vSize(end_of_before - start_of_after); //Distance of the original move that we'll remove.
|
||||
const int64_t before_distance = vSize(end_of_before - start_of_this); //From end of previous element to start of this element.
|
||||
const int64_t after_distance = vSize(end_of_this - start_of_after); //From end of this element to start of next element.
|
||||
const int64_t distance = before_distance + after_distance - removed_distance;
|
||||
if (distance < *best_distance)
|
||||
{
|
||||
*best_distance = distance;
|
||||
*best_insert = after_insert;
|
||||
*best_orientation_index = orientation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif /* TRAVELLINGSALESMAN_H */
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
//Copyright (c) 2015 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "TravellingSalesmanTest.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TravellingSalesmanTest);
|
||||
|
||||
void TravellingSalesmanTest::setUp()
|
||||
{
|
||||
tsp_points = new TravellingSalesman<Point>([&](Point point) -> std::vector<std::pair<Point, Point>> { return { std::make_pair(point, point) }; }); //For points, just return the point itself as both start and end points.
|
||||
tsp_paths = new TravellingSalesman<std::vector<Point>>( //For paths, return the first and last element of the path.
|
||||
[&](std::vector<Point> path) -> std::vector<std::pair<Point, Point>>
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
return { std::make_pair(Point(0, 0), Point(0, 0)) };
|
||||
}
|
||||
std::vector<std::pair<Point, Point>> result;
|
||||
result.push_back(std::make_pair(path[0], path.back()));
|
||||
result.push_back(std::make_pair(path.back(), path[0]));
|
||||
return result;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void TravellingSalesmanTest::tearDown()
|
||||
{
|
||||
delete tsp_points;
|
||||
delete tsp_paths;
|
||||
}
|
||||
|
||||
void TravellingSalesmanTest::twoPointsTest()
|
||||
{
|
||||
std::vector<Point> points;
|
||||
points.push_back(Point(0,0));
|
||||
points.push_back(Point(100,0));
|
||||
std :: vector<size_t> orientations;
|
||||
std :: vector<Point> result = tsp_points -> findPath(points, orientations);
|
||||
|
||||
bijective(points,result); //Assert that all points in the two vectors are equal.
|
||||
}
|
||||
|
||||
void TravellingSalesmanTest::fivePointsTest()
|
||||
{
|
||||
std::vector<Point> points;
|
||||
points.push_back(Point(-100,0));
|
||||
points.push_back(Point(0,-100));
|
||||
points.push_back(Point(0,100));
|
||||
points.push_back(Point(100,0));
|
||||
points.push_back(Point(0,0));
|
||||
|
||||
std :: vector<size_t> orientations;
|
||||
std :: vector<Point> result = tsp_points -> findPath(points, orientations);
|
||||
|
||||
bijective(points,result); //Assert that all points in the two vectors are equal.
|
||||
}
|
||||
|
||||
void TravellingSalesmanTest::bijective(std::vector<Point> first,std::vector<Point> second)
|
||||
{
|
||||
CPPUNIT_ASSERT_MESSAGE("The resulting path does not have the same length as the provided point set.",first.size() == second.size());
|
||||
|
||||
std::vector<bool> matched; //Indicates which of the elements of the second vector are already matched.
|
||||
matched.reserve(second.size());
|
||||
for(size_t i = 0;i < matched.size();i++) //Initialise all matched to false.
|
||||
{
|
||||
matched.push_back(false);
|
||||
}
|
||||
for(Point first_point : first)
|
||||
{
|
||||
for(size_t second_point_index = 0;second_point_index < second.size();second_point_index++)
|
||||
{
|
||||
if(first_point == second[second_point_index])
|
||||
{
|
||||
matched[second_point_index] = true;
|
||||
goto CONTINUE_FIRST;
|
||||
}
|
||||
}
|
||||
CPPUNIT_FAIL("A point in the original path was not included in the result.");
|
||||
|
||||
CONTINUE_FIRST: ; //Continue with the outer loop.
|
||||
}
|
||||
for(bool match : matched) //Check if there are any unmatched points left.
|
||||
{
|
||||
CPPUNIT_ASSERT_MESSAGE("A point in the result did not come from the original path.",match);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
//Copyright (c) 2015 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef TRAVELLINGSALESMANTEST_H
|
||||
#define TRAVELLINGSALESMANTEST_H
|
||||
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <../src/utils/TravellingSalesman.h>
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class TravellingSalesmanTest : public CppUnit::TestFixture
|
||||
{
|
||||
CPPUNIT_TEST_SUITE(TravellingSalesmanTest);
|
||||
CPPUNIT_TEST(twoPointsTest);
|
||||
CPPUNIT_TEST(fivePointsTest);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
/*!
|
||||
* \brief Sets up the test suite to prepare for testing.
|
||||
*
|
||||
* In this case, an instance of <em>TravellingSalesman</em> is produced with
|
||||
* pre-defined functions for getting the start and end points of a point and
|
||||
* another instance with paths.
|
||||
*/
|
||||
void setUp();
|
||||
|
||||
/*!
|
||||
* \brief Tears down the test suite when testing is done.
|
||||
*
|
||||
* The two instances of TravellingSalesman are deleted.
|
||||
*/
|
||||
void tearDown();
|
||||
|
||||
//The test cases.
|
||||
void twoPointsTest();
|
||||
void fivePointsTest();
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief A TSP solver that approximates the shortest path between a set of
|
||||
* points.
|
||||
*/
|
||||
TravellingSalesman<Point>* tsp_points;
|
||||
|
||||
/*!
|
||||
* \brief A TSP solver that approximates the shortest path that includes a
|
||||
* set of paths.
|
||||
*/
|
||||
TravellingSalesman<std::vector<Point>>* tsp_paths;
|
||||
|
||||
/*!
|
||||
* \brief Asserts that the two sets implied by the provided vectors are
|
||||
* bijective.
|
||||
*
|
||||
* They are bijective if and only if all points in the first vector are also
|
||||
* in the second vector and vice versa.
|
||||
*
|
||||
* \param first The first vector to check whether it is bijective with the
|
||||
* second vector.
|
||||
* \param second The second vector to check whether it is bijective with the
|
||||
* first vector.
|
||||
*/
|
||||
void bijective(std::vector<Point> first,std::vector<Point> second);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* TRAVELLINGSALESMANTEST_H */
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário