quickhull/Quickhull.h

154 lines
5.1 KiB
C++

#ifndef QUICKHULL_H
#define QUICKHULL_H
#include <list>
#include <bits/stdc++.h> // For INT_MIN & INT_MAX
#include "Point.h"
#include "Line.h"
#include "Triangle.h"
class Quickhull
{
public:
static void get_hull(std::list<Point> input, std::list<Point> &output)
{
// Get leftmost and rightmost point
Point leftmost(INFINITY, 0.0), rightmost(-INFINITY, 0.0);
for (const Point &point : input)
{
if (point.x() < leftmost.x()) {
leftmost = point;
} 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)
input.remove(leftmost);
input.remove(rightmost);
// Create a line from leftmost to rightmost
Line line = Line(leftmost, rightmost);
// Sort points between left and right of that line
std::list<Point> points_left, points_right;
for (const Point &point : input)
{
if (line.is_point_right(point))
{
points_right.emplace_back(point);
}
else
{
points_left.emplace_back(point);
}
}
// Call get_hull_with_line with the left points, as well as with the right points, and the line
get_hull_with_line(points_left, output, line);
get_hull_with_line(points_right, output, line);
}
private:
static void get_hull_with_line(std::list<Point> &input, std::list<Point> &output, const Line &line)
{
// If the input vector is empty, we're done
if (input.empty()) return;
// Find the point which is furthest away from the line, add it to the output
Point furthest_point;
float furthest_distance = -1.0;
for (const Point &point : input)
{
float this_distance = line.distance_squared_to(point);
if (this_distance > furthest_distance)
{
furthest_distance = this_distance;
furthest_point = point;
}
}
// 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);
}
}
input.remove_if([furthest_distance, line](Point point)
{
return furthest_distance == line.distance_squared_to(point);
});
// Hotfix end
output.emplace_back(furthest_point);
input.remove(furthest_point);
// Build a triangle with these 3 points
// We need to differentiate based on which side the furthest point is on
// in order to keep the meaning of left/right consistent.
Line new_line1, new_line2;
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_line2 = Line(furthest_point, line.from());
}
else
{
new_line1 = Line(line.from(), furthest_point);
new_line2 = Line(furthest_point, line.to());
}
// TODO: Test if this improves performance
//Triangle triangle(a, b, c);
//input.remove_if([triangle](Point point)
//{
// return triangle.is_point_inside(point);
//});
// Get points right of new_line1 and 2
std::list<Point> left_of_line1, left_of_line2;
for (const Point& point : input)
{
if (!new_line1.is_point_right(point))
{
left_of_line1.emplace_back(point);
}
// TODO: Can we do else if here, or could we then miss out on points?
if (!new_line2.is_point_right(point))
{
left_of_line2.emplace_back(point);
}
}
// 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_line2, output, new_line2);
}
};
#endif // QUICKHULL_H