Generating points along SVG paths
This commit is contained in:
parent
48b9769777
commit
a84d4c5e54
|
@ -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)
|
5
main.cpp
5
main.cpp
|
@ -1,18 +1,17 @@
|
|||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <format>
|
||||
|
||||
#include "renderwindow.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QWidget>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <qtimer.h>
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
std::setlocale(LC_NUMERIC, "C");
|
||||
|
||||
QApplication application(argc, argv);
|
||||
|
||||
SDL_InitSubSystem(SDL_INIT_VIDEO);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
//
|
||||
|
||||
#include "renderwindow.h"
|
||||
#include "svgpathextractor.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <QPushButton>
|
||||
|
@ -10,7 +11,11 @@
|
|||
#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") {
|
||||
|
@ -23,8 +28,26 @@ std::unique_ptr<Strategy> 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<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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,968 @@
|
|||
//
|
||||
// Created by natty on 15.7.23.
|
||||
//
|
||||
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include "svgpathextractor.h"
|
||||
#include "renderwindow.h"
|
||||
|
||||
template<typename T>
|
||||
std::pair<T, std::string_view::const_iterator> map(std::pair<std::string_view, std::string_view::const_iterator> in);
|
||||
|
||||
template<>
|
||||
std::pair<double, std::string_view::const_iterator> map(std::pair<std::string_view, std::string_view::const_iterator> in) {
|
||||
return { std::stod(std::string(in.first)), in.second };
|
||||
}
|
||||
|
||||
template<>
|
||||
std::pair<bool, std::string_view::const_iterator> map(std::pair<std::string_view, std::string_view::const_iterator> 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<std::string_view, std::string_view::const_iterator> throwIfEmpty(std::pair<std::string_view, std::string_view::const_iterator> in) {
|
||||
if (in.first.empty()) {
|
||||
throw std::runtime_error("Unexpect end of input");
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
std::pair<std::string_view, std::string_view::const_iterator> 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<size_t>(it - path) }, it};
|
||||
}
|
||||
|
||||
std::pair<std::string_view, std::string_view::const_iterator> 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<size_t>(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<Token, std::string_view::const_iterator> 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> 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<bool>(isupper(*it))
|
||||
}
|
||||
}
|
||||
}, it + 1};
|
||||
}
|
||||
|
||||
if (*it == '-' || *it == '.' || (*it >= '0' && *it <= '9')) {
|
||||
auto [value, it2] = map<double>(nomDouble(it, end));
|
||||
return {{
|
||||
.type = Token::Type::Number,
|
||||
.data = {
|
||||
.value = value
|
||||
}
|
||||
}, it2};
|
||||
}
|
||||
|
||||
return {{
|
||||
.type = Token::Type::Unknown
|
||||
}, it};
|
||||
}
|
||||
|
||||
void interpolateCubic(std::vector<Vertex>& output, size_t segments, double sx, double sy, double x1, double y1, double x2, double y2, double x, double y) {
|
||||
auto segmentsScaled = static_cast<size_t>(std::round(std::hypot(x - sx, y - sy) / 50.0 * static_cast<double>(segments)));
|
||||
|
||||
for (size_t i = 1; i < segmentsScaled; ++i) {
|
||||
const double t = static_cast<double>(i) / static_cast<double>(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<Vertex>& output, size_t segments, double sx, double sy, double x1, double y1, double x, double y) {
|
||||
auto segmentsScaled = static_cast<size_t>(std::round(std::hypot(x - sx, y - sy) / 50.0 * static_cast<double>(segments)));
|
||||
|
||||
for (size_t i = 1; i < segmentsScaled; ++i) {
|
||||
const double t = static_cast<double>(i) / static_cast<double>(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<Vertex>& output, size_t segments, double sx, double sy, double x, double y) {
|
||||
auto segmentsScaled = static_cast<size_t>(std::round(std::hypot(x - sx, y - sy) / 50.0 * static_cast<double>(segments)));
|
||||
|
||||
for (size_t i = 1; i < segmentsScaled; ++i) {
|
||||
const double t = static_cast<double>(i) / static_cast<double>(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<Vertex>& 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<size_t>(std::round(std::hypot(rx, ry) / 50.0 * static_cast<double>(segments)));
|
||||
|
||||
for (size_t i = 0; i < segmentsScaled; ++i)
|
||||
{
|
||||
const double t = static_cast<double>(i) / static_cast<double>(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<Vertex> extractVertices(std::string_view path) {
|
||||
|
||||
std::vector<Vertex> vertices;
|
||||
std::optional<Command> 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<double>(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<double>(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<double>(throwIfEmpty(nomDouble(it, path.end())));
|
||||
it3 = skipSeparators(it3, path.end());
|
||||
auto [x2, it4] = map<double>(throwIfEmpty(nomDouble(it3, path.end())));
|
||||
it4 = skipSeparators(it4, path.end());
|
||||
auto [y2, it5] = map<double>(throwIfEmpty(nomDouble(it4, path.end())));
|
||||
it5 = skipSeparators(it5, path.end());
|
||||
auto [x, it6] = map<double>(throwIfEmpty(nomDouble(it5, path.end())));
|
||||
it6 = skipSeparators(it6, path.end());
|
||||
auto [y, it7] = map<double>(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<double>(throwIfEmpty(nomDouble(it, path.end())));
|
||||
it5 = skipSeparators(it5, path.end());
|
||||
auto [x, it6] = map<double>(throwIfEmpty(nomDouble(it5, path.end())));
|
||||
it6 = skipSeparators(it6, path.end());
|
||||
auto [y, it7] = map<double>(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<double>(throwIfEmpty(nomDouble(it, path.end())));
|
||||
it3 = skipSeparators(it3, path.end());
|
||||
auto [x, it4] = map<double>(throwIfEmpty(nomDouble(it3, path.end())));
|
||||
it4 = skipSeparators(it4, path.end());
|
||||
auto [y, it5] = map<double>(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<double>(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<double>(throwIfEmpty(nomDouble(it, path.end())));
|
||||
it3 = skipSeparators(it3, path.end());
|
||||
auto [rot, it4] = map<double>(throwIfEmpty(nomDouble(it3, path.end())));
|
||||
it4 = skipSeparators(it4, path.end());
|
||||
auto [largeArcFlag, it5] = map<bool>(throwIfEmpty(nomFlag(it4, path.end())));
|
||||
it5 = skipSeparators(it5, path.end());
|
||||
auto [sweepFlag, it6] = map<bool>(throwIfEmpty(nomFlag(it5, path.end())));
|
||||
it6 = skipSeparators(it6, path.end());
|
||||
auto [x, it7] = map<double>(throwIfEmpty(nomDouble(it6, path.end())));
|
||||
it7 = skipSeparators(it6, path.end());
|
||||
auto [y, it8] = map<double>(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<double>(throwIfEmpty(nomDouble(it, path.end())));
|
||||
it2 = skipSeparators(it2, path.end());
|
||||
auto [y, it3] = map<double>(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<double>(throwIfEmpty(nomDouble(it, path.end())));
|
||||
it2 = skipSeparators(it2, path.end());
|
||||
auto [y, it3] = map<double>(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<double>(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<double>(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<double>(throwIfEmpty(nomDouble(it, path.end())));
|
||||
it2 = skipSeparators(it2, path.end());
|
||||
auto [y1, it3] = map<double>(throwIfEmpty(nomDouble(it2, path.end())));
|
||||
it3 = skipSeparators(it3, path.end());
|
||||
auto [x2, it4] = map<double>(throwIfEmpty(nomDouble(it3, path.end())));
|
||||
it4 = skipSeparators(it4, path.end());
|
||||
auto [y2, it5] = map<double>(throwIfEmpty(nomDouble(it4, path.end())));
|
||||
it5 = skipSeparators(it5, path.end());
|
||||
auto [x, it6] = map<double>(throwIfEmpty(nomDouble(it5, path.end())));
|
||||
it6 = skipSeparators(it6, path.end());
|
||||
auto [y, it7] = map<double>(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<double>(throwIfEmpty(nomDouble(it, path.end())));
|
||||
it4 = skipSeparators(it4, path.end());
|
||||
auto [y2, it5] = map<double>(throwIfEmpty(nomDouble(it4, path.end())));
|
||||
it5 = skipSeparators(it5, path.end());
|
||||
auto [x, it6] = map<double>(throwIfEmpty(nomDouble(it5, path.end())));
|
||||
it6 = skipSeparators(it6, path.end());
|
||||
auto [y, it7] = map<double>(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<double>(throwIfEmpty(nomDouble(it, path.end())));
|
||||
it4 = skipSeparators(it4, path.end());
|
||||
auto [y1, it5] = map<double>(throwIfEmpty(nomDouble(it4, path.end())));
|
||||
it5 = skipSeparators(it5, path.end());
|
||||
auto [x, it6] = map<double>(throwIfEmpty(nomDouble(it5, path.end())));
|
||||
it6 = skipSeparators(it6, path.end());
|
||||
auto [y, it7] = map<double>(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<double>(throwIfEmpty(nomDouble(it, path.end())));
|
||||
it2 = skipSeparators(it2, path.end());
|
||||
auto [y, it3] = map<double>(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<double>(throwIfEmpty(nomDouble(it, path.end())));
|
||||
it2 = skipSeparators(it2, path.end());
|
||||
auto [ry, it3] = map<double>(throwIfEmpty(nomDouble(it2, path.end())));
|
||||
it3 = skipSeparators(it3, path.end());
|
||||
auto [rot, it4] = map<double>(throwIfEmpty(nomDouble(it3, path.end())));
|
||||
it4 = skipSeparators(it4, path.end());
|
||||
auto [largeArcFlag, it5] = map<bool>(throwIfEmpty(nomFlag(it4, path.end())));
|
||||
it5 = skipSeparators(it5, path.end());
|
||||
auto [sweepFlag, it6] = map<bool>(throwIfEmpty(nomFlag(it5, path.end())));
|
||||
it6 = skipSeparators(it6, path.end());
|
||||
auto [x, it7] = map<double>(throwIfEmpty(nomDouble(it6, path.end())));
|
||||
it7 = skipSeparators(it7, path.end());
|
||||
auto [y, it8] = map<double>(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;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// Created by natty on 15.7.23.
|
||||
//
|
||||
|
||||
#ifndef TSP_SVGPATHEXTRACTOR_H
|
||||
#define TSP_SVGPATHEXTRACTOR_H
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "svgpathextractor.h"
|
||||
#include "renderwindow.h"
|
||||
|
||||
std::vector<Vertex> extractVertices(std::string_view path);
|
||||
|
||||
|
||||
#endif //TSP_SVGPATHEXTRACTOR_H
|
Loading…
Reference in New Issue