src/ui/canvas.cpp

changeset 191
d355d4c52d51
parent 189
815fbaae9cb2
child 197
0e729e681a2c
--- 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

mercurial