From 603b9df7c44f7fee2b0ec60bae9b5432feb8db4a Mon Sep 17 00:00:00 2001 From: fabien servant Date: Thu, 2 Jun 2022 20:40:39 +0200 Subject: [ui] PanoramaViewer: change the way the user interact with the panorama widget --- meshroom/ui/components/scene3D.py | 101 +++++++++++++++++++++++++++++- meshroom/ui/qml/Viewer/PanoramaViewer.qml | 57 +++++++++++++---- 2 files changed, 143 insertions(+), 15 deletions(-) diff --git a/meshroom/ui/components/scene3D.py b/meshroom/ui/components/scene3D.py index b4569c19..ac1cc1e1 100644 --- a/meshroom/ui/components/scene3D.py +++ b/meshroom/ui/components/scene3D.py @@ -1,4 +1,4 @@ -from math import acos, pi, sqrt +from math import acos, pi, sqrt, atan2, cos, sin, asin from PySide2.QtCore import QObject, Slot, QSize, Signal, QPointF from PySide2.Qt3DCore import Qt3DCore @@ -109,6 +109,101 @@ class Transformations3DHelper(QObject): # ---------- Exposed to QML ---------- # + @Slot(QVector3D, QVector3D, result=QQuaternion) + def rotationBetweenAandB(self, A, B): + + A = A/A.length() + B = B/B.length() + + # Get rotation matrix between 2 vectors + v = QVector3D.crossProduct(A, B) + s = v.length() + c = QVector3D.dotProduct(A, B) + return QQuaternion.fromAxisAndAngle(v / s, atan2(s, c) * 180 / pi) + + @Slot(QVector3D, result=QVector3D) + def fromEquirectangular(self, vector): + return QVector3D(cos(vector.x()) * sin(vector.y()), sin(vector.x()), cos(vector.x()) * cos(vector.y())) + + @Slot(QVector3D, result=QVector3D) + def toEquirectangular(self, vector): + return QVector3D(asin(vector.y()), atan2(vector.x(), vector.z()), 0) + + @Slot(QVector3D, QVector2D, QVector2D, result=QVector3D) + def updatePanorama(self, euler, ptStart, ptEnd): + + delta = 1e-3 + + #Get initial rotation + qStart = QQuaternion.fromEulerAngles(euler.y(), euler.x(), euler.z()) + + #Convert input to points on unit sphere + vStart = self.fromEquirectangular(QVector3D(ptStart)) + vStartdY = self.fromEquirectangular(QVector3D(ptStart.x(), ptStart.y() + delta, 0)) + vEnd = self.fromEquirectangular(QVector3D(ptEnd)) + + qAdd = QQuaternion.rotationTo(vStart, vEnd) + + + #Get the 3D point on unit sphere which would correspond to the no rotation +X + vCurrent = qAdd.rotatedVector(vStartdY) + vIdeal = self.fromEquirectangular(QVector3D(ptEnd.x(), ptEnd.y() + delta, 0)) + + #project on rotation plane + lambdaEnd = 1 / QVector3D.dotProduct(vEnd, vCurrent) + lambdaIdeal = 1 / QVector3D.dotProduct(vEnd, vIdeal) + vPlaneCurrent = lambdaEnd * vCurrent + vPlaneIdeal = lambdaIdeal * vIdeal + + #Get the directions + rotStart = (vPlaneCurrent - vEnd).normalized() + rotEnd = (vPlaneIdeal - vEnd).normalized() + + # Get rotation matrix between 2 vectors + v = QVector3D.crossProduct(rotEnd, rotStart) + s = QVector3D.dotProduct(v, vEnd) + c = QVector3D.dotProduct(rotStart, rotEnd) + angle = atan2(s, c) * 180 / pi + + qImage = QQuaternion.fromAxisAndAngle(vEnd, -angle) + + return (qImage * qAdd * qStart).toEulerAngles() + + @Slot(QVector3D, QVector2D, QVector2D, result=QVector3D) + def updatePanoramaInPlane(self, euler, ptStart, ptEnd): + + delta = 1e-3 + + #Get initial rotation + qStart = QQuaternion.fromEulerAngles(euler.y(), euler.x(), euler.z()) + + #Convert input to points on unit sphere + vStart = self.fromEquirectangular(QVector3D(ptStart)) + vEnd = self.fromEquirectangular(QVector3D(ptEnd)) + + #Get the 3D point on unit sphere which would correspond to the no rotation +X + vIdeal = self.fromEquirectangular(QVector3D(ptStart.x(), ptStart.y() + delta, 0)) + + #project on rotation plane + lambdaEnd = 1 / QVector3D.dotProduct(vStart, vEnd) + lambdaIdeal = 1 / QVector3D.dotProduct(vStart, vIdeal) + vPlaneEnd = lambdaEnd * vEnd + vPlaneIdeal = lambdaIdeal * vIdeal + + #Get the directions + rotStart = (vPlaneEnd - vStart).normalized() + rotEnd = (vPlaneIdeal - vStart).normalized() + + # Get rotation matrix between 2 vectors + v = QVector3D.crossProduct(rotEnd, rotStart) + s = QVector3D.dotProduct(v, vStart) + c = QVector3D.dotProduct(rotStart, rotEnd) + angle = atan2(s, c) * 180 / pi + + qAdd = QQuaternion.fromAxisAndAngle(vStart, angle) + + return (qAdd * qStart).toEulerAngles() + @Slot(QVector4D, Qt3DRender.QCamera, QSize, result=QVector2D) def pointFromWorldToScreen(self, point, camera, windowSize): """ Compute the Screen point corresponding to a World Point. @@ -123,7 +218,7 @@ class Transformations3DHelper(QObject): viewMatrix = camera.transform().matrix().inverted() projectedPoint = (camera.projectionMatrix() * viewMatrix[0]).map(point) projectedPoint2D = QVector2D( - projectedPoint.x()/projectedPoint.w(), + projectedPoint.x()/projectedPoint.w(), projectedPoint.y()/projectedPoint.w() ) @@ -145,7 +240,7 @@ class Transformations3DHelper(QObject): initialScaleMat (QMatrix4x4): initial scale matrix translateVec (QVector3D): vector used for the local translation """ - # Compute the translation transformation matrix + # Compute the translation transformation matrix translationMat = QMatrix4x4() translationMat.translate(translateVec) diff --git a/meshroom/ui/qml/Viewer/PanoramaViewer.qml b/meshroom/ui/qml/Viewer/PanoramaViewer.qml index 70f641f8..5ca2e46d 100644 --- a/meshroom/ui/qml/Viewer/PanoramaViewer.qml +++ b/meshroom/ui/qml/Viewer/PanoramaViewer.qml @@ -59,6 +59,10 @@ AliceVision.PanoramaViewer { property var xStart : 0 property var yStart : 0 + property var previous_yaw: 0; + property var previous_pitch: 0; + property var previous_roll: 0; + property double yaw: 0; property double pitch: 0; property double roll: 0; @@ -132,18 +136,43 @@ AliceVision.PanoramaViewer { // Rotate Panorama if (isRotating && isEditable) { - var xoffset = mouse.x - lastX; - var yoffset = mouse.y - lastY; - lastX = mouse.x; - lastY = mouse.y; - - // Update Euler Angles - if (mouse.modifiers & Qt.AltModifier) { - root.roll = limitAngle(root.roll + toDegrees((xoffset / width) * mouseMultiplier)) - } - else { - root.yaw = limitAngle(root.yaw + toDegrees((xoffset / width) * mouseMultiplier)) - root.pitch = limitPitch(root.pitch + toDegrees(-(yoffset / height) * mouseMultiplier)) + + var nx = Math.max(0, mouse.x) + var nx = Math.min(width - 1, mouse.x) + var ny = Math.max(0, mouse.y) + var ny = Math.min(height - 1, mouse.y) + + var xoffset = nx - lastX; + var yoffset = ny - lastY; + + if (xoffset != 0 || yoffset !=0) + { + var latitude_start = (yStart / height) * Math.PI - (Math.PI / 2); + var longitude_start = ((xStart / width) * 2 * Math.PI) - Math.PI; + var latitude_end = (ny / height) * Math.PI - ( Math.PI / 2); + var longitude_end = ((nx / width) * 2 * Math.PI) - Math.PI; + + var start_pt = Qt.vector2d(latitude_start, longitude_start) + var end_pt = Qt.vector2d(latitude_end, longitude_end) + + var previous_euler = Qt.vector3d(previous_yaw, previous_pitch, previous_roll) + + if (mouse.modifiers & Qt.ControlModifier) + { + var result = Transformations3DHelper.updatePanoramaInPlane(previous_euler, start_pt, end_pt) + root.pitch = result.x + root.yaw = result.y + root.roll = result.z + } + else + { + var result = Transformations3DHelper.updatePanorama(previous_euler, start_pt, end_pt) + root.pitch = result.x + root.yaw = result.y + root.roll = result.z + } + + } _reconstruction.setAttribute(activeNode.attribute("manualTransform.manualRotation.x"), Math.round(root.pitch)); @@ -160,6 +189,10 @@ AliceVision.PanoramaViewer { xStart = mouse.x; yStart = mouse.y; + + previous_yaw = yaw; + previous_pitch = pitch; + previous_roll = roll; } onReleased: { -- cgit v1.2.3