diff -r dc33f8a707c4 -r 59b6027b9843 src/document.cpp
--- a/src/document.cpp Sun Jun 26 20:54:09 2022 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,459 +0,0 @@
-/*
- * LDForge: LDraw parts authoring CAD
- * Copyright (C) 2013 - 2020 Teemu Piippo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#include
-#include
-#include "algorithm/earcut.h"
-#include "document.h"
-#include "model.h"
-#include "ui/objecteditor.h"
-#include "gl/partrenderer.h"
-#include "circularprimitive.h"
-
-// Make mapbox::earcut work with glm::vec3
-template<> struct mapbox::util::nth<0, glm::vec3>
-{
- static constexpr float get(const glm::vec3& t) { return t.x; }
-};
-
-template<> struct mapbox::util::nth<1, glm::vec3>
-{
- static constexpr float get(const glm::vec3& t) { return t.y; }
-};
-
-EditTools::EditTools(QObject* parent) :
- QObject{parent},
- RenderLayer{}
-{
-}
-
-EditTools::~EditTools()
-{
-}
-
-void EditTools::setEditMode(EditingMode newMode)
-{
- this->mode = newMode;
-}
-
-void EditTools::setGridMatrix(const glm::mat4& newGridMatrix)
-{
- this->gridMatrix = newGridMatrix;
- this->gridPlane = planeFromTriangle({
- this->gridMatrix * glm::vec4{0, 0, 0, 1},
- this->gridMatrix * glm::vec4{1, 0, 0, 1},
- this->gridMatrix * glm::vec4{0, 1, 0, 1},
- });
-}
-
-void EditTools::setCircleToolOptions(const CircleToolOptions& options)
-{
- this->circleToolOptions = options;
-}
-
-void EditTools::mvpMatrixChanged(const glm::mat4& matrix)
-{
- this->mvpMatrix = matrix;
-}
-
-void EditTools::mouseMoved(const QMouseEvent* event)
-{
- this->worldPosition = this->renderer->screenToModelCoordinates(event->pos(), this->gridPlane);
- if (this->worldPosition.has_value())
- {
- // Snap the position to grid. This procedure is basically the "change of basis" and almost follows the
- // A⁻¹ × M × A formula which is used to perform a transformation in some other coordinate system, except
- // we actually use the inverted matrix first and the regular one last to perform the transformation of
- // grid coordinates in our XY coordinate system. Also, we're rounding the coordinates which is obviously
- // not a linear transformation, but fits the pattern anyway.
- // First transform the coordinates to the XY plane...
- this->worldPosition = glm::inverse(this->gridMatrix) * glm::vec4{*this->worldPosition, 1};
- // Then round the coordinates to integer precision...
- this->worldPosition = glm::round(*this->worldPosition);
- // And finally transform it back to grid coordinates by transforming it with the
- // grid matrix.
- this->worldPosition = this->gridMatrix * glm::vec4{*this->worldPosition, 1};
- this->polygon.back() = *this->worldPosition;
- }
- this->numpoints = this->polygon.size();
- if (this->isCloseToExistingPoints()) {
- this->numpoints -= 1;
- }
-}
-
-static QVector convertWorldPointsToScreenPoints(
- const std::vector &worldPoints,
- const PartRenderer* renderer)
-{
- QVector points2d;
- points2d.reserve(static_cast(worldPoints.size()));
- for (const glm::vec3& point : worldPoints)
- {
- points2d.push_back(renderer->modelToScreenCoordinates(point));
- }
- return points2d;
-}
-
-static Winding worldPolygonWinding(
- const std::vector &points,
- const PartRenderer* renderer)
-{
- return winding(QPolygonF{convertWorldPointsToScreenPoints(points, renderer)});
-}
-
-static void drawWorldPoint(
- QPainter* painter,
- const glm::vec3& worldPoint,
- const PartRenderer* renderer)
-{
- const QPointF center = renderer->modelToScreenCoordinates(worldPoint);
- painter->drawEllipse(inscribe(CircleF{center, 5}));
-}
-
-static void drawWorldPolyline(
- QPainter *painter,
- const std::vector &points,
- const PartRenderer* renderer)
-{
- painter->drawPolyline(QPolygonF{convertWorldPointsToScreenPoints(points, renderer)});
-}
-
-static void drawWorldPolygon(
- QPainter* painter,
- const std::vector &points,
- const PartRenderer* renderer)
-{
- painter->drawPolygon(QPolygonF{convertWorldPointsToScreenPoints(points, renderer)});
-}
-
-static std::vector> modelActionPoints(const ModelAction& action)
-{
- std::vector> result;
- if (const AppendToModel* append = std::get_if(&action)) {
- const ModelElement& newElement = append->newElement;
- if (const LineSegment* seg = std::get_if>(&newElement)) {
- result.push_back({seg->p1, seg->p2});
- }
- else if (const Triangle* tri = std::get_if>(&newElement)) {
- result.push_back({tri->p1, tri->p2, tri->p3});
- }
- else if (const Quadrilateral* quad = std::get_if>(&newElement)) {
- result.push_back({quad->p1, quad->p2, quad->p3, quad->p4});
- }
- else if (const CircularPrimitive* circ = std::get_if>(&newElement)) {
- rasterize(*circ, [&](const ModelElement& element){
- const auto& subpoints = modelActionPoints(AppendToModel{element});
- std::copy(subpoints.begin(), subpoints.end(), std::back_inserter(result));
- });
- }
- }
- return result;
-}
-
-namespace {
-struct Pens
-{
- const QBrush pointBrush;
- const QPen pointPen;
- const QPen textPen;
- const QPen polygonPen;
- const QPen badPolygonPen;
- const QBrush greenPolygonBrush;
- const QBrush redPolygonBrush;
-};
-}
-
-static const Pens brightPens{
- .pointBrush = {Qt::black},
- .pointPen = {QBrush{Qt::black}, 2.0},
- .textPen = {Qt::black},
- .polygonPen = {QBrush{Qt::black}, 2.0, Qt::DashLine},
- .greenPolygonBrush = {QColor{64, 255, 128, 192}},
- .redPolygonBrush = {QColor{255, 96, 96, 192}},
-};
-
-static const Pens darkPens{
- .pointBrush = {Qt::white},
- .pointPen = {QBrush{Qt::white}, 2.0},
- .textPen = {Qt::white},
- .polygonPen = {QBrush{Qt::white}, 2.0, Qt::DashLine},
- .greenPolygonBrush = {QColor{64, 255, 128, 192}},
- .redPolygonBrush = {QColor{255, 96, 96, 192}},
-};
-
-void EditTools::overpaint(QPainter* painter)
-{
- painter->save();
- const Pens& pens = (this->renderer->isDark() ? darkPens : brightPens);
- this->renderPreview(painter, &pens);
- QFont font;
- font.setBold(true);
- if (this->worldPosition.has_value())
- {
- painter->setRenderHint(QPainter::Antialiasing);
- painter->setPen(pens.pointPen);
- painter->setBrush(pens.greenPolygonBrush);
- const QPointF pos = this->renderer->modelToScreenCoordinates(*this->worldPosition);
- painter->drawEllipse(pos, 5, 5);
- drawBorderedText(painter, pos + QPointF{5, 5}, font, vectorToString(*this->worldPosition));
- }
- painter->restore();
-}
-
-const std::vector EditTools::modelActions() const
-{
- switch(this->mode) {
- case SelectMode:
- return {};
- case DrawMode:
- return drawModeActions();
- case CircleMode:
- return circleModeActions();
- }
-}
-
-void EditTools::renderPreview(QPainter* painter, const void* pensptr)
-{
- const Pens& pens = *reinterpret_cast(pensptr);
- painter->setPen(pens.polygonPen);
- for (const ModelAction& action : this->modelActions()) {
- for (const std::vector& points : modelActionPoints(action)) {
- if (points.size() == 2) {
- drawWorldPolyline(painter, points, renderer);
- }
- else {
- if (worldPolygonWinding(points, this->renderer) == Winding::Clockwise) {
- painter->setBrush(pens.greenPolygonBrush);
- }
- else {
- painter->setBrush(pens.redPolygonBrush);
- }
- drawWorldPolygon(painter, points, this->renderer);
- }
- }
- }
- painter->setBrush(pens.pointBrush);
- painter->setPen(pens.pointPen);
- for (const glm::vec3& point : this->polygon) {
- drawWorldPoint(painter, point, this->renderer);
- }
-}
-
-void EditTools::removeLastPoint()
-{
- if (this->polygon.size() > 1) {
- this->polygon.erase(this->polygon.end() - 1);
- }
-}
-
-bool EditTools::isCloseToExistingPoints() const
-{
- if (this->worldPosition.has_value()) {
- const glm::vec3& pos = *this->worldPosition;
- return std::any_of(this->polygon.begin(), this->polygon.end() - 1, [&pos](const glm::vec3& p){
- return isclose(pos, p);
- });
- }
- else {
- return false;
- }
-}
-
-EditingMode EditTools::currentEditingMode() const
-{
- return this->mode;
-}
-
-void EditTools::mouseClick(const QMouseEvent* event)
-{
- switch(this->mode) {
- case SelectMode:
- if (event->button() == Qt::LeftButton) {
- const ModelId highlighted = this->renderer->pick(event->pos());
- Q_EMIT this->select({highlighted}, false);
- }
- break;
- case DrawMode:
- if (event->button() == Qt::LeftButton and this->worldPosition.has_value()) {
- if (isCloseToExistingPoints()) {
- this->closeShape();
- }
- else {
- this->polygon.push_back(*this->worldPosition);
- }
- }
- break;
- case CircleMode:
- if (event->button() == Qt::LeftButton and this->worldPosition.has_value()) {
- if (this->polygon.size() == 2) {
- this->closeShape();
- }
- else {
- this->polygon.push_back(*this->worldPosition);
- }
- }
- break;
- }
- if (event->button() == Qt::RightButton and this->polygon.size() > 1) {
- this->removeLastPoint();
- }
-}
-
-struct MergedTriangles
-{
- std::vector quadrilaterals;
- std::set cutTriangles;
-};
-
-static MergedTriangles mergeTriangles(
- const std::vector& indices,
- const std::vector& polygon)
-{
- MergedTriangles result;
- using indextype = std::uint16_t;
- using indexpair = std::pair;
- struct boundaryinfo { indextype third; std::size_t triangleid; };
- std::map boundaries;
- for (std::size_t i = 0; i < indices.size(); i += 3) {
- const auto add = [&](const std::size_t o1, const std::size_t o2, const std::size_t o3){
- const auto key = std::make_pair(indices[i + o1], indices[i + o2]);
- boundaries[key] = {indices[i + o3], i};
- };
- add(0, 1, 2);
- add(1, 2, 0);
- add(2, 0, 1);
- }
- std::vector> quadindices;
- std::vector quads;
- bool repeat = true;
- const auto iscut = [&result](const std::size_t i){
- return result.cutTriangles.find(i) != result.cutTriangles.end();
- };
- while (repeat) {
- repeat = false;
- // Go through triangle boundaries
- for (const auto& it1 : boundaries) {
- const indexpair& pair1 = it1.first;
- const boundaryinfo& boundary1 = it1.second;
- // .. the ones we haven't already merged anyway
- if (not iscut(boundary1.triangleid)) {
- // Look for its inverse boundary to find the touching triangle
- const auto pair2 = std::make_pair(pair1.second, pair1.first);
- const auto it2 = boundaries.find(pair2);
- // Also if that hasn't been cut
- if (it2 != boundaries.end() and not iscut(it2->second.triangleid)) {
- const Quadrilateral quad{
- polygon[pair1.first],
- polygon[it2->second.third],
- polygon[pair1.second],
- polygon[boundary1.third],
- };
- if (isConvex(quad)) {
- result.quadrilaterals.push_back(quad);
- result.cutTriangles.insert(boundary1.triangleid);
- result.cutTriangles.insert(it2->second.triangleid);
- repeat = true;
- }
- }
- }
- }
- }
- return result;
-}
-
-
-const std::vector EditTools::circleModeActions() const
-{
- std::vector result;
- if (this->numpoints == 2) {
- const glm::vec3 x = polygon[1] - polygon[0];
- glm::mat4 transform{
- glm::vec4{x, 0},
- this->gridMatrix[2],
- glm::vec4{glm::cross(glm::vec3{-this->gridMatrix[2]}, x), 0},
- glm::vec4{this->polygon[0], 1},
- };
- Colored circ{
- CircularPrimitive{
- .type = this->circleToolOptions.type,
- .fraction = this->circleToolOptions.fraction,
- .transformation = transform,
- },
- MAIN_COLOR
- };
- result.push_back(AppendToModel{.newElement = circ});
- }
- return result;
-}
-
-const std::vector EditTools::drawModeActions() const
-{
- std::vector result;
- if (this->numpoints == 2) {
- result.push_back(AppendToModel{
- .newElement = Colored{
- LineSegment{
- .p1 = this->polygon[0],
- .p2 = this->polygon[1],
- },
- EDGE_COLOR,
- }
- });
- }
- else if (this->numpoints > 2) {
- const glm::mat4 inverseGrid = glm::inverse(this->gridMatrix);
- std::vector> polygons{1};
- std::vector& polygon2d = polygons.back();
- polygon2d.reserve(this->numpoints);
- for (std::size_t i = 0; i < this->numpoints; ++i) {
- polygon2d.push_back(inverseGrid * glm::vec4{this->polygon[i], 1});
- }
- using indextype = std::uint16_t;
- const std::vector indices = mapbox::earcut(polygons);
- MergedTriangles mergedTriangles = mergeTriangles(indices, this->polygon);
- for (const Quadrilateral& quad : mergedTriangles.quadrilaterals) {
- result.push_back(AppendToModel{
- .newElement = Colored{quad, MAIN_COLOR},
- });
- }
- for (std::size_t i = 0; i < indices.size(); i += 3) {
- if (mergedTriangles.cutTriangles.find(i) == mergedTriangles.cutTriangles.end()) {
- result.push_back(AppendToModel{
- .newElement = Colored{
- Triangle{
- .p1 = this->polygon[indices[i]],
- .p2 = this->polygon[indices[i + 1]],
- .p3 = this->polygon[indices[i + 2]],
- },
- MAIN_COLOR,
- }
- });
- }
- }
- }
- return result;
-}
-
-void EditTools::closeShape()
-{
- for (const ModelAction& action : this->modelActions()) {
- Q_EMIT this->modelAction(action);
- }
- this->polygon.clear();
- this->polygon.push_back(this->worldPosition.value_or(glm::vec3{0, 0, 0}));
-}