Merge branch 'main' of http://git.hexaquo.at/mgs/quickhull into main
This commit is contained in:
commit
0d49399240
50
Line.h
Normal file
50
Line.h
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cmath> // 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);
|
||||||
|
}
|
||||||
|
};
|
13
Makefile
13
Makefile
@ -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
|
||||||
@ -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:
|
# 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=<sfml-install-path>/lib && ./sfml-app
|
# export LD_LIBRARY_PATH=<sfml-install-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
|
# 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
|
main.o: main.cpp Timing.h
|
||||||
$(CXX) $(CXXFLAGS) -c main.cpp
|
$(CXX) $(CXXFLAGS) -c main.cpp
|
||||||
@ -18,7 +21,5 @@ Display.o: Display.h
|
|||||||
|
|
||||||
Timing.o: Timing.h
|
Timing.o: Timing.h
|
||||||
|
|
||||||
Quickhull.o: Quickhull.h
|
|
||||||
|
|
||||||
clean :
|
clean :
|
||||||
-rm *.o quickhull
|
-rm *.o quickhull performance
|
||||||
|
37
Point.h
37
Point.h
@ -8,13 +8,46 @@ private:
|
|||||||
public:
|
public:
|
||||||
Point(float x, float y) : m_x(x), m_y(y) {}
|
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;
|
return m_x;
|
||||||
}
|
}
|
||||||
|
|
||||||
float y () const
|
float y() const
|
||||||
{
|
{
|
||||||
return m_y;
|
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());
|
||||||
|
}
|
||||||
};
|
};
|
@ -1,26 +0,0 @@
|
|||||||
|
|
||||||
#include "Quickhull.h"
|
|
||||||
|
|
||||||
void Quickhull::get_hull(std::vector<Point> &input, std::vector<Point> &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<Point> &input, std::vector<Point> &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
|
|
||||||
}
|
|
109
Quickhull.h
109
Quickhull.h
@ -1,14 +1,117 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
#include <list>
|
||||||
|
#include <bits/stdc++.h> // For INT_MIN & INT_MAX
|
||||||
|
|
||||||
#include "Point.h"
|
#include "Point.h"
|
||||||
|
#include "Line.h"
|
||||||
|
#include "Triangle.h"
|
||||||
|
|
||||||
class Quickhull
|
class Quickhull
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static void get_hull(std::vector<Point> &, std::vector<Point> &);
|
static void get_hull(std::list<Point> &input, std::list<Point> &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<Point> 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:
|
private:
|
||||||
static void get_hull_with_line(std::vector<Point> &, std::vector<Point> &, Point, Point);
|
static void get_hull_with_line(std::list<Point> &input, std::list<Point> &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());
|
||||||
|
}
|
||||||
};
|
};
|
37
Triangle.h
Normal file
37
Triangle.h
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
};
|
22
performance.cpp
Normal file
22
performance.cpp
Normal 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;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user