Compare commits

...

4 Commits

Author SHA1 Message Date
23fef77aaa Update Makefile 2020-12-06 22:59:01 +01:00
7bfa0f0ed2 Add Akl Heuristic in performance mode
Doesn't seem to make much of a difference though :(

but another issue was fixed in the meantime (we weren't actually usign the leftmost and rightmost points!)
2020-12-06 22:09:35 +01:00
ef01f37834 Improve performance of straight line edge case handling 2020-12-06 19:56:16 +01:00
82c0df1840 Fix rectangle behavior properly, minor improvements 2020-12-06 19:37:47 +01:00
2 changed files with 77 additions and 53 deletions

View File

@ -1,5 +1,5 @@
CXX = g++ CXX = g++
CXXFLAGS = -Wall -O3 -g CXXFLAGS = -Wall -O3
SFMLFLAGS = -lsfml-system -lsfml-window -lsfml-graphics SFMLFLAGS = -lsfml-system -lsfml-window -lsfml-graphics
# In case you installed SFML to a non-standard path, you'll need to tell the compiler where to find the SFML headers (.hpp files): # In case you installed SFML to a non-standard path, you'll need to tell the compiler where to find the SFML headers (.hpp files):
@ -20,5 +20,5 @@ main.o: main.cpp
Display.o: Display.h Display.o: Display.h
clean : clean:
-rm *.o quickhull performance -rm *.o quickhull

View File

@ -8,6 +8,7 @@
#include "Display.h" #include "Display.h"
#include "Triangle.h" #include "Triangle.h"
#include "Utility.h" // For IsPointInRectangle
class Quickhull class Quickhull
{ {
@ -19,21 +20,56 @@ public:
// Get leftmost and rightmost point // Get leftmost and rightmost point
Point leftmost(INFINITY, 0.0), rightmost(-INFINITY, 0.0); Point leftmost(INFINITY, 0.0), rightmost(-INFINITY, 0.0);
for (const Point &point : input) if (akl)
{ {
if (point.x() < leftmost.x()) { // Also get highest and lowest point
leftmost = point; Point lowest(0.0, -INFINITY), highest(0.0, INFINITY);
} else if (point.y() > rightmost.y()) {
rightmost = point;
}
}
// Add them to the output
output.emplace_back(leftmost);
output.emplace_back(rightmost);
// Remove them from the input (as well as duplicates) for (const Point &point : input)
input.remove(leftmost); {
input.remove(rightmost); if (point.x() < leftmost.x()) {
leftmost = point;
} else if (point.x() > rightmost.x()) {
rightmost = point;
}
if (point.y() < highest.y()) {
highest = point;
} else if (point.y() > lowest.y()) {
lowest = point;
}
}
Triangle t1(highest, rightmost, lowest);
Triangle t2(lowest, leftmost, highest);
// Remove all points in this rectangle
input.remove_if([highest, leftmost, lowest, rightmost](const Point &point)
{
return IsPointInRectangle(point, highest, leftmost, lowest, rightmost);
});
output.emplace_back(leftmost);
output.emplace_back(rightmost);
output.emplace_back(highest);
output.emplace_back(lowest);
} else {
for (const Point &point : input)
{
if (point.x() < leftmost.x()) {
leftmost = point;
} else if (point.x() > rightmost.x()) {
rightmost = point;
}
}
// Add them to the output
output.emplace_back(leftmost);
output.emplace_back(rightmost);
// Remove them from the input (as well as duplicates)
input.remove(leftmost);
input.remove(rightmost);
}
// Create a line from leftmost to rightmost // Create a line from leftmost to rightmost
Line line = Line(leftmost, rightmost); Line line = Line(leftmost, rightmost);
@ -113,46 +149,38 @@ private:
// If the input vector is empty, we're done // If the input vector is empty, we're done
if (input.empty()) return; if (input.empty()) return;
// Find the point which is furthest away from the line, add it to the output // Find the points which are furthest away from the line
Point furthest_point;
float furthest_distance = -1.0; float furthest_distance = -1.0;
std::list<Point> furthest_points;
for (const Point &point : input) for (const Point &point : input)
{ {
float this_distance = line.distance_squared_to(point); float this_distance = line.distance_squared_to(point);
// It's possible for there to be multiple closests points (e.g. in the case)
// of a rectangle). We need to handle all these properly, so we make a list
// of all points at the furthest distance.
if (this_distance > furthest_distance) if (this_distance > furthest_distance)
{ {
furthest_distance = this_distance; furthest_distance = this_distance;
furthest_point = point; furthest_points.clear();
furthest_points.emplace_back(point);
} }
} else if (this_distance == furthest_distance)
// TODO: e.g. in the case of a rectangle, it's possible for there to be
// multiple closest points (sometimes all at distance 0). How do we handle
// these properly? We definitely need to remove them all from input later;
// do we also need to handle them all further? This hotfix works, but seems
// like an unnecessarily big performance hit for that edge case.
// FIXME: This workaround also causes problems with extremely large numbers
// of randomly generated numbers, causing random lines within the data!
// Points inside the hull are added because of random lines.
for (const Point &point : input)
{
float this_distance = line.distance_squared_to(point);
// TODO: Both are required, otherwise only one side of the rectangle is
// taken -- why?
if (this_distance == furthest_distance || line.distance_squared_to(point) == 0)
{ {
output.emplace_back(point); furthest_points.emplace_back(point);
} }
} }
input.remove_if([furthest_distance, line](Point point)
{
return furthest_distance == line.distance_squared_to(point);
});
// Hotfix end
output.emplace_back(furthest_point); for (const Point &point : furthest_points)
input.remove(furthest_point); {
// All furthest points we found are part of the hull, so add them to the output
// and remove them from the input.
output.emplace_back(point);
input.remove(point);
}
Point furthest_point = furthest_points.front();
// Build a triangle with these 3 points // Build a triangle with these 3 points
@ -163,7 +191,6 @@ private:
if (line.is_point_right(furthest_point)) if (line.is_point_right(furthest_point))
{ {
// TOOD: It's probably more efficient to set the fields?
new_line1 = Line(line.to(), furthest_point); new_line1 = Line(line.to(), furthest_point);
new_line2 = Line(furthest_point, line.from()); new_line2 = Line(furthest_point, line.from());
} }
@ -173,12 +200,9 @@ private:
new_line2 = Line(furthest_point, line.to()); new_line2 = Line(furthest_point, line.to());
} }
// TODO: Test if this improves performance // We don't need to remove points inside the triangle created by those lines.
//Triangle triangle(a, b, c); // That happens implicitly since they are not handled further due to the
//input.remove_if([triangle](Point point) // following step, which assigns each line its corresponding points to handle:
//{
// return triangle.is_point_inside(point);
//});
// Get points right of new_line1 and 2 // Get points right of new_line1 and 2
std::list<Point> left_of_line1, left_of_line2; std::list<Point> left_of_line1, left_of_line2;
@ -189,15 +213,15 @@ private:
{ {
left_of_line1.emplace_back(point); left_of_line1.emplace_back(point);
} }
// TODO: Can we do else if here, or could we then miss out on points? // TODO: Are there any possible edge cases where this 'else if' won't work,
if (!new_line2.is_point_right(point)) // and we need an 'if' instead?
else if (!new_line2.is_point_right(point))
{ {
left_of_line2.emplace_back(point); left_of_line2.emplace_back(point);
} }
} }
// Recursively call get_hull_with_line for each side of the triangle // Recursively call get_hull_with_line for each side of the triangle
// TODO: We can skip the original one
get_hull_with_line(left_of_line1, output, new_line1); get_hull_with_line(left_of_line1, output, new_line1);
get_hull_with_line(left_of_line2, output, new_line2); get_hull_with_line(left_of_line2, output, new_line2);
} }