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!
This commit is contained in:
karl 2020-11-28 22:20:06 +01:00
parent acfb3a4500
commit 79111d184b
6 changed files with 84 additions and 9 deletions

12
Line.h
View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <cmath> // For abs, sqrt
#include "Point.h" #include "Point.h"
class Line class Line
@ -35,6 +37,14 @@ public:
{ {
return false; 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);
} }
}; };

View File

@ -1,5 +1,5 @@
CXX = g++ 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): # 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<sfml-install-path>/include # g++ -c main.cpp -I<sfml-install-path>/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 # 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 $(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 main.o: main.cpp Timing.h
$(CXX) $(CXXFLAGS) -c main.cpp $(CXX) $(CXXFLAGS) -c main.cpp
@ -19,4 +22,4 @@ Display.o: Display.h
Timing.o: Timing.h Timing.o: Timing.h
clean : clean :
-rm *.o quickhull -rm *.o quickhull performance

View File

@ -20,12 +20,12 @@ public:
return m_y; return m_y;
} }
Point operator+(const Point &other) Point operator+(const Point &other) const
{ {
return Point(x() + other.x(), y() + other.y()); 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()); return Point(x() - other.x(), y() - other.y());
} }
@ -45,4 +45,9 @@ public:
return *this; return *this;
} }
bool operator==(const Point &other) const
{
return (x() == other.x() && y() == other.y());
}
}; };

View File

@ -13,7 +13,7 @@ public:
static void get_hull(std::list<Point> &input, std::list<Point> &output) static void get_hull(std::list<Point> &input, std::list<Point> &output)
{ {
// Get leftmost and rightmost point // 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) for (const Point &point : input)
{ {
@ -62,13 +62,26 @@ private:
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 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); output.emplace_back(furthest_point);
input.remove(furthest_point);
// Build a triangle with these 3 points // Build a triangle with these 3 points
// The order with which we must pass the points depends on where the new furthest point is // 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; Point a, b, c;
if (line.is_point_right(furthest_point)) if (line.is_point_right(furthest_point))
{ {
@ -86,12 +99,19 @@ private:
Triangle triangle(a, b, c); Triangle triangle(a, b, c);
// Remove points inside this triangle // 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) 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 // 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());
} }
}; };

View File

@ -19,4 +19,19 @@ public:
{ {
return m_l1.is_point_right(other) && m_l2.is_point_right(other) && m_l3.is_point_right(other); 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;
}
}; };

22
performance.cpp Normal file
View File

@ -0,0 +1,22 @@
#include "Quickhull.h"
int main()
{
std::list<Point> 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<Point> hull;
Quickhull::get_hull(points, hull);
for (const Point &point : hull)
{
std::cout << point.x() << ", " << point.y() << std::endl;
}
return 0;
}