src/ui/canvas.cpp

Sat, 29 Feb 2020 23:51:03 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Sat, 29 Feb 2020 23:51:03 +0200
changeset 65
87c906545fc3
parent 64
f99d52b1646b
child 66
77c819262b7a
permissions
-rw-r--r--

document the grid snapping transformations

#include <QMouseEvent>
#include <QPainter>
#include "canvas.h"

Canvas::Canvas(
	Model* model,
	DocumentManager* documents,
	const ldraw::ColorTable& colorTable,
	QWidget* parent) :
	PartRenderer{model, documents, colorTable, parent},
	gridProgram{this}
{
	this->setMouseTracking(true);
}

void Canvas::handleSelectionChange(const QSet<ldraw::Id>& selectedIds, const QSet<ldraw::Id>& deselectedIds)
{
	Q_ASSERT(not selectedIds.contains(ldraw::NULL_ID));
	this->selection.subtract(deselectedIds);
	this->selection.unite(selectedIds);
	this->compiler->setSelectedObjects(this->selection);
	this->update();
}

void Canvas::mouseMoveEvent(QMouseEvent* event)
{
	const ldraw::Id id = this->pick(event->pos());
	this->highlighted = id;
	this->totalMouseMove += (event->pos() - this->lastMousePosition).manhattanLength();
	this->worldPosition = this->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};
	}
	if (this->worldPosition.has_value())
	{
		this->newStatusText("Position: (%1, %2, %3)"_q
			.arg(toDouble(this->worldPosition->x))
			.arg(toDouble(this->worldPosition->y))
			.arg(toDouble(this->worldPosition->z)));
	}
	else
	{
		this->newStatusText("Position: <none>"_q);
	}
	PartRenderer::mouseMoveEvent(event);
}

void Canvas::mousePressEvent(QMouseEvent* event)
{
	this->totalMouseMove = 0;
	this->lastMousePosition = event->pos();
	PartRenderer::mousePressEvent(event);
}

void Canvas::mouseReleaseEvent(QMouseEvent* event)
{
	if (this->totalMouseMove < (2.0 / sqrt(2)) * 5.0)
	{
		if (this->highlighted == ldraw::NULL_ID)
		{
			this->selection = {};
		}
		else
		{
			this->selection = {this->highlighted};
		}
		this->compiler->setSelectedObjects(this->selection);
		emit selectionChanged(this->selection);
		this->update();
	}
	PartRenderer::mouseReleaseEvent(event);
}

void Canvas::initializeGL()
{
	// We first create the grid program and connect everything and only then call the part renderer's initialization
	// functions so that when initialization sets up, the signals also set up the matrices on our side.
	this->gridProgram.emplace(this);
	this->gridProgram->initialize();
	connect(this, &PartRenderer::projectionMatrixChanged, [&]()
	{
		this->gridProgram->setProjectionMatrix(this->projectionMatrix);
	});
	connect(this, &PartRenderer::modelMatrixChanged, [&]()
	{
		this->gridProgram->setModelMatrix(this->modelMatrix);
	});
	connect(this, &PartRenderer::viewMatrixChanged, [&]()
	{
		this->gridProgram->setViewMatrix(this->viewMatrix);
	});
	connect(this, &PartRenderer::renderPreferencesChanged, [&]()
	{
		if (this->gridProgram.has_value())
		{
			const bool isDark = luma(this->renderPreferences.backgroundColor) < 0.25;
			this->gridProgram->setGridColor(isDark ? Qt::white : Qt::black);
		}
	});
	PartRenderer::initializeGL();
	this->gridMatrix = glm::mat4{
		{-4, 0, 0, 0},
		{0, 6.9266, -3.6955, 0},
		{0, -16.7222, -1.5307, 0},
		{0, -13.273, -9.255, 1},
	};
	this->updateGridMatrix();
}

void Canvas::paintGL()
{
	PartRenderer::paintGL();
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	this->gridProgram->draw();
	glDisable(GL_BLEND);
	if (this->worldPosition.has_value())
	{
		QPainter painter{this};
		painter.setRenderHint(QPainter::Antialiasing);
		painter.setPen(Qt::black);
		painter.setBrush(Qt::green);
		const QPointF pos = this->modelToScreenCoordinates(*this->worldPosition);
		painter.drawEllipse(pos, 5, 5);
		painter.setPen(Qt::white);
		painter.drawText(pos + QPointF{5, 5}, vectorToString(*this->worldPosition));
	}
	{
		QPainter axisPainter{this};
		axisPainter.setRenderHint(QPainter::Antialiasing);
		axisPainter.setPen(Qt::red);
		axisPainter.drawLine(
			this->modelToScreenCoordinates({10, 0, 0}),
			this->modelToScreenCoordinates({-10, 0, 0}));
		axisPainter.setPen(Qt::green);
		axisPainter.drawLine(
			this->modelToScreenCoordinates({0, 10, 0}),
			this->modelToScreenCoordinates({0, -10, 0}));
		axisPainter.setPen(Qt::blue);
		axisPainter.drawLine(
			this->modelToScreenCoordinates({0, 0, 10}),
			this->modelToScreenCoordinates({0, 0, -10}));
	}
}

void Canvas::updateGridMatrix()
{
	const geom::Triangle triangle {
		this->gridMatrix * glm::vec4{0, 0, 0, 1},
		this->gridMatrix * glm::vec4{1, 0, 0, 1},
		this->gridMatrix * glm::vec4{0, 1, 0, 1},
	};
	this->gridPlane = geom::planeFromTriangle(triangle);
	this->gridProgram->setGridMatrix(this->gridMatrix);
}

mercurial