Add 3d mouse picker

master
Jeremy Hu 2019-08-08 20:54:33 +09:30
parent c99ce31755
commit bde3c01da0
16 changed files with 294 additions and 4 deletions

View File

@ -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

View File

@ -362,6 +362,10 @@ Tips:
<source>Check for Updates...</source>
<translation>...</translation>
</message>
<message>
<source>Paint brush</source>
<translation></translation>
</message>
</context>
<context>
<name>ExportPreviewWidget</name>

View File

@ -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;

View File

@ -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;

View File

@ -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<QString, std::map<QString, QString>> &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<QString, std::map<QString, QString>> 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:

View File

@ -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());
});

View File

@ -26,6 +26,7 @@ signals:
void initialized();
void uninialized();
void waitingExportFinished(const QString &filename, bool succeed);
void mouseTargetVertexPositionChanged(const QVector3D &position);
public:
DocumentWindow();
~DocumentWindow();

View File

@ -2,6 +2,7 @@
GraphicsContainerWidget::GraphicsContainerWidget()
{
setMouseTracking(true);
}
void GraphicsContainerWidget::resizeEvent(QResizeEvent *event)

View File

@ -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;
}

View File

@ -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

View File

@ -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<QVector3D, QVector3D> 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);

View File

@ -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<QVector3D, QVector3D> screenPositionToMouseRay(const QPoint &screenPosition);
};
#endif

90
src/mousepicker.cpp Normal file
View File

@ -0,0 +1,90 @@
#include <QDebug>
#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<float>::max();
for (size_t i = 0; i < m_outcome.triangles.size(); ++i) {
const auto &triangleIndices = m_outcome.triangles[i];
std::vector<QVector3D> 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<QVector3D> &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<QVector3D> 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;
}

33
src/mousepicker.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef DUST3D_MOUSE_PICKER_H
#define DUST3D_MOUSE_PICKER_H
#include <QObject>
#include <QVector3D>
#include <vector>
#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<QVector3D> &triangle,
const QVector3D &triangleNormal,
QVector3D *intersection=nullptr);
};
#endif

View File

@ -285,6 +285,7 @@ enum class SkeletonDocumentEditMode
{
Add = 0,
Select,
Paint,
Drag,
ZoomIn,
ZoomOut

View File

@ -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;