reimplement EditTools as a render layer

Tue, 14 Jun 2022 17:55:50 +0300

author
Teemu Piippo <teemu.s.piippo@gmail.com>
date
Tue, 14 Jun 2022 17:55:50 +0300
changeset 217
6d95c1a41e6e
parent 216
c7241f504117
child 218
63125c36de73

reimplement EditTools as a render layer

src/document.cpp file | annotate | diff | comparison | revisions
src/document.h file | annotate | diff | comparison | revisions
src/documentmanager.cpp file | annotate | diff | comparison | revisions
src/gl/common.h file | annotate | diff | comparison | revisions
src/gl/compiler.h file | annotate | diff | comparison | revisions
src/gl/gridprogram.cpp file | annotate | diff | comparison | revisions
src/gl/gridprogram.h file | annotate | diff | comparison | revisions
src/gl/partrenderer.cpp file | annotate | diff | comparison | revisions
src/gl/partrenderer.h file | annotate | diff | comparison | revisions
src/main.cpp file | annotate | diff | comparison | revisions
--- 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
--- a/src/document.h	Mon Jun 13 02:18:25 2022 +0300
+++ b/src/document.h	Tue Jun 14 17:55:50 2022 +0300
@@ -22,6 +22,7 @@
 #include <QToolBar>
 #include "model.h"
 #include "vertexmap.h"
+#include "gl/common.h"
 
 enum EditingMode
 {
@@ -31,15 +32,6 @@
 
 Q_DECLARE_METATYPE(EditingMode);
 
-struct DrawState
-{
-	std::vector<glm::vec3> polygon;
-	std::vector<glm::vec3> previewPolygon;
-	glm::vec3 previewPoint;
-	bool isconcave = false;
-	EditingMode mode = SelectMode;
-};
-
 struct AppendToModel
 {
 	ModelElement newElement;
@@ -54,30 +46,37 @@
 
 Q_DECLARE_METATYPE(ModelAction)
 
-class EditTools : public QObject
+class EditTools final : public QObject, public RenderLayer
 {
 	Q_OBJECT
+	std::vector<glm::vec3> polygon;
+	std::vector<glm::vec3> previewPolygon;
+	glm::vec3 previewPoint;
+	bool isconcave = false;
+	EditingMode mode = SelectMode;
+	glm::mat4 mvpMatrix;
+	glm::mat4 gridMatrix{1};
+	Plane gridPlane;
+	opt<glm::vec3> worldPosition;
 public:
-	explicit EditTools(
-		Model* model,
-		const ColorTable& colorTable,
-		QObject *parent = nullptr);
+	explicit EditTools(QObject *parent = nullptr);
 	~EditTools() override;
 	void applyToVertices(VertexMap::ApplyFunction fn) const;
 	const QSet<ModelId> selectedObjects() const;
-	const ColorTable& colorTable;
-	Model* const model;
 	EditingMode currentEditingMode() const;
 	Q_SLOT void setEditMode(EditingMode mode);
-	Q_SLOT void canvasMouseClick(QMouseEvent* event);
-	Q_SLOT void canvasMouseMove(QMouseEvent* event);
+	Q_SLOT void setGridMatrix(const glm::mat4& gridMatrix);
 Q_SIGNALS:
 	void newStatusText(const QString& newStatusText);
-	void splitterChanged();
 	void modelAction(const ModelAction& action);
-	void drawStateChanged(const DrawState& drawState);
+	void select(const QSet<ModelId>& ids, bool retain);
+protected:
+	void mvpMatrixChanged(const glm::mat4& matrix) override;
+	void mouseMoved(const QMouseEvent* event) override;
+	void mouseClick(const QMouseEvent* event) override;
+	void overpaint(QPainter* painter) override;
 private:
 	void closeShape();
-	DrawState drawState;
-	VertexMap vertexMap;
+	void updatePreviewPolygon();
+	void removeLastPoint();
 };
--- a/src/documentmanager.cpp	Mon Jun 13 02:18:25 2022 +0300
+++ b/src/documentmanager.cpp	Tue Jun 14 17:55:50 2022 +0300
@@ -37,6 +37,7 @@
 {
 	const ModelId modelId{++this->modelIdCounter};
 	this->openModels.emplace(std::make_pair(modelId, ModelInfo{
+		.model = std::make_unique<Model>(this),
 		.id = modelId,
 		.opentype = OpenType::ManuallyOpened,
 	}));
--- a/src/gl/common.h	Mon Jun 13 02:18:25 2022 +0300
+++ b/src/gl/common.h	Tue Jun 14 17:55:50 2022 +0300
@@ -31,11 +31,20 @@
 
 class RenderLayer
 {
+protected:
+	class PartRenderer* renderer;
 public:
+	virtual ~RenderLayer(){}
 	virtual void initializeGL(){}
 	virtual void paintGL(){}
 	virtual void overpaint(QPainter*){}
 	virtual void mvpMatrixChanged(const glm::mat4& mvpMatrix) = 0;
+	virtual void mouseMoved(const QMouseEvent*){}
+	virtual void mouseClick(const QMouseEvent*){}
+	void setRendererPointer(class PartRenderer* renderer)
+	{
+		this->renderer = renderer;
+	}
 };
 
 namespace gl
--- a/src/gl/compiler.h	Mon Jun 13 02:18:25 2022 +0300
+++ b/src/gl/compiler.h	Tue Jun 14 17:55:50 2022 +0300
@@ -71,9 +71,8 @@
 		} shaderObjects[gl::NUM_ARRAY_CLASSES];
 	};
 
-	void build(
-		ModelShaders* shaders,
-		Model *model,
+	void build(ModelShaders* shaders,
+		Model* model,
 		const ColorTable& colorTable,
 		DocumentManager* context,
 		const RenderPreferences& preferences);
--- a/src/gl/gridprogram.cpp	Mon Jun 13 02:18:25 2022 +0300
+++ b/src/gl/gridprogram.cpp	Tue Jun 14 17:55:50 2022 +0300
@@ -68,7 +68,10 @@
 
 void GridLayer::setGridMatrix(const glm::mat4& newGridMatrix)
 {
-	this->shader.setUniformMatrix("grid", newGridMatrix);
+	this->gridMatrix = newGridMatrix;
+	if (this->isInitialized) {
+		this->shader.setUniformMatrix("grid", newGridMatrix);
+	}
 }
 
 void GridLayer::setGridColor(const QColor& newGridColor)
@@ -97,7 +100,7 @@
 	this->isInitialized = true;
 	constexpr auto data = calcGridData<50>();
 	this->shader.setUniformVector("gridColor", this->gridColor);
-	this->setGridMatrix({{1, 0, 0, 0}, {0, 0, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}});
+	this->setGridMatrix(this->gridMatrix);
 	this->shader.bufferData(data.data(), data.size(), sizeof data[0]);
 }
 
--- a/src/gl/gridprogram.h	Mon Jun 13 02:18:25 2022 +0300
+++ b/src/gl/gridprogram.h	Tue Jun 14 17:55:50 2022 +0300
@@ -24,6 +24,7 @@
 {
 	BasicShader shader;
 	glm::vec4 gridColor = {1.0f, 1.0f, 1.0f, 0.75f};
+	glm::mat4 gridMatrix{1};
 	bool isInitialized = false;
 public:
 	void setGridMatrix(const glm::mat4& newGridMatrix);
--- a/src/gl/partrenderer.cpp	Mon Jun 13 02:18:25 2022 +0300
+++ b/src/gl/partrenderer.cpp	Tue Jun 14 17:55:50 2022 +0300
@@ -18,6 +18,7 @@
 
 #include <glm/ext/matrix_transform.hpp>
 #include <glm/ext/matrix_clip_space.hpp>
+#include <QPainter>
 #include <GL/glu.h>
 #include <QMouseEvent>
 #include <QMessageBox>
@@ -132,6 +133,10 @@
 	for (RenderLayer* layer : this->activeRenderLayers) {
 		layer->paintGL();
 	}
+	QPainter painter{this};
+	for (RenderLayer* layer : this->activeRenderLayers) {
+		layer->overpaint(&painter);
+	}
 }
 
 void PartRenderer::renderScene()
@@ -292,6 +297,7 @@
 {
 	const bool left = event->buttons() & Qt::LeftButton;
 	const QPoint move = event->pos() - this->lastMousePosition;
+	this->totalMouseMove += move.manhattanLength();
 	if (left and not move.isNull())
 	{
 		// q_x is the rotation of the brick along the vertical y-axis, because turning the
@@ -306,6 +312,27 @@
 		this->updateModelMatrix();
 	}
 	this->lastMousePosition = event->pos();
+	for (RenderLayer* layer : this->activeRenderLayers) {
+		layer->mouseMoved(event);
+	}
+	this->update();
+}
+
+void PartRenderer::mousePressEvent(QMouseEvent* event)
+{
+	this->totalMouseMove = 0;
+	this->lastMousePosition = event->pos();
+}
+
+void PartRenderer::mouseReleaseEvent(QMouseEvent* event)
+{
+	if (this->totalMouseMove < (2.0 / sqrt(2)) * 5.0)
+	{
+		for (RenderLayer* layer : this->activeRenderLayers) {
+			layer->mouseClick(event);
+		}
+		this->update();
+	}
 }
 
 void PartRenderer::wheelEvent(QWheelEvent* event)
@@ -320,6 +347,7 @@
 void PartRenderer::addRenderLayer(RenderLayer* layer)
 {
 	this->activeRenderLayers.push_back(layer);
+	layer->setRendererPointer(this);
 	this->update();
 }
 
@@ -364,12 +392,17 @@
 {
 	const glm::vec3 projected = glm::project(
 		point,
-		this->viewMatrix * glm::mat4_cast(this->modelQuaternion),
+		this->viewMatrix * this->modelMatrix,
 		this->projectionMatrix,
 		this->viewportVector);
 	return toQPointF(glm::vec2{projected.x, this->height() - projected.y});
 }
 
+bool PartRenderer::isDark() const
+{
+	return luma(this->renderPreferences.backgroundColor) < 0.25;
+}
+
 Line<3> PartRenderer::cameraLine(const QPointF& point) const
 {
 	const glm::vec3 p1 = this->unproject({point.x(), point.y(), 0});
@@ -386,7 +419,7 @@
 {
 	return glm::unProject(
 		glm::vec3{win.x, this->height() - win.y, win.z},
-		this->viewMatrix * glm::mat4_cast(this->modelQuaternion),
+		this->viewMatrix * this->modelMatrix,
 		this->projectionMatrix,
 		viewportVector);
 }
@@ -445,3 +478,10 @@
 {
 	return this->highlighted;
 }
+
+void PartRenderer::setSelection(const QSet<ModelId>& selection)
+{
+	Q_ASSERT(not selection.contains({0}));
+	gl::setModelShaderSelectedObjects(&this->shaders, selection);
+	this->update();
+}
--- a/src/gl/partrenderer.h	Mon Jun 13 02:18:25 2022 +0300
+++ b/src/gl/partrenderer.h	Tue Jun 14 17:55:50 2022 +0300
@@ -6,9 +6,28 @@
 #include "documentmanager.h"
 #include "types/boundingbox.h"
 
-class PartRenderer : public QOpenGLWidget
+class PartRenderer final : public QOpenGLWidget
 {
 	Q_OBJECT
+	Model* const model;
+	DocumentManager* const documents;
+	const ColorTable& colorTable;
+	BoundingBox boundingBox;
+	gl::ModelShaders shaders;
+	ModelId highlighted = {0};
+	glm::mat4 projectionMatrix;
+	glm::mat4 viewMatrix;
+	glm::mat4 modelMatrix;
+	glm::vec4 viewportVector;
+	glm::quat modelQuaternion;
+	QPoint lastMousePosition;
+	int totalMouseMove = 0;
+	gl::RenderPreferences renderPreferences;
+	double zoom = 1.0;
+	bool initialized = false;
+	bool needBuild = true;
+	std::vector<RenderLayer*> activeRenderLayers;
+	std::vector<RenderLayer*> inactiveRenderLayers;
 public:
 	PartRenderer(
 		Model* model,
@@ -20,46 +39,31 @@
 	ModelId getHighlightedObject() const;
 	void addRenderLayer(RenderLayer* layer);
 	void setLayerEnabled(RenderLayer* layer, bool enabled);
-protected:
+	std::optional<glm::vec3> screenToModelCoordinates(const QPointF& point, const Plane& plane) const;
+	QPointF modelToScreenCoordinates(const glm::vec3& point) const;
+	bool isDark() const;
 	ModelId pick(QPoint where);
-	void initializeGL() override;
-	void resizeGL(int width, int height) override;
-	void paintGL() override;
-	void mouseMoveEvent(QMouseEvent* event) override;
-	void wheelEvent(QWheelEvent* event) override;
-	Model* const model;
-	DocumentManager* const documents;
-	const ColorTable& colorTable;
-	BoundingBox boundingBox;
-	gl::ModelShaders shaders;
-	ModelId highlighted = {0};
-	std::optional<glm::vec3> screenToModelCoordinates(const QPointF &point, const Plane& plane) const;
-	QPointF modelToScreenCoordinates(const glm::vec3& point) const;
-	Line<3> cameraLine(const QPointF& point) const;
-	glm::vec3 unproject(const glm::vec3& win) const;
-	glm::mat4 projectionMatrix;
-	glm::mat4 viewMatrix;
-	glm::mat4 modelMatrix;
-	glm::vec4 viewportVector;
-	glm::quat modelQuaternion;
-	QPoint lastMousePosition;
-	gl::RenderPreferences renderPreferences;
+	void setSelection(const QSet<ModelId>& selectedIds);
 Q_SIGNALS:
 	void projectionMatrixChanged(const glm::mat4& newMatrix);
 	void modelMatrixChanged(const glm::mat4& newMatrix);
 	void viewMatrixChanged(const glm::mat4& newMatrix);
 	void renderPreferencesChanged();
 private:
+	void initializeGL() override;
+	void resizeGL(int width, int height) override;
+	void paintGL() override;
+	void mouseMoveEvent(QMouseEvent* event) override;
+	void mousePressEvent(QMouseEvent* event) override;
+	void mouseReleaseEvent(QMouseEvent* event) override;
+	void wheelEvent(QWheelEvent* event) override;
+	Line<3> cameraLine(const QPointF& point) const;
+	glm::vec3 unproject(const glm::vec3& win) const;
 	void setFragmentStyle(gl::FragmentStyle fragStyle);
 	void renderScene();
 	void updateViewMatrix();
 	void updateModelMatrix();
 	Q_SLOT void build();
-	double zoom = 1.0;
-	bool initialized = false;
-	bool needBuild = true;
 	void renderVao(const gl::ArrayClass arrayClass);
 	void checkForGLErrors();
-	std::vector<RenderLayer*> activeRenderLayers;
-	std::vector<RenderLayer*> inactiveRenderLayers;
 };
--- a/src/main.cpp	Mon Jun 13 02:18:25 2022 +0300
+++ b/src/main.cpp	Tue Jun 14 17:55:50 2022 +0300
@@ -149,7 +149,8 @@
 
 static ModelSubWindow* currentModelSubWindow(Ui_MainWindow* ui)
 {
-	return qobject_cast<ModelSubWindow*>(ui->mdiArea->activeSubWindow());
+	auto* w = ui->mdiArea->activeSubWindow();
+	return qobject_cast<ModelSubWindow*>(w);
 }
 
 static ModelData* currentModelData(Ui_MainWindow* ui, DocumentManager* documents)
@@ -395,39 +396,39 @@
 		Model* model = documents.getModelById(modelId);
 		if (model != nullptr) {
 			ModelData* data = new ModelData(&documents);
-			data->tools = std::make_unique<EditTools>(model, colorTable);
+			data->tools = std::make_unique<EditTools>();
 			data->canvas = std::make_unique<PartRenderer>(model, &documents, colorTable);
-			data->itemSelectionModel = std::make_unique<QItemSelectionModel>(model);
+			data->itemSelectionModel = std::make_unique<QItemSelectionModel>();
+			data->itemSelectionModel->setModel(model);
 			data->axesLayer = std::make_unique<AxesLayer>();
+			constexpr glm::mat4 XZ = {{1, 0, 0, 0}, {0, 0, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}};
 			data->gridLayer = std::make_unique<GridLayer>();
+			data->gridLayer->setGridMatrix(XZ);
+			data->tools->setGridMatrix(XZ);
 			data->model = model;
 			data->canvas->addRenderLayer(data->axesLayer.get());
 			data->canvas->setLayerEnabled(data->axesLayer.get(), settings.drawAxes());
 			data->canvas->addRenderLayer(data->gridLayer.get());
+			data->canvas->addRenderLayer(data->tools.get());
 			documents.setModelPayload(modelId, data);
 			QObject::connect(
 				data->tools.get(),
 				&EditTools::modelAction,
 				std::bind(executeAction, model, std::placeholders::_1));
-#if 0
-			QObject::connect(data->itemSelectionModel.get(), &QItemSelectionModel::selectionChanged,
-				[modelId, &documents](
-				const QItemSelection& selected,
-				const QItemSelection& deselected)
-			{
+			QObject::connect(
+				data->itemSelectionModel.get(),
+				&QItemSelectionModel::selectionChanged,
+				[modelId, &documents]{
 				ModelData* data = findModelData(&documents, modelId);
 				if (data != nullptr) {
 					auto resolveIndex = [&data](const QModelIndex& index){
 						return data->model->idAt(index.row());
 					};
-					auto resolve = [&resolveIndex](const QItemSelection& selection)
-					{
-						return fn::map<QSet<ModelId>>(selection.indexes(), resolveIndex);
-					};
-					data->canvas->handleSelectionChange(resolve(selected), resolve(deselected));
+					const auto selection = data->itemSelectionModel->selection();
+					const auto indices = fn::map<QSet<ModelId>>(selection.indexes(), resolveIndex);
+					data->canvas->setSelection(indices);
 				}
 			});
-#endif
 			data->canvas->setRenderPreferences(renderPreferences);
 			QObject::connect(
 				data->tools.get(),
@@ -435,8 +436,27 @@
 				[&](const QString& newStatusText) {
 					mainWindow.statusBar()->showMessage(newStatusText);
 				});
+			QObject::connect(
+				data->tools.get(),
+				&EditTools::select,
+				[modelId, &documents](const QSet<ModelId>& indices, bool retain) {
+					ModelData* data = findModelData(&documents, modelId);
+					if (data != nullptr) {
+						if (not retain) {
+							data->itemSelectionModel->clear();
+						}
+						for (const ModelId id : indices) {
+							opt<int> index = data->model->find(id);
+							if (index.has_value()) {
+								const QModelIndex qindex = data->model->index(*index);
+								data->itemSelectionModel->select(qindex, QItemSelectionModel::Select);
+							}
+						}
+					}
+				});
 			const QFileInfo fileInfo{*documents.modelPath(modelId)};
-			QMdiSubWindow* subWindow = ui.mdiArea->addSubWindow(data->canvas.get());
+			ModelSubWindow* subWindow = new ModelSubWindow{modelId, ui.mdiArea};
+			subWindow->setWidget(data->canvas.get());
 			subWindow->setWindowTitle(tabName(fileInfo));
 			subWindow->show();
 		}

mercurial