diff --git a/dust3d.pro b/dust3d.pro index bd7b75f3..dbf711b9 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -410,6 +410,9 @@ HEADERS += src/intnumberwidget.h SOURCES += src/imagepreviewwidget.cpp HEADERS += src/imagepreviewwidget.h +SOURCES += src/mousepicker.cpp +HEADERS += src/mousepicker.h + SOURCES += src/main.cpp HEADERS += src/version.h diff --git a/languages/dust3d_zh_CN.ts b/languages/dust3d_zh_CN.ts index d6e81582..704f62b3 100644 --- a/languages/dust3d_zh_CN.ts +++ b/languages/dust3d_zh_CN.ts @@ -362,6 +362,10 @@ Tips: Check for Updates... 检查新版本... + + Paint brush + 画刷 + ExportPreviewWidget diff --git a/shaders/pbr-qt.frag b/shaders/pbr-qt.frag index 7a1ac35a..1c506292 100644 --- a/shaders/pbr-qt.frag +++ b/shaders/pbr-qt.frag @@ -75,6 +75,8 @@ uniform highp sampler2D metalnessRoughnessAmbientOcclusionMapId; uniform highp int metalnessMapEnabled; uniform highp int roughnessMapEnabled; uniform highp int ambientOcclusionMapEnabled; +uniform highp int mousePickEnabled; +uniform highp vec3 mousePickTargetPosition; const int MAX_LIGHTS = 8; const int TYPE_POINT = 0; @@ -320,6 +322,11 @@ void main() if (textureEnabled == 1) { color = texture2D(textureId, vertTexCoord).rgb; } + if (mousePickEnabled == 1) { + if (distance(mousePickTargetPosition, vert) <= 0.1) { + color = color + vec3(0.99, 0.4, 0.13); + } + } color = pow(color, vec3(gamma)); highp vec3 normal = vertNormal; diff --git a/src/document.cpp b/src/document.cpp index 66207f93..3b32dcb4 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -15,6 +15,7 @@ #include "motionsgenerator.h" #include "skeletonside.h" #include "scriptrunner.h" +#include "mousepicker.h" unsigned long Document::m_maxSnapshot = 1000; @@ -64,7 +65,9 @@ Document::Document() : m_meshGenerationId(0), m_nextMeshGenerationId(1), m_scriptRunner(nullptr), - m_isScriptResultObsolete(false) + m_isScriptResultObsolete(false), + m_mousePicker(nullptr), + m_isMouseTargetResultObsolete(false) { connect(&Preferences::instance(), &Preferences::partColorChanged, this, &Document::applyPreferencePartColorChange); connect(&Preferences::instance(), &Preferences::flatShadingChanged, this, &Document::applyPreferenceFlatShadingChange); @@ -1922,6 +1925,56 @@ void Document::postProcessedMeshResultReady() } } +void Document::pickMouseTarget(const QVector3D &nearPosition, const QVector3D &farPosition) +{ + m_mouseRayNear = nearPosition; + m_mouseRayFar = farPosition; + + if (nullptr != m_mousePicker) { + m_isMouseTargetResultObsolete = true; + return; + } + + m_isMouseTargetResultObsolete = false; + + if (!m_currentOutcome) { + qDebug() << "MeshLoader is null"; + return; + } + + qDebug() << "Mouse picking.."; + + QThread *thread = new QThread; + m_mousePicker = new MousePicker(*m_currentOutcome, m_mouseRayNear, m_mouseRayFar); + m_mousePicker->moveToThread(thread); + connect(thread, &QThread::started, m_mousePicker, &MousePicker::process); + connect(m_mousePicker, &MousePicker::finished, this, &Document::mouseTargetReady); + connect(m_mousePicker, &MousePicker::finished, thread, &QThread::quit); + connect(thread, &QThread::finished, thread, &QThread::deleteLater); + thread->start(); +} + +void Document::mouseTargetReady() +{ + m_mouseTargetPosition = m_mousePicker->targetPosition(); + + delete m_mousePicker; + m_mousePicker = nullptr; + + emit mouseTargetChanged(); + + qDebug() << "Mouse pick done"; + + if (m_isMouseTargetResultObsolete) { + pickMouseTarget(m_mouseRayNear, m_mouseRayFar); + } +} + +const QVector3D &Document::mouseTargetPosition() const +{ + return m_mouseTargetPosition; +} + const Outcome &Document::currentPostProcessedOutcome() const { return *m_postProcessedOutcome; diff --git a/src/document.h b/src/document.h index 28b735e0..96061789 100644 --- a/src/document.h +++ b/src/document.h @@ -30,6 +30,7 @@ class MaterialPreviewsGenerator; class MotionsGenerator; class ScriptRunner; +class MousePicker; class HistoryItem { @@ -471,6 +472,7 @@ signals: void scriptRunning(); void scriptErrorChanged(); void scriptConsoleLogChanged(); + void mouseTargetChanged(); public: // need initialize QImage *textureGuideImage; QImage *textureImage; @@ -539,6 +541,7 @@ public: const std::map> &variables() const; const QString &scriptError() const; const QString &scriptConsoleLog() const; + const QVector3D &mouseTargetPosition() const; public slots: void undo() override; void redo() override; @@ -577,6 +580,8 @@ public slots: void materialPreviewsReady(); void generateMotions(); void motionsReady(); + void pickMouseTarget(const QVector3D &nearPosition, const QVector3D &farPosition); + void mouseTargetReady(); void setPartLockState(QUuid partId, bool locked); void setPartVisibleState(QUuid partId, bool visible); void setPartSubdivState(QUuid partId, bool subdived); @@ -721,6 +726,11 @@ private: // need initialize std::map> m_mergedVariables; ScriptRunner *m_scriptRunner; bool m_isScriptResultObsolete; + MousePicker *m_mousePicker; + bool m_isMouseTargetResultObsolete; + QVector3D m_mouseRayNear; + QVector3D m_mouseRayFar; + QVector3D m_mouseTargetPosition; QString m_scriptError; QString m_scriptConsoleLog; private: diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index 6b3dde76..7553454b 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -156,6 +156,10 @@ DocumentWindow::DocumentWindow() : QPushButton *selectButton = new QPushButton(QChar(fa::mousepointer)); selectButton->setToolTip(tr("Select node on canvas")); Theme::initAwesomeButton(selectButton); + + QPushButton *paintButton = new QPushButton(QChar(fa::paintbrush)); + paintButton->setToolTip(tr("Paint brush")); + Theme::initAwesomeButton(paintButton); QPushButton *dragButton = new QPushButton(QChar(fa::handrocko)); dragButton->setToolTip(tr("Enter drag mode")); @@ -228,6 +232,7 @@ DocumentWindow::DocumentWindow() : toolButtonLayout->addWidget(addButton); toolButtonLayout->addWidget(selectButton); + toolButtonLayout->addWidget(paintButton); toolButtonLayout->addWidget(dragButton); toolButtonLayout->addWidget(zoomInButton); toolButtonLayout->addWidget(zoomOutButton); @@ -278,6 +283,11 @@ DocumentWindow::DocumentWindow() : m_modelRenderWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); m_modelRenderWidget->move(DocumentWindow::m_modelRenderWidgetInitialX, DocumentWindow::m_modelRenderWidgetInitialY); + connect(m_modelRenderWidget, &ModelWidget::mouseRayChanged, m_document, &Document::pickMouseTarget); + connect(m_document, &Document::mouseTargetChanged, this, [=]() { + m_modelRenderWidget->setMousePickTargetPositionInModelSpace(m_document->mouseTargetPosition()); + }); + m_graphicsWidget->setModelWidget(m_modelRenderWidget); containerWidget->setModelWidget(m_modelRenderWidget); @@ -767,6 +777,10 @@ DocumentWindow::DocumentWindow() : connect(selectButton, &QPushButton::clicked, [=]() { m_document->setEditMode(SkeletonDocumentEditMode::Select); }); + + connect(paintButton, &QPushButton::clicked, [=]() { + m_document->setEditMode(SkeletonDocumentEditMode::Paint); + }); connect(dragButton, &QPushButton::clicked, [=]() { m_document->setEditMode(SkeletonDocumentEditMode::Drag); @@ -792,6 +806,10 @@ DocumentWindow::DocumentWindow() : connect(m_radiusLockButton, &QPushButton::clicked, [=]() { m_document->setRadiusLockState(!m_document->radiusLocked); }); + + connect(m_document, &Document::editModeChanged, this, [=]() { + m_modelRenderWidget->enableMousePicking(SkeletonDocumentEditMode::Paint == m_document->editMode); + }); m_partListDockerVisibleSwitchConnection = connect(m_document, &Document::skeletonChanged, [=]() { if (m_graphicsWidget->hasItems()) { @@ -973,6 +991,7 @@ DocumentWindow::DocumentWindow() : connect(graphicsWidget, &SkeletonGraphicsWidget::cursorChanged, [=]() { m_modelRenderWidget->setCursor(graphicsWidget->cursor()); + containerWidget->setCursor(graphicsWidget->cursor()); //m_skeletonRenderWidget->setCursor(graphicsWidget->cursor()); }); diff --git a/src/documentwindow.h b/src/documentwindow.h index 5cf6c9e6..339d9354 100644 --- a/src/documentwindow.h +++ b/src/documentwindow.h @@ -26,6 +26,7 @@ signals: void initialized(); void uninialized(); void waitingExportFinished(const QString &filename, bool succeed); + void mouseTargetVertexPositionChanged(const QVector3D &position); public: DocumentWindow(); ~DocumentWindow(); diff --git a/src/graphicscontainerwidget.cpp b/src/graphicscontainerwidget.cpp index b0aed1fd..289a22d4 100644 --- a/src/graphicscontainerwidget.cpp +++ b/src/graphicscontainerwidget.cpp @@ -2,6 +2,7 @@ GraphicsContainerWidget::GraphicsContainerWidget() { + setMouseTracking(true); } void GraphicsContainerWidget::resizeEvent(QResizeEvent *event) diff --git a/src/modelshaderprogram.cpp b/src/modelshaderprogram.cpp index 94d55fcb..f985c0af 100644 --- a/src/modelshaderprogram.cpp +++ b/src/modelshaderprogram.cpp @@ -53,6 +53,8 @@ ModelShaderProgram::ModelShaderProgram(bool usePBR) m_roughnessMapEnabledLoc = this->uniformLocation("roughnessMapEnabled"); m_ambientOcclusionMapEnabledLoc = this->uniformLocation("ambientOcclusionMapEnabled"); m_metalnessRoughnessAmbientOcclusionMapIdLoc = this->uniformLocation("metalnessRoughnessAmbientOcclusionMapId"); + m_mousePickEnabledLoc = this->uniformLocation("mousePickEnabled"); + m_mousePickTargetPositionLoc = this->uniformLocation("mousePickTargetPosition"); } int ModelShaderProgram::projectionMatrixLoc() @@ -120,3 +122,12 @@ int ModelShaderProgram::metalnessRoughnessAmbientOcclusionMapIdLoc() return m_metalnessRoughnessAmbientOcclusionMapIdLoc; } +int ModelShaderProgram::mousePickEnabledLoc() +{ + return m_mousePickEnabledLoc; +} + +int ModelShaderProgram::mousePickTargetPositionLoc() +{ + return m_mousePickTargetPositionLoc; +} diff --git a/src/modelshaderprogram.h b/src/modelshaderprogram.h index f317be53..d5cd496a 100644 --- a/src/modelshaderprogram.h +++ b/src/modelshaderprogram.h @@ -20,6 +20,8 @@ public: int roughnessMapEnabledLoc(); int ambientOcclusionMapEnabledLoc(); int metalnessRoughnessAmbientOcclusionMapIdLoc(); + int mousePickEnabledLoc(); + int mousePickTargetPositionLoc(); static const QString &loadShaderSource(const QString &name); private: int m_projectionMatrixLoc; @@ -35,6 +37,8 @@ private: int m_roughnessMapEnabledLoc; int m_ambientOcclusionMapEnabledLoc; int m_metalnessRoughnessAmbientOcclusionMapIdLoc; + int m_mousePickEnabledLoc; + int m_mousePickTargetPositionLoc; }; #endif diff --git a/src/modelwidget.cpp b/src/modelwidget.cpp index b56d886c..d0d2dc8c 100644 --- a/src/modelwidget.cpp +++ b/src/modelwidget.cpp @@ -10,6 +10,7 @@ // Modifed from http://doc.qt.io/qt-5/qtopengl-hellogl2-glwidget-cpp.html bool ModelWidget::m_transparent = true; +const QVector3D ModelWidget::m_cameraPosition = QVector3D(0, 0, -4.0); ModelWidget::ModelWidget(QWidget *parent) : QOpenGLWidget(parent), @@ -19,7 +20,8 @@ ModelWidget::ModelWidget(QWidget *parent) : m_program(nullptr), m_moveStarted(false), m_moveEnabled(true), - m_zoomEnabled(true) + m_zoomEnabled(true), + m_mousePickingEnabled(false) { // --transparent causes the clear color to be transparent. Therefore, on systems that // support it, the widget will become transparent apart from the logo. @@ -130,7 +132,7 @@ void ModelWidget::initializeGL() // Our camera never changes in this example. m_camera.setToIdentity(); // FIXME: if change here, please also change the camera pos in PBR shader - m_camera.translate(0, 0, -4.0); + m_camera.translate(m_cameraPosition.x(), m_cameraPosition.y(), m_cameraPosition.z()); // Light position is fixed. // FIXME: PBR render no longer use this parameter @@ -162,6 +164,15 @@ void ModelWidget::paintGL() m_program->setUniformValue(m_program->textureEnabledLoc(), 0); m_program->setUniformValue(m_program->normalMapEnabledLoc(), 0); + if (m_mousePickingEnabled && !m_mousePickTargetPositionInModelSpace.isNull()) { + m_program->setUniformValue(m_program->mousePickEnabledLoc(), 1); + m_program->setUniformValue(m_program->mousePickTargetPositionLoc(), + m_world * m_mousePickTargetPositionInModelSpace); + } else { + m_program->setUniformValue(m_program->mousePickEnabledLoc(), 0); + m_program->setUniformValue(m_program->mousePickTargetPositionLoc(), QVector3D()); + } + m_meshBinder.paint(m_program); m_program->release(); @@ -173,6 +184,20 @@ void ModelWidget::resizeGL(int w, int h) m_projection.perspective(45.0f, GLfloat(w) / h, 0.01f, 100.0f); } +std::pair ModelWidget::screenPositionToMouseRay(const QPoint &screenPosition) +{ + auto modelView = m_camera * m_world; + float x = qMax(qMin(screenPosition.x(), width() - 1), 0); + float y = qMax(qMin(screenPosition.y(), height() - 1), 0); + QVector3D nearScreen = QVector3D(x, height() - y, 0.0); + QVector3D farScreen = QVector3D(x, height() - y, 1.0); + auto viewPort = QRect(0, 0, width(), height()); + auto nearPosition = nearScreen.unproject(modelView, m_projection, viewPort); + auto farPosition = farScreen.unproject(modelView, m_projection, viewPort); + qDebug() << "near:" << nearPosition << "far:" << farPosition << "x:" << x << "y:" << y; + return std::make_pair(nearPosition, farPosition); +} + void ModelWidget::toggleWireframe() { if (m_meshBinder.isWireframesVisible()) @@ -217,11 +242,17 @@ bool ModelWidget::inputMouseReleaseEventFromOtherWidget(QMouseEvent *event) bool ModelWidget::inputMouseMoveEventFromOtherWidget(QMouseEvent *event) { + QPoint pos = convertInputPosFromOtherWidget(event); + + if (m_mousePickingEnabled) { + auto segment = screenPositionToMouseRay(pos); + emit mouseRayChanged(segment.first, segment.second); + } + if (!m_moveStarted) { return false; } - QPoint pos = convertInputPosFromOtherWidget(event); int dx = pos.x() - m_lastPos.x(); int dy = pos.y() - m_lastPos.y(); @@ -275,6 +306,12 @@ void ModelWidget::zoom(float delta) setGeometry(geometry().marginsAdded(margins)); } +void ModelWidget::setMousePickTargetPositionInModelSpace(QVector3D position) +{ + m_mousePickTargetPositionInModelSpace = position; + update(); +} + void ModelWidget::updateMesh(MeshLoader *mesh) { m_meshBinder.updateMesh(mesh); @@ -291,6 +328,11 @@ void ModelWidget::enableZoom(bool enabled) m_zoomEnabled = enabled; } +void ModelWidget::enableMousePicking(bool enabled) +{ + m_mousePickingEnabled = enabled; +} + void ModelWidget::mousePressEvent(QMouseEvent *event) { inputMousePressEventFromOtherWidget(event); diff --git a/src/modelwidget.h b/src/modelwidget.h index e5d2e970..ddc964c7 100644 --- a/src/modelwidget.h +++ b/src/modelwidget.h @@ -17,6 +17,8 @@ class SkeletonGraphicsFunctions; class ModelWidget : public QOpenGLWidget, protected QOpenGLFunctions { Q_OBJECT +signals: + void mouseRayChanged(const QVector3D &near, const QVector3D &far); public: ModelWidget(QWidget *parent = 0); ~ModelWidget(); @@ -33,6 +35,7 @@ public: void toggleWireframe(); void enableMove(bool enabled); void enableZoom(bool enabled); + void enableMousePicking(bool enabled); bool inputMousePressEventFromOtherWidget(QMouseEvent *event); bool inputMouseMoveEventFromOtherWidget(QMouseEvent *event); bool inputWheelEventFromOtherWidget(QWheelEvent *event); @@ -44,6 +47,7 @@ public slots: void setZRotation(int angle); void cleanup(); void zoom(float delta); + void setMousePickTargetPositionInModelSpace(QVector3D position); signals: void xRotationChanged(int angle); void yRotationChanged(int angle); @@ -68,6 +72,8 @@ private: bool m_moveStarted; bool m_moveEnabled; bool m_zoomEnabled; + bool m_mousePickingEnabled; + QVector3D m_mousePickTargetPositionInModelSpace; private: QPoint m_lastPos; ModelMeshBinder m_meshBinder; @@ -75,8 +81,10 @@ private: QMatrix4x4 m_camera; QMatrix4x4 m_world; static bool m_transparent; + static const QVector3D m_cameraPosition; QPoint m_moveStartPos; QRect m_moveStartGeometry; + std::pair screenPositionToMouseRay(const QPoint &screenPosition); }; #endif diff --git a/src/mousepicker.cpp b/src/mousepicker.cpp new file mode 100644 index 00000000..cb271e6a --- /dev/null +++ b/src/mousepicker.cpp @@ -0,0 +1,90 @@ +#include +#include "mousepicker.h" + +MousePicker::MousePicker(const Outcome &outcome, const QVector3D &mouseRayNear, const QVector3D &mouseRayFar) : + m_outcome(outcome), + m_mouseRayNear(mouseRayNear), + m_mouseRayFar(mouseRayFar) +{ +} + +MousePicker::~MousePicker() +{ +} + +void MousePicker::process() +{ + float minDistance2 = std::numeric_limits::max(); + for (size_t i = 0; i < m_outcome.triangles.size(); ++i) { + const auto &triangleIndices = m_outcome.triangles[i]; + std::vector triangle = { + m_outcome.vertices[triangleIndices[0]], + m_outcome.vertices[triangleIndices[1]], + m_outcome.vertices[triangleIndices[2]], + }; + const auto &triangleNormal = m_outcome.triangleNormals[i]; + QVector3D intersection; + if (intersectSegmentAndTriangle(m_mouseRayNear, m_mouseRayFar, + triangle, + triangleNormal, + &intersection)) { + float distance2 = (intersection - m_mouseRayNear).lengthSquared(); + if (distance2 < minDistance2) { + m_targetPosition = intersection; + minDistance2 = distance2; + } + } + } + + emit finished(); +} + +const QVector3D &MousePicker::targetPosition() +{ + return m_targetPosition; +} + +bool MousePicker::intersectSegmentAndPlane(const QVector3D &segmentPoint0, const QVector3D &segmentPoint1, + const QVector3D &pointOnPlane, const QVector3D &planeNormal, + QVector3D *intersection) +{ + auto u = segmentPoint1 - segmentPoint0; + auto w = segmentPoint0 - pointOnPlane; + auto d = QVector3D::dotProduct(planeNormal, u); + auto n = QVector3D::dotProduct(-planeNormal, w); + if (qAbs(d) < 0.00000001) + return false; + auto s = n / d; + if (s < 0 || s > 1 || qIsNaN(s) || qIsInf(s)) + return false; + if (nullptr != intersection) + *intersection = segmentPoint0 + s * u; + return true; +} + +bool MousePicker::intersectSegmentAndTriangle(const QVector3D &segmentPoint0, const QVector3D &segmentPoint1, + const std::vector &triangle, + const QVector3D &triangleNormal, + QVector3D *intersection) +{ + QVector3D possibleIntersection; + if (!intersectSegmentAndPlane(segmentPoint0, segmentPoint1, + triangle[0], triangleNormal, &possibleIntersection)) { + return false; + } + auto ray = (segmentPoint0 - segmentPoint1).normalized(); + std::vector normals; + for (size_t i = 0; i < 3; ++i) { + size_t j = (i + 1) % 3; + normals.push_back(QVector3D::normal(possibleIntersection, triangle[i], triangle[j])); + } + if (QVector3D::dotProduct(normals[0], ray) <= 0) + return false; + if (QVector3D::dotProduct(normals[0], normals[1]) <= 0) + return false; + if (QVector3D::dotProduct(normals[0], normals[2]) <= 0) + return false; + if (nullptr != intersection) + *intersection = possibleIntersection; + return true; +} diff --git a/src/mousepicker.h b/src/mousepicker.h new file mode 100644 index 00000000..d5c1ae1b --- /dev/null +++ b/src/mousepicker.h @@ -0,0 +1,33 @@ +#ifndef DUST3D_MOUSE_PICKER_H +#define DUST3D_MOUSE_PICKER_H +#include +#include +#include +#include "outcome.h" + +class MousePicker : public QObject +{ + Q_OBJECT +public: + MousePicker(const Outcome &outcome, const QVector3D &mouseRayNear, const QVector3D &mouseRayFar); + ~MousePicker(); + const QVector3D &targetPosition(); +signals: + void finished(); +public slots: + void process(); +private: + Outcome m_outcome; + QVector3D m_mouseRayNear; + QVector3D m_mouseRayFar; + QVector3D m_targetPosition; + static bool intersectSegmentAndPlane(const QVector3D &segmentPoint0, const QVector3D &segmentPoint1, + const QVector3D &pointOnPlane, const QVector3D &planeNormal, + QVector3D *intersection=nullptr); + static bool intersectSegmentAndTriangle(const QVector3D &segmentPoint0, const QVector3D &segmentPoint1, + const std::vector &triangle, + const QVector3D &triangleNormal, + QVector3D *intersection=nullptr); +}; + +#endif diff --git a/src/skeletondocument.h b/src/skeletondocument.h index a63fede5..af15c95b 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -285,6 +285,7 @@ enum class SkeletonDocumentEditMode { Add = 0, Select, + Paint, Drag, ZoomIn, ZoomOut diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index 4fc3ffde..872e75fb 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -598,6 +598,9 @@ void SkeletonGraphicsWidget::updateCursor() case SkeletonDocumentEditMode::Select: setCursor(QCursor(Theme::awesome()->icon(fa::mousepointer).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize), Theme::toolIconFontSize / 5, 0)); break; + case SkeletonDocumentEditMode::Paint: + setCursor(QCursor(Theme::awesome()->icon(fa::paintbrush).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize))); + break; case SkeletonDocumentEditMode::Drag: setCursor(QCursor(Theme::awesome()->icon(m_dragStarted ? fa::handrocko : fa::handpapero).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize))); break;