#ifndef QUICKHULL_H #define QUICKHULL_H #include #include #include #include #include "Display.h" #include "Point.h" //#include "Line.h" #include "Triangle.h" class Quickhull { public: static void run(std::list points, std::list& hull, bool akl) { auto start = std::chrono::high_resolution_clock::now(); std::cout << "running quickhull perf mode..." << std::endl; Quickhull::get_hull(points, hull); auto diff = std::chrono::duration(std::chrono::high_resolution_clock::now() - start); std::cout << "time spent: " << diff.count() << "ms" << std::endl; // shit runs about 120 milliseconds for 1Mio numbers // showing points in window after calculation // create the window sf::RenderWindow window(sf::VideoMode(WIDTH, HEIGHT), "ALGO Prog2: Quickhull - performance"); sf::CircleShape normal_p(2); normal_p.setFillColor(sf::Color(250, 250, 250)); sf::CircleShape hull_p(2); hull_p.setFillColor(sf::Color(250, 100, 50)); // run the program as long as the window is open while (window.isOpen()) { // check all the window's events that were triggered since the last iteration of the loop sf::Event event; while (window.pollEvent(event)) { // "close requested" event: we close the window if (event.type == sf::Event::Closed) window.close(); } // clear the window with black color window.clear(sf::Color::Black); // Draw all points for (const Point& point : points) { normal_p.setPosition(point.x(), point.y()); window.draw(normal_p); } // Draw hull points for (const Point& point : hull) { hull_p.setPosition(point.x(), point.y()); window.draw(hull_p); } // end the current frame window.display(); } } static void get_hull(std::list input, std::list &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 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 &input, std::list &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 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