Mon, 04 Jul 2022 19:53:13 +0300
Experiment to delete the Model class and rely solely on text documents
--- a/src/basics.h Mon Jul 04 15:37:22 2022 +0300 +++ b/src/basics.h Mon Jul 04 19:53:13 2022 +0300 @@ -60,6 +60,17 @@ constexpr auto operator<=>(const ModelId&) const = default; }; +struct ElementId +{ + std::int32_t value; + constexpr auto operator<=>(const ElementId& other) const = default; +}; + +constexpr auto qHash(ElementId id) +{ + return qHash(id.value); +} + //! \brief count the amount of elements in a basic array template<typename T, int N> constexpr int countof(T(&)[N])
--- a/src/documentmanager.cpp Mon Jul 04 15:37:22 2022 +0300 +++ b/src/documentmanager.cpp Mon Jul 04 19:53:13 2022 +0300 @@ -111,6 +111,7 @@ * @param openType rationale behind opening this file * @returns model id, or no value on error */ +#include <QPlainTextDocumentLayout> std::optional<ModelId> DocumentManager::openModel( const QString& path, QTextStream& errorStream, @@ -119,10 +120,9 @@ QFile file{path}; const QString name = pathToName(QFileInfo{path}); file.open(QFile::ReadOnly | QFile::Text); - std::unique_ptr<Model> newModel = std::make_unique<Model>(nullptr); - QTextStream textStream{&file}; - Parser parser{textStream}; - parser.parseBody(*newModel); + std::unique_ptr<QTextDocument> newModel = std::make_unique<QTextDocument>(nullptr); + newModel->setDocumentLayout(new QPlainTextDocumentLayout{newModel.get()}); + newModel->setPlainText(file.readAll()); std::optional<ModelId> result; if (file.error() == QFile::NoError) { @@ -195,8 +195,7 @@ QSaveFile file{info->path}; file.setDirectWriteFallback(true); if (file.open(QSaveFile::WriteOnly)) { - QTextStream stream{&file}; - ::save(*info->model.get(), &stream); + file.write(info->model->toPlainText().toUtf8()); const bool commitSucceeded = file.commit(); if (not commitSucceeded) { errors << QObject::tr("Could not save: %1").arg(file.errorString()); @@ -332,10 +331,7 @@ } } }; - QObject::connect(model, &Model::dataChanged, modelModified); - QObject::connect(model, &Model::rowsInserted, modelModified); - QObject::connect(model, &Model::rowsRemoved, modelModified); - QObject::connect(model, &Model::modelReset, modelModified); + QObject::connect(model, &QTextDocument::contentsChanged, modelModified); } } @@ -359,9 +355,12 @@ static std::set<QString> referenceNames(const Model* model) { std::set<QString> result; - iterate<Colored<SubfileReference>>(*model, [&result](const SubfileReference& ref){ - result.insert(ref.name); - }); + for (const QString& line : model->toPlainText().split("\n")) { + const opt<ParsedLine> parsed = parse(line); + if (parsed.has_value() and std::holds_alternative<LineType1>(*parsed)) { + result.insert(std::get<LineType1>(*parsed).value.name); + } + } return result; }
--- a/src/documentmanager.h Mon Jul 04 15:37:22 2022 +0300 +++ b/src/documentmanager.h Mon Jul 04 19:53:13 2022 +0300 @@ -35,7 +35,7 @@ public: struct ModelInfo { - std::unique_ptr<Model> model; + std::unique_ptr<QTextDocument> model; ModelId id; QString path; OpenType opentype;
--- a/src/gl/compiler.cpp Mon Jul 04 15:37:22 2022 +0300 +++ b/src/gl/compiler.cpp Mon Jul 04 19:53:13 2022 +0300 @@ -295,8 +295,8 @@ vertex.position = point; vertex.normal = normal; vertex.color = glm::vec4{color.redF(), color.greenF(), color.blueF(), color.alphaF()}; - vertex.id = polygon.id.value; - vertex.pickcolor = idToColor(polygon.id.value); + vertex.id = polygon.linenumber; + vertex.pickcolor = idToColor(polygon.linenumber); }); }); for (gl::ModelShaders::ShaderObject& shader : shaders->shaderObjects) @@ -309,13 +309,12 @@ } } -ElementId gl::idFromUcharColor(const std::array<GLubyte, 3>& data) +int gl::idFromUcharColor(const std::array<GLubyte, 3>& data) { - return { + return static_cast<std::int32_t>(data[0]) | static_cast<std::int32_t>(data[1]) << 8 | - static_cast<std::int32_t>(data[2]) << 16 - }; + static_cast<std::int32_t>(data[2]) << 16; } void gl::bindModelShaderVertexArray(gl::ModelShaders* shaders, gl::ArrayClass arrayClass) @@ -332,6 +331,7 @@ shaderObject.vertexArray.release(); } +/* void gl::setModelShaderSelectedObjects(gl::ModelShaders* shaders, const QSet<ElementId> &ids) { for (ModelShaders::ShaderObject& object : shaders->shaderObjects) @@ -347,6 +347,7 @@ object.buffer.release(); } } +*/ std::size_t gl::vertexCount(const gl::ModelShaders* shaders, const gl::ArrayClass arrayClass) {
--- a/src/gl/compiler.h Mon Jul 04 15:37:22 2022 +0300 +++ b/src/gl/compiler.h Mon Jul 04 19:53:13 2022 +0300 @@ -27,7 +27,6 @@ #include "src/model.h" #include "src/gl/common.h" -class Model; class DocumentManager; namespace gl @@ -80,9 +79,9 @@ void initializeModelShaders(ModelShaders* modelShaders); void bindModelShaderVertexArray(gl::ModelShaders* shaders, gl::ArrayClass arrayClass); void releaseModelShaderVertexArray(gl::ModelShaders* shaders, gl::ArrayClass arrayClass); - void setModelShaderSelectedObjects(gl::ModelShaders* shaders, const QSet<ElementId>& ids); + //void setModelShaderSelectedObjects(gl::ModelShaders* shaders, const QSet<ElementId>& ids); std::size_t vertexCount(const ModelShaders *shaders, gl::ArrayClass arrayClass); - ElementId idFromUcharColor(const std::array<GLubyte, 3>& data); + int idFromUcharColor(const std::array<GLubyte, 3>& data); template<typename... Ts> void setShaderUniform(gl::ModelShaders* shaders, const char* uniformName, Ts&&... args)
--- a/src/gl/partrenderer.cpp Mon Jul 04 15:37:22 2022 +0300 +++ b/src/gl/partrenderer.cpp Mon Jul 04 19:53:13 2022 +0300 @@ -50,10 +50,7 @@ surfaceFormat.setSamples(8); this->setFormat(surfaceFormat); const auto setNeedBuild = [&]{this->needBuild = true;}; - connect(model, &Model::rowsInserted, setNeedBuild); - connect(model, &Model::rowsRemoved, setNeedBuild); - connect(model, &Model::dataChanged, setNeedBuild); - connect(model, &Model::modelReset, setNeedBuild); + connect(model, &QTextDocument::contentsChange, setNeedBuild); const auto updateLayerMvpMatrix = [this]{ const glm::mat4 newMvpMatrix = this->projectionMatrix * this->viewMatrix * this->modelMatrix; for (RenderLayer* layer : this->activeRenderLayers) { @@ -82,7 +79,7 @@ for (RenderLayer* layer : this->inactiveRenderLayers) { layer->initializeGL(); } - connect(this->model, &Model::dataChanged, this, &PartRenderer::build); + connect(this->model, &QTextDocument::contentsChanged, this, &PartRenderer::build); this->initialized = true; this->modelQuaternion = glm::angleAxis(glm::radians(30.0f), glm::vec3{-1, 0, 0}); this->modelQuaternion *= glm::angleAxis(glm::radians(225.0f), glm::vec3{-0, 1, 0}); @@ -437,7 +434,7 @@ viewportVector); } -ElementId PartRenderer::pick(QPoint where) +int32_t PartRenderer::pick(QPoint where) { // y is flipped, take that into account where.setY(this->height() - where.y()); @@ -494,11 +491,13 @@ return this->highlighted; } -void PartRenderer::setSelection(const QSet<ElementId>& selection) +void PartRenderer::setSelection(const QSet<int32_t>& selection) { +#if 0 Q_ASSERT(not selection.contains({0})); gl::setModelShaderSelectedObjects(&this->shaders, selection); this->update(); +#endif } glm::vec3 PartRenderer::cameraVector(const QPointF& point) const
--- a/src/gl/partrenderer.h Mon Jul 04 15:37:22 2022 +0300 +++ b/src/gl/partrenderer.h Mon Jul 04 19:53:13 2022 +0300 @@ -44,8 +44,8 @@ std::optional<glm::vec3> screenToModelCoordinates(const QPointF& point, const Plane& plane) const; QPointF modelToScreenCoordinates(const glm::vec3& point) const; bool isDark() const; - ElementId pick(QPoint where); - void setSelection(const QSet<ElementId>& selectedIds); + std::int32_t pick(QPoint where); + void setSelection(const QSet<std::int32_t>& selectedIds); glm::vec3 cameraVector(const QPointF& point) const; Line<3> cameraLine(const QPointF& point) const; Q_SIGNALS:
--- a/src/layers/edittools.cpp Mon Jul 04 15:37:22 2022 +0300 +++ b/src/layers/edittools.cpp Mon Jul 04 19:53:13 2022 +0300 @@ -292,7 +292,7 @@ switch(this->mode) { case SelectMode: if (event->button() == Qt::LeftButton) { - const ElementId highlighted = this->renderer->pick(event->pos()); + const std::int32_t highlighted = this->renderer->pick(event->pos()); Q_EMIT this->select({highlighted}, false); } break;
--- a/src/layers/edittools.h Mon Jul 04 15:37:22 2022 +0300 +++ b/src/layers/edittools.h Mon Jul 04 19:53:13 2022 +0300 @@ -62,7 +62,7 @@ Q_SIGNALS: void newStatusText(const QString& newStatusText); void modelAction(const ModelAction& action); - void select(const QSet<ElementId>& ids, bool retain); + void select(const QSet<std::int32_t>& ids, bool retain); void suggestCursor(const QCursor& cursor); protected: void mvpMatrixChanged(const glm::mat4& matrix) override;
--- a/src/ldrawalgorithm.cpp Mon Jul 04 15:37:22 2022 +0300 +++ b/src/ldrawalgorithm.cpp Mon Jul 04 19:53:13 2022 +0300 @@ -20,6 +20,7 @@ std::vector<ModelAction> ldraw::makeUnofficial(const Model* model) { std::vector<ModelAction> actions; +#if 0 if (model->size() >= 4) { if (const Comment* comment = std::get_if<Comment>(&(*model)[3])) { const QString& body = comment->text; @@ -41,28 +42,6 @@ } } } +#endif return actions; } - -ModelElement inverted(const ModelElement& element) -{ - return std::visit(overloaded{ - [](Colored<SubfileReference> ref) -> ModelElement { - ref.inverted = not ref.inverted; - return ref; - }, - [](Colored<CircularPrimitive> circ) -> ModelElement { - circ.inverted = not circ.inverted; - return circ; - }, - [](Colored<Triangle> triangle) -> ModelElement { - std::swap(triangle.p1, triangle.p2); - return triangle; - }, - [](Colored<Quadrilateral> quad) -> ModelElement { - std::swap(quad.p2, quad.p4); - return quad; - }, - [](const ModelElement& x) { return x; } - }, element); -}
--- a/src/ldrawalgorithm.h Mon Jul 04 15:37:22 2022 +0300 +++ b/src/ldrawalgorithm.h Mon Jul 04 19:53:13 2022 +0300 @@ -58,5 +58,3 @@ }); } } - -ModelElement inverted(const ModelElement &element);
--- a/src/main.cpp Mon Jul 04 15:37:22 2022 +0300 +++ b/src/main.cpp Mon Jul 04 19:53:13 2022 +0300 @@ -52,7 +52,6 @@ std::unique_ptr<EditTools> tools; std::unique_ptr<AxesLayer> axesLayer; std::unique_ptr<GridLayer> gridLayer; - std::unique_ptr<QTextDocument> textbuffer; std::unique_ptr<QTextCursor> textcursor; Model* model; }; @@ -484,6 +483,7 @@ }, }, action); */ + }; const auto restoreSettings = [&]{ recentlyOpenedFiles = setting<Setting::RecentFiles>(); @@ -523,15 +523,8 @@ data->canvas->setLayerEnabled(data->axesLayer.get(), setting<Setting::DrawAxes>()); data->canvas->addRenderLayer(data->gridLayer.get()); data->canvas->addRenderLayer(data->tools.get()); - QString modeltext; - QTextStream stream{&modeltext}; - ::save(*model, &stream); - data->textbuffer = std::make_unique<QTextDocument>(); - data->textbuffer->setPlainText(modeltext); - data->textbuffer->setDefaultFont(monospace()); - data->textbuffer->setDocumentLayout(new QPlainTextDocumentLayout(data->textbuffer.get())); - new LDrawSyntaxHighlighter{data->textbuffer.get()}; - data->textcursor = std::make_unique<QTextCursor>(data->textbuffer.get()); + new LDrawSyntaxHighlighter{model}; + data->textcursor = std::make_unique<QTextCursor>(model); documents.setModelPayload(modelId, data); QObject::connect( data->tools.get(), @@ -716,7 +709,7 @@ if (modelSubWindow != nullptr) { if (ModelData* data = documents.findPayload<ModelData>(modelSubWindow->modelId)) { checkEditingModeAction(data->tools->currentEditingMode()); - ui.modelEdit->setDocument(data->textbuffer.get()); + ui.modelEdit->setDocument(data->model); ui.modelEdit->setTextCursor(*data->textcursor); } } @@ -775,12 +768,6 @@ &QPlainTextEdit::textChanged, [&]{ if (ModelData* data = currentModelData(&ui, &documents)) { - Model* const model = data->model; - model->clear(); - QString text = ui.modelEdit->toPlainText(); - QTextStream stream{&text}; - Parser parser(stream); - parser.parseBody(*data->model); documents.loadDependenciesForAllModels(libraries); data->canvas->update(); }
--- a/src/model.cpp Mon Jul 04 15:37:22 2022 +0300 +++ b/src/model.cpp Mon Jul 04 19:53:13 2022 +0300 @@ -113,9 +113,6 @@ return std::visit(overloaded{ [](const Colored<SubfileReference>& ref) { QString result; - if (ref.inverted) { - result += QStringLiteral("0 BFC INVERTNEXT\r\n"); - } result += QStringLiteral("1 %1 %2 %3") .arg(ref.color.index) .arg(transformToString(ref.transformation)) @@ -169,50 +166,6 @@ }, element); } -Model::Model(QObject *parent) : - QAbstractListModel{parent} -{ -} - -Model::~Model() -{ -} - -ElementId Model::append(const ModelElement &value) -{ - const std::size_t position = this->size(); - const ElementId id = this->runningId; - this->runningId.value += 1; - const int row = narrow<int>(signed_cast(this->size())); - Q_EMIT this->beginInsertRows({}, row, row); - this->body.push_back({value, id}); - this->positions[id] = position; - Q_EMIT this->endInsertRows(); - return id; -} - -const ModelElement &Model::at(std::size_t position) const -{ - return this->body[position].data; -} - -ElementId Model::idAt(std::size_t position) const -{ - return this->body[position].id; -} - -void Model::assignAt(std::size_t position, const ModelElement &element) -{ - this->body[position].data = element; - const QModelIndex index = this->index(narrow<int>(signed_cast(position))); - Q_EMIT this->dataChanged(index, index); -} - -std::optional<std::size_t> Model::find(ElementId id) const -{ - return pointerToOptional(findInMap(this->positions, id)); -} - template<typename K, typename V> void removeFromMap(std::map<K, V>& map, const K& key) { @@ -222,72 +175,9 @@ } } -void Model::remove(const std::size_t index) -{ - if (index < this->body.size()) { - const int row = narrow<int>(signed_cast(index)); - Q_EMIT this->beginRemoveRows({}, row, row); - removeFromMap(this->positions, this->body[index].id); - this->body.erase(this->body.begin() + row); - for (std::size_t i = index; i < this->body.size(); ++i) { - this->positions[this->body[i].id] = i; - } - Q_EMIT this->endRemoveRows(); - } -} - -int Model::rowCount(const QModelIndex &) const -{ - return narrow<int>(signed_cast(this->size())); -} - -QVariant Model::data(const QModelIndex &index, int role) const -{ - const std::size_t i = unsigned_cast(index.row()); - const ModelElement& element = this->body[i].data; - switch(role) - { - case Qt::DecorationRole: - return iconForElement(element); - case Qt::DisplayRole: - return modelElementToString(element); - /* - case Qt::ForegroundRole: - return object->textRepresentationForeground(); - case Qt::BackgroundRole: - return object->textRepresentationBackground(); - case Qt::FontRole: - return object->textRepresentationFont(); - */ - default: - return {}; - } -} - -const ModelElement &Model::operator[](std::size_t index) const -{ - return this->body[index].data; -} - -std::size_t Model::size() const -{ - return this->body.size(); -} - -void Model::clear() -{ - this->beginResetModel(); - this->body.clear(); - this->positions.clear(); - this->runningId = {1}; - this->endResetModel(); -} - void save(const Model &model, QTextStream* stream) { - for (std::size_t i = 0; i < model.size(); ++i) { - (*stream) << modelElementToString(model[i]) << "\r\n"; - } + *stream << model.toPlainText(); } /** @@ -296,6 +186,7 @@ */ void updateHeaderNameField(Model& model, const QString &name) { +#if 0 // Update the "Name: 1234.dat" comment if (model.size() >= 2) { if (const Comment* nameObject = std::get_if<Comment>(&model[1])) { @@ -304,4 +195,5 @@ } } } +#endif }
--- a/src/model.h Mon Jul 04 15:37:22 2022 +0300 +++ b/src/model.h Mon Jul 04 19:53:13 2022 +0300 @@ -22,11 +22,13 @@ #include "src/basics.h" #include "src/colors.h" +#include <QTextDocument> +using Model = QTextDocument; + struct SubfileReference { QString name; glm::mat4 transformation; - bool inverted = false; }; template<typename T> @@ -116,34 +118,6 @@ using PolygonElement = Colored<PlainPolygonElement>; -template<typename T> -struct remove_color {}; - -template<typename T> -struct remove_color<Colored<T>> { using type = T; }; - -template<typename T> -struct remove_color<Colored<T>&> { using type = T&; }; - -template<typename T> -struct remove_color<const Colored<T>&> { using type = const T&; }; - -template<typename T> -struct remove_color<Colored<T>&&> { using type = T&&; }; - -template<typename T> -using remove_color_t = typename remove_color<T>::type; - -static_assert(std::is_same_v<remove_color_t<Colored<Triangle>>, Triangle>); -static_assert(std::is_same_v<remove_color_t<Colored<Triangle>&>, Triangle&>); -static_assert(std::is_same_v<remove_color_t<const Colored<Triangle>&>, const Triangle&>); - -template<typename T> -constexpr remove_color_t<T&&> extract_colored(T&& x) -{ - return static_cast<remove_color_t<T&&>>(x); -} - template<typename Ret, typename Fn1, typename Fn2, typename Fn3, typename Fn4, typename T> constexpr auto visitPolygon(Fn1&& f1, Fn2&& f2, Fn3&& f3, Fn4&& f4, T&& element) { @@ -193,74 +167,6 @@ element); } -QString modelElementToString(const ModelElement& element); -struct ElementId -{ - std::int32_t value; - constexpr auto operator<=>(const ElementId& other) const = default; -}; - -constexpr auto qHash(ElementId id) -{ - return qHash(id.value); -} - -class Model : public QAbstractListModel -{ - Q_OBJECT - struct Entry { - ModelElement data; - ElementId id; - }; - std::vector<Entry> body; - std::map<ElementId, std::size_t> positions; - ElementId runningId = {1}; -public: - explicit Model(QObject* parent); - virtual ~Model(); - ElementId append(const ModelElement& value); - const ModelElement& at(std::size_t position) const; - ElementId idAt(std::size_t position) const; - void assignAt(std::size_t position, const ModelElement& element); - std::optional<std::size_t> find(ElementId id) const; - void remove(std::size_t index); - int rowCount(const QModelIndex&) const override; - QVariant data(const QModelIndex& index, int role) const override; - const ModelElement& operator[](std::size_t index) const; - std::size_t size() const; - void clear(); - auto operator[](const std::size_t index) { - struct { - Model& model; - const std::size_t index; - operator const ModelElement&() { - return model.at(index); - } - auto& operator=(const ModelElement& newData) { - model.assignAt(index, newData); - return *this; - } - const auto* operator&() { - return &(this->operator const ModelElement&()); - } - } result{*this, index}; - return result; - } -}; - -void save(const Model& model, QTextStream* stream); -void updateHeaderNameField(Model& model, const QString &name); - -template<typename T> -void iterate(const Model& model, std::function<void(const T&)> fn) -{ - for (std::size_t i = 0; i < model.size(); ++i) { - if (std::holds_alternative<T>(model[i])) { - fn(std::get<T>(model[i])); - } - } -} - constexpr Colored<LineSegment> edge(const glm::vec3& p1, const glm::vec3& p2) { return Colored<LineSegment>{{.p1 = p1, .p2 = p2}, EDGE_COLOR};
--- a/src/parser.cpp Mon Jul 04 15:37:22 2022 +0300 +++ b/src/parser.cpp Mon Jul 04 19:53:13 2022 +0300 @@ -16,238 +16,175 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <QRegExp> +#include <QRegularExpression> #include <QIODevice> #include "src/ldrawalgorithm.h" #include "src/model.h" #include "src/parser.h" -struct BodyParseError -{ - QString message; -}; +#define NUMBER_REGEX R"([+-]?(?:(?:\d+\.?\d*)|(?:\.\d+)))" +#define SPACE_REGEX R"(\s+)" +#define VEC3_REGEX "(" NUMBER_REGEX SPACE_REGEX NUMBER_REGEX SPACE_REGEX NUMBER_REGEX ")" +#define TWO_VECTORS VEC3_REGEX SPACE_REGEX VEC3_REGEX +#define THREE_VECTORS TWO_VECTORS SPACE_REGEX VEC3_REGEX +#define FOUR_VECTORS THREE_VECTORS SPACE_REGEX VEC3_REGEX -/* - * Constructs an LDraw parser - */ -Parser::Parser(QTextStream& stream, QObject* parent) : - QObject {parent}, - stream {stream} {} - -/* - * Reads a single line from the device. - */ -QString Parser::readLine() +static const auto& exprs() { - return this->stream.readLine().trimmed(); + static const struct + { + QRegularExpression subfileRe{QStringLiteral( + R"(^\s*(1)\s+(\d+)\s+)" FOUR_VECTORS SPACE_REGEX R"(([^ ]+)\s*$)" + )}; + QRegularExpression edgeRe{QStringLiteral( + R"(^\s*(2)\s+(\d+)\s+)" TWO_VECTORS R"(\s*$)" + )}; + QRegularExpression triangleRe{QStringLiteral( + R"(^\s*(3)\s+(\d+)\s+)" THREE_VECTORS R"(\s*$)" + )}; + QRegularExpression quadRe{QStringLiteral( + R"(^\s*(4)\s+(\d+)\s+)" FOUR_VECTORS R"(\s*$)" + )}; + QRegularExpression cedgeRe{QStringLiteral( + R"(^\s*(5)\s+(\d+)\s+)" FOUR_VECTORS R"(\s*$)" + )}; +#if 0 + QRegularExpression bfcRe{QStringLiteral( + R"(^\s*(0) (BFC (?:CERTIFY CCW|CERTIFY CW|NOCERTIFY|INVERTNEXT|CLIP|NOCLIP))\s*$)" + )}; +#endif + QRegularExpression commentRe{QStringLiteral( + R"(^\s*(0)\s+(.+)$)" + )}; + } result; + return result; +} +template<Attribute Attrib, typename T, Attribute... Attribs> +QString attrib(const LineType<T, Attribs...>& parsed) +{ + const int index = attribIndex<LineType<T, Attribs...>, Attrib>; + const TextRange& range = parsed.positions[index]; + return parsed.content.mid(range.start, range.length); } -/** - * @brief Parses the model body into the given model. - * @param editor Handle to model edit context - */ -void Parser::parseBody(Model& model) +template<Attribute X, Attribute Y, Attribute Z, typename T, Attribute... Attribs> +glm::vec3 vectorAttrib(const LineType<T, Attribs...>& parsed) { - bool invertNext = false; - while (not this->stream.atEnd()) - { - // Some LDraw parts such as 53588.dat can contain "BFC INVERTNEXT" with multiple inner whitespaces. - // So we need to pass the string through QString::simplified to catch these cases. - const QString line = this->readLine().trimmed(); - if (line == "0 BFC INVERTNEXT" or line == "0 BFC CERTIFY INVERTNEXT") - { - invertNext = true; - continue; - } - ModelElement element = parseLDrawLine(line); - if (invertNext) - { - element = inverted(element); - } - model.append(element); - invertNext = false; - } + return glm::vec3{ + attrib<X>(parsed).toFloat(), + attrib<Y>(parsed).toFloat(), + attrib<Z>(parsed).toFloat(), + }; +} + +template<typename T> +ColorIndex colorAttrib(T* parsed) +{ + return ColorIndex{attrib<Attribute::Color>(*parsed).toInt()}; } -static ldraw::Color colorFromString(const QString& colorString) +opt<ParsedLine> parse(const QString& line) { - bool colorSucceeded; - const ldraw::Color color = {colorString.toInt(&colorSucceeded)}; - if (colorSucceeded) - { - return color; - } - else - { - throw BodyParseError{"colour was not an integer value"}; - } -} - -static glm::vec3 vertexFromStrings( - const QStringList& tokens, - const int startingPosition) -{ - bool ok_x; - const float x = tokens[startingPosition].toFloat(&ok_x); - bool ok_y; - const float y = tokens[startingPosition + 1].toFloat(&ok_y); - bool ok_z; - const float z = tokens[startingPosition + 2].toFloat(&ok_z); - if (not ok_x or not ok_y or not ok_z) - { - throw BodyParseError{"vertex contained illegal co-ordinates"}; + const auto tryRe = [&line](const QRegularExpression& re){ + opt<QRegularExpressionMatch> result = re.match(line); + if (not result->hasMatch()) { + result.reset(); + } + return result; + }; + opt<ParsedLine> result; + //! \brief Put value into result, add matched ranges to it and return a pointer + const auto init = [&]<typename T>(T&& value, const QRegularExpressionMatch& match){ + result = value; + T* parsed = &std::get<T>(*result); + for (int i = 0; i < countof(T::attributes); ++i) { + parsed->positions[i] = { + .start = match.capturedStart(i + 1), + .length = match.capturedLength(i + 1), + }; + } + return parsed; + }; + if (auto line1Match = tryRe(exprs().subfileRe)) { + LineType1* const parsed = init(LineType1{}, *line1Match); + parsed->value = { + SubfileReference{ + .name = attrib<Attribute::Name>(*parsed), + .transformation = glm::mat4{ + glm::vec4{vectorAttrib<Attribute::X2, Attribute::Y2, Attribute::Z2>(*parsed), 0}, + glm::vec4{vectorAttrib<Attribute::X3, Attribute::Y3, Attribute::Z3>(*parsed), 0}, + glm::vec4{vectorAttrib<Attribute::X4, Attribute::Y4, Attribute::Z4>(*parsed), 0}, + glm::vec4{vectorAttrib<Attribute::X1, Attribute::Y1, Attribute::Z1>(*parsed), 0} + }, + }, + colorAttrib(parsed), + }; } - return {x, y, z}; -} - -static glm::mat4 matrixFromStrings( - const QStringList& tokens, - const int startingPosition, - const int positionStartingIndex) -{ - glm::mat4 result = glm::mat4{1}; - for (int i = 0; i < 9; i += 1) - { - const int row = i / 3; - const int column = i % 3; - const int index = i + startingPosition; - if (index >= tokens.size()) - { - throw BodyParseError{"too few tokens available"}; - } - bool ok; - // note that glm::mat4 is column-major - result[column][row] = tokens[index].toFloat(&ok); - if (not ok) - { - throw BodyParseError{"non-numeric values for matrix"}; - } + else if (auto line2Match = tryRe(exprs().edgeRe)) { + LineType2* const parsed = init(LineType2{}, *line2Match); + parsed->value = { + LineSegment{ + .p1 = vectorAttrib<Attribute::X1, Attribute::Y1, Attribute::Z1>(*parsed), + .p2 = vectorAttrib<Attribute::X2, Attribute::Y2, Attribute::Z2>(*parsed), + }, + colorAttrib(parsed), + }; + } + else if (auto line3Match = tryRe(exprs().triangleRe)) { + LineType3* const parsed = init(LineType3{}, *line3Match); + parsed->value = { + Triangle{ + .p1 = vectorAttrib<Attribute::X1, Attribute::Y1, Attribute::Z1>(*parsed), + .p2 = vectorAttrib<Attribute::X2, Attribute::Y2, Attribute::Z2>(*parsed), + .p3 = vectorAttrib<Attribute::X3, Attribute::Y3, Attribute::Z3>(*parsed), + }, + colorAttrib(parsed), + }; } - for (int i = 0; i < 3; i += 1) - { - bool ok; - const auto value = tokens[i + positionStartingIndex].toFloat(&ok); - result[3][i] = value; - if (not ok) - { - throw BodyParseError{"non-numeric values for matrix"}; - } + else if (auto line4Match = tryRe(exprs().quadRe)) { + LineType4* const parsed = init(LineType4{}, *line4Match); + parsed->value = { + Quadrilateral{ + .p1 = vectorAttrib<Attribute::X1, Attribute::Y1, Attribute::Z1>(*parsed), + .p2 = vectorAttrib<Attribute::X2, Attribute::Y2, Attribute::Z2>(*parsed), + .p3 = vectorAttrib<Attribute::X3, Attribute::Y3, Attribute::Z3>(*parsed), + .p4 = vectorAttrib<Attribute::X4, Attribute::Y4, Attribute::Z4>(*parsed), + }, + colorAttrib(parsed), + }; + } + else if (auto line5Match = tryRe(exprs().cedgeRe)) { + LineType5* const parsed = init(LineType5{}, *line5Match); + parsed->value = { + ConditionalEdge{ + .p1 = vectorAttrib<Attribute::X1, Attribute::Y1, Attribute::Z1>(*parsed), + .p2 = vectorAttrib<Attribute::X2, Attribute::Y2, Attribute::Z2>(*parsed), + .c1 = vectorAttrib<Attribute::X3, Attribute::Y3, Attribute::Z3>(*parsed), + .c2 = vectorAttrib<Attribute::X4, Attribute::Y4, Attribute::Z4>(*parsed), + }, + colorAttrib(parsed), + }; } return result; } -static Comment parseType0Line(const QString& line) -{ - return {line.mid(1).trimmed()}; -} - -static ModelElement parseType1Line(const QStringList& tokens) -{ - constexpr int colorPosition = 1; - constexpr int positionPosition = 2; // 2..4 - constexpr int transformPosition = 5; // 5..13 - constexpr int namePosition = 14; - if (tokens.size() != 15) - { - throw BodyParseError{"wrong amount of tokens in a type-1 line"}; - } - const ldraw::Color color = colorFromString(tokens[colorPosition]); - const glm::mat4 transform = matrixFromStrings(tokens, transformPosition, positionPosition); - const QString& name = tokens[namePosition]; - static QRegExp re{R"((?:(\d+)\\)?(\d+)-(\d)+([a-z]+)\.dat)"}; - if (re.exactMatch(name)) { - const auto p = std::find(std::begin(circularPrimitiveStems), std::end(circularPrimitiveStems), re.cap(4)); - const unsigned int divisions = (re.cap(1).isEmpty()) ? 16 : re.cap(1).toUInt(); - const unsigned int segments = re.cap(2).toUInt() * divisions / re.cap(3).toUInt(); - if (p != std::end(circularPrimitiveStems)) { - const auto type = static_cast<CircularPrimitive::Type>(p - std::begin(circularPrimitiveStems)); - return Colored<CircularPrimitive>{ - CircularPrimitive{ - .type = type, - .fraction = {segments, divisions}, - .transformation = transform, - }, - color, - }; - } - } - return Colored<SubfileReference>{ - { - .name = name, - .transformation = transform, - }, - color, - }; -} - -template<int NumVertices> -static auto parsePolygon(const QStringList& tokens) -{ - constexpr int colorPosition = 1; - auto vertexPosition = [](int n) { return 2 + 3*n; }; - if (tokens.size() != 2 + 3 * NumVertices) - { - throw BodyParseError{"wrong amount of tokens"}; - } - const ldraw::Color color = colorFromString(tokens[colorPosition]); - std::array<glm::vec3, NumVertices> vertices; - for (int i = 0; i < NumVertices; i += 1) - { - vertices[unsigned_cast(i)] = vertexFromStrings(tokens, vertexPosition(i)); - } - return std::make_pair(vertices, color); -} - -ModelElement parseLDrawLine(QString line) -{ - try - { - const QStringList tokens = line.simplified().split(" "); - if (tokens.empty() or tokens == QStringList{{""}}) - { - return Empty{}; - } - bool ok_code; - const int code = tokens[0].toInt(&ok_code); - if (not ok_code) - { - throw BodyParseError{QObject::tr("line type was not an integer")}; - } - switch (code) - { - case 0: - return parseType0Line(line); - case 1: - return parseType1Line(tokens); - case 2: - { - const auto pair = parsePolygon<2>(tokens); - return Colored<LineSegment>{{pair.first[0], pair.first[1]}, pair.second}; - } - case 3: - { - const auto pair = parsePolygon<3>(tokens); - return Colored<Triangle>{{pair.first[0], pair.first[1], pair.first[2]}, pair.second - }; - } - case 4: - { - const auto pair = parsePolygon<4>(tokens); - const Quadrilateral quad{pair.first[0], pair.first[1], pair.first[2], pair.first[3]}; - return Colored<Quadrilateral>{quad, pair.second}; - } - case 5: - { - const auto pair = parsePolygon<4>(tokens); - const ConditionalEdge cedge{pair.first[0], pair.first[1], pair.first[2], pair.first[3]}; - return Colored<ConditionalEdge>{cedge, pair.second}; - } - default: - throw BodyParseError{QObject::tr("bad line type '%1'").arg(code)}; - } - } - catch(const BodyParseError& error) - { - return ParseError{line}; +#if 0 +static QRegExp re{R"((?:(\d+)\\)?(\d+)-(\d)+([a-z]+)\.dat)"}; +if (re.exactMatch(name)) { + const auto p = std::find(std::begin(circularPrimitiveStems), std::end(circularPrimitiveStems), re.cap(4)); + const unsigned int divisions = (re.cap(1).isEmpty()) ? 16 : re.cap(1).toUInt(); + const unsigned int segments = re.cap(2).toUInt() * divisions / re.cap(3).toUInt(); + if (p != std::end(circularPrimitiveStems)) { + const auto type = static_cast<CircularPrimitive::Type>(p - std::begin(circularPrimitiveStems)); + return Colored<CircularPrimitive>{ + CircularPrimitive{ + .type = type, + .fraction = {segments, divisions}, + .transformation = transform, + }, + color, + }; } } +#endif +
--- a/src/parser.h Mon Jul 04 15:37:22 2022 +0300 +++ b/src/parser.h Mon Jul 04 19:53:13 2022 +0300 @@ -20,15 +20,124 @@ #include "src/basics.h" #include "src/model.h" -class Parser : public QObject +struct TextRange { - Q_OBJECT -public: - Parser(QTextStream& stream, QObject* parent = nullptr); - void parseBody(Model &model); -private: - QString readLine(); - QTextStream& stream; + int start; + int length; +}; + + +enum class Attribute { + LineType, + Color, + X1, + Y1, + Z1, + X2, + Y2, + Z2, + X3, + Y3, + Z3, + X4, + Y4, + Z4, + Name, + Text +}; + +template<typename T, Attribute... Attribs> +struct LineType +{ + static constexpr Attribute attributes[] = {Attribs...}; + QString content; + TextRange positions[countof(attributes)]; + T value; }; -ModelElement parseLDrawLine(QString line); +using LineType0 = LineType<Comment, Attribute::Text>; +using LineType1 = LineType<Colored<SubfileReference>, + Attribute::LineType, + Attribute::Color, + Attribute::X1, + Attribute::Y1, + Attribute::Z1, + Attribute::X2, + Attribute::Y2, + Attribute::Z2, + Attribute::X3, + Attribute::Y3, + Attribute::Z3, + Attribute::X4, + Attribute::Y4, + Attribute::Z4, + Attribute::Name>; +using LineType2 = LineType<Colored<LineSegment>, + Attribute::LineType, + Attribute::Color, + Attribute::X1, + Attribute::Y1, + Attribute::Z1, + Attribute::X2, + Attribute::Y2, + Attribute::Z2>; +using LineType3 = LineType<Colored<Triangle>, + Attribute::LineType, + Attribute::Color, + Attribute::X1, + Attribute::Y1, + Attribute::Z1, + Attribute::X2, + Attribute::Y2, + Attribute::Z2, + Attribute::X3, + Attribute::Y3, + Attribute::Z3>; +using LineType4 = LineType<Colored<Quadrilateral>, + Attribute::LineType, + Attribute::Color, + Attribute::X1, + Attribute::Y1, + Attribute::Z1, + Attribute::X2, + Attribute::Y2, + Attribute::Z2, + Attribute::X3, + Attribute::Y3, + Attribute::Z3, + Attribute::X4, + Attribute::Y4, + Attribute::Z4>; +using LineType5 = LineType<Colored<ConditionalEdge>, + Attribute::LineType, + Attribute::Color, + Attribute::X1, + Attribute::Y1, + Attribute::Z1, + Attribute::X2, + Attribute::Y2, + Attribute::Z2, + Attribute::X3, + Attribute::Y3, + Attribute::Z3, + Attribute::X4, + Attribute::Y4, + Attribute::Z4>; + +template<typename T, Attribute Attrib> +constexpr int findAttributeIndex() +{ + constexpr auto it = std::find( + std::begin(T::attributes), + std::end(T::attributes), + Attrib + ); + static_assert(it != std::end(T::attributes), "Attribute not found"); + return it - std::begin(T::attributes); +} + +template<typename T, Attribute Attrib> +constexpr int attribIndex = findAttributeIndex<T, Attrib>(); +static_assert(attribIndex<LineType3, Attribute::X1> == 2); +using ParsedLine = std::variant<LineType0, LineType1, LineType2, LineType3, LineType4, LineType5>; +opt<ParsedLine> parse(const QString& line);
--- a/src/polygoncache.cpp Mon Jul 04 15:37:22 2022 +0300 +++ b/src/polygoncache.cpp Mon Jul 04 19:53:13 2022 +0300 @@ -2,6 +2,7 @@ #include "src/documentmanager.h" #include "src/invert.h" #include "src/polygoncache.h" +#include "src/parser.h" Model* resolve(const QString& name, const ModelId callingModelId, DocumentManager* documents) { @@ -40,16 +41,26 @@ return a xor n_xor(rest...); } +namespace { +struct GetPolygonsContext +{ + bool invertnext = false; + ModelId modelId; + class DocumentManager* documents; +}; +} + template<typename Fn, typename Fn2> static void inlineSubfileReference( const PolygonCache::vector_type& polygons, const Colored<SubfileReference>& ref, + GetPolygonsContext* context, Fn&& add, Fn2&& reserve) { const bool needInverting = 0 ^ (glm::determinant(ref.transformation) < 0) - ^ (ref.inverted); + ^ (context->invertnext); reserve(polygons.size()); for (const PolygonElement& cacheElement : polygons) { PolygonElement polygon = transformed(cacheElement, ref.transformation); @@ -63,42 +74,60 @@ } } -Model* findDependency(const SubfileReference& ref, GetPolygonsContext* context) +static Model* findDependency(const SubfileReference& ref, GetPolygonsContext* context) { return context->documents->findDependencyByName(context->modelId, ref.name); } template<typename Fn, typename Fn2> static void collectPolygons( - const ModelElement& element, + const ParsedLine& element, Winding& winding, GetPolygonsContext* context, Fn&& add, Fn2&& reserve) { + bool foundinvertnext = false; std::visit<void>(overloaded{ - [&](const Colored<LineSegment>& edge) { - add({edge, edge.color}); - }, - [&](const Colored<Triangle>& triangle) { - add({triangle, triangle.color}); + [&](const LineType0& line0) { + const QString text = line0.value.text.simplified(); + if (text == QStringLiteral("BFC INVERTNEXT")) { + context->invertnext = true; + foundinvertnext = true; + } + else if (text == QStringLiteral("BFC CERTIFY CW")) { + winding = Clockwise; + } + else if (text == QStringLiteral("BFC CERTIFY CCW")) { + winding = Anticlockwise; + } + else if (text == QStringLiteral("BFC NOCERTIFY")) { + winding = NoWinding; + } }, - [&](const Colored<Quadrilateral>& quad) { - add({quad, quad.color}); + [&](const LineType2& line2) { + add({line2.value, line2.value.color}); + }, + [&](const LineType3& line3) { + add({line3.value, line3.value.color}); }, - [&](const Colored<ConditionalEdge>& cedge) { - add({cedge, cedge.color}); + [&](const LineType4& line4) { + add({line4.value, line4.value.color}); }, - [&add, context, &reserve](const Colored<SubfileReference>& ref) { - Model* const dependency = findDependency(ref, context); + [&](const LineType5& line5) { + add({line5.value, line5.value.color}); + }, + [&add, context, &reserve](const LineType1& line1) { + Model* const dependency = findDependency(line1.value, context); if (PolygonCache* cache = (dependency != nullptr) ? findPolygonCacheForModel(dependency, context->documents) : nullptr ) { recacheIfNeeded(cache, dependency, context->documents); - inlineSubfileReference(cache->polygons, ref, add, reserve); + inlineSubfileReference(cache->polygons, line1.value, context, add, reserve); } }, + #if 0 [&add](const Colored<CircularPrimitive>& circ) { rasterize(circ, [&](const PlainPolygonElement& polygon, ColorIndex color){ if (color == MAIN_COLOR) { @@ -107,19 +136,11 @@ add(PolygonElement{polygon, color}); }); }, - [&winding](const Comment& comment) { - if (comment.text == QStringLiteral("BFC CERTIFY CW")) { - winding = Clockwise; - } - else if (comment.text == QStringLiteral("BFC CERTIFY CCW")) { - winding = Anticlockwise; - } - else if (comment.text == QStringLiteral("BFC NOCERTIFY")) { - winding = NoWinding; - } - }, - [](const ModelElement&) {} + #endif }, element); + if (not foundinvertnext) { + context->invertnext = false; + } } static std::vector<WithId<PolygonElement>> inlinePolygons( @@ -128,19 +149,22 @@ { Winding winding = NoWinding; std::vector<WithId<PolygonElement>> result; - for (std::size_t i = 0; i < model->size(); i += 1) - { - const ModelElement& element = (*model)[i]; - const ElementId id = model->idAt(i); - collectPolygons(element, winding, context, - [&result, winding, id](const PolygonElement& poly){ - result.push_back({poly, id}); - if (winding == Winding::Clockwise) { - gl::invert(result.back()); - } - }, [&result](std::size_t incomingsize){ - reserveMore(result, incomingsize); - }); + int i = 0; + const auto add = [&result, winding, i](const PolygonElement& poly){ + result.push_back({poly, i}); + if (winding == Winding::Clockwise) { + gl::invert(result.back()); + } + }; + const auto reserve = [&result](std::size_t incomingsize){ + reserveMore(result, incomingsize); + }; + for (const QString& line : model->toPlainText().split("\n")) { + const opt<ParsedLine> parsedline = parse(line); + if (parsedline.has_value()) { + collectPolygons(*parsedline, winding, context, add, reserve); + } + ++i; } return result; } @@ -153,7 +177,11 @@ const std::optional<ModelId> modelId = documents->findIdForModel(model); if (modelId.has_value()) { - GetPolygonsContext context{modelId.value(), documents}; + GetPolygonsContext context{ + .invertnext = false, + .modelId = *modelId, + .documents = documents, + }; cache->polygons = inlinePolygons(model, &context); } cache->needRecache = false;
--- a/src/polygoncache.h Mon Jul 04 15:37:22 2022 +0300 +++ b/src/polygoncache.h Mon Jul 04 19:53:13 2022 +0300 @@ -1,19 +1,13 @@ #pragma once +#include <QTextDocument> #include "src/basics.h" #include "src/model.h" #include "src/gl/common.h" -#include "src/documentmanager.h" template<typename T> struct WithId : T { - ElementId id; -}; - -struct GetPolygonsContext -{ - ModelId modelId; - class DocumentManager* documents; + std::int32_t linenumber; }; struct PolygonCache @@ -27,4 +21,4 @@ PolygonCache* cache, Model* model, class DocumentManager* documents); -PolygonCache* findPolygonCacheForModel(Model* model, DocumentManager* context); +PolygonCache* findPolygonCacheForModel(QTextDocument* model, DocumentManager* context);
--- a/src/vertexmap.cpp Mon Jul 04 15:37:22 2022 +0300 +++ b/src/vertexmap.cpp Mon Jul 04 19:53:13 2022 +0300 @@ -13,24 +13,6 @@ VertexMap::VertexMap(const Model *model) : model{model} { - connect( - model, - &Model::dataChanged, - this, - &VertexMap::build - ); - connect( - model, - &Model::rowsInserted, - this, - &VertexMap::build - ); - connect( - model, - &Model::rowsRemoved, - this, - &VertexMap::build - ); this->build(); } @@ -116,6 +98,7 @@ this->map.clear(); this->vertices.clear(); this->vertexHashes.clear(); +#if 0 for (std::size_t i = 0; i < this->model->size(); ++i) { const ModelElement& element = this->model->at(i); @@ -173,6 +156,7 @@ } info.transform = glm::scale(info.transform, glm::vec3{scale, scale, scale}); } +#endif Q_EMIT this->verticesChanged(); }