--- a/src/document.cpp Mon Jun 13 02:18:25 2022 +0300 +++ b/src/document.cpp Tue Jun 14 17:55:50 2022 +0300 @@ -17,65 +17,193 @@ */ #include <QMouseEvent> -#include <QMessageBox> -#include <QVBoxLayout> +#include <QPainter> #include "document.h" #include "model.h" #include "ui/objecteditor.h" +#include "gl/partrenderer.h" -EditTools::EditTools( - Model* model, - const ColorTable& colorTable, - QObject* parent) : +EditTools::EditTools(QObject* parent) : QObject{parent}, - colorTable{colorTable}, - model{model}, - vertexMap{model} + RenderLayer{} { -#if 0 - connect(this->canvas, &Canvas::mouseClick, this, &EditTools::canvasMouseClick); - connect(this->canvas, &Canvas::mouseMove, this, &EditTools::canvasMouseMove); - connect(this->canvas, &Canvas::newStatusText, this, &EditTools::newStatusText); - connect(this->model, &Model::dataChanged, this->canvas, qOverload<>(&Canvas::update)); - connect(&this->vertexMap, &VertexMap::verticesChanged, [&]() - { - this->canvas->rebuildVertices(&this->vertexMap); - }); - this->canvas->drawState = &this->drawState; -#endif } EditTools::~EditTools() { } -void EditTools::applyToVertices(VertexMap::ApplyFunction fn) const -{ - this->vertexMap.apply(fn); -} - void EditTools::setEditMode(EditingMode mode) { - this->drawState.mode = mode; + this->mode = mode; +} + +void EditTools::setGridMatrix(const glm::mat4& gridMatrix) +{ + this->gridMatrix = gridMatrix; + 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::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->updatePreviewPolygon(); +} + +static QVector<QPointF> convertWorldPointsToScreenPoints( + const std::vector<glm::vec3> &worldPoints, + const PartRenderer* renderer) +{ + QVector<QPointF> points2d; + points2d.reserve(worldPoints.size()); + for (const glm::vec3& point : worldPoints) + { + points2d.push_back(renderer->modelToScreenCoordinates(point)); + } + return points2d; +} + +static Winding worldPolygonWinding( + const std::vector<glm::vec3> &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})); } -void updatePreviewPolygon(DrawState* drawState) +static void drawWorldPolyline( + QPainter *painter, + const std::vector<glm::vec3> &points, + const PartRenderer* renderer) +{ + painter->drawPolyline(QPolygonF{convertWorldPointsToScreenPoints(points, renderer)}); +} + +static void drawWorldPolygon( + QPainter* painter, + const std::vector<glm::vec3> &points, + const PartRenderer* renderer) +{ + painter->drawPolygon(QPolygonF{convertWorldPointsToScreenPoints(points, renderer)}); +} + +void EditTools::overpaint(QPainter* painter) { - drawState->previewPolygon = drawState->polygon; - drawState->previewPolygon.resize(drawState->polygon.size() + 1); - drawState->previewPolygon.back() = drawState->previewPoint; - if (drawState->previewPolygon.size() > 2) + struct Pens { - drawState->isconcave = not isConvex(drawState->previewPolygon); + const QBrush pointBrush; + const QPen pointPen; + const QPen polygonPen; + const QPen badPolygonPen; + const QBrush greenPolygonBrush; + const QBrush redPolygonBrush; + }; + static const Pens brightPens{ + .pointBrush = {Qt::white}, + .pointPen = {QBrush{Qt::black}, 2.0}, + .polygonPen = {QBrush{Qt::black}, 2.0, Qt::DashLine}, + .badPolygonPen = {QBrush{Qt::red}, 2.0, Qt::DashLine}, + .greenPolygonBrush = {QColor{64, 255, 128, 192}}, + .redPolygonBrush = {QColor{255, 96, 96, 192}}, + }; + static const Pens darkPens{ + .pointBrush = {Qt::black}, + .pointPen = {QBrush{Qt::white}, 2.0}, + .polygonPen = {QBrush{Qt::white}, 2.0, Qt::DashLine}, + .badPolygonPen = {QBrush{Qt::red}, 2.0, Qt::DashLine}, + .greenPolygonBrush = {QColor{64, 255, 128, 192}}, + .redPolygonBrush = {QColor{255, 96, 96, 192}}, + }; + const Pens& pens = (this->renderer->isDark() ? darkPens : brightPens); + switch(this->mode) { + case SelectMode: + break; + case DrawMode: + { + painter->setPen(this->isconcave ? pens.badPolygonPen : pens.polygonPen); + if (this->previewPolygon.size() > 2 and not this->isconcave) + { + if (worldPolygonWinding(this->previewPolygon, this->renderer) == Winding::Clockwise) { + painter->setBrush(pens.greenPolygonBrush); + } + else { + painter->setBrush(pens.redPolygonBrush); + } + drawWorldPolygon(painter, this->previewPolygon, this->renderer); + } + else { + drawWorldPolyline(painter, this->previewPolygon, this->renderer); + } + painter->setBrush(pens.pointBrush); + painter->setPen(pens.pointPen); + for (const glm::vec3& point : this->polygon) { + drawWorldPoint(painter, point, this->renderer); + } + } + break; + } + if (this->worldPosition.has_value()) + { + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(Qt::white); + painter->setBrush(Qt::green); + const QPointF pos = this->renderer->modelToScreenCoordinates(*this->worldPosition); + painter->drawEllipse(pos, 5, 5); + painter->drawText(pos + QPointF{5, 5}, vectorToString(*this->worldPosition)); } } -void removeLastPoint(DrawState* drawState) +void EditTools::updatePreviewPolygon() { - if (drawState->polygon.size() > 0) + this->previewPolygon = this->polygon; + if (this->worldPosition.has_value()) { + this->previewPolygon.resize(this->polygon.size() + 1); + this->previewPolygon.back() = *this->worldPosition; + } + if (this->previewPolygon.size() > 2) { - drawState->polygon.erase(drawState->polygon.end() - 1); - updatePreviewPolygon(drawState); + this->isconcave = not isConvex(this->previewPolygon); + } +} + +void EditTools::removeLastPoint() +{ + if (this->polygon.size() > 0) + { + this->polygon.erase(this->polygon.end() - 1); + this->updatePreviewPolygon(); } } @@ -84,106 +212,51 @@ return any(points, std::bind(isclose, std::placeholders::_1, pos)); } -void EditTools::canvasMouseClick(QMouseEvent*) +EditingMode EditTools::currentEditingMode() const { -#if 0 - switch(this->drawState.mode) - { + return this->mode; +} + +void EditTools::mouseClick(const QMouseEvent* event) +{ + switch(this->mode) { case SelectMode: - if (event->button() == Qt::LeftButton) - { - const ModelId highlighted = this->canvas->getHighlightedObject(); - QSet<ModelId> selected; - if (highlighted != ModelId{0}) { - selected.insert(highlighted); - } - //this->select(selected); - event->accept(); + 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) { - if (isCloseToExistingPoints(this->drawState.polygon, worldPosition)) { + if (event->button() == Qt::LeftButton and this->worldPosition.has_value()) { + if (isCloseToExistingPoints(this->polygon, *this->worldPosition)) { this->closeShape(); } else { - this->drawState.polygon.push_back(pos); - updatePreviewPolygon(&this->drawState); + this->polygon.push_back(*this->worldPosition); + this->updatePreviewPolygon(); } - event->accept(); } else if (true and event->button() == Qt::RightButton - and this->drawState.polygon.size() > 0 + and this->polygon.size() > 0 ) { - this->drawState.polygon.erase(this->drawState.polygon.end() - 1); - updatePreviewPolygon(&this->drawState); - event->accept(); + this->polygon.erase(this->polygon.end() - 1); + updatePreviewPolygon(); } break; } -#endif } -void EditTools::canvasMouseMove(QMouseEvent*) -{ -#if 0 - switch(this->drawState.mode) - { - case SelectMode: - break; - case DrawMode: - if (this->canvas->worldPosition.has_value()) - { - this->drawState.previewPoint = this->canvas->worldPosition.value(); - updatePreviewPolygon(&this->drawState); - this->update(); - } - event->accept(); - break; - } -#endif -} -#if 0 -/* -void EditorTabWidget::select(const QSet<ModelId> &selected) -{ - QItemSelectionModel* selectionModel = this->ui.listView->selectionModel(); - QItemSelection itemSelection; - for (const ModelId id : selected) - { - const std::optional<int> row = this->model->find(id); - if (row.has_value()) - { - const QModelIndex qindex = this->model->index(*row); - itemSelection.select(qindex, qindex); - } - } - selectionModel->select(itemSelection, QItemSelectionModel::ClearAndSelect); -} -*/ -const QSet<ModelId> EditTools::selectedObjects() const -{ - return this->canvas->selectedObjects(); -} -#endif -EditingMode EditTools::currentEditingMode() const -{ - return this->drawState.mode; -} -#if 0 void EditTools::closeShape() { - if (this->drawState.polygon.size() >= 2 and this->drawState.polygon.size() <= 4) - { - switch (this->drawState.polygon.size()) - { + if (this->polygon.size() >= 2 and this->polygon.size() <= 4) { + switch (this->polygon.size()) { case 2: Q_EMIT this->modelAction(AppendToModel{ .newElement = Colored<LineSegment>{ LineSegment{ - .p1 = this->drawState.polygon[0], - .p2 = this->drawState.polygon[1], + .p1 = this->polygon[0], + .p2 = this->polygon[1], }, EDGE_COLOR, } @@ -193,9 +266,9 @@ Q_EMIT this->modelAction(AppendToModel{ .newElement = Colored<Triangle>{ Triangle{ - .p1 = this->drawState.polygon[0], - .p2 = this->drawState.polygon[1], - .p3 = this->drawState.polygon[2], + .p1 = this->polygon[0], + .p2 = this->polygon[1], + .p3 = this->polygon[2], }, MAIN_COLOR, } @@ -205,10 +278,10 @@ Q_EMIT this->modelAction(AppendToModel{ .newElement = Colored<Quadrilateral>{ Quadrilateral{ - .p1 = this->drawState.polygon[0], - .p2 = this->drawState.polygon[1], - .p3 = this->drawState.polygon[2], - .p4 = this->drawState.polygon[3], + .p1 = this->polygon[0], + .p2 = this->polygon[1], + .p3 = this->polygon[2], + .p4 = this->polygon[3], }, MAIN_COLOR, } @@ -216,8 +289,6 @@ break; } } - this->drawState.polygon.clear(); - updatePreviewPolygon(&this->drawState); + this->polygon.clear(); + this->updatePreviewPolygon(); } - -#endif