From d1d170f5582db877f41eac0fb5ecbf53b4acd49e Mon Sep 17 00:00:00 2001 From: karl Date: Sat, 28 Nov 2020 20:47:52 +0100 Subject: [PATCH 1/3] Some work on Quickhull Also added basic arithmetic to Point, and a class for a Line --- Line.h | 40 +++++++++++++++++++++++++++++++++ Makefile | 6 ++--- Point.h | 32 ++++++++++++++++++++++++-- Quickhull.cpp | 26 --------------------- Quickhull.h | 62 ++++++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 131 insertions(+), 35 deletions(-) create mode 100644 Line.h delete mode 100644 Quickhull.cpp diff --git a/Line.h b/Line.h new file mode 100644 index 0000000..1727495 --- /dev/null +++ b/Line.h @@ -0,0 +1,40 @@ +#pragma once + +#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) + { + 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; + } + + } +}; \ No newline at end of file diff --git a/Makefile b/Makefile index 3d01542..a1ab1a7 100644 --- a/Makefile +++ b/Makefile @@ -7,9 +7,9 @@ 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 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 Timing.o Display.o -lsfml-system -lsfml-window -lsfml-graphics main.o: main.cpp Timing.h $(CXX) $(CXXFLAGS) -c main.cpp @@ -18,7 +18,5 @@ Display.o: Display.h Timing.o: Timing.h -Quickhull.o: Quickhull.h - clean : -rm *.o quickhull diff --git a/Point.h b/Point.h index 2f7ce72..90f464c 100644 --- a/Point.h +++ b/Point.h @@ -8,13 +8,41 @@ private: public: Point(float x, float y) : m_x(x), m_y(y) {} - float x() + Point() : m_x(0), m_y(0) {} + + float x() const { return m_x; } - float y() + float y() const { return m_y; } + + Point operator+(const Point &other) + { + return Point(x() + other.x(), y() + other.y()); + } + + Point operator-(const Point &other) + { + 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; + } }; \ 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..c458cb2 100644 --- a/Quickhull.h +++ b/Quickhull.h @@ -1,14 +1,70 @@ #pragma once -#include +#include +#include // For INT_MIN & INT_MAX #include "Point.h" +#include "Line.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(INT_MAX, 0), rightmost(INT_MIN, 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 + + // Build a triangle with these 3 points + // Remove points inside this triangle + + // Recursively call get_hull_with_line for each side of the triangle + } }; \ No newline at end of file From acfb3a4500c7eb2a093c4df5241faceb5c65e276 Mon Sep 17 00:00:00 2001 From: karl Date: Sat, 28 Nov 2020 21:11:32 +0100 Subject: [PATCH 2/3] More Quickhull implementation; add Triangle class --- Line.h | 2 +- Makefile | 4 ++-- Quickhull.h | 27 +++++++++++++++++++++++++++ Triangle.h | 22 ++++++++++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 Triangle.h diff --git a/Line.h b/Line.h index 1727495..b156d02 100644 --- a/Line.h +++ b/Line.h @@ -22,7 +22,7 @@ public: // 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) + bool is_point_right(Point other) const { other -= from(); diff --git a/Makefile b/Makefile index a1ab1a7..109ae4d 100644 --- a/Makefile +++ b/Makefile @@ -7,9 +7,9 @@ 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 Quickhull.h Point.h Line.h 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 Quickhull.h Point.h Line.h 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 main.o: main.cpp Timing.h $(CXX) $(CXXFLAGS) -c main.cpp diff --git a/Quickhull.h b/Quickhull.h index c458cb2..a49ade5 100644 --- a/Quickhull.h +++ b/Quickhull.h @@ -5,6 +5,7 @@ #include "Point.h" #include "Line.h" +#include "Triangle.h" class Quickhull { @@ -61,10 +62,36 @@ private: if (input.empty()) return; // Find the point which is furthest away from the line, add it to the output + Point furthest_point; // TODO + + output.emplace_back(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 + 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 + 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 } }; \ No newline at end of file diff --git a/Triangle.h b/Triangle.h new file mode 100644 index 0000000..d271c1a --- /dev/null +++ b/Triangle.h @@ -0,0 +1,22 @@ +#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); + } +}; \ No newline at end of file From 79111d184b54e897d8b2f151bdca30fdc881f5c7 Mon Sep 17 00:00:00 2001 From: karl Date: Sat, 28 Nov 2020 22:20:06 +0100 Subject: [PATCH 3/3] Finish Quickhull functionality + basic test The performance mode, which I'm working on, can now be tested with `make performance && ./performance`. A very simple test was added, and it works! --- Line.h | 12 +++++++++++- Makefile | 7 +++++-- Point.h | 9 +++++++-- Quickhull.h | 28 ++++++++++++++++++++++++---- Triangle.h | 15 +++++++++++++++ performance.cpp | 22 ++++++++++++++++++++++ 6 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 performance.cpp diff --git a/Line.h b/Line.h index b156d02..6038a82 100644 --- a/Line.h +++ b/Line.h @@ -1,5 +1,7 @@ #pragma once +#include // For abs, sqrt + #include "Point.h" class Line @@ -35,6 +37,14 @@ public: { 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 109ae4d..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 @@ -11,6 +11,9 @@ 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 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 @@ -19,4 +22,4 @@ Display.o: Display.h Timing.o: Timing.h clean : - -rm *.o quickhull + -rm *.o quickhull performance diff --git a/Point.h b/Point.h index 90f464c..92a2864 100644 --- a/Point.h +++ b/Point.h @@ -20,12 +20,12 @@ public: return m_y; } - Point operator+(const Point &other) + Point operator+(const Point &other) const { return Point(x() + other.x(), y() + other.y()); } - Point operator-(const Point &other) + Point operator-(const Point &other) const { return Point(x() - other.x(), y() - other.y()); } @@ -45,4 +45,9 @@ public: 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.h b/Quickhull.h index a49ade5..0e392cb 100644 --- a/Quickhull.h +++ b/Quickhull.h @@ -13,7 +13,7 @@ public: static void get_hull(std::list &input, std::list &output) { // Get leftmost and rightmost point - Point leftmost(INT_MAX, 0), rightmost(INT_MIN, 0); + Point leftmost(INFINITY, 0.0), rightmost(-INFINITY, 0.0); for (const Point &point : input) { @@ -62,13 +62,26 @@ private: if (input.empty()) return; // Find the point which is furthest away from the line, add it to the output - Point furthest_point; // TODO + 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)) { @@ -86,12 +99,19 @@ private: 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); + return triangle.is_point_inside(point); }); // Recursively call get_hull_with_line for each side of the triangle - // TODO + // 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 index d271c1a..ffc7085 100644 --- a/Triangle.h +++ b/Triangle.h @@ -19,4 +19,19 @@ public: { 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