Comparar commits

...

93 Commits

Autor SHA1 Mensagem Data
Ghostkeeper 6c931e2935 Fix modulo by size of lines
All lines have size 2, which is why it was never a problem, but in theory if the polygon has a different line size it will now work correctly.

Contributes to issue CURA-329.
2016-02-03 12:55:19 +01:00
Ghostkeeper 295a5a9fc4 Factor out points of insert distance calculations
These points are factored out to intermediary variables to reduce width of the code at the cost of height.

Contributes to issue CURA-329.
2016-02-03 12:55:14 +01:00
Tim Kuipers cc8dadf571 fix: variable name shadowing in pathorderOptimizer (CURA-329) 2016-02-03 12:55:09 +01:00
Ghostkeeper 118e71c7af Cache lines for simpler expressions
Some of these expressions were deemed too complex to understand. In an effort to make them simpler, more sloc were added.

Contributes to issue CURA-329.
2016-02-03 12:54:55 +01:00
Ghostkeeper b5d29a66b2 Fix modular arithmetic with negative input
Stupid ISO14882. Modular arithmetic would define that -2 % 7 results in 5, not -2. But C++ implements modulus as keeping the same sign as the left-hand parameter. Thus we have to make sure the left parameter is always positive.

Contributes to issue CURA-329.
2016-02-03 12:54:52 +01:00
Ghostkeeper e937e98b27 Extract trying to insert a line in a cluster to function
I don't wholly agree with this change, since it basically has to transfer the entire context to a separate function of which the functionality is not entirely separate. But at least it makes the cluster() function a bit shorter.

Contributes to issue CURA-329.
2016-02-03 12:54:49 +01:00
Ghostkeeper c3705604cd Extract trying to insert a line in a cluster to function
I don't wholly agree with this change, since it basically has to transfer the entire context to a separate function of which the functionality is not entirely separate. But at least it makes the cluster() function a bit shorter.

Contributes to issue CURA-329.
2016-02-03 12:54:46 +01:00
Ghostkeeper e1993a8ccd Remove TODOs: No action required.
It has been decided and these outlined functions will stay this way.

Contributes to issue CURA-329.
2016-02-03 12:54:43 +01:00
Ghostkeeper b819f696eb Revert Add function to check if two lines are near to each other
Was kind of a superfluous function.

Contributes to issue CURA-329.
2016-02-03 12:54:39 +01:00
Ghostkeeper b1b37cf19b Rename orientation to orientation_index
If the type is not visible directly in the code, this clarifies that it is supposed to be an index in an array.

Contributes to issue CURA-329.
2016-02-03 12:54:36 +01:00
Ghostkeeper 9808be9dfa Cache first and last line indices in get_orientations
No need to fetch them from the vector every time. This is faster if it wouldn't be optimised out by the compiler, and results in more readable code.

Contributes to issue CURA-329.
2016-02-03 12:54:34 +01:00
Ghostkeeper 06e8848ee2 Rename shuffle to shuffled
The vector that holds the shuffled waypoints holds the shuffled stuff, so call it shuffled.

Contributes to issue CURA-329.
2016-02-03 12:54:31 +01:00
Ghostkeeper c43d5ccbf3 Fix codestyle violations
These codestyle violations shouldn't be fixed here since it ruins the git history, but it seems this change isn't getting through without formatting. Therefore I fix them now. Fixed using Netbeans' auto-formatter (and checked manually: it rightly interpreted template brackets as binary operators but we don't see them that way).

Contributes to issue CURA-329.
2016-02-03 12:54:28 +01:00
Ghostkeeper e3e4ccdba8 Add function to check if two lines are near to each other
If they are, they should be placed in the same cluster. Currently this check is completely superfluous since the same check needs to be used to determine in what direction a line should be placed.

Contributes to issue CURA-329.
2016-02-03 12:54:25 +01:00
Ghostkeeper e0eacf0675 Remove unused header of checkIfLineIsBest
The implementation of this function was removed. The header should also have been removed.

Contributes to issue CURA-329.
2016-02-03 12:54:22 +01:00
Ghostkeeper ad1489e0ee Outline the procedures to insert waypoints
In the optimisation step, outline the procedures to insert elements in the resulting lists to their own functions. I really don't agree with this change, since I think it reduces readability as well as efficiency, but it was desired by code review to reduce the deepest indentation of the code, which is a good thing.

Contributes to issue CURA-329.
2016-02-03 12:54:19 +01:00
Ghostkeeper 3efe717154 Codestyle
The line in this loop was updated by my IDE's refactor, so I should've updated it to the code style.

Contributes to issue CURA-329.
2016-02-03 12:54:16 +01:00
Ghostkeeper 17bd906318 Rename LineOrderOptimizer's polygons to lines
They should only be lines. There is no check for it anywhere, though.

Contributes to issue CURA-329.
2016-02-03 12:54:14 +01:00
Ghostkeeper b8cdf0da24 Rename LineOrderOptimizer's polygons to lines
They should only be lines. There is no check for it anywhere, though.

Contributes to issue CURA-329.
2016-02-03 12:54:12 +01:00
Ghostkeeper 801641823c Update documentation
See that's the problem of having lots of documentation: The documentation may be out of date.

Contributes to issue CURA-329.
2016-02-03 12:54:09 +01:00
Ghostkeeper ac25e429b8 Update documentation of findPath.
I removed some old functions earlier but kept the wrong documentation and the documentation wasn't updated for the orientation change either.

Contributes to issue CURA-329.
2016-02-03 12:54:07 +01:00
Ghostkeeper 54e4b2d44e Create typedef Cluster for line clusters
A cluster is a vector of indices to the polygons vector.

Contributes to issue CURA-329.
2016-02-03 12:53:47 +01:00
Ghostkeeper 573699fd07 Extend the cluster() documentation
It describes how lines are precisely clustered and why.

Contributes to issue CURA-329.
2016-02-03 12:53:43 +01:00
Ghostkeeper 304a84a25c Introduce some newlines to improve readability
It should better segment the code into pieces.

Contributes to issue CURA-329.
2016-02-03 12:53:41 +01:00
Ghostkeeper 0e6412289c Update documentation of initial polyStart
Contributes to issue CURA-329.
2016-02-03 12:53:39 +01:00
Ghostkeeper e5901df8e1 Remove unused checkIfLineIsBest function
Penalising sharp corners is no longer applicable, since within clusters both endpoints of adjacent lines must be near each other, so the corner will always be approximately 180 degrees. Between clusters, combing deforms most of the travel moves which makes the corner angle calculation inaccurate.

Contributes to issue CURA-329.
2016-02-03 12:53:37 +01:00
Ghostkeeper e2f96fb1ad Document the usage of indices to polygons in LineOrderOptimizer
I can't document the real reason why indices are used instead of references. That is located in a different part of the code base that is much order.

Contributes to issue CURA-329.
2016-02-03 12:53:34 +01:00
Ghostkeeper 190908ab27 Store waypoint start/end points as pair
The pairs are termed "orientations". This actually makes the code a lot more self-documenting.

Contributes to issue CURA-329.
2016-02-03 12:53:22 +01:00
Ghostkeeper ff775d12f2 Separate declaration of functions from TSP solver declaration
The declaration of the TSP solver with two bulky anonymous functions is unwieldy. The functions are now defined separately and passed to the constructor of TSP.

Contributes to issue CURA-329.
2016-02-03 12:53:19 +01:00
Ghostkeeper a2fd4c0bf3 Document template types
The element template type was a source of confusion with respect to passing by value or by reference. It should be better documented now.

Contributes to issue CURA-329.
2016-02-03 12:53:17 +01:00
Ghostkeeper 404edc5cdc Code readability and documentation of orientation decoding
Where the optimal orientation is used to correctly place the line in order, the code could be made more readable.

Contributes to issue CURA-329.
2016-02-03 12:53:14 +01:00
Ghostkeeper de4edc36bb Fix mirroring when cluster size is 1
In the path order, the mirroring was incorrectly detected when cluster size is 1, since there are only 2 orientations for those clusters instead of 4.

Contributes to issue CURA-329.
2016-02-03 12:53:11 +01:00
Ghostkeeper 1623e58063 Improve clustering heuristic when not aligned
In grid infill, the infill lines are not necessarily aligned. This makes clustering more difficult, but this should help at no additional computational cost. The best distance is now only computed from the travel move that's to be made, while the check for both endpoints to be near remains in effect.

Contributes to issue CURA-329.
2016-02-03 12:53:09 +01:00
Ghostkeeper bb1c00f255 Smaller clustering grid size fallback
The clustering grid size should only be 0 if there is no infill. In those cases, the skin is the only part that needs clustering. For skin, a cluster size of 2mm is more appropriate.

Contributes to issue CURA-329.
2016-02-03 12:53:06 +01:00
Ghostkeeper 773e152454 Vary clustering grid size by infill density
This makes the algorithm run much faster and more reliably if the infill density is low.

Contributes to issue CURA-329.
2016-02-03 12:53:03 +01:00
Ghostkeeper 9c59eff999 Rename ListElement to WaypointListIterator
It technically points to an element, and is not the element itself. It is also used to iterate over the list, so yes it should be called an iterator.

Contributes to CURA-329.
2016-02-03 12:53:01 +01:00
Ghostkeeper b21b7e3115 Remove unused constructor
It used to be used to construct the empty waypoint for the starting_point, but that waypoint is no longer created.

Contributes to CURA-329.
2016-02-03 12:52:57 +01:00
Ghostkeeper 36b09753af Fix even/odd parity check for reversing elements
Because modulo 1 on integers doesn't do anything.

Contributes to CURA-329.
2016-02-03 12:52:55 +01:00
Ghostkeeper 71b466a933 Allow mirroring of infill/skin lines
To allow infill and skin lines to be traversed individually in reverse direction, the TSP solver now accepts an arbitrary number of orientations to insert an element. This also allows us to easily check for the actual start and end points of an element.

Contributes to CURA-329.
2016-02-03 12:52:52 +01:00
Ghostkeeper f0141230b2 Add test for basic bijective result
All points in the unoptimised set must be included in the optimised set and vice-versa.

Contributes to CURA-329.
2016-02-03 12:52:49 +01:00
Ghostkeeper d4631e3f69 Don't make stroke width 4
I must've accidentally included this in a previous commit. It was necessary to debug larger images properly with my image viewer.
2016-02-03 12:52:46 +01:00
Ghostkeeper 64aa1bcd97 Properly reverse list if starting point is at the end
If the optimal point to start is at the end of the TSP path, the TSP path is now properly reversed instead of just reversing the internal order of the elements.

Contributes to issue CURA-329.
2016-02-03 12:52:43 +01:00
Ghostkeeper d34e168562 Fix calculation of path closing distance
This distance was calculated using list.end(), which is a nullptr. This gave uninitialised data, and valgrind wasn't even able to detect it. Luckily, I was. It should be list.back().

Contributes to issue CURA-329.
2016-02-03 12:52:41 +01:00
Ghostkeeper 8f7afd75da Correct documentation
It is now inserting at the end rather than the start when best_insert is equal to result.end(), and inserting results in putting the element before the specified element.

Contributes to issue CURA-329.
2016-02-03 12:52:38 +01:00
Ghostkeeper 0380fd183d Set starting point depending on direction of elements
The direction of the elements is known at this point, so why not use it to optimise further.

Contributes to issue CURA-329.
2016-02-03 12:52:35 +01:00
Ghostkeeper c114904828 Fix checking for the last element inserting starting point
We were using --result.end(), but it turns out that this doesn't work in one statement. We have to create a variable and decrement that.

Contributes to issue CURA-329.
2016-02-03 12:52:32 +01:00
Ghostkeeper 9c08d3dd82 Properly average points
Arithmetic on points isn't defined, though the compiler doesn't complain. We'll have to do it manually.

Contributes to issue CURA-329.
2016-02-03 12:49:54 +01:00
Ghostkeeper f3dbde8e7f Properly iterate over cluster in reverse order
The index was off.

Contributes to issue CURA-329.
2016-02-03 12:49:51 +01:00
Ghostkeeper 7b0f4d204b Add method to write vector of polygons to SVG
This helps with debugging. I just want to be able to draw any geometry to SVG.
2016-02-03 12:49:48 +01:00
Ghostkeeper 79a2dc0a72 Only compute reverse elements if reverse is allowed
Otherwise we're just wasting computer cycles, man.

Contributes to issue CURA-329.
2016-02-03 12:49:45 +01:00
Ghostkeeper c97bcdf005 Order with average points and insert starting point last
This commit has two major changes for which a rewrite was required: The order of the pieces is now determined with point-based TSP rather than line-based. The average between the start and end point is used. This provides better results if the clusters are big, but worse results if the lines intersect (which is not our use case). Then to determine the direction of the elements, it is simply iterated through one by one after all elements are in the sequence.
Also, rather than inserting the starting point at the start, it is now inserted at the end. If it turns out that the starting point should've been inserted somewhere in the middle, that will be corrected after all elements are inserted.

Contributes to issue CURA-329.
2016-02-03 12:49:43 +01:00
Ghostkeeper 03677b8ac7 No longer sort line clusters
The clusters are now constructed with a nearest neighbour-ish search. This guarantees that the lines are already in order. No sorting necessary.

Contributes to issue CURA-329.
2016-02-03 12:49:40 +01:00
Ghostkeeper 9db6352281 Store polyStart during clustering
During clustering, the best orientation of the line is already decided in order to find the closest line. This orientation is immediately stored at that time, instead of guessing it later by the order of the lines at the serialisation. This should make the algorithm more robust at no additional computational cost.

Contributes to issue CURA-329.
2016-02-03 12:49:34 +01:00
Ghostkeeper 3da8607afd Remove old line order optimisation code
This code was kept commented out for a while to have a reference, but I won't need it any longer.

Contributes to issue CURA-329.
2016-02-03 12:49:31 +01:00
Ghostkeeper 4f248088ce Rewrite clustering code
The clustering now uses a nearest neighbour algorithm on the grid to generate the initial clusters to sort.

Contributes to issue CURA-329.
2016-02-03 12:49:28 +01:00
Ghostkeeper d08580f969 Terminate early if no lines to optimise
About 2/3rd of the times there are no lines to optimise for the LineOrderOptimizer. Instead of creating empty cluster vectors, intermediary vectors for reversing, optimising empty vectors with TSP, etc. it now just quits at the start of the optimiser.

Contributes to issue CURA-329.
2016-02-03 12:49:24 +01:00
Ghostkeeper a3652a4fc4 Communicate path order via polyStart properly
The polyStart field should contain an entry for every polygon in the same order as the input polygons, rather than the order of the output polygons.

Contributes to issue CURA-329.
2016-02-03 12:49:22 +01:00
Ghostkeeper 49426a0417 Visualise PolygonRef in SVG
This new method allows you to easily visualise PolygonRefs.
2016-02-03 12:49:17 +01:00
Ghostkeeper 01163bf581 Create constructor for AABB for PolygonRef and vector<PolygonRef>
This is so I won't have to unpack it in the code that uses the AABB.
2016-02-03 12:49:14 +01:00
Ghostkeeper b96bdf41b5 Use default comparator for std::pair
Turns out that the default comparator for std::pair already uses .first to compare two pairs, which is exactly what I had implemented.

Contributes to issue CURA-329.
2016-02-03 12:49:11 +01:00
Ghostkeeper 943c41ed7b Fix off-by-one error on reverse path lookup
When it serialises a path that should be reversed, there was an off-by one error. It now starts polygon_index by 1, which fixes the segfault.

Contributes to issue CURA-329.
2016-02-03 12:49:09 +01:00
Ghostkeeper a6cd192156 Fix off-by-one error on reverse path lookup
When it serialises a path that should be reversed, there was an off-by one error. It now starts polygon_index by 1, which fixes the segfault.

Contributes to issue CURA-329.
2016-02-03 12:49:06 +01:00
Ghostkeeper 06c82f3426 Factor out the cluster when serialising resulting optimised path
This makes the code a bit more maintainable.

Contributes to issue CURA-329.
2016-02-03 12:49:04 +01:00
Ghostkeeper eb06f19f11 Reverse sorting order within cluster
Since the lines were already almost sorted, this makes the sorting a lot faster.

Contributes to issue CURA-329.
2016-02-03 12:49:01 +01:00
Ghostkeeper 4fc471ecf9 Don't forget to use the sorted lines within a cluster
The lines were beautifully sorted by y-intercept as far as I know, but the sorted list was then discarded and the unsorted list was used. This fixes it.

Contributes to issue CURA-329.
2016-02-03 12:48:57 +01:00
Ghostkeeper ff018d40c0 Fix segfault
The construct of `bool picked[n] = {false}` gave a segfault for some reason, even though I just copied it from the internet where they used the same construct with an int array. Maybe this doesn't work properly for booleans with this construct in GCC.

Contributes to issue CURA-329.
2016-02-03 12:48:54 +01:00
Ghostkeeper cc9199ee50 Use clustering to make path optimiser more efficient
Groups of lines where both endpoints are close together are clustered. Within a cluster, the lines are sorted in order of increasing y-intercept, which should work excellently for infill and skin lines. The clusters are then optimised with TSP. Not properly tested yet since elsewhere in the code it has a segfault, but I wanted to make these separate commits.

Contributes to issue CURA-329.
2016-02-03 12:48:48 +01:00
Ghostkeeper 7bcef0ad97 Prettier array initialisation
Should've looked this up first. I keep forgetting this syntactic sugar.

Contributes to issue CURA-329.
2016-02-03 12:48:45 +01:00
Ghostkeeper f5f9595627 Write a clustering method for infill lines
This will cluster lines of infill together so that they don't need to be ordered all individually. The clustering method is fairly dumb right now. It could be made much more effective with some smarter data structures.

Contributes to issue CURA-329.
2016-02-03 12:48:42 +01:00
Ghostkeeper d2f2417012 Optimise indices of size_t instead of ints
Contributes to issue CURA-329.
2016-02-03 12:48:39 +01:00
Ghostkeeper 5299c76521 Document the calling of TSP.
Contributes to issue CURA-329.
2016-02-03 12:48:36 +01:00
Ghostkeeper 80f976d1f3 Remove duplicate code for reverse paths
In the previous commit I immediately finalised reverse paths in their waypoints as soon as it was known that the path was to be reversed. This changed the code for the non-reversed try (when reverse paths are allowed) more or less equal to the code for when reversing paths isn't allowed. I removed the code for this non-reversed try, making it always try non-reversed and only try reversed if reversed is allowed.

Contributes to issue CURA-329.
2016-02-03 12:48:33 +01:00
Ghostkeeper b117e64288 Store reverse path into waypoint
This makes the algorithm run a bit faster and makes the code simpler. Instead of relying only on the is_reversed field and checking that field every time we need to look up the endpoints of the element, we now simply swap the endpoints if the element needs to be reversed.

Contributes to issue CURA-329.
2016-02-03 12:48:30 +01:00
Ghostkeeper ca41665c3e std::list.insert() inserts before an item, not after
I understood this wrong from the documentation. This makes several things easier. This could be optimised slightly but that is of later concern.

Contributes to issue CURA-329.
2016-02-03 12:48:25 +01:00
Ghostkeeper b94c95149a Use vSize rather than a custom float-implementation
This one is a bit slower but it is neater to re-use code, and we are more certain that it works.

Contributes to issue CURA-329.
2016-02-03 12:48:22 +01:00
Ghostkeeper 5b95b7a557 Properly call TravellingSalesman from optimiser
It needs to optimise the order of indices to PolygonRefs at the moment. I'd like to refactor this construction away... But that takes some effort.

Contributes to issue CURA-329.
2016-02-03 12:48:20 +01:00
Ghostkeeper bdc31425ba Skip the starting waypoint
If there is a starting point, it produces a waypoint somewhere in the path (either the first or the last point). This waypoint shouldn't be included in the eventual path. Skip it. This can be done a bit more efficient but that is of later concern.

Contributes to issue CURA-329.
2016-02-03 12:48:17 +01:00
Ghostkeeper f001f31e78 TSP pass by value instead of by reference
This prevents segfaults when the underlying vectors are resized for any reason.
2016-02-03 12:48:14 +01:00
Ghostkeeper 6d56e2c622 Actually add waypoints to shuffle vector
It seems I had forgotten to add waypoints to be shuffled.

Contributes to issue CURA-329.
2016-02-03 12:48:11 +01:00
Ghostkeeper c983a7e83b TSP prepends new path before first element
Instead of trying to prepend new elements before the original first element, it now prepends new elements before the currently first element.
2016-02-03 12:48:08 +01:00
Ghostkeeper 86d0ce4e6b Refactor the TSP solver
The algorithm is no longer implemented twice, but once with a load of ifs checking for allow_reverse. I'm still not sure which is neater...
2016-02-03 12:48:05 +01:00
Ghostkeeper f79933b140 Implement findPath
This version tries to insert elements also in reverse direction, which has some effects on various parts of the code. It also turned out that the previous version wasn't compiling properly but that didn't come up because the function was never used, so it was being optimised out.
2016-02-03 12:45:44 +01:00
Ghostkeeper a47716161a Document the linearising of the result
This wasn't quite clear in a re-read of my code.
2016-02-03 12:45:41 +01:00
Ghostkeeper 831f5ea16e Refactor TSP to use templates
Kind of a big rewrite and not properly tested yet.
2016-02-03 12:45:37 +01:00
Ghostkeeper 219d400e62 Correct license header
Yeah that was wrong.
2016-02-03 12:45:34 +01:00
Ghostkeeper 2b8c92cba9 Use TSP solver in path order optimiser
It also prints out the optimised path now, though that is not what we'd like to see eventually.
2016-02-03 12:45:30 +01:00
Ghostkeeper 44c5c11160 Fix memory pre-allocation
Turns out that it doesn't work to put the required memory in the constructor. You have to call .reserve(int) separately.
2016-02-03 12:45:26 +01:00
Ghostkeeper 1f5cdf5d6b Add TSP for line segments
This implementation is for TSP that visits a selection of line segments, following each line segment in the order that makes the total path short. Line segments may also be traversed in reverse direction.
2016-02-03 12:45:23 +01:00
Ghostkeeper 7607992eb6 Fix start point check
The check to never insert before the start point was wrong. It should've inserted before only if the selected point is not the starting point or there is no starting point.
2016-02-03 12:45:19 +01:00
Ghostkeeper 2461073d64 Add new waypoints to bucket grid
This would allow them to be found quickly, or rather, to be found at all when looking for nearby points.
2016-02-03 12:45:15 +01:00
Ghostkeeper 3a43fe2885 Delete waypoints after use
This prevents a memory leak.
2016-02-03 12:45:10 +01:00
Ghostkeeper 9e2d5217d0 Add return documentation
Forgot to add that previously.
2016-02-03 12:45:07 +01:00
Ghostkeeper 353e151c6b Initial TSP implementation
This implementation should work for TSP with points. TSP with lines coming up next. Has not yet been tested thoroughly, but the code is not used anywhere.
2016-02-03 12:45:01 +01:00
10 arquivos alterados com 874 adições e 139 exclusões
+1
Ver Arquivo
@@ -86,6 +86,7 @@ set(engine_SRCS # Except main.cpp.
set(engine_TEST
GCodePlannerTest
LinearAlg2DTest
TravellingSalesmanTest
)
# Generating ProtoBuf protocol.
+1 -1
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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;
}
}
}
+65 -9
Ver Arquivo
@@ -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
+37
Ver Arquivo
@@ -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)
{
-5
Ver Arquivo
@@ -205,11 +205,6 @@ public:
// if (! emplaced.second)
// logError("Error! BucketGrid2D couldn't insert object!");
};
};
}//namespace cura
+39 -1
Ver Arquivo
@@ -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());
}
/*!
+405
Ver Arquivo
@@ -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 */
+91
Ver Arquivo
@@ -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);
}
}
}
+73
Ver Arquivo
@@ -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 */