diff --git a/Display.cpp b/Display.cpp index 3596edb..973329a 100644 --- a/Display.cpp +++ b/Display.cpp @@ -1,16 +1,71 @@ -// make sure sfml is installed: -// linux - sudo apt-get install libsfml-dev -// windows - manual dl from https://www.sfml-dev.org/download.php -#include #include +#include +#include #include "Display.h" #include "Point.h" +#include "Utility.h" +Display::Display (const std::vector &pts) +{ + if (!m_font.loadFromFile("arial.ttf")) + { + std::cerr << "font arial.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.setPosition(pt.x() - OFFSET, pt.y() - OFFSET); // handle with origin or manually calc position + 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); + //m_points.append(shape); + + sf::Text label; + //label.setPosition(pt.x() - OFFSET / 2, pt.y() - OFFSET / 2 - 3); + 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); + //m_points.append(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::RenderWindow window(sf::VideoMode(WIDTH, HEIGHT), "SFML works!"); + 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; @@ -20,19 +75,208 @@ void Display::show() window.close(); } - window.clear(); - for (auto &pt : m_points) + // a) + //update(elapsed); + //render(window); + + /* b) + timeSinceLastUpdate += clock.restart(); + // only handle every few seconds because we need no animations + if (timeSinceLastUpdate >= frameTime) { - sf::CircleShape shape(10.f); - shape.setPosition(pt.x(), pt.y()); - shape.setFillColor(sf::Color::Green); - window.draw(shape); - } - window.display(); + 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(2000)); } } -void Display::setData(std::vector pts) +void Display::update () { - m_points = pts; + // 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 + + 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..."); + + /* EDIT: manual iteration for combining x and x minmax; also need not previous sorting + std::pair minmax = getMinMaxX(m_points); + std::cout << "min: " << minmax.first.x() << ", " << minmax.first.y() << + ", max: " << minmax.second.x() << ", " << minmax.second.y() << std::endl; + + m_hull.setPrimitiveType(sf::Lines); + m_hull.append(sf::Vertex(sf::Vector2f(minmax.first.x(), minmax.first.y()), sf::Color::Blue)); + m_hull.append(sf::Vertex(sf::Vector2f(minmax.second.x(), minmax.second.y()), sf::Color::Blue));*/ + + // 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; + } + + /* TODO: use a convex shape? Or build from vertices in render? + sf::ConvexShape convex; + convex.setPointCount(5); + convex.setPoint(0, topLeft); + convex.setPoint(0, topRight); + convex.setPoint(0, botRight); + convex.setPoint(0, botLeft);*/ + + //m_hull.setPrimitiveType(sf::Lines); + 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); + + for (auto& pt : m_points) + { + sf::Vector2f pos = pt.getPosition(); + float x = pos.x; + float y = pos.y; + if (x < left.x) left = pos; + if (x > right.x) right = pos; + } + + //m_hull.setPrimitiveType(sf::Lines); + m_hull.setPrimitiveType(sf::LineStrip); + m_hull.append(sf::Vertex(left, sf::Color::Blue)); + m_hull.append(sf::Vertex(right, sf::Color::Blue)); + } + } + else if (curStep == 2) + { + // second step: split board and find furthest point + m_textStatus.setString(text + "split board and find furthest point..."); + + for (auto& pt : m_points) + { + pt.setFillColor(sign( + pt.getPosition().x, pt.getPosition().y, + m_hull[0].position.x, m_hull[0].position.y, + m_hull[1].position.x, m_hull[1].position.y) > 0 ? sf::Color::Red : sf::Color::Green); + } + } + else if (curStep == 3) + { + // third step: draw triangle, remove inner points + m_textStatus.setString(text + "find furthest point and draw triangle..."); + + sf::Vector2f pos = m_points[0].getPosition(); + float maxDistance = 0; + for (auto& pt : m_points) + { + float distance = pDistance( + pt.getPosition().x, pt.getPosition().y, + m_hull[0].position.x, m_hull[0].position.y, + m_hull[1].position.x, m_hull[1].position.y); + + if (pt.getFillColor() == sf::Color::Green) + { + std::cout << "distance to green point " << pt.getPosition().x << ", " << pt.getPosition().y << ": " << distance << std::endl; + + } + else if (pt.getFillColor() == sf::Color::Red) + { + std::cout << "distance to red point " << pt.getPosition().x << ", " << pt.getPosition().y << ": " << distance << std::endl; + } + + if (distance > maxDistance) + { + pos = pt.getPosition(); + maxDistance = distance; + } + } + + if (maxDistance > 0) + { + // TODO: not append but insert between last line points + m_hull.append(sf::Vertex(pos, sf::Color::Blue)); + } + } + else if (curStep == 4) + { + // fourth step: remove inner points + m_textStatus.setString(text + "remove inner points..."); + } + else if (curStep == 5) + { + // fifth step: adding new hull point + + // TEMP: TEST check if ends + //if (m_step >= 10) m_points.clear(); + + if (m_points.size() == 0) m_textStatus.setString(text + "finished calculating convex hull"); + else m_textStatus.setString(text + "adding new hull point..."); + } + else m_textStatus.setString(text + "invalid status!"); + + if (curStep != 5 || m_points.size() > 0) m_step++; +} + +void Display::render (sf::RenderWindow &window) +{ + window.clear(sf::Color::White); + + // always print remaining points + /*size_t points = m_points.size(); + for (size_t i = 0; i < points; ++i) + { + drawPoint(window, i); + }*/ + + //for (auto& pt : m_points) // points and labels should have the same size -> combine in one loop + size_t points = m_points.size(); + for (size_t i = 0; i < points; ++i) + { + window.draw(m_points[i]); + window.draw(m_labels[i]); + } + + // draw already calculated hull + //if (step >= 1) + { + //window.draw(&m_hull[0], m_hull.size(), sf::Lines); + window.draw(&m_hull[0], m_hull.getVertexCount(), m_hull.getPrimitiveType()); + } + + window.draw(m_textStatus); + + window.display(); } \ No newline at end of file diff --git a/Display.h b/Display.h index 9ab88ca..76b53bd 100644 --- a/Display.h +++ b/Display.h @@ -1,17 +1,39 @@ #pragma once +#ifndef DISPLAY_H + +// make sure sfml is installed: +// linux - sudo apt-get install libsfml-dev +// windows - manual dl from https://www.sfml-dev.org/download.php +#include class Point; +#define OFFSET 10.f #define WIDTH 800 #define HEIGHT 600 class Display { private: - std::vector m_points; + sf::Font m_font; + sf::Text m_textStatus; + + //std::vector m_points; + std::vector m_points; + std::vector m_labels; + //sf::VertexArray m_points; + //sf::VertexArray m_labels; + + //std::vector m_hull; + sf::VertexArray m_hull; + + unsigned int m_step = 0; + void update(); + void render(sf::RenderWindow &); public: - void show(); + Display(const std::vector &); - void setData (std::vector); -}; \ No newline at end of file + void show(); +}; +#endif // DISPLAY_H \ No newline at end of file diff --git a/README.md b/README.md index 1d99016..98f163f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,10 @@ # quickhull +## TODO: + +- Point class + + - adding "features" such as "IsInTriangle", ... ? + + - direct member access instead of getters? + diff --git a/Utility.h b/Utility.h new file mode 100644 index 0000000..7d2ec37 --- /dev/null +++ b/Utility.h @@ -0,0 +1,91 @@ +#pragma once +#ifndef UTILITY_H + +static float sign(float x, float y, float x1, float y1, float x2, float y2) +{ + return (x - x2) * (y1 - y2) - (x1 - x2) * (y - y2); +} + +static float sign (Point &p1, Point &p2, Point &p3) +{ + return (p1.x() - p3.x()) * (p2.y() - p3.y()) - (p2.x() - p3.x()) * (p1.y() - p3.y()); +} + +static bool IsPointInTriangle(Point &pt, Point &p1, Point &p2, Point &p3) +{ + float d1 = sign(pt, p1, p2); + float d2 = sign(pt, p2, p3); + float d3 = sign(pt, p3, p1); + + bool has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0); + bool has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0); + + return !(has_neg && has_pos); +} + +static float pDistance(float x, float y, float x1, float y1, float x2, float y2) { + + float A = x - x1; + float B = y - y1; + float C = x2 - x1; + float D = y2 - y1; + + float dot = A * C + B * D; + float len_sq = C * C + D * D; + float param = -1; + if (len_sq != 0) // in case of 0 length line + param = dot / len_sq; + + float xx, yy; + if (param < 0) { + xx = x1; + yy = y1; + } + else if (param > 1) { + xx = x2; + yy = y2; + } + else { + xx = x1 + param * C; + yy = y1 + param * D; + } + + float dx = x - xx; + float dy = y - yy; + return dx * dx + dy * dy; + //return sqrt(dx * dx + dy * dy); +} + +static bool SortForMinXMaxY (const Point& a, const Point& b) +{ + if (a.x() != b.x()) + { + return (a.x() < b.x()); + } + return (a.y() > b.y()); +} + +static bool SortForMinYMaxX(const Point& a, const Point& b) +{ + if (a.y() != b.y()) + { + return (a.x() < b.x()); + } + return (a.y() > b.y()); +} + +static void sortPoints (std::vector& pts) +{ + std::sort(pts.begin(), pts.end(), SortForMinXMaxY); +} + +// TODO: what happens if all/more points are on hor/vert line? -> sort for x, than y should handle this +static std::pair getMinMaxX(std::vector& pts) +{ + // TODO: check if already sorted? assume array is sorted? call sort utility function??? + //sortPoints(pts); + std::sort(pts.begin(), pts.end(), SortForMinXMaxY); + + return std::make_pair(pts[0], pts[pts.size() - 1]); +} +#endif // UTILITY_H \ No newline at end of file diff --git a/arial.ttf b/arial.ttf new file mode 100644 index 0000000..ff0815c Binary files /dev/null and b/arial.ttf differ diff --git a/main.cpp b/main.cpp index a0efeff..f6d3240 100644 --- a/main.cpp +++ b/main.cpp @@ -8,6 +8,7 @@ #include "Display.h" #include "Point.h" // TODO: check if there is a usable SFML or c++ class #include "Timing.h" +#include "Utility.h" // TODOs: // - use SFML vec2 instead of Point class @@ -41,10 +42,32 @@ int main (int argc, char **argv) std::cout << "generating random numbers..." << std::endl; //srand(static_cast (time(0))); srand(static_cast (0)); // fixed seed for testing + + // 2) rectangle + //float diff = (2.f * HEIGHT / valCount); + + // 3) circle + //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) { - float x = static_cast (rand()) / (static_cast (RAND_MAX / WIDTH)); - float y = static_cast (rand()) / (static_cast (RAND_MAX / HEIGHT)); + // 1) random generation + //x = OFFSET + static_cast (rand()) / (static_cast (RAND_MAX / (WIDTH - OFFSET))); + //y = OFFSET + static_cast (rand()) / (static_cast (RAND_MAX / (HEIGHT - OFFSET))); + x = OFFSET + static_cast (rand()) / (static_cast (RAND_MAX / (WIDTH - 2*OFFSET))); + y = OFFSET + static_cast (rand()) / (static_cast (RAND_MAX / (HEIGHT - 2*OFFSET))); + + // 2) rectangle + //x = (i%2 == 0) ? OFFSET : WIDTH - OFFSET; + //y = diff/2 + (i/2) * diff; + + // 3) position in circle around center, x/y plane + //x = WIDTH/2 + rad * cosf(i * deg); + //y = HEIGHT/2 + rad * sinf(i * deg); + points.push_back(Point(x, y)); } } @@ -76,7 +99,8 @@ int main (int argc, char **argv) } } - // TODO: need sort here? + // TODO: sort here, once and for all? xD + //sortPoints(points); for (Point& pt : points) { std::cout << "pt: " << pt.x() << ", " << pt.y() << std::endl; @@ -84,9 +108,10 @@ int main (int argc, char **argv) if (vis) { - // TODO: use data as ctor argument? pointer? - Display display; - display.setData(points); + // TEST to check SFML coordinate system + //points.push_back(Point(0, 0)); + + Display display(points); display.show(); }