#include #include #include #include "Display.h" #include "Point.h" #include "Utility.h" Display::Display (const std::vector &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–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–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(); }