418 lines
14 KiB
C++
418 lines
14 KiB
C++
|
||
#include <iostream>
|
||
#include <chrono>
|
||
#include <thread>
|
||
|
||
#include "Display.h"
|
||
#include "Point.h"
|
||
#include "Utility.h"
|
||
|
||
Display::Display (const std::vector<Point> &pts, int stepSize) : m_stepSize(stepSize)
|
||
{
|
||
if (!m_font.loadFromFile("Resources/LiberationSans-Regular.ttf"))
|
||
{
|
||
std::cerr << "font LiberationSans-Regular.ttf could not be loaded!" << std::endl;
|
||
}
|
||
|
||
m_textStatus.setPosition(OFFSET, HEIGHT - 2*OFFSET);
|
||
m_textStatus.setFont(m_font);
|
||
m_textStatus.setString("initializing...");
|
||
m_textStatus.setCharacterSize(12);
|
||
m_textStatus.setFillColor(sf::Color::Black);
|
||
|
||
size_t points = pts.size();
|
||
for (size_t i = 0; i < points; ++i)
|
||
{
|
||
const Point& pt = pts[i];
|
||
sf::CircleShape shape(OFFSET);
|
||
shape.setOrigin(OFFSET, OFFSET);
|
||
shape.setPosition(pt.x(), pt.y());
|
||
shape.setFillColor(sf::Color::Green);
|
||
shape.setOutlineThickness(1.f);
|
||
shape.setOutlineColor(sf::Color::Black);
|
||
m_points.push_back(shape);
|
||
|
||
sf::Text label;
|
||
label.setOrigin(OFFSET / 2 - 1, OFFSET / 2 + 3);
|
||
label.setPosition(pt.x(), pt.y());
|
||
label.setFont(m_font);
|
||
label.setString(std::to_string(i));
|
||
label.setCharacterSize(12);
|
||
label.setFillColor(sf::Color::Black);
|
||
m_labels.push_back(label);
|
||
}
|
||
}
|
||
|
||
// multiple options possible:
|
||
// a) draw every frame, using elapsed time for updates
|
||
// --> not really needed because we have no animations... also uses a lot of performance
|
||
// b) checking every frame if elapsed time since last tick is larger than a specified offset, then draw the field
|
||
// --> mostly doing nothing but still uses a lot of performance O_o
|
||
// c) just start a simple (c++11 <3) non-busy sleep
|
||
// --> may delay user input / events but don't care atm :p
|
||
void Display::show()
|
||
{
|
||
sf::ContextSettings settings;
|
||
settings.antialiasingLevel = 8;
|
||
|
||
sf::RenderWindow window(sf::VideoMode(WIDTH, HEIGHT), "ALGO Prog2: Quickhull - visualization", sf::Style::Default, settings);
|
||
/* b)
|
||
window.setFramerateLimit(0.2);
|
||
|
||
sf::Clock clock;
|
||
sf::Time frameTime = sf::seconds(2); //sf::seconds(1.f / 60.f)
|
||
sf::Time timeSinceLastUpdate = frameTime; //sf::Time::Zero;*/
|
||
while (window.isOpen())
|
||
{
|
||
sf::Event event;
|
||
while (window.pollEvent(event))
|
||
{
|
||
if (event.type == sf::Event::Closed)
|
||
window.close();
|
||
}
|
||
|
||
// a)
|
||
//update(elapsed);
|
||
//render(window);
|
||
|
||
/* b)
|
||
timeSinceLastUpdate += clock.restart();
|
||
// only handle every few seconds because we need no animations
|
||
if (timeSinceLastUpdate >= frameTime)
|
||
{
|
||
std::cout << "entering update and render" << std::endl;
|
||
|
||
// start by getting the most left and right point
|
||
timeSinceLastUpdate -= frameTime;
|
||
|
||
update();
|
||
render(window);
|
||
}*/
|
||
|
||
// c)
|
||
// choose a simple sleep
|
||
update();
|
||
render(window);
|
||
|
||
std::this_thread::sleep_for(std::chrono::milliseconds(m_stepSize));
|
||
}
|
||
}
|
||
|
||
void Display::update ()
|
||
{
|
||
// TODO: maybe include Akl<6B>Toussaint heuristic first?
|
||
// https://en.wikipedia.org/wiki/Convex_hull_algorithms#Akl%E2%80%93Toussaint_heuristic
|
||
|
||
unsigned int curStep = (m_step > 5) ? ((m_step - 2) % 4 + 2) : (m_step % 6); // skip init and first step after first run
|
||
if (m_stepSize == 0 && curStep > 0)
|
||
{
|
||
std::cout << "any key to continue with next step...";
|
||
std::cin.get();
|
||
}
|
||
|
||
std::string text = "(" + std::to_string(m_step) + ") step " + std::to_string(curStep) + ": ";
|
||
if (curStep == 1)
|
||
{
|
||
// first step: select min - max x coordinates
|
||
m_textStatus.setString(text + "select min - max x coordinates...");
|
||
|
||
// if use Akl<6B>Toussaint heuristic
|
||
bool useAkl = false;
|
||
if (useAkl)
|
||
{
|
||
sf::Vector2f topLeft(WIDTH, HEIGHT);
|
||
sf::Vector2f topRight(0, HEIGHT);
|
||
sf::Vector2f botLeft(WIDTH, 0);
|
||
sf::Vector2f botRight(0, 0);
|
||
|
||
for (auto& pt : m_points)
|
||
{
|
||
sf::Vector2f pos = pt.getPosition();
|
||
float x = pos.x;
|
||
float y = pos.y;
|
||
// TODO: only check explicit x and y seperate!
|
||
if (x < topLeft.x && y < topLeft.y) topLeft = pos;
|
||
if (x > topRight.x && y < topLeft.y) topRight = pos;
|
||
if (x < botLeft.x && y > botLeft.y) botLeft = pos;
|
||
if (x > botRight.x && y > botRight.y) botRight = pos;
|
||
}
|
||
|
||
m_hull.setPrimitiveType(sf::LineStrip);
|
||
m_hull.append(sf::Vertex(topLeft, sf::Color::Blue));
|
||
m_hull.append(sf::Vertex(topRight, sf::Color::Blue));
|
||
m_hull.append(sf::Vertex(botRight, sf::Color::Blue));
|
||
m_hull.append(sf::Vertex(botLeft, sf::Color::Blue));
|
||
m_hull.append(sf::Vertex(topLeft, sf::Color::Blue));
|
||
}
|
||
else
|
||
{
|
||
sf::Vector2f left(WIDTH, HEIGHT);
|
||
sf::Vector2f right(0, 0);
|
||
|
||
int i_left = WIDTH;
|
||
int i_right = 0;
|
||
|
||
size_t points = m_points.size();
|
||
for (size_t i = 0; i < points; ++i)
|
||
{
|
||
sf::Vector2f pos = m_points[i].getPosition();
|
||
float x = pos.x;
|
||
if (x < left.x)
|
||
{
|
||
i_left = i;
|
||
left = pos;
|
||
}
|
||
if (x > right.x)
|
||
{
|
||
i_right = i;
|
||
right = pos;
|
||
}
|
||
}
|
||
|
||
// adding starting points from most x to left
|
||
if (i_left < i_right)
|
||
{
|
||
std::swap(i_left, i_right);
|
||
}
|
||
for (int i = 0; i < 2; ++i)
|
||
{
|
||
int pos = (i == 0) ? i_left : i_right;
|
||
m_points[pos].setFillColor(sf::Color::Blue);
|
||
m_hullPoints.push_back(m_points[pos]);
|
||
m_points.erase(m_points.begin() + pos);
|
||
}
|
||
|
||
m_lines.push_back(Line(Point(left.x, left.y), Point(right.x, right.y)));
|
||
m_lines.push_back(Line(Point(right.x, right.y), Point(left.x, left.y))); // add first line in both directions to work with "right side"
|
||
m_curLineIdx = 1;
|
||
m_curLine = m_lines[1];
|
||
|
||
// TODO: split points and use map
|
||
}
|
||
}
|
||
else if (curStep == 2)
|
||
{
|
||
// second step: split board
|
||
m_textStatus.setString(text + "split board...");
|
||
|
||
// get current line with points
|
||
while (m_curLineIdx == -1)
|
||
{
|
||
size_t lines = m_lines.size();
|
||
if (lines < 1) // no more open lines -> fin
|
||
{
|
||
std::cout << "## no more open lines -> fin!" << std::endl;
|
||
m_points.clear();
|
||
if (m_points.size() == 0) m_textStatus.setString(text + "finished calculating convex hull in " + std::to_string((m_step / 4 + 1)) + " cycles");
|
||
return;
|
||
}
|
||
|
||
Line cand = m_lines[lines - 1];
|
||
int positives = 0;
|
||
for (auto& pt : m_points)
|
||
{
|
||
positives += (sign(pt.getPosition().x, pt.getPosition().y,
|
||
cand.from().x(), cand.from().y(),
|
||
cand.to().x(), cand.to().y()) > 0) ? 1 : 0;
|
||
}
|
||
|
||
if (positives > 0)
|
||
{
|
||
m_curLineIdx = lines - 1;
|
||
m_curLine = m_lines[lines - 1];
|
||
}
|
||
else
|
||
{
|
||
m_lines.pop_back();
|
||
}
|
||
}
|
||
|
||
for (auto& pt : m_points)
|
||
{
|
||
pt.setFillColor(sign(
|
||
pt.getPosition().x, pt.getPosition().y,
|
||
m_lines[m_curLineIdx].from().x(), m_lines[m_curLineIdx].from().y(),
|
||
m_lines[m_curLineIdx].to().x(), m_lines[m_curLineIdx].to().y()) > 0 ? sf::Color::Red : sf::Color::Green);
|
||
}
|
||
}
|
||
else if (curStep == 3)
|
||
{
|
||
// get line with more than one point -> or use m_curLine
|
||
// calc furthest point
|
||
// create new lines
|
||
|
||
// third step: draw triangle, remove inner points
|
||
m_textStatus.setString(text + "find furthest point and draw triangle...");
|
||
|
||
sf::Vector2f pos;
|
||
float maxDistance = 0;
|
||
int i_cand = 0;
|
||
size_t points = m_points.size();
|
||
for (size_t i = 0; i < points; ++i)
|
||
{
|
||
if (m_points[i].getFillColor() == sf::Color::Green) continue;
|
||
|
||
sf::Vector2f cand = m_points[i].getPosition();
|
||
float distance = pDistance(
|
||
cand.x, cand.y,
|
||
m_lines[m_curLineIdx].from().x(), m_lines[m_curLineIdx].from().y(),
|
||
m_lines[m_curLineIdx].to().x(), m_lines[m_curLineIdx].from().y());
|
||
|
||
if (distance > maxDistance)
|
||
{
|
||
i_cand = i;
|
||
pos = cand;
|
||
maxDistance = distance;
|
||
}
|
||
}
|
||
|
||
if (maxDistance > 0)
|
||
{
|
||
// move point from all points to hull points
|
||
m_points[i_cand].setFillColor(sf::Color::Blue);
|
||
m_hullPoints.push_back(m_points[i_cand]);
|
||
std::cout << "removing pt " << m_points[i_cand].getPosition().x << ", " << m_points[i_cand].getPosition().y << " at index " << std::to_string(i_cand) << " after inserted to hullpoints" << std::endl;
|
||
m_points.erase(m_points.begin() + i_cand);
|
||
|
||
Point to = m_lines[m_curLineIdx].to();
|
||
Point from = m_lines[m_curLineIdx].from();
|
||
m_lines.push_back(Line(Point(pos.x, pos.y),to));
|
||
m_lines[m_lines.size() - 2] = Line(from, Point(pos.x, pos.y)); // updates list entry
|
||
}
|
||
else
|
||
{
|
||
m_lines.pop_back(); // remove last element -> TODO: directly use stack?
|
||
}
|
||
}
|
||
else if (curStep == 4)
|
||
{
|
||
// fourth step: remove inner points
|
||
m_textStatus.setString(text + "remove inner points...");
|
||
|
||
size_t lines = m_lines.size() - 1;
|
||
if (lines > 0)
|
||
{
|
||
// assure clockwise order
|
||
Point pt1 = m_lines[lines - 1].from();
|
||
Point pt2 = m_lines[lines - 1].to();
|
||
Point pt3 = m_lines[lines].to();
|
||
if (sign(pt2, pt1, pt3) > 0)
|
||
{
|
||
std::swap(pt1, pt2);
|
||
}
|
||
|
||
size_t points = m_points.size();
|
||
for (int i = points - 1; i >= 0; i--)
|
||
{
|
||
Point pt(m_points[i].getPosition().x, m_points[i].getPosition().y);
|
||
if (IsPointInTriangle(pt, pt1, pt2, pt3))
|
||
{
|
||
std::cout << "remove pt inside triangle -> " << i << " with pos: " << pt.x() << ", " << pt.y() << std::endl;
|
||
m_points.erase(m_points.begin() + i);
|
||
}
|
||
else
|
||
{
|
||
m_points[i].setFillColor(sf::Color::Green);
|
||
}
|
||
}
|
||
}
|
||
//m_curLine = nullptr;
|
||
//m_curLineIdx = -1;
|
||
}
|
||
else if (curStep == 5)
|
||
{
|
||
// fifth step: adding new hull point
|
||
|
||
m_curLineIdx = -1;
|
||
//m_curLine = m_lines[lines - 1];
|
||
|
||
if (m_points.size() == 0) m_textStatus.setString(text + "finished calculating convex hull in " + std::to_string((m_step / 4 + 1)) + " cycles");
|
||
else m_textStatus.setString(text + "adding new hull point...");
|
||
}
|
||
else if (m_step > 0) m_textStatus.setString(text + "invalid status!");
|
||
|
||
if (curStep != 5 || m_points.size() > 0) m_step++;
|
||
}
|
||
|
||
bool less (sf::CircleShape a, sf::CircleShape b)
|
||
{
|
||
float centerx = WIDTH / 2;
|
||
float centery = HEIGHT / 2;
|
||
float ax = a.getPosition().x;
|
||
float ay = a.getPosition().y;
|
||
float bx = b.getPosition().x;
|
||
float by = b.getPosition().y;
|
||
if (ax - centerx >= 0 && bx - centerx < 0)
|
||
return true;
|
||
if (ax - centerx < 0 && bx - centerx >= 0)
|
||
return false;
|
||
if (ax - centerx == 0 && bx - centerx == 0) {
|
||
if (ay - centery >= 0 || by - centery >= 0)
|
||
return ay > by;
|
||
return by > ay;
|
||
}
|
||
|
||
// compute the cross product of vectors (center -> a) x (center -> b)
|
||
int det = (ax - centerx) * (by - centery) - (bx - centerx) * (ay - centery);
|
||
if (det < 0)
|
||
return true;
|
||
if (det > 0)
|
||
return false;
|
||
|
||
// points a and b are on the same line from the center
|
||
// check which point is closer to the center
|
||
int d1 = (ax - centerx) * (ax - centerx) + (ay - centery) * (ay - centery);
|
||
int d2 = (bx - centerx) * (bx - centerx) + (by - centery) * (by - centery);
|
||
return d1 > d2;
|
||
}
|
||
|
||
void Display::render (sf::RenderWindow &window)
|
||
{
|
||
window.clear(sf::Color::White);
|
||
|
||
// draw already calculated hull points
|
||
std::sort(m_hullPoints.begin(), m_hullPoints.end(), less); // sort clockwise
|
||
|
||
size_t elements = m_hullPoints.size();
|
||
for (size_t i = 0; i < elements; ++i)
|
||
{
|
||
window.draw(m_hullPoints[i]);
|
||
|
||
sf::Vertex ptTo;
|
||
if (i < elements - 1) ptTo = sf::Vertex(m_hullPoints[i + 1].getPosition(), sf::Color::Blue);
|
||
else ptTo = sf::Vertex(m_hullPoints[0].getPosition(), sf::Color::Blue);
|
||
sf::Vertex line[] = { sf::Vertex(sf::Vector2f(m_hullPoints[i].getPosition()), sf::Color::Blue), ptTo };
|
||
|
||
window.draw(line, 2, sf::Lines);
|
||
}
|
||
|
||
// always print remaining points
|
||
elements = m_points.size();
|
||
for (size_t i = 0; i < elements; ++i)
|
||
{
|
||
window.draw(m_points[i]);
|
||
}
|
||
|
||
// seperately print labels for points
|
||
elements = m_labels.size();
|
||
for (size_t i = 0; i < elements; ++i)
|
||
{
|
||
window.draw(m_labels[i]);
|
||
}
|
||
|
||
// draw line the algorithm is currently working on
|
||
if (m_curLineIdx != -1)
|
||
{
|
||
sf::Vertex line[] =
|
||
{
|
||
sf::Vertex(sf::Vector2f(m_curLine.from().x(), m_curLine.from().y()), sf::Color::Red),
|
||
sf::Vertex(sf::Vector2f(m_curLine.to().x(), m_curLine.to().y()), sf::Color::Red)
|
||
};
|
||
window.draw(line, 2, sf::Lines);
|
||
}
|
||
|
||
// show amount of steps and current status
|
||
window.draw(m_textStatus);
|
||
|
||
window.display();
|
||
} |