tsp/renderwindow.cpp

356 lines
10 KiB
C++

//
// Created by natty on 15.7.23.
//
#include "renderwindow.h"
#include "svgpathextractor.h"
#include <iostream>
#include <QPushButton>
#include <random>
#include <QComboBox>
#include <QLabel>
#include <QTimer>
#include <QMenu>
#include <QMenuBar>
#include <QFileDialog>
#include <fstream>
#include <ranges>
std::unique_ptr<Strategy> strategyFromName(const std::string& name) {
if (name == "Anna") {
return std::make_unique<AnnaStrategy>();
} else {
throw std::runtime_error("Unknown strategy");
}
}
RenderWindow::RenderWindow() : window(nullptr), renderer(nullptr) {
this->mainWindowWidget = new QWidget();
this->layout = new QVBoxLayout(this->mainWindowWidget);
auto* menu = new QMenuBar();
auto* fileMenu = menu->addMenu("File");
auto* openAction = fileMenu->addAction("Open pattern");
openAction->setShortcut(QKeySequence::Open);
QObject::connect(openAction, &QAction::triggered, this, [this](){
auto filename = QFileDialog::getOpenFileName(this, "Open pattern", "", "SVG path files (*.*)");
if (!filename.isNull()) {
this->loadPattern(filename);
}
});
auto* quitAction = fileMenu->addAction("Quit");
quitAction->setShortcut(QKeySequence::Quit);
quitAction->setMenuRole(QAction::QuitRole);
QObject::connect(quitAction, &QAction::triggered, this, &RenderWindow::close);
this->setMenuBar(menu);
this->renderArea = new QWidget();
this->layout->addWidget(this->renderArea);
this->controlsLayout = new QHBoxLayout();
this->layout->addLayout(this->controlsLayout);
auto* strategyLabel = new QLabel("Strategy:");
this->controlsLayout->addWidget(strategyLabel);
this->strategySelector = new QComboBox();
this->strategySelector->addItem("Anna");
QObject::connect(this->strategySelector, &QComboBox::currentIndexChanged, this, &RenderWindow::resetState);
this->controlsLayout->addWidget(this->strategySelector);
this->controlsLayout->addStretch();
this->distanceLabel = new QLabel("Distance: 0");
this->controlsLayout->addWidget(this->distanceLabel);
this->controlsLayout->addStretch();
auto *resetButton = new QPushButton("Reset");
QObject::connect(resetButton, &QPushButton::clicked, this, &RenderWindow::resetState);
this->controlsLayout->addWidget(resetButton);
auto *hardResetButton = new QPushButton("Reset (new vertices)");
QObject::connect(hardResetButton, &QPushButton::clicked, this, &RenderWindow::hardResetState);
this->controlsLayout->addWidget(hardResetButton);
auto* solveButton = new QPushButton("Solve");
QObject::connect(solveButton, &QPushButton::clicked, this, &RenderWindow::solve);
this->controlsLayout->addWidget(solveButton);
this->playButton = new QPushButton("Play");
this->playButton->setCheckable(true);
QObject::connect(this->playButton, &QPushButton::toggled, this, &RenderWindow::togglePlay);
this->controlsLayout->addWidget(this->playButton);
auto* stepButton = new QPushButton("Step");
QObject::connect(stepButton, &QPushButton::clicked, this, &RenderWindow::step);
this->controlsLayout->addWidget(stepButton);
this->setCentralWidget(this->mainWindowWidget);
this->animTimer = new QTimer(this);
QObject::connect(this->animTimer, &QTimer::timeout, this, &RenderWindow::step);
this->renderArea->setUpdatesEnabled(false);
this->renderArea->setFixedSize(800, 800);
SDL_SetHint(SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT, "1");
this->window = SDL_CreateWindowFrom(reinterpret_cast<const void*>(this->renderArea->winId()));
if (this->window == nullptr) {
std::cerr << "Error creating SDL window: " << SDL_GetError() << std::endl;
throw std::runtime_error("Error creating SDL window");
}
this->renderer = SDL_CreateRenderer(this->window, -1, SDL_RENDERER_ACCELERATED);
if (this->renderer == nullptr) {
std::cerr << "Error creating SDL renderer: " << SDL_GetError() << std::endl;
throw std::runtime_error("Error creating SDL renderer");
}
this->hardResetState();
}
void RenderWindow::hardResetState()
{
this->state.vertices = {};
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<double> dist(100.0, 700.0);
for (size_t i = 0; i < 100; i++) {
this->state.vertices.push_back({
.x = dist(gen),
.y = dist(gen)
});
}
this->resetState();
}
void RenderWindow::resetState() {
this->animTimer->stop();
this->playButton->setChecked(false);
this->strategy = strategyFromName(this->strategySelector->currentText().toStdString());
this->state.edges = {};
this->render();
}
void RenderWindow::paintEvent(QPaintEvent* event)
{
QMainWindow::paintEvent(event);
this->render();
}
void RenderWindow::render() {
SDL_SetRenderDrawColor(this->renderer, 0, 0, 0, 255);
SDL_RenderClear(this->renderer);
SDL_SetRenderDrawColor(this->renderer, 255, 255, 255, 255);
for (auto& vertex : this->state.vertices) {
SDL_Rect rect = {
static_cast<int>(vertex.x - 3),
static_cast<int>(vertex.y - 3),
6,
6
};
SDL_RenderFillRect(this->renderer, &rect);
}
double dist = 0.0;
for (auto& edge : this->state.edges) {
SDL_SetRenderDrawColor(this->renderer, 255, 255, 0, 255);
dist += std::hypot(
this->state.vertices[edge.from].x - this->state.vertices[edge.to].x,
this->state.vertices[edge.from].y - this->state.vertices[edge.to].y
);
SDL_RenderDrawLine(
this->renderer,
static_cast<int>(this->state.vertices[edge.from].x),
static_cast<int>(this->state.vertices[edge.from].y),
static_cast<int>(this->state.vertices[edge.to].x),
static_cast<int>(this->state.vertices[edge.to].y)
);
}
this->distanceLabel->setText(QString("Distance: %1").arg(dist));
SDL_RenderPresent(this->renderer);
}
void RenderWindow::step() {
if (this->strategy->step(this->state)) {
this->animTimer->stop();
this->playButton->setChecked(false);
}
this->render();
}
void RenderWindow::solve() {
if (this->state.edges.size() == this->state.vertices.size()) {
this->resetState();
}
while (!this->strategy->step(this->state)) {}
this->render();
}
RenderWindow::~RenderWindow() {
if (this->renderer != nullptr) {
SDL_DestroyRenderer(this->renderer);
this->renderer = nullptr;
}
if (this->window != nullptr) {
SDL_DestroyWindow(this->window);
this->window = nullptr;
}
}
void RenderWindow::togglePlay(bool play)
{
if (play) {
if (this->state.edges.size() == this->state.vertices.size()) {
this->resetState();
}
this->animTimer->start(5);
} else {
this->animTimer->stop();
}
}
void RenderWindow::loadPattern(const QString& qString)
{
try {
std::ifstream file(qString.toStdString());
file.seekg(0, std::ios::end);
size_t size = file.tellg();
std::string buffer(size, '\0');
file.seekg(0);
file.read(&buffer[0], static_cast<std::streamsize>(size));
auto vertices = extractVertices(buffer);
const auto min_x = std::ranges::min(vertices | std::ranges::views::transform([](const Vertex& v) { return v.x; }));
const auto min_y = std::ranges::min(vertices | std::ranges::views::transform([](const Vertex& v) { return v.y; }));
const auto max_x = std::ranges::max(vertices | std::ranges::views::transform([](const Vertex& v) { return v.x; }));
const auto max_y = std::ranges::max(vertices | std::ranges::views::transform([](const Vertex& v) { return v.y; }));
const double scale = std::min(600.0 / (max_x - min_x), 600.0 / (max_y - min_y));
for (auto& vertex : vertices) {
vertex.x = (vertex.x - min_x) * scale + 100.0;
vertex.y = (vertex.y - min_y) * scale + 100.0;
}
this->state.vertices = vertices;
this->resetState();
} catch (std::exception& e) {
std::cerr << "Error loading pattern: " << e.what() << std::endl;
}
}
bool AnnaStrategy::step(State& state) {
if (!this->init) {
this->init = true;
this->unvisited = {};
for (size_t i = 0; i < state.vertices.size(); i++) {
this->unvisited.push_back(i);
}
std::shuffle(this->unvisited.begin(), this->unvisited.end(), std::mt19937(std::random_device()()));
}
if (state.vertices.size() < 2) {
return true;
}
if (state.vertices.size() == 2) {
state.edges = {{
.from = 0,
.to = 1
}};
return true;
}
if (this->unvisited.empty())
return true;
if (state.vertices.size() - this->unvisited.size() == 0) {
size_t a, b, c;
a = this->unvisited.back();
this->unvisited.pop_back();
b = this->unvisited.back();
this->unvisited.pop_back();
c = this->unvisited.back();
this->unvisited.pop_back();
state.edges = {{
.from = a,
.to = b
}, {
.from = b,
.to = c
}, {
.from = c,
.to = a
}};
return false;
}
size_t i = this->unvisited.back();
this->unvisited.pop_back();
const auto& vertex = state.vertices[i];
auto& closest_edge = *std::min_element(state.edges.begin(), state.edges.end(), [&state, &vertex](const Edge& a, const Edge& b) {
return a.distanceOf(state, vertex) < b.distanceOf(state, vertex);
});
size_t old_to = closest_edge.to;
closest_edge.to = i;
state.edges.push_back({
.from = i,
.to = old_to
});
return false;
}
double Edge::distanceOf(const State& state, const Vertex& vertex) const {
// Adapted from https://stackoverflow.com/a/1501725
auto x1 = state.vertices[this->from].x;
auto y1 = state.vertices[this->from].y;
auto x2 = state.vertices[this->to].x;
auto y2 = state.vertices[this->to].y;
auto dx = x2 - x1;
auto dy = y2 - y1;
auto l2 = dx * dx + dy * dy;
constexpr auto epsilon = 0.0001;
if (l2 < epsilon)
return std::hypot(vertex.x - x1, vertex.y - y1);
const double t = std::max(0.0, std::min(1.0, ((vertex.x - x1) * dx + (vertex.y - y1) * dy) / l2));
const double projection_x = x1 + t * dx;
const double projection_y = y1 + t * dy;
return std::hypot(vertex.x - projection_x, vertex.y - projection_y);
}