Загрузка данных
#include <SFML/Graphics.hpp>
#include <vector>
#include <algorithm>
#include <iostream>
struct Point {
double x, y;
};
// Векторное (косое) произведение векторов AB и AC.
// > 0 — левый поворот (против часовой стрелки)
// = 0 — точки коллинеарны
// < 0 — правый поворот
double cross(const Point& a, const Point& b, const Point& c) {
return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
}
// Алгоритм Эндрю (monotone chain). Возвращает вершины оболочки
// в порядке против часовой стрелки.
std::vector<Point> andrewHull(std::vector<Point> pts) {
int n = (int)pts.size();
if (n < 2) return pts;
std::sort(pts.begin(), pts.end(), [](const Point& a, const Point& b) {
if (a.x != b.x) return a.x < b.x;
return a.y < b.y;
});
// Удалим дубликаты
pts.erase(std::unique(pts.begin(), pts.end(),
[](const Point& a, const Point& b){ return a.x == b.x && a.y == b.y; }),
pts.end());
n = (int)pts.size();
if (n < 2) return pts;
std::vector<Point> hull;
hull.reserve(2 * n);
// Нижняя оболочка
for (const auto& p : pts) {
while (hull.size() >= 2 &&
cross(hull[hull.size() - 2], hull[hull.size() - 1], p) <= 0)
hull.pop_back();
hull.push_back(p);
}
// Верхняя оболочка
int lower_size = (int)hull.size() + 1; // +1, т.к. последняя точка нижней = первая верхней
for (int i = (int)pts.size() - 2; i >= 0; --i) {
const Point& p = pts[i];
while ((int)hull.size() >= lower_size &&
cross(hull[hull.size() - 2], hull[hull.size() - 1], p) <= 0)
hull.pop_back();
hull.push_back(p);
}
hull.pop_back(); // последняя точка совпадает с первой
return hull;
}
// Простая экранная "кнопка"
struct Button {
sf::RectangleShape shape;
sf::Text label;
bool contains(sf::Vector2i p) const {
return shape.getGlobalBounds().contains((float)p.x, (float)p.y);
}
};
int main() {
const unsigned W = 900, H = 650;
sf::RenderWindow window(sf::VideoMode(W, H), "Convex Hull — Andrew's algorithm");
window.setFramerateLimit(60);
sf::Font font;
if (!font.loadFromFile("arial.ttf")) {
// В Linux/Mac можно подставить другой шрифт, например:
// font.loadFromFile("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf");
std::cerr << "Warning: font not loaded, text will be invisible.\n";
}
// ----- Кнопки -----
Button btnBuild, btnClear;
auto makeButton = [&](Button& b, float x, const std::string& txt) {
b.shape.setSize({110.f, 36.f});
b.shape.setPosition(x, 12.f);
b.shape.setFillColor(sf::Color(60, 60, 80));
b.shape.setOutlineColor(sf::Color::White);
b.shape.setOutlineThickness(1.f);
b.label.setFont(font);
b.label.setString(txt);
b.label.setCharacterSize(18);
b.label.setFillColor(sf::Color::White);
sf::FloatRect tb = b.label.getLocalBounds();
b.label.setPosition(x + 55.f - tb.width / 2.f - tb.left,
12.f + 18.f - tb.height / 2.f - tb.top);
};
makeButton(btnBuild, W - 240.f, "Build (B)");
makeButton(btnClear, W - 120.f, "Clear (C)");
// ----- Данные -----
std::vector<Point> points;
std::vector<Point> hull;
const float AREA_TOP = 60.f; // верхняя граница рабочей области
sf::Text hint(font);
hint.setString("LMB — add point | B — build hull | C — clear");
hint.setCharacterSize(16);
hint.setFillColor(sf::Color::White);
hint.setPosition(12.f, 20.f);
// ----- Главный цикл -----
while (window.isOpen()) {
sf::Event ev;
while (window.pollEvent(ev)) {
if (ev.type == sf::Event::Closed) window.close();
if (ev.type == sf::Event::MouseButtonPressed &&
ev.mouseButton.button == sf::Mouse::Left) {
sf::Vector2i m(ev.mouseButton.x, ev.mouseButton.y);
if (btnBuild.contains(m)) {
hull = andrewHull(points);
} else if (btnClear.contains(m)) {
points.clear();
hull.clear();
} else if (m.y > AREA_TOP) {
points.push_back({(double)m.x, (double)m.y});
}
}
if (ev.type == sf::Event::KeyPressed) {
if (ev.key.code == sf::Keyboard::B) hull = andrewHull(points);
if (ev.key.code == sf::Keyboard::C) { points.clear(); hull.clear(); }
if (ev.key.code == sf::Keyboard::Escape) window.close();
}
}
window.clear(sf::Color(30, 30, 40));
// Рабочая область
sf::RectangleShape area({(float)W, (float)H - AREA_TOP});
area.setPosition(0.f, AREA_TOP);
area.setFillColor(sf::Color(45, 45, 60));
window.draw(area);
// Выпуклая оболочка
if (hull.size() >= 2) {
sf::VertexArray poly(sf::LineStrip, hull.size() + 1);
for (size_t i = 0; i < hull.size(); ++i)
poly[i].position = sf::Vector2f((float)hull[i].x, (float)hull[i].y);
poly[hull.size()].position = poly[0].position; // замкнуть
for (size_t i = 0; i < poly.getVertexCount(); ++i)
poly[i].color = sf::Color(80, 220, 120);
window.draw(poly);
// Полупрозрачная заливка (треугольный веер от первой вершины)
if (hull.size() >= 3) {
sf::VertexArray fill(sf::TrianglesFan, hull.size() + 1);
fill[0].position = sf::Vector2f((float)hull[0].x, (float)hull[0].y);
fill[0].color = sf::Color(80, 220, 120, 50);
for (size_t i = 0; i < hull.size(); ++i) {
fill[i + 1].position = sf::Vector2f((float)hull[i].x, (float)hull[i].y);
fill[i + 1].color = sf::Color(80, 220, 120, 50);
}
window.draw(fill);
}
}
// Точки
for (const auto& p : points) {
sf::CircleShape c(5.f);
c.setFillColor(sf::Color(255, 220, 80));
c.setOrigin(5.f, 5.f);
c.setPosition((float)p.x, (float)p.y);
window.draw(c);
}
// Подсказка и кнопки
window.draw(hint);
window.draw(btnBuild.shape); window.draw(btnBuild.label);
window.draw(btnClear.shape); window.draw(btnClear.label);
window.display();
}
return 0;
}