--- a/src/ui/canvas.cpp Wed May 25 13:49:45 2022 +0300 +++ b/src/ui/canvas.cpp Wed May 25 17:24:51 2022 +0300 @@ -1,13 +1,20 @@ #include <QMouseEvent> #include <QPainter> +#include "modeleditor.h" +#include "document.h" #include "canvas.h" +#include "linetypes/edge.h" +#include "linetypes/triangle.h" +#include "linetypes/quadrilateral.h" Canvas::Canvas( Model* model, + Document *document, DocumentManager* documents, const ldraw::ColorTable& colorTable, QWidget* parent) : - PartRenderer{model, documents, colorTable, parent} + PartRenderer{model, documents, colorTable, parent}, + document{document} { this->setMouseTracking(true); } @@ -39,6 +46,31 @@ } } +void updatePreviewPolygon(DrawState* drawState) +{ + drawState->previewPolygon = drawState->polygon; + drawState->previewPolygon.resize(drawState->polygon.size() + 1); + drawState->previewPolygon.back() = drawState->previewPoint; + if (drawState->previewPolygon.size() > 2) + { + drawState->isconcave = not geom::isConvex(drawState->previewPolygon); + } +} + +void removeLastPoint(DrawState* drawState) +{ + if (drawState->polygon.size() > 0) + { + drawState->polygon.erase(drawState->polygon.end() - 1); + updatePreviewPolygon(drawState); + } +} + +bool isCloseToExistingPoints(const std::vector<glm::vec3>& points, const glm::vec3 &pos) +{ + return any(points, std::bind(geom::isclose, std::placeholders::_1, pos)); +} + void Canvas::mouseMoveEvent(QMouseEvent* event) { const ldraw::id_t id = this->pick(event->pos()); @@ -62,7 +94,21 @@ // grid matrix. this->worldPosition = this->gridMatrix * glm::vec4{*this->worldPosition, 1}; } - Q_EMIT this->mouseMove(this, event); + switch(this->mode) + { + case SelectMode: + break; + case DrawMode: + const auto& worldPosition = this->getWorldPosition(); + if (worldPosition.has_value()) + { + this->drawState.previewPoint = worldPosition.value(); + updatePreviewPolygon(&this->drawState); + this->update(); + } + event->accept(); + break; + } PartRenderer::mouseMoveEvent(event); this->update(); } @@ -78,7 +124,45 @@ { if (this->totalMouseMove < (2.0 / sqrt(2)) * 5.0) { - Q_EMIT this->mouseClick(this, event); + switch(this->mode) + { + case SelectMode: + if (event->button() == Qt::LeftButton) + { + const ldraw::id_t highlighted = this->getHighlightedObject(); + this->clearSelection(); + if (highlighted != ldraw::NULL_ID) + { + this->addToSelection(highlighted); + } + event->accept(); + } + break; + case DrawMode: + if (event->button() == Qt::LeftButton and this->worldPosition.has_value()) + { + const glm::vec3& pos = worldPosition.value(); + if (isCloseToExistingPoints(this->drawState.polygon, pos)) + { + this->closeShape(); + } + else + { + this->drawState.polygon.push_back(pos); + updatePreviewPolygon(&this->drawState); + } + event->accept(); + } + else if (true + and event->button() == Qt::RightButton + and this->drawState.polygon.size() > 0 + ) { + this->drawState.polygon.erase(this->drawState.polygon.end() - 1); + updatePreviewPolygon(&this->drawState); + event->accept(); + } + break; + } } PartRenderer::mouseReleaseEvent(event); this->update(); @@ -114,6 +198,16 @@ this->updateCanvasRenderPreferences(); } +static const struct +{ + const QBrush pointBrush = {Qt::white}; + const QPen polygonPen = {QBrush{Qt::black}, 2.0, Qt::DashLine}; + const QPen badPolygonPen = {QBrush{Qt::red}, 2.0, Qt::DashLine}; + const QPen pointPen = {QBrush{Qt::black}, 2.0}; + const QBrush greenPolygonBrush = {QColor{64, 255, 128, 192}}; + const QBrush redPolygonBrush = {QColor{255, 96, 96, 192}}; +} pens; + void Canvas::paintGL() { PartRenderer::paintGL(); @@ -154,17 +248,44 @@ painter.drawEllipse(pos, 5, 5); painter.drawText(pos + QPointF{5, 5}, vectorToString(*this->worldPosition)); } + QPainter painter{this}; + painter.setRenderHint(QPainter::Antialiasing); + if (this->renderPreferences.drawAxes) { - QPainter painter{this}; - painter.setRenderHint(QPainter::Antialiasing); - if (this->renderPreferences.drawAxes) + this->renderAxesLabels(painter); + } + switch(this->mode) + { + case SelectMode: + break; + case DrawMode: { - this->renderAxesLabels(painter); + painter.setPen(this->drawState.isconcave ? ::pens.badPolygonPen : ::pens.polygonPen); + if (this->drawState.previewPolygon.size() > 2 and not this->drawState.isconcave) + { + if (this->worldPolygonWinding(this->drawState.previewPolygon) == Winding::Clockwise) + { + painter.setBrush(::pens.greenPolygonBrush); + } + else + { + painter.setBrush(::pens.redPolygonBrush); + } + this->drawWorldPolygon(&painter, this->drawState.previewPolygon); + } + else + { + this->drawWorldPolyline(&painter, this->drawState.previewPolygon); + } + painter.setBrush(::pens.pointBrush); + painter.setPen(::pens.pointPen); + for (const glm::vec3& point : this->drawState.polygon) + { + this->drawWorldPoint(&painter, point); + } + this->drawWorldPoint(&painter, this->drawState.previewPoint); } - if (this->overpaintCallback != nullptr) - { - this->overpaintCallback(this, &painter); - } + break; } } } @@ -264,22 +385,20 @@ /** * @brief Adjusts the grid to be so that it is perpendicular to the camera. */ -void Canvas::adjustGridToView() +void adjustGridToView(Canvas* canvas) { - const glm::vec3 cameraDirection = this->cameraVector(); - const glm::vec3 vector_x = glm::normalize(this->gridMatrix * glm::vec4{1, 0, 0, 1}); - const glm::vec3 vector_y = glm::normalize(this->gridMatrix * glm::vec4{0, 1, 0, 1}); + const glm::vec3 cameraDirection = canvas->cameraVector(); + const glm::mat4& grid = canvas->getGridMatrix(); + const glm::vec3 vector_x = glm::normalize(grid * glm::vec4{1, 0, 0, 1}); + const glm::vec3 vector_y = glm::normalize(grid * glm::vec4{0, 1, 0, 1}); const float angle_x = std::abs(glm::dot(vector_x, cameraDirection)); const float angle_y = std::abs(glm::dot(vector_y, cameraDirection)); - if (angle_x < angle_y) - { - this->setGridMatrix(glm::rotate(this->gridMatrix, PI<float> / 2, glm::vec3{1, 0, 0})); - } - else - { - this->setGridMatrix(glm::rotate(this->gridMatrix, PI<float> / 2, glm::vec3{0, 1, 0})); - } - this->update(); + canvas->setGridMatrix(glm::rotate( + grid, + pi<> * 0.5f, + (angle_x < angle_y) ? glm::vec3{1, 0, 0} : glm::vec3{0, 1, 0} + )); + canvas->update(); } /** @@ -295,6 +414,34 @@ return this->gridMatrix; } +void Canvas::closeShape() +{ + if (this->drawState.polygon.size() >= 2 and this->drawState.polygon.size() <= 4) + { + std::unique_ptr<ModelEditor> modelEditor = this->document->editModel(); + switch (this->drawState.polygon.size()) + { + case 2: + modelEditor->append<ldraw::Edge>( + vectorToArray<2>(this->drawState.polygon), + ldraw::EDGE_COLOR); + break; + case 3: + modelEditor->append<ldraw::Triangle>( + vectorToArray<3>(this->drawState.polygon), + ldraw::MAIN_COLOR); + break; + case 4: + modelEditor->append<ldraw::Quadrilateral>( + vectorToArray<4>(this->drawState.polygon), + ldraw::MAIN_COLOR); + break; + } + } + this->drawState.polygon.clear(); + updatePreviewPolygon(&this->drawState); +} + /** * @brief Paints a circle at where @c worldPoint is located on the screen. * @param painter Painter to use to render