mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-05-10 07:36:52 +02:00
[ui] PanoramaViewer: change the way the user interact with the panorama widget
This commit is contained in:
parent
3822bc847e
commit
603b9df7c4
2 changed files with 142 additions and 14 deletions
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue