// // Created by natty on 15.7.23. // #include "renderwindow.h" #include "svgpathextractor.h" #include #include #include #include #include #include #include #include #include #include #include std::unique_ptr strategyFromName(const std::string& name) { if (name == "Anna") { return std::make_unique(); } 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(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 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(vertex.x - 3), static_cast(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(this->state.vertices[edge.from].x), static_cast(this->state.vertices[edge.from].y), static_cast(this->state.vertices[edge.to].x), static_cast(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(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); }