Fri, 06 Mar 2020 16:08:53 +0200
begin work on axes program
#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); } */ // use a relatively high threshold so that we know when the grid is somewhat perpendicular so we can // automatically change it properly if (isGridPerpendicularToScreen(0.03f)) { 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 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->newStatusText("rotate by X axis"); this->gridMatrix = glm::rotate(this->gridMatrix, float{M_PI} / 2, glm::vec3{1, 0, 0}); } else { this->newStatusText("rotate by Y axis"); this->gridMatrix = glm::rotate(this->gridMatrix, float{M_PI} / 2, glm::vec3{0, 1, 0}); } this->updateGridMatrix(); this->update(); } else { this->newStatusText("don't rotate"); } 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->gridMatrix = glm::mat4{ {1, 0, 0, 0}, {0, 0, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 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); } glm::vec3 Canvas::cameraVector() const { // Find out where the grid is projected on the screen const QPoint gridOrigin2d = pointFToPoint(this->modelToScreenCoordinates(this->gridPlane.anchor)); // Find out which direction the camera is looking at the grid origin in 3d return glm::normalize(this->cameraLine(gridOrigin2d).direction); } /** * @brief Calculates if the screen is perpendicular to the current grid * @return yes no */ bool Canvas::isGridPerpendicularToScreen(float threshold) const { const glm::vec3 cameraDirection = this->cameraVector(); // Compute the dot product. The parameters given are: // - the normal of the grid plane, which is the vector from the grid origin perpendicular to the grid // - the direction of the camera looking at the grid, which is the inverse of the vector from the grid // origin towards the camera // If the dot product between these two vectors is 0, the grid normal is perpendicular to the camera vector // and the grid is perpendicular to the screen. const float dot = glm::dot(glm::normalize(this->gridPlane.normal), glm::normalize(cameraDirection)); return std::abs(dot) < threshold; }