[ui] Viewer3D: move TrackballController code to Python side

Port JS trackball camera controller code to Python for improved performance and stability.
This commit is contained in:
Yann Lanthony 2018-12-11 16:38:37 +01:00
parent bc9abe3b07
commit b2e1743a6f
3 changed files with 69 additions and 46 deletions

View file

@ -3,8 +3,9 @@ def registerTypes():
from PySide2.QtQml import qmlRegisterType from PySide2.QtQml import qmlRegisterType
from meshroom.ui.components.edge import EdgeMouseArea from meshroom.ui.components.edge import EdgeMouseArea
from meshroom.ui.components.filepath import FilepathHelper from meshroom.ui.components.filepath import FilepathHelper
from meshroom.ui.components.scene3D import Scene3DHelper from meshroom.ui.components.scene3D import Scene3DHelper, TrackballController
qmlRegisterType(EdgeMouseArea, "GraphEditor", 1, 0, "EdgeMouseArea") qmlRegisterType(EdgeMouseArea, "GraphEditor", 1, 0, "EdgeMouseArea")
qmlRegisterType(FilepathHelper, "Meshroom.Helpers", 1, 0, "FilepathHelper") # TODO: uncreatable qmlRegisterType(FilepathHelper, "Meshroom.Helpers", 1, 0, "FilepathHelper") # TODO: uncreatable
qmlRegisterType(Scene3DHelper, "Meshroom.Helpers", 1, 0, "Scene3DHelper") # TODO: uncreatable qmlRegisterType(Scene3DHelper, "Meshroom.Helpers", 1, 0, "Scene3DHelper") # TODO: uncreatable
qmlRegisterType(TrackballController, "Meshroom.Helpers", 1, 0, "TrackballController")

View file

@ -1,8 +1,11 @@
from PySide2.QtCore import QObject, Slot from math import acos, pi, sqrt
from PySide2.QtCore import QObject, Slot, QSize, Signal, QPointF
from PySide2.Qt3DCore import Qt3DCore from PySide2.Qt3DCore import Qt3DCore
from PySide2.Qt3DRender import Qt3DRender from PySide2.Qt3DRender import Qt3DRender
from PySide2.QtGui import QVector3D, QQuaternion from PySide2.QtGui import QVector3D, QQuaternion, QVector2D
from meshroom.ui.utils import makeProperty
class Scene3DHelper(QObject): class Scene3DHelper(QObject):
@ -42,7 +45,54 @@ class Scene3DHelper(QObject):
count += sum([attr.count() for attr in geo.attributes() if attr.name() == "vertexPosition"]) count += sum([attr.count() for attr in geo.attributes() if attr.name() == "vertexPosition"])
return count / 3 return count / 3
@Slot(QQuaternion, QVector3D, result=QVector3D)
def rotatedVector(self, quaternion, vector): class TrackballController(QObject):
""" Returns the rotation of 'vector' with 'quaternion'. """ """
return quaternion.rotatedVector(vector) Trackball-like camera controller.
Based on the C++ version from https://github.com/cjmdaixi/Qt3DTrackball
"""
_windowSize = QSize()
_camera = None
_trackballSize = 1.0
_rotationSpeed = 5.0
def projectToTrackball(self, screenCoords):
sx = screenCoords.x()
sy = self._windowSize.height() - screenCoords.y()
p2d = QVector2D(sx / self._windowSize.width() - 0.5, sy / self._windowSize.height() - 0.5)
z = 0.0
r2 = pow(self._trackballSize, 2)
lengthSquared = p2d.lengthSquared()
if lengthSquared <= r2 * 0.5:
z = sqrt(r2 - lengthSquared)
else:
z = r2 * 0.5 / p2d.length()
return QVector3D(p2d.x(), p2d.y(), z)
@staticmethod
def clamp(x):
return max(-1, min(x, 1))
def createRotation(self, firstPoint, nextPoint):
lastPos3D = self.projectToTrackball(firstPoint).normalized()
currentPos3D = self.projectToTrackball(nextPoint).normalized()
angle = acos(self.clamp(QVector3D.dotProduct(currentPos3D, lastPos3D)))
direction = QVector3D.crossProduct(currentPos3D, lastPos3D)
return angle, direction
@Slot(QPointF, QPointF, float)
def rotate(self, lastPosition, currentPosition, dt):
angle, direction = self.createRotation(lastPosition, currentPosition)
rotatedAxis = self._camera.transform().rotation().rotatedVector(direction)
angle *= self._rotationSpeed * dt
self._camera.rotateAboutViewCenter(QQuaternion.fromAxisAndAngle(rotatedAxis, angle * pi * 180))
windowSizeChanged = Signal()
windowSize = makeProperty(QSize, '_windowSize', windowSizeChanged)
cameraChanged = Signal()
camera = makeProperty(Qt3DRender.QCamera, '_camera', cameraChanged)
trackballSizeChanged = Signal()
trackballSize = makeProperty(float, '_trackballSize', trackballSizeChanged)
rotationSpeedChanged = Signal()
rotationSpeed = makeProperty(float, '_rotationSpeed', rotationSpeedChanged)

View file

@ -5,6 +5,8 @@ import Qt3D.Input 2.1
import Qt3D.Logic 2.0 import Qt3D.Logic 2.0
import QtQml 2.2 import QtQml 2.2
import Meshroom.Helpers 1.0
Entity { Entity {
id: root id: root
@ -15,9 +17,9 @@ Entity {
property bool moving: pressed || (actionAlt.active && keyboardHandler._pressed) property bool moving: pressed || (actionAlt.active && keyboardHandler._pressed)
property alias focus: keyboardHandler.focus property alias focus: keyboardHandler.focus
readonly property bool pickingActive: actionControl.active && keyboardHandler._pressed readonly property bool pickingActive: actionControl.active && keyboardHandler._pressed
property real rotationSpeed: 2.0 property alias rotationSpeed: trackball.rotationSpeed
property size windowSize property alias windowSize: trackball.windowSize
property real trackballSize: 1.0 property alias trackballSize: trackball.trackballSize
readonly property alias pressed: mouseHandler._pressed readonly property alias pressed: mouseHandler._pressed
signal mousePressed(var mouse) signal mousePressed(var mouse)
@ -29,6 +31,11 @@ Entity {
KeyboardDevice { id: keyboardSourceDevice } KeyboardDevice { id: keyboardSourceDevice }
MouseDevice { id: mouseSourceDevice } MouseDevice { id: mouseSourceDevice }
TrackballController {
id: trackball
camera: root.camera
}
MouseHandler { MouseHandler {
id: mouseHandler id: mouseHandler
property bool _pressed property bool _pressed
@ -148,36 +155,6 @@ Entity {
] ]
} }
// Based on the C++ version from https://github.com/cjmdaixi/Qt3DTrackball
function projectToTrackball(screenCoords)
{
var sx = screenCoords.x, sy = windowSize.height - screenCoords.y;
var p2d = Qt.vector2d(sx / windowSize.width - 0.5, sy / windowSize.height - 0.5);
var z = 0.0;
var r2 = trackballSize * trackballSize;
var lengthSquared = p2d.length() * p2d.length();
if(lengthSquared <= r2 * 0.5)
z = Math.sqrt(r2 - lengthSquared);
else
z = r2 * 0.5 / p2d.length();
return Qt.vector3d(p2d.x, p2d.y, z);
}
function clamp(x)
{
return Math.max(-1, Math.min(x, 1));
}
function createRotation(firstPoint, nextPoint)
{
var lastPos3D = projectToTrackball(firstPoint).normalized();
var currentPos3D = projectToTrackball(nextPoint).normalized();
var obj = {};
obj.angle = Math.acos(clamp(currentPos3D.dotProduct(lastPos3D)));
obj.dir = currentPos3D.crossProduct(lastPos3D);
return obj;
}
components: [ components: [
FrameAction { FrameAction {
onTriggered: { onTriggered: {
@ -189,12 +166,7 @@ Entity {
return; return;
} }
if(actionLMB.active){ // trackball rotation if(actionLMB.active){ // trackball rotation
var res = createRotation(mouseHandler.lastPosition, mouseHandler.currentPosition); trackball.rotate(mouseHandler.lastPosition, mouseHandler.currentPosition, dt);
var transform = root.camera.components[1]; // transform is camera first component
var currentRotation = transform.rotation;
var rotatedAxis = Scene3DHelper.rotatedVector(transform.rotation, res.dir);
res.angle *= rotationSpeed * dt;
root.camera.rotateAboutViewCenter(transform.fromAxisAndAngle(rotatedAxis, res.angle * Math.PI * 180));
mouseHandler.lastPosition = mouseHandler.currentPosition; mouseHandler.lastPosition = mouseHandler.currentPosition;
return; return;
} }