diff --git a/Line.h b/Line.h new file mode 100644 index 0000000..6038a82 --- /dev/null +++ b/Line.h @@ -0,0 +1,50 @@ +#pragma once + +#include // For abs, sqrt + +#include "Point.h" + +class Line +{ +private: + Point m_from, m_to, m_to_from_origin; + +public: + Line(Point from, Point to) : m_from(from), m_to(to), m_to_from_origin(to - from) {} + + Point from() const + { + return m_from; + } + + Point to() const + { + return m_to; + } + + // Return true if the given point is to the right of this line. + // False is also returned if the point is directly on the line. + bool is_point_right(Point other) const + { + other -= from(); + + // Cross product greater than zero? + if (m_to_from_origin.x() * other.y() - m_to_from_origin.y() * other.x() > 0) + { + return true; + } + else + { + return false; + } + } + + float distance_to(Point other) const + { + float a = from().y() - to().y(); + float b = to().x() - from().x(); + float c = from().x() * to().y() - to().x() * from().y(); + + return abs(a * other.x() + b * other.y() + c) / sqrt(a * a + b * b); + } +}; \ No newline at end of file diff --git a/Makefile b/Makefile index 3d01542..6ea65ec 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CXX = g++ -CXXFLAGS = -Wall -O3 +CXXFLAGS = -Wall -O3 -g # 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): # g++ -c main.cpp -I/include @@ -7,9 +7,12 @@ CXXFLAGS = -Wall -O3 # If SFML is not installed in a standard path, you need to tell the dynamic linker where to find the SFML libraries first by specifying LD_LIBRARY_PATH: # export LD_LIBRARY_PATH=/lib && ./sfml-app -quickhull: main.o Timing.o Display.o +quickhull: main.o Quickhull.h Point.h Line.h Triangle.h Timing.o Display.o # link with sfml libs; -lsfml-network -lsfml-audio currently not needed - $(CXX) $(CXXFLAGS) -o quickhull main.o Timing.o Display.o -lsfml-system -lsfml-window -lsfml-graphics + $(CXX) $(CXXFLAGS) -o quickhull main.o Quickhull.h Point.h Line.h Triangle.h Timing.o Display.o -lsfml-system -lsfml-window -lsfml-graphics + +performance: performance.cpp + $(CXX) $(CXXFLAGS) -o performance performance.cpp main.o: main.cpp Timing.h $(CXX) $(CXXFLAGS) -c main.cpp @@ -18,7 +21,5 @@ Display.o: Display.h Timing.o: Timing.h -Quickhull.o: Quickhull.h - clean : - -rm *.o quickhull + -rm *.o quickhull performance diff --git a/Point.h b/Point.h index cc72cbf..92a2864 100644 --- a/Point.h +++ b/Point.h @@ -8,13 +8,46 @@ private: public: Point(float x, float y) : m_x(x), m_y(y) {} - float x () const + Point() : m_x(0), m_y(0) {} + + float x() const { return m_x; } - float y () const + float y() const { return m_y; } + + Point operator+(const Point &other) const + { + return Point(x() + other.x(), y() + other.y()); + } + + Point operator-(const Point &other) const + { + return Point(x() - other.x(), y() - other.y()); + } + + Point &operator+=(const Point &other) + { + m_x += other.x(); + m_y += other.y(); + + return *this; + } + + Point &operator-=(const Point &other) + { + m_x -= other.x(); + m_y -= other.y(); + + return *this; + } + + bool operator==(const Point &other) const + { + return (x() == other.x() && y() == other.y()); + } }; \ No newline at end of file diff --git a/Quickhull.cpp b/Quickhull.cpp deleted file mode 100644 index f92e5ca..0000000 --- a/Quickhull.cpp +++ /dev/null @@ -1,26 +0,0 @@ - -#include "Quickhull.h" - -void Quickhull::get_hull(std::vector &input, std::vector &output) -{ - // Get leftmost and rightmost point - // Add them to the output. - - // Create a line from leftmost to rightmost - - // Sort points between left and right of that line - - // Call get_hull_with_line with the left points, as well as with the right points, and the line -} - -void Quickhull::get_hull_with_line(std::vector &input, std::vector &output, Point, Point) -{ - // If the input vector is empty, we're done - - // Find the point which is furthest away from the line, add it to the output - - // Build a triangle with these 3 points - // Remove points inside this triangle - - // Recursively call get_hull_with_line for each side of the triangle -} diff --git a/Quickhull.h b/Quickhull.h index ea44b79..0e392cb 100644 --- a/Quickhull.h +++ b/Quickhull.h @@ -1,14 +1,117 @@ #pragma once -#include +#include +#include // For INT_MIN & INT_MAX #include "Point.h" +#include "Line.h" +#include "Triangle.h" class Quickhull { public: - static void get_hull(std::vector &, std::vector &); + 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::vector &, std::vector &, Point, Point); + 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 = 0.0; + + for (const Point &point : input) + { + float this_distance = line.distance_to(point); + if (this_distance > furthest_distance) + { + furthest_distance = this_distance; + furthest_point = point; + } + } + + output.emplace_back(furthest_point); + input.remove(furthest_point); + + // Build a triangle with these 3 points + + // The order with which we must pass the points depends on where the new furthest point is + // TODO: Is there a nicer way to do this? + Point a, b, c; + if (line.is_point_right(furthest_point)) + { + a = line.from(); + b = line.to(); + c = furthest_point; + } + else + { + a = line.from(); + b = furthest_point; + c = line.to(); + } + + Triangle triangle(a, b, c); + + // Remove points inside this triangle + // TODO: I think we can actually skip this, and instead only + // pass points to the left (?) of the individual line to the + // new get_hull_with_line call. That way the ones inside are + // implicitly ignored. + input.remove_if([triangle](Point point) + { + return triangle.is_point_inside(point); + }); + + // Recursively call get_hull_with_line for each side of the triangle + // TODO: We can skip the original one + get_hull_with_line(input, output, triangle.l1()); + get_hull_with_line(input, output, triangle.l2()); + get_hull_with_line(input, output, triangle.l3()); + } }; \ No newline at end of file diff --git a/Triangle.h b/Triangle.h new file mode 100644 index 0000000..ffc7085 --- /dev/null +++ b/Triangle.h @@ -0,0 +1,37 @@ +#pragma once + +#include "Point.h" +#include "Line.h" + +class Triangle +{ +private: + Point m_p1, m_p2, m_p3; + Line m_l1, m_l2, m_l3; + +public: + // The points must come in __clockwise__ order. + Triangle(Point p1, Point p2, Point p3) + : m_p1(p1), m_p2(p2), m_p3(p3), + m_l1(Line(p1, p2)), m_l2(Line(p2, p3)), m_l3(Line(p3, p1)) {} + + bool is_point_inside(Point other) const + { + return m_l1.is_point_right(other) && m_l2.is_point_right(other) && m_l3.is_point_right(other); + } + + Line l1() + { + return m_l1; + } + + Line l2() + { + return m_l2; + } + + Line l3() + { + return m_l3; + } +}; \ No newline at end of file diff --git a/performance.cpp b/performance.cpp new file mode 100644 index 0000000..4a7cdeb --- /dev/null +++ b/performance.cpp @@ -0,0 +1,22 @@ +#include "Quickhull.h" + +int main() +{ + std::list points = { + Point(-1, -1), + Point(-1, 1), + Point(1, -1), + Point(1, 1), + Point(0.5, 0) // Should not be in the hull + }; + std::list hull; + + Quickhull::get_hull(points, hull); + + for (const Point &point : hull) + { + std::cout << point.x() << ", " << point.y() << std::endl; + } + + return 0; +} \ No newline at end of file