From a84d4c5e54a042db7a108ea497c5082aaff4ab76 Mon Sep 17 00:00:00 2001 From: Natty Date: Sun, 16 Jul 2023 20:51:40 +0200 Subject: [PATCH] Generating points along SVG paths --- CMakeLists.txt | 2 +- main.cpp | 5 +- renderwindow.cpp | 58 ++- renderwindow.h | 2 + svgpathextractor.cpp | 968 +++++++++++++++++++++++++++++++++++++++++++ svgpathextractor.h | 17 + 6 files changed, 1046 insertions(+), 6 deletions(-) create mode 100644 svgpathextractor.cpp create mode 100644 svgpathextractor.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 644e40d..19bcb23 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,5 +8,5 @@ find_package(SDL2 COMPONENTS SDL2 REQUIRED) set(CMAKE_AUTOMOC ON) -add_executable(tsp main.cpp renderwindow.cpp renderwindow.h) +add_executable(tsp main.cpp renderwindow.cpp renderwindow.h svgpathextractor.cpp svgpathextractor.h) target_link_libraries(tsp Qt6::Core Qt6::Widgets SDL2::SDL2) \ No newline at end of file diff --git a/main.cpp b/main.cpp index 41625be..fed8f48 100644 --- a/main.cpp +++ b/main.cpp @@ -1,18 +1,17 @@ #include #include -#include -#include #include "renderwindow.h" #include -#include #include #include int main(int argc, char *argv[]) { + std::setlocale(LC_NUMERIC, "C"); + QApplication application(argc, argv); SDL_InitSubSystem(SDL_INIT_VIDEO); diff --git a/renderwindow.cpp b/renderwindow.cpp index bcf6136..201c745 100644 --- a/renderwindow.cpp +++ b/renderwindow.cpp @@ -3,6 +3,7 @@ // #include "renderwindow.h" +#include "svgpathextractor.h" #include #include @@ -10,7 +11,11 @@ #include #include #include - +#include +#include +#include +#include +#include std::unique_ptr strategyFromName(const std::string& name) { if (name == "Anna") { @@ -23,8 +28,26 @@ std::unique_ptr strategyFromName(const std::string& name) { 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); @@ -111,6 +134,7 @@ void RenderWindow::hardResetState() void RenderWindow::resetState() { this->animTimer->stop(); + this->playButton->setChecked(false); this->strategy = strategyFromName(this->strategySelector->currentText().toStdString()); @@ -203,12 +227,42 @@ void RenderWindow::togglePlay(bool play) this->resetState(); } - this->animTimer->start(50); + 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; diff --git a/renderwindow.h b/renderwindow.h index b5358cd..6fe0fb2 100644 --- a/renderwindow.h +++ b/renderwindow.h @@ -85,6 +85,8 @@ class RenderWindow : public QMainWindow { void resetState(); void hardResetState(); void togglePlay(bool play); + + void loadPattern(const QString& qString); }; #endif //TSP_RENDERWINDOW_H diff --git a/svgpathextractor.cpp b/svgpathextractor.cpp new file mode 100644 index 0000000..8707db8 --- /dev/null +++ b/svgpathextractor.cpp @@ -0,0 +1,968 @@ +// +// Created by natty on 15.7.23. +// + +#include +#include +#include +#include +#include "svgpathextractor.h" +#include "renderwindow.h" + +template +std::pair map(std::pair in); + +template<> +std::pair map(std::pair in) { + return { std::stod(std::string(in.first)), in.second }; +} + +template<> +std::pair map(std::pair in) { + if (in.first == "0") { + return { false, in.second }; + } else if (in.first == "1") { + return { true, in.second }; + } else { + throw std::runtime_error(std::format("Flag parse error: {}", in.first)); + } +} + +std::string_view::const_iterator skipWhitespace(std::string_view::const_iterator path, std::string_view::const_iterator end) { + auto it = path; + while (it != end && (*it == ' ' || *it == '\t' || *it == '\xA' || *it == '\xC' || *it == '\xD')) { + ++it; + } + return it; +} + +std::string_view::const_iterator skipComma(std::string_view::const_iterator path, std::string_view::const_iterator end) { + auto it = path; + while (it != end && (*it == ',')) { + ++it; + } + return it; +} + +std::string_view::const_iterator skipSeparators(std::string_view::const_iterator path, std::string_view::const_iterator end) { + return skipWhitespace(skipComma(skipWhitespace(path, end), end), end); +} + +std::pair throwIfEmpty(std::pair in) { + if (in.first.empty()) { + throw std::runtime_error("Unexpect end of input"); + } + + return in; +} + +std::pair nomDouble(std::string_view::const_iterator path, std::string_view::const_iterator end) { + auto it = path; + while (it != end && (*it == '-' && it == path || *it == '.' || (*it >= '0' && *it <= '9'))) { + ++it; + } + return {{ &*path, static_cast(it - path) }, it}; +} + +std::pair nomFlag(std::string_view::const_iterator path, std::string_view::const_iterator end) { + auto it = path; + while (it != end && ((*it == '0' || *it == '1'))) { + ++it; + } + return {{ &*path, static_cast(it - path) }, it}; +} + +enum class CommandType { + Move, + Line, + HorLine, + VerLine, + Curve, + SmoothCurve, + Quadratic, + SmoothQuadratic, + EllipticalArc, + ClosePath +}; + +std::ostream& operator<< (std::ostream& stream, const CommandType& type) { + switch (type) + { + case CommandType::Move: + stream << "Move"; + break; + case CommandType::Line: + stream << "Line"; + break; + case CommandType::HorLine: + stream << "HorLine"; + break; + case CommandType::VerLine: + stream << "VerLine"; + break; + case CommandType::Curve: + stream << "Curve"; + break; + case CommandType::SmoothCurve: + stream << "SmoothCurve"; + break; + case CommandType::Quadratic: + stream << "Quadratic"; + break; + case CommandType::SmoothQuadratic: + stream << "SmoothQuadratic"; + break; + case CommandType::EllipticalArc: + stream << "EllipticalArc"; + break; + case CommandType::ClosePath: + stream << "ClosePath"; + break; + } + + return stream; +} + +struct Command { + CommandType type; + bool relative; +}; + +std::ostream& operator<< (std::ostream& stream, const Command& command) { + stream << command.type; + if (command.relative) { + stream << "Relative"; + } + return stream; +} + +struct Token { + enum class Type { + Command, + Number, + End, + Unknown + } type; + union TokenData { + double value; + Command command; + } data; +}; + +std::ostream& operator<< (std::ostream& stream, const Token& token) { + switch (token.type) { + case Token::Type::Command: + stream << token.data.command; + break; + case Token::Type::Number: + stream << token.data.value; + break; + case Token::Type::End: + stream << "End"; + break; + case Token::Type::Unknown: + stream << "Unknown"; + break; + } + return stream; +} + +std::pair nomToken(bool skipSeparator, std::string_view::const_iterator it, std::string_view::const_iterator end) +{ + if (skipSeparator) + it = skipSeparators(it, end); + else + it = skipWhitespace(it, end); + + if (it == end) { + return {{ .type = Token::Type::End }, it}; + } + + std::optional commandType; + switch (std::tolower(*it)) { + case 'm': + commandType = CommandType::Move; + break; + case 'z': + commandType = CommandType::ClosePath; + break; + case 'l': + commandType = CommandType::Line; + break; + case 'h': + commandType = CommandType::HorLine; + break; + case 'v': + commandType = CommandType::VerLine; + break; + case 'c': + commandType = CommandType::Curve; + break; + case 's': + commandType = CommandType::SmoothCurve; + break; + case 'q': + commandType = CommandType::Quadratic; + break; + case 't': + commandType = CommandType::SmoothQuadratic; + break; + case 'a': + commandType = CommandType::EllipticalArc; + break; + } + + if (commandType) { + return {{ + .type = Token::Type::Command, + .data = { + .command = { + .type = *commandType, + .relative = !static_cast(isupper(*it)) + } + } + }, it + 1}; + } + + if (*it == '-' || *it == '.' || (*it >= '0' && *it <= '9')) { + auto [value, it2] = map(nomDouble(it, end)); + return {{ + .type = Token::Type::Number, + .data = { + .value = value + } + }, it2}; + } + + return {{ + .type = Token::Type::Unknown + }, it}; +} + +void interpolateCubic(std::vector& output, size_t segments, double sx, double sy, double x1, double y1, double x2, double y2, double x, double y) { + auto segmentsScaled = static_cast(std::round(std::hypot(x - sx, y - sy) / 50.0 * static_cast(segments))); + + for (size_t i = 1; i < segmentsScaled; ++i) { + const double t = static_cast(i) / static_cast(segmentsScaled); + const double t2 = t * t; + const double t3 = t2 * t; + const double inv_t = 1.0 - t; + const double inv_t2 = inv_t * inv_t; + const double inv_t3 = inv_t2 * inv_t; + + const double px = inv_t3 * sx + 3 * inv_t2 * t * x1 + 3 * inv_t * t2 * x2 + t3 * x; + const double py = inv_t3 * sy + 3 * inv_t2 * t * y1 + 3 * inv_t * t2 * y2 + t3 * y; + + output.emplace_back(px, py); + } +} + +void interpolateQuadratic(std::vector& output, size_t segments, double sx, double sy, double x1, double y1, double x, double y) { + auto segmentsScaled = static_cast(std::round(std::hypot(x - sx, y - sy) / 50.0 * static_cast(segments))); + + for (size_t i = 1; i < segmentsScaled; ++i) { + const double t = static_cast(i) / static_cast(segmentsScaled); + const double t2 = t * t; + const double inv_t = 1.0 - t; + const double inv_t2 = inv_t * inv_t; + + const double px = inv_t2 * sx + 2 * inv_t * t * x1 + t2 * x; + const double py = inv_t2 * sy + 2 * inv_t * t * y1 + t2 * y; + + output.emplace_back(px, py); + } +} + +void interpolateLine(std::vector& output, size_t segments, double sx, double sy, double x, double y) { + auto segmentsScaled = static_cast(std::round(std::hypot(x - sx, y - sy) / 50.0 * static_cast(segments))); + + for (size_t i = 1; i < segmentsScaled; ++i) { + const double t = static_cast(i) / static_cast(segmentsScaled); + const double inv_t = 1.0 - t; + + const double px = inv_t * sx + t * x; + const double py = inv_t * sy + t * y; + + output.emplace_back(px, py); + } +} + + +double vecAngle(double ux, double uy, double vx, double vy) { + double sign = ux * vy - uy * vx < 0.0 ? -1.0 : 1.0; + double dot = ux * vx + uy * vy; + double uLen = std::hypot(ux, uy); + double vLen = std::hypot(vx, vy); + return sign * std::acos(dot / (uLen * vLen)); +} + +void interpolateArc(std::vector& output, const size_t segments, double rx, double ry, double rot, + bool largeArcFlag, bool sweepFlag, double x1, double y1, double x2, double y2) +{ + constexpr double PI = 3.14159; + constexpr double PI2 = 2.0 * PI; + + double phi = rot * PI / 180.0; + + double dx = x1 - x2; + double dy = y1 - y2; + + double dxHalf = dx / 2.0; + double dyHalf = dy / 2.0; + double xAvg = (x1 + x2) / 2.0; + double yAvg = (y1 + y2) / 2.0; + + double sinPhi = std::sin(phi); + double cosPhi = std::cos(phi); + + double x1p = cosPhi * dxHalf + sinPhi * dyHalf; + double y1p = -sinPhi * dxHalf + cosPhi * dyHalf; + + double x1p2 = x1p * x1p; + double y1p2 = y1p * y1p; + + double rx2 = rx * rx; + double ry2 = ry * ry; + + double lambda = std::sqrt(x1p2 / rx2 + y1p2 / ry2); + if (lambda > 1) { + rx *= lambda; + ry *= lambda; + } + + double sig = largeArcFlag == sweepFlag ? -1.0 : 1.0; + double aNum = rx2 * ry2 - rx2 * y1p2 - ry2 * x1p2; + double aDenom = rx2 * y1p2 + ry2 * x1p2; + double a = sig * std::sqrt(std::abs(aNum / aDenom)); + + double cxp = a * (rx * y1p / ry); + double cyp = a * -(ry * x1p / rx); + + double cx = cosPhi * cxp - sinPhi * cyp + xAvg; + double cy = sinPhi * cxp + cosPhi * cyp + yAvg; + + double svx = (x1p - cxp) / rx; + double svy = (y1p - cyp) / ry; + double theta = vecAngle(1.0, 0.0, svx, svy); + double deltaThetaUnbound = vecAngle(svx, svy, (-x1p - cxp) / rx, (-y1p - cyp) / ry); + double deltaTheta = std::fmod(deltaThetaUnbound, PI2); + if (deltaTheta > 0.0 && !sweepFlag) + deltaTheta -= PI2; + if (deltaTheta < 0.0 && sweepFlag) + deltaTheta += PI2; + + auto segmentsScaled = static_cast(std::round(std::hypot(rx, ry) / 50.0 * static_cast(segments))); + + for (size_t i = 0; i < segmentsScaled; ++i) + { + const double t = static_cast(i) / static_cast(segmentsScaled - 1); + double px = rx * std::cos(theta + deltaTheta * t); + double py = ry * std::sin(theta + deltaTheta * t); + double x = cx + px * cosPhi - py * sinPhi; + double y = cy + px * sinPhi + py * cosPhi; + + output.emplace_back(x, y); + } +} + +static constexpr size_t SEGMENTS = 20; + +std::vector extractVertices(std::string_view path) { + + std::vector vertices; + std::optional lastCommand{}; + bool repeatLastCommand = false; + Vertex pos{}; + Vertex lastControlPoint{}; + Vertex subpathStart{}; + + auto it = path.cbegin(); + while (it != path.end()) { + auto lastCommandType = lastCommand ? lastCommand->type : CommandType::ClosePath; + auto [token, itNext] = nomToken(lastCommandType != CommandType::ClosePath, it, path.end()); + it = itNext; + + switch (token.type) { + case Token::Type::End: + break; + + case Token::Type::Number: + switch (lastCommandType) { + case CommandType::Move: + { + if (!repeatLastCommand) { + vertices.push_back(pos); + } + + auto x = token.data.value; + it = skipSeparators(it, path.end()); + auto [y, it2] = map(throwIfEmpty(nomDouble(it, path.end()))); + + double xAbs = lastCommand->relative ? pos.x + x : x; + double yAbs = lastCommand->relative ? pos.y + y : y; + + interpolateLine( + vertices, + SEGMENTS, + pos.x, pos.y, + xAbs, yAbs + ); + + pos = { xAbs, yAbs }; + lastControlPoint = pos; + + vertices.push_back(pos); + break; + } + case CommandType::Line: + { + auto x = token.data.value; + it = skipSeparators(it, path.end()); + auto [y, it2] = map(throwIfEmpty(nomDouble(it, path.end()))); + + double xAbs = lastCommand->relative ? pos.x + x : x; + double yAbs = lastCommand->relative ? pos.y + y : y; + + interpolateLine( + vertices, + SEGMENTS, + pos.x, pos.y, + xAbs, yAbs + ); + + pos = { xAbs, yAbs }; + lastControlPoint = pos; + + vertices.push_back(pos); + break; + } + case CommandType::HorLine: + { + auto x = token.data.value; + + double xAbs = lastCommand->relative ? pos.x + x : x; + + interpolateLine( + vertices, + SEGMENTS, + pos.x, pos.y, + xAbs, pos.y + ); + + pos = { xAbs, pos.y }; + lastControlPoint = pos; + + vertices.push_back(pos); + break; + } + case CommandType::VerLine: + { + auto y = token.data.value; + + double yAbs = lastCommand->relative ? pos.y + y : y; + + interpolateLine( + vertices, + SEGMENTS, + pos.x, pos.y, + pos.x, yAbs + ); + + pos = { pos.x, yAbs }; + lastControlPoint = pos; + + vertices.push_back(pos); + break; + } + case CommandType::Curve: + { + auto x1 = token.data.value; + it = skipSeparators(it, path.end()); + auto [y1, it3] = map(throwIfEmpty(nomDouble(it, path.end()))); + it3 = skipSeparators(it3, path.end()); + auto [x2, it4] = map(throwIfEmpty(nomDouble(it3, path.end()))); + it4 = skipSeparators(it4, path.end()); + auto [y2, it5] = map(throwIfEmpty(nomDouble(it4, path.end()))); + it5 = skipSeparators(it5, path.end()); + auto [x, it6] = map(throwIfEmpty(nomDouble(it5, path.end()))); + it6 = skipSeparators(it6, path.end()); + auto [y, it7] = map(throwIfEmpty(nomDouble(it6, path.end()))); + it = it7; + + double x2abs = lastCommand->relative ? pos.x + x2 : x2; + double y2abs = lastCommand->relative ? pos.y + y2 : y2; + double xAbs = lastCommand->relative ? pos.x + x : x; + double yAbs = lastCommand->relative ? pos.y + y : y; + + interpolateCubic( + vertices, + SEGMENTS, + pos.x, pos.y, + lastCommand->relative ? pos.x + x1 : x1, lastCommand->relative ? pos.y + y1 : y1, + x2abs, y2abs, + xAbs, yAbs + ); + + lastControlPoint = { x2abs, y2abs }; + pos = { xAbs, yAbs }; + + vertices.push_back(pos); + + break; + } + case CommandType::SmoothCurve: + { + auto x2 = token.data.value; + it = skipSeparators(it, path.end()); + auto [y2, it5] = map(throwIfEmpty(nomDouble(it, path.end()))); + it5 = skipSeparators(it5, path.end()); + auto [x, it6] = map(throwIfEmpty(nomDouble(it5, path.end()))); + it6 = skipSeparators(it6, path.end()); + auto [y, it7] = map(throwIfEmpty(nomDouble(it6, path.end()))); + it = it7; + + double x1abs = 2.0 * pos.x - lastControlPoint.x; + double y1abs = 2.0 * pos.y - lastControlPoint.y; + + double x2abs = lastCommand->relative ? pos.x + x2 : x2; + double y2abs = lastCommand->relative ? pos.y + y2 : y2; + double xAbs = lastCommand->relative ? pos.x + x : x; + double yAbs = lastCommand->relative ? pos.y + y : y; + + interpolateCubic( + vertices, + SEGMENTS, + pos.x, pos.y, + x1abs, y1abs, + x2abs, y2abs, + xAbs, yAbs + ); + + lastControlPoint = { x2abs, y2abs }; + pos = { xAbs, yAbs }; + + vertices.push_back(pos); + + break; + } + + case CommandType::Quadratic: + { + auto x1 = token.data.value; + it = skipSeparators(it, path.end()); + auto [y1, it3] = map(throwIfEmpty(nomDouble(it, path.end()))); + it3 = skipSeparators(it3, path.end()); + auto [x, it4] = map(throwIfEmpty(nomDouble(it3, path.end()))); + it4 = skipSeparators(it4, path.end()); + auto [y, it5] = map(throwIfEmpty(nomDouble(it4, path.end()))); + it = it5; + + double x1abs = lastCommand->relative ? pos.x + x1 : x1; + double y1abs = lastCommand->relative ? pos.y + y1 : y1; + + double xAbs = lastCommand->relative ? pos.x + x : x; + double yAbs = lastCommand->relative ? pos.y + y : y; + + interpolateQuadratic( + vertices, + SEGMENTS, + pos.x, pos.y, + x1abs, y1abs, + xAbs, yAbs + ); + + lastControlPoint = { x1abs, y1abs }; + pos = { xAbs, yAbs }; + + vertices.push_back(pos); + + break; + } + case CommandType::SmoothQuadratic: + { + auto x = token.data.value; + it = skipSeparators(it, path.end()); + auto [y, it2] = map(throwIfEmpty(nomDouble(it, path.end()))); + it = it2; + + double x1abs = 2.0 * pos.x - lastControlPoint.x; + double y1abs = 2.0 * pos.y - lastControlPoint.y; + + double xAbs = lastCommand->relative ? pos.x + x : x; + double yAbs = lastCommand->relative ? pos.y + y : y; + + interpolateQuadratic( + vertices, + SEGMENTS, + pos.x, pos.y, + x1abs, y1abs, + xAbs, yAbs + ); + + lastControlPoint = { x1abs, y1abs }; + pos = { xAbs, yAbs }; + + vertices.push_back(pos); + + break; + } + case CommandType::EllipticalArc: + { + it = skipSeparators(it, path.end()); + auto rx = token.data.value; + auto [ry, it3] = map(throwIfEmpty(nomDouble(it, path.end()))); + it3 = skipSeparators(it3, path.end()); + auto [rot, it4] = map(throwIfEmpty(nomDouble(it3, path.end()))); + it4 = skipSeparators(it4, path.end()); + auto [largeArcFlag, it5] = map(throwIfEmpty(nomFlag(it4, path.end()))); + it5 = skipSeparators(it5, path.end()); + auto [sweepFlag, it6] = map(throwIfEmpty(nomFlag(it5, path.end()))); + it6 = skipSeparators(it6, path.end()); + auto [x, it7] = map(throwIfEmpty(nomDouble(it6, path.end()))); + it7 = skipSeparators(it6, path.end()); + auto [y, it8] = map(throwIfEmpty(nomDouble(it7, path.end()))); + it = it8; + + rx = abs(rx); + ry = abs(ry); + + double xAbs = lastCommand->relative ? pos.x + x : x; + double yAbs = lastCommand->relative ? pos.y + y : y; + + interpolateArc(vertices, SEGMENTS, rx, ry, rot, largeArcFlag, sweepFlag, pos.x, pos.y, xAbs, yAbs); + pos = { xAbs, yAbs }; + + lastControlPoint = pos; + + break; + } + case CommandType::ClosePath: + throw std::runtime_error("Unexpected number"); + } + + if (!repeatLastCommand) { + repeatLastCommand = true; + } + + break; + + case Token::Type::Command: + switch (token.data.command.type) { + case CommandType::Move: + { + it = skipWhitespace(it, path.end()); + auto [x, it2] = map(throwIfEmpty(nomDouble(it, path.end()))); + it2 = skipSeparators(it2, path.end()); + auto [y, it3] = map(throwIfEmpty(nomDouble(it2, path.end()))); + it = it3; + + if (token.data.command.relative) { + pos.x += x; + pos.y += y; + } else { + pos = { x, y }; + } + + subpathStart = pos; + lastControlPoint = pos; + + break; + } + case CommandType::Line: + { + if (!repeatLastCommand && (lastCommand && lastCommand->type == CommandType::Move)) { + vertices.push_back(pos); + } + + it = skipWhitespace(it, path.end()); + auto [x, it2] = map(throwIfEmpty(nomDouble(it, path.end()))); + it2 = skipSeparators(it2, path.end()); + auto [y, it3] = map(throwIfEmpty(nomDouble(it2, path.end()))); + it = it3; + + double xAbs = token.data.command.relative ? pos.x + x : x; + double yAbs = token.data.command.relative ? pos.y + y : y; + + interpolateLine( + vertices, + SEGMENTS, + pos.x, pos.y, + xAbs, yAbs + ); + + pos = { xAbs, yAbs }; + lastControlPoint = pos; + + vertices.push_back(pos); + + break; + } + case CommandType::HorLine: + { + if (!repeatLastCommand && (lastCommand && lastCommand->type == CommandType::Move)) { + vertices.push_back(pos); + } + + it = skipWhitespace(it, path.end()); + auto [x, it2] = map(throwIfEmpty(nomDouble(it, path.end()))); + it = it2; + + double xAbs = token.data.command.relative ? pos.x + x : x; + + interpolateLine( + vertices, + SEGMENTS, + pos.x, pos.y, + xAbs, pos.y + ); + + pos = { xAbs, pos.y }; + + lastControlPoint = pos; + vertices.push_back(pos); + + break; + + } + case CommandType::VerLine: + { + if (!repeatLastCommand && (lastCommand && lastCommand->type == CommandType::Move)) { + vertices.push_back(pos); + } + + it = skipWhitespace(it, path.end()); + auto [y, it2] = map(throwIfEmpty(nomDouble(it, path.end()))); + it = it2; + + double yAbs = token.data.command.relative ? pos.y + y : y; + + interpolateLine( + vertices, + SEGMENTS, + pos.x, pos.y, + pos.x, yAbs + ); + + pos = { pos.x, yAbs }; + lastControlPoint = pos; + + vertices.push_back(pos); + + break; + } + case CommandType::Curve: + { + if (!repeatLastCommand && (lastCommand && lastCommand->type == CommandType::Move)) { + vertices.push_back(pos); + } + + it = skipWhitespace(it, path.end()); + auto [x1, it2] = map(throwIfEmpty(nomDouble(it, path.end()))); + it2 = skipSeparators(it2, path.end()); + auto [y1, it3] = map(throwIfEmpty(nomDouble(it2, path.end()))); + it3 = skipSeparators(it3, path.end()); + auto [x2, it4] = map(throwIfEmpty(nomDouble(it3, path.end()))); + it4 = skipSeparators(it4, path.end()); + auto [y2, it5] = map(throwIfEmpty(nomDouble(it4, path.end()))); + it5 = skipSeparators(it5, path.end()); + auto [x, it6] = map(throwIfEmpty(nomDouble(it5, path.end()))); + it6 = skipSeparators(it6, path.end()); + auto [y, it7] = map(throwIfEmpty(nomDouble(it6, path.end()))); + it = it7; + + double x2abs = token.data.command.relative ? pos.x + x2 : x2; + double y2abs = token.data.command.relative ? pos.y + y2 : y2; + double xAbs = token.data.command.relative ? pos.x + x : x; + double yAbs = token.data.command.relative ? pos.y + y : y; + + interpolateCubic( + vertices, + SEGMENTS, + pos.x, pos.y, + token.data.command.relative ? pos.x + x1 : x1, token.data.command.relative ? pos.y + y1 : y1, + x2abs, y2abs, + xAbs, yAbs + ); + + lastControlPoint = { x2abs, y2abs }; + pos = { xAbs, yAbs }; + + vertices.push_back(pos); + + break; + } + case CommandType::SmoothCurve: + { + if (!repeatLastCommand && (lastCommand && lastCommand->type == CommandType::Move)) { + vertices.push_back(pos); + } + + it = skipWhitespace(it, path.end()); + auto [x2, it4] = map(throwIfEmpty(nomDouble(it, path.end()))); + it4 = skipSeparators(it4, path.end()); + auto [y2, it5] = map(throwIfEmpty(nomDouble(it4, path.end()))); + it5 = skipSeparators(it5, path.end()); + auto [x, it6] = map(throwIfEmpty(nomDouble(it5, path.end()))); + it6 = skipSeparators(it6, path.end()); + auto [y, it7] = map(throwIfEmpty(nomDouble(it6, path.end()))); + it = it7; + + double x1abs, y1abs; + if (!lastCommand || (lastCommand->type != CommandType::Curve && lastCommand->type != CommandType::SmoothCurve)) { + x1abs = pos.x; + y1abs = pos.y; + } else { + x1abs = 2.0 * pos.x - lastControlPoint.x; + y1abs = 2.0 * pos.y - lastControlPoint.y; + } + + double x2abs = token.data.command.relative ? pos.x + x2 : x2; + double y2abs = token.data.command.relative ? pos.y + y2 : y2; + double xAbs = token.data.command.relative ? pos.x + x : x; + double yAbs = token.data.command.relative ? pos.y + y : y; + + interpolateCubic( + vertices, + SEGMENTS, + pos.x, pos.y, + x1abs, y1abs, + x2abs, y2abs, + xAbs, yAbs + ); + + lastControlPoint = { x2abs, y2abs }; + pos = { xAbs, yAbs }; + + vertices.push_back(pos); + + break; + } + case CommandType::Quadratic: + { + if (!repeatLastCommand && (lastCommand && lastCommand->type == CommandType::Move)) { + vertices.push_back(pos); + } + + it = skipWhitespace(it, path.end()); + auto [x1, it4] = map(throwIfEmpty(nomDouble(it, path.end()))); + it4 = skipSeparators(it4, path.end()); + auto [y1, it5] = map(throwIfEmpty(nomDouble(it4, path.end()))); + it5 = skipSeparators(it5, path.end()); + auto [x, it6] = map(throwIfEmpty(nomDouble(it5, path.end()))); + it6 = skipSeparators(it6, path.end()); + auto [y, it7] = map(throwIfEmpty(nomDouble(it6, path.end()))); + it = it7; + + double x1abs = token.data.command.relative ? pos.x + x1 : x1; + double y1abs = token.data.command.relative ? pos.y + y1 : y1; + double xAbs = token.data.command.relative ? pos.x + x : x; + double yAbs = token.data.command.relative ? pos.y + y : y; + + interpolateQuadratic( + vertices, + SEGMENTS, + pos.x, pos.y, + x1abs, y1abs, + xAbs, yAbs + ); + + pos = { xAbs, yAbs }; + lastControlPoint = { x1abs, y1abs }; + + vertices.push_back(pos); + + break; + } + case CommandType::SmoothQuadratic: + { + if (!repeatLastCommand && (lastCommand && lastCommand->type == CommandType::Move)) { + vertices.push_back(pos); + } + + it = skipWhitespace(it, path.end()); + auto [x, it2] = map(throwIfEmpty(nomDouble(it, path.end()))); + it2 = skipSeparators(it2, path.end()); + auto [y, it3] = map(throwIfEmpty(nomDouble(it2, path.end()))); + it = it3; + + double x1abs, y1abs; + if (!lastCommand || (lastCommand->type != CommandType::Quadratic && lastCommand->type != CommandType::SmoothQuadratic)) { + x1abs = pos.x; + y1abs = pos.y; + } else { + x1abs = 2.0 * pos.x - lastControlPoint.x; + y1abs = 2.0 * pos.y - lastControlPoint.y; + } + + double xAbs = token.data.command.relative ? pos.x + x : x; + double yAbs = token.data.command.relative ? pos.y + y : y; + + interpolateQuadratic( + vertices, + SEGMENTS, + pos.x, pos.y, + x1abs, y1abs, + xAbs, yAbs + ); + + lastControlPoint = { x1abs, y1abs }; + pos = { xAbs, yAbs }; + + vertices.push_back(pos); + + break; + } + case CommandType::EllipticalArc: + { + it = skipWhitespace(it, path.end()); + auto [rx, it2] = map(throwIfEmpty(nomDouble(it, path.end()))); + it2 = skipSeparators(it2, path.end()); + auto [ry, it3] = map(throwIfEmpty(nomDouble(it2, path.end()))); + it3 = skipSeparators(it3, path.end()); + auto [rot, it4] = map(throwIfEmpty(nomDouble(it3, path.end()))); + it4 = skipSeparators(it4, path.end()); + auto [largeArcFlag, it5] = map(throwIfEmpty(nomFlag(it4, path.end()))); + it5 = skipSeparators(it5, path.end()); + auto [sweepFlag, it6] = map(throwIfEmpty(nomFlag(it5, path.end()))); + it6 = skipSeparators(it6, path.end()); + auto [x, it7] = map(throwIfEmpty(nomDouble(it6, path.end()))); + it7 = skipSeparators(it7, path.end()); + auto [y, it8] = map(throwIfEmpty(nomDouble(it7, path.end()))); + it = it8; + + rx = abs(rx); + ry = abs(ry); + + double xAbs = token.data.command.relative ? pos.x + x : x; + double yAbs = token.data.command.relative ? pos.y + y : y; + + interpolateArc(vertices, SEGMENTS, rx, ry, rot, largeArcFlag, sweepFlag, pos.x, pos.y, xAbs, yAbs); + pos = { xAbs, yAbs }; + + lastControlPoint = pos; + + break; + } + case CommandType::ClosePath: + { + interpolateLine( + vertices, + SEGMENTS, + pos.x, pos.y, + subpathStart.x, subpathStart.y + ); + + break; + } + + } + lastCommand = token.data.command; + repeatLastCommand = false; + break; + case Token::Type::Unknown: + throw std::runtime_error("Unknown token"); + } + } + + return vertices; +} \ No newline at end of file diff --git a/svgpathextractor.h b/svgpathextractor.h new file mode 100644 index 0000000..1c6f5ca --- /dev/null +++ b/svgpathextractor.h @@ -0,0 +1,17 @@ +// +// Created by natty on 15.7.23. +// + +#ifndef TSP_SVGPATHEXTRACTOR_H +#define TSP_SVGPATHEXTRACTOR_H + +#include +#include + +#include "svgpathextractor.h" +#include "renderwindow.h" + +std::vector extractVertices(std::string_view path); + + +#endif //TSP_SVGPATHEXTRACTOR_H