[ui] PanoramaViewer: change the way the user interact with the panorama widget

This commit is contained in:
fabien servant 2022-06-02 20:40:39 +02:00 committed by Fabien Castan
parent 3822bc847e
commit 603b9df7c4
2 changed files with 142 additions and 14 deletions

View file

@ -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.QtCore import QObject, Slot, QSize, Signal, QPointF
from PySide2.Qt3DCore import Qt3DCore from PySide2.Qt3DCore import Qt3DCore
@ -109,6 +109,101 @@ class Transformations3DHelper(QObject):
# ---------- Exposed to QML ---------- # # ---------- 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) @Slot(QVector4D, Qt3DRender.QCamera, QSize, result=QVector2D)
def pointFromWorldToScreen(self, point, camera, windowSize): def pointFromWorldToScreen(self, point, camera, windowSize):
""" Compute the Screen point corresponding to a World Point. """ Compute the Screen point corresponding to a World Point.
@ -123,7 +218,7 @@ class Transformations3DHelper(QObject):
viewMatrix = camera.transform().matrix().inverted() viewMatrix = camera.transform().matrix().inverted()
projectedPoint = (camera.projectionMatrix() * viewMatrix[0]).map(point) projectedPoint = (camera.projectionMatrix() * viewMatrix[0]).map(point)
projectedPoint2D = QVector2D( projectedPoint2D = QVector2D(
projectedPoint.x()/projectedPoint.w(), projectedPoint.x()/projectedPoint.w(),
projectedPoint.y()/projectedPoint.w() projectedPoint.y()/projectedPoint.w()
) )
@ -145,7 +240,7 @@ class Transformations3DHelper(QObject):
initialScaleMat (QMatrix4x4): initial scale matrix initialScaleMat (QMatrix4x4): initial scale matrix
translateVec (QVector3D): vector used for the local translation translateVec (QVector3D): vector used for the local translation
""" """
# Compute the translation transformation matrix # Compute the translation transformation matrix
translationMat = QMatrix4x4() translationMat = QMatrix4x4()
translationMat.translate(translateVec) translationMat.translate(translateVec)

View file

@ -59,6 +59,10 @@ AliceVision.PanoramaViewer {
property var xStart : 0 property var xStart : 0
property var yStart : 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 yaw: 0;
property double pitch: 0; property double pitch: 0;
property double roll: 0; property double roll: 0;
@ -132,18 +136,43 @@ AliceVision.PanoramaViewer {
// Rotate Panorama // Rotate Panorama
if (isRotating && isEditable) { if (isRotating && isEditable) {
var xoffset = mouse.x - lastX;
var yoffset = mouse.y - lastY;
lastX = mouse.x;
lastY = mouse.y;
// Update Euler Angles var nx = Math.max(0, mouse.x)
if (mouse.modifiers & Qt.AltModifier) { var nx = Math.min(width - 1, mouse.x)
root.roll = limitAngle(root.roll + toDegrees((xoffset / width) * mouseMultiplier)) var ny = Math.max(0, mouse.y)
} var ny = Math.min(height - 1, mouse.y)
else {
root.yaw = limitAngle(root.yaw + toDegrees((xoffset / width) * mouseMultiplier)) var xoffset = nx - lastX;
root.pitch = limitPitch(root.pitch + toDegrees(-(yoffset / height) * mouseMultiplier)) 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)); _reconstruction.setAttribute(activeNode.attribute("manualTransform.manualRotation.x"), Math.round(root.pitch));
@ -160,6 +189,10 @@ AliceVision.PanoramaViewer {
xStart = mouse.x; xStart = mouse.x;
yStart = mouse.y; yStart = mouse.y;
previous_yaw = yaw;
previous_pitch = pitch;
previous_roll = roll;
} }
onReleased: { onReleased: {