Compare commits

..

5 Commits

Author SHA1 Message Date
c0bba8b264 Add temp workaround for rectangle
Getting both rectangle sides to work as intended is tricky. This works, but should be replaced with something better.
2020-11-29 01:27:59 +01:00
b9fd11ca0d Fix bug where distance would be too close to 0 to register
the furthest_distance now starts at -1 instead of 0 - if there's a point with a distance of 0, that's fine.

This caused issues in the circle previously.
2020-11-29 01:01:03 +01:00
1e9511a3ac Add basic visualization to performance mode 2020-11-29 00:25:18 +01:00
083ba53d67 Add NumberGenerator.h for different methods 2020-11-29 00:24:51 +01:00
535d912742 Likely performance improvement by limiting points
Instead of just deleting points in the triangle, we only check specific ones which are relevant
2020-11-28 23:58:17 +01:00
5 changed files with 190 additions and 36 deletions

12
Line.h
View File

@ -12,6 +12,8 @@ private:
public: public:
Line(Point from, Point to) : m_from(from), m_to(to), m_to_from_origin(to - from) {} Line(Point from, Point to) : m_from(from), m_to(to), m_to_from_origin(to - from) {}
Line() = default;
Point from() const Point from() const
{ {
return m_from; return m_from;
@ -22,6 +24,16 @@ public:
return m_to; return m_to;
} }
void set_from(Point from)
{
m_from = from;
}
void set_to(Point to)
{
m_to = to;
}
// Return true if the given point is to the right of this line. // 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. // False is also returned if the point is directly on the line.
bool is_point_right(Point other) const bool is_point_right(Point other) const

View File

@ -1,5 +1,6 @@
CXX = g++ CXX = g++
CXXFLAGS = -Wall -O3 -g CXXFLAGS = -Wall -O3 -g
SFMLFLAGS = -lsfml-system -lsfml-window -lsfml-graphics
# 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
@ -9,10 +10,10 @@ CXXFLAGS = -Wall -O3 -g
quickhull: main.o Quickhull.h Point.h Line.h Triangle.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 # 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 $(SFMLFLAGS)
performance: performance.cpp performance: performance.cpp Display.o
$(CXX) $(CXXFLAGS) -o performance performance.cpp $(CXX) $(CXXFLAGS) -o performance Display.o performance.cpp $(SFMLFLAGS)
main.o: main.cpp Timing.h main.o: main.cpp Timing.h
$(CXX) $(CXXFLAGS) -c main.cpp $(CXX) $(CXXFLAGS) -c main.cpp

72
NumberGenerator.h Normal file
View File

@ -0,0 +1,72 @@
#include "Point.h"
#include <list>
#include <cstdlib> // for srand, rand
class NumberGenerator
{
private:
static const int OFFSET = 0;
static const int WIDTH = 700;
static const int HEIGHT = 700;
public:
static std::list<Point> get_random_numbers(int valCount)
{
std::list<Point> points;
//srand(static_cast <unsigned> (time(0)));
srand(static_cast <unsigned> (0)); // fixed seed for testing
float x = 0;
float y = 0;
for (int i = 0; i < valCount; ++i)
{
x = OFFSET + static_cast <float> (rand()) / (static_cast <float> (RAND_MAX / (WIDTH - 2*OFFSET)));
y = OFFSET + static_cast <float> (rand()) / (static_cast <float> (RAND_MAX / (HEIGHT - 2*OFFSET)));
points.push_back(Point(x, y));
}
return points;
}
static std::list<Point> get_rectangle_numbers(int valCount)
{
std::list<Point> points;
float diff = (2.f * HEIGHT / valCount);
float x = 0;
float y = 0;
for (int i = 0; i < valCount; ++i)
{
x = (i%2 == 0) ? OFFSET : WIDTH - OFFSET;
y = diff/2 + (i/2) * diff;
points.push_back(Point(x, y));
}
return points;
}
static std::list<Point> get_circle_numbers(int valCount)
{
std::list<Point> points;
float rad = HEIGHT / 2; //8.0f;
float deg = (float)(2 * 3.14159 / valCount);
float x = 0;
float y = 0;
for (int i = 0; i < valCount; ++i)
{
x = WIDTH/2 + rad * cosf(i * deg);
y = HEIGHT/2 + rad * sinf(i * deg);
points.push_back(Point(x, y));
}
return points;
}
};

View File

@ -10,7 +10,7 @@
class Quickhull class Quickhull
{ {
public: 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(INFINITY, 0.0), rightmost(-INFINITY, 0.0); Point leftmost(INFINITY, 0.0), rightmost(-INFINITY, 0.0);
@ -63,7 +63,7 @@ private:
// 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; Point furthest_point;
float furthest_distance = 0.0; float furthest_distance = -1.0;
for (const Point &point : input) for (const Point &point : input)
{ {
@ -75,43 +75,78 @@ private:
} }
} }
// 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_to(point);
// TODO: Both are required, otherwise only one side of the rectangle is
// taken -- why?
if (this_distance == furthest_distance || line.distance_to(point) == 0)
{
output.emplace_back(point);
}
}
input.remove_if([furthest_distance, line](Point point)
{
return furthest_distance == line.distance_to(point);
});
// Hotfix end
output.emplace_back(furthest_point); output.emplace_back(furthest_point);
input.remove(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 // We need to differentiate based on which side the furthest point is on
// TODO: Is there a nicer way to do this? // in order to keep the meaning of left/right consistent.
Point a, b, c;
Line new_line1, new_line2;
if (line.is_point_right(furthest_point)) if (line.is_point_right(furthest_point))
{ {
a = line.from(); // TOOD: It's probably more efficient to set the fields?
b = line.to(); new_line1 = Line(line.to(), furthest_point);
c = furthest_point; new_line2 = Line(furthest_point, line.from());
} }
else else
{ {
a = line.from(); new_line1 = Line(line.from(), furthest_point);
b = furthest_point; new_line2 = Line(furthest_point, line.to());
c = line.to();
} }
Triangle triangle(a, b, c); // TODO: Test if this improves performance
//Triangle triangle(a, b, c);
//input.remove_if([triangle](Point point)
//{
// return triangle.is_point_inside(point);
//});
// Remove points inside this triangle // Get points right of new_line1 and 2
// TODO: I think we can actually skip this, and instead only std::list<Point> left_of_line1, left_of_line2;
// pass points to the left (?) of the individual line to the
// new get_hull_with_line call. That way the ones inside are for (const Point& point : input)
// implicitly ignored.
input.remove_if([triangle](Point point)
{ {
return triangle.is_point_inside(point); 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 // Recursively call get_hull_with_line for each side of the triangle
// TODO: We can skip the original one // TODO: We can skip the original one
get_hull_with_line(input, output, triangle.l1()); get_hull_with_line(left_of_line1, output, new_line1);
get_hull_with_line(input, output, triangle.l2()); get_hull_with_line(left_of_line2, output, new_line2);
get_hull_with_line(input, output, triangle.l3());
} }
}; };

View File

@ -1,22 +1,56 @@
#include "Quickhull.h" #include "Quickhull.h"
#include "NumberGenerator.h"
#include "Display.h"
int main() int main()
{ {
std::list<Point> points = { std::list<Point> points = NumberGenerator::get_rectangle_numbers(100);
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; std::list<Point> hull;
Quickhull::get_hull(points, hull); Quickhull::get_hull(points, hull);
// create the window
sf::RenderWindow window(sf::VideoMode(800, 800), "k-d-tree");
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() + 50, point.y() + 50);
window.draw(normal_p);
}
// Draw hull points
for (const Point &point : hull) for (const Point &point : hull)
{ {
std::cout << point.x() << ", " << point.y() << std::endl; hull_p.setPosition(point.x() + 50, point.y() + 50);
window.draw(hull_p);
} }
// end the current frame
window.display();
}
return 0; return 0;
} }