Meshroom/meshroom/ui/qml/Viewer3D/TransformGizmo.qml
Julien-Haudegond da765a5f98 [ui] Viewer3D: TransformGizmo - moving transform operations to Python
- Moving transform operations from QML to Python to get access to more Qt functionalities and to make a better separation of logic/display.
2020-08-13 14:24:44 +02:00

444 lines
19 KiB
QML

import Qt3D.Core 2.0
import Qt3D.Render 2.9
import Qt3D.Input 2.0
import Qt3D.Extras 2.10
import QtQuick 2.9
import Qt3D.Logic 2.0
import QtQuick.Controls 2.3
import Utils 1.0
Entity {
id: root
property real gizmoScale: 0.15
property Camera camera
property var windowSize
property var frontLayerComponent
property var window
readonly property Transform objectTransform : Transform {}
signal pickedChanged(bool pressed)
components: [gizmoDisplayTransform, mouseHandler, frontLayerComponent]
/***** ENUMS *****/
enum Axis {
X,
Y,
Z
}
enum Type {
POSITION,
ROTATION,
SCALE
}
/***** TRANSFORMATIONS (using local vars) *****/
function doRelativeTranslation(initialModelMatrix, translateVec) {
Transformations3DHelper.relativeLocalTranslate(gizmoDisplayTransform, initialModelMatrix.position, initialModelMatrix.rotation, initialModelMatrix.scale, translateVec) // Update gizmo matrix
Transformations3DHelper.relativeLocalTranslate(objectTransform, initialModelMatrix.position, initialModelMatrix.rotation, initialModelMatrix.scale, translateVec) // Update object matrix
}
function doRelativeRotation(initialModelMatrix, axis, degree) {
Transformations3DHelper.relativeLocalRotate(gizmoDisplayTransform, initialModelMatrix.position, initialModelMatrix.quaternion, initialModelMatrix.scale, axis, degree) // Update gizmo matrix
Transformations3DHelper.relativeLocalRotate(objectTransform, initialModelMatrix.position, initialModelMatrix.quaternion, initialModelMatrix.scale, axis, degree) // Update object matrix
}
function doRelativeScale(initialModelMatrix, scaleVec) {
Transformations3DHelper.relativeLocalScale(objectTransform, initialModelMatrix.position, initialModelMatrix.rotation, initialModelMatrix.scale, scaleVec) // Update object matrix
}
function resetTranslation() {
gizmoDisplayTransform.translation = Qt.vector3d(0,0,0)
objectTransform.translation = Qt.vector3d(0,0,0)
}
function resetRotation() {
gizmoDisplayTransform.rotation = Qt.quaternion(1,0,0,0)
objectTransform.rotation = Qt.quaternion(1,0,0,0)
}
function resetScale() {
objectTransform.scale3D = Qt.vector3d(1,1,1)
}
function resetATransformType(transformType) {
switch(transformType) {
case TransformGizmo.Type.POSITION: resetTranslation(); break
case TransformGizmo.Type.ROTATION: resetRotation(); break
case TransformGizmo.Type.SCALE: resetScale(); break
}
}
/***** DEVICES *****/
MouseDevice { id: mouseSourceDevice }
MouseHandler {
id: mouseHandler
sourceDevice: enabled ? mouseSourceDevice : null
property var objectPicker: null
property bool enabled: false
onPositionChanged: {
if (objectPicker) {
// Get the selected axis
let pickedAxis
switch(objectPicker.gizmoAxis) {
case TransformGizmo.Axis.X: pickedAxis = Qt.vector3d(1,0,0); break
case TransformGizmo.Axis.Y: pickedAxis = Qt.vector3d(0,1,0); break
case TransformGizmo.Axis.Z: pickedAxis = Qt.vector3d(0,0,1); break
}
switch(objectPicker.gizmoType) {
case TransformGizmo.Type.POSITION: {
const sensibility = 0.02
// Compute the current vector PickedPoint -> CurrentMousePoint
const pickedPosition = objectPicker.screenPoint
const mouseVector = Qt.vector2d(mouse.x - pickedPosition.x, -(mouse.y - pickedPosition.y))
// Transform the positive picked axis vector from World Coord to Screen Coord
const gizmoLocalPointOnAxis = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 1))
const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1))
const screenPoint2D = Transformations3DHelper.pointFromWorldToScreen(gizmoLocalPointOnAxis, camera, windowSize)
const screenCenter2D = Transformations3DHelper.pointFromWorldToScreen(gizmoCenterPoint, camera, windowSize)
const screenAxisVector = Qt.vector2d(screenPoint2D.x - screenCenter2D.x, -(screenPoint2D.y - screenCenter2D.y))
// Get the cosinus of the angle from the screenAxisVector to the mouseVector
const cosAngle = screenAxisVector.dotProduct(mouseVector) / (screenAxisVector.length() * mouseVector.length())
const offset = cosAngle * mouseVector.length() * sensibility
// If the mouse is not at the same spot as the pickedPoint, we do translation
if (offset) doRelativeTranslation(objectPicker.modelMatrix, pickedAxis.times(offset)) // Do a translation from the initial Object Model Matrix when we picked the gizmo
return
}
case TransformGizmo.Type.ROTATION: {
// Get Screen Coordinates of the gizmo center
const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1))
const screenCenter2D = Transformations3DHelper.pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize)
// Get the vector screenCenter2D -> PickedPoint
const originalVector = Qt.vector2d(objectPicker.screenPoint.x - screenCenter2D.x, -(objectPicker.screenPoint.y - screenCenter2D.y))
// Compute the current vector screenCenter2D -> CurrentMousePoint
const mouseVector = Qt.vector2d(mouse.x - screenCenter2D.x, -(mouse.y - screenCenter2D.y))
// Get the angle from the originalVector to the mouseVector
const angle = Math.atan2(-originalVector.y*mouseVector.x + originalVector.x*mouseVector.y, originalVector.x*mouseVector.x + originalVector.y*mouseVector.y) * 180 / Math.PI
// Get the orientation of the gizmo in function of the camera
const gizmoLocalAxisVector = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 0))
const gizmoToCameraVector = camera.position.toVector4d().minus(gizmoCenterPoint)
const orientation = gizmoLocalAxisVector.dotProduct(gizmoToCameraVector) > 0 ? 1 : -1
if (angle !== 0) doRelativeRotation(objectPicker.modelMatrix, pickedAxis, angle*orientation)
return
}
case TransformGizmo.Type.SCALE: {
const sensibility = 0.05
// Get Screen Coordinates of the gizmo center
const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1))
const screenCenter2D = Transformations3DHelper.pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize)
// Compute the scale unit
const scaleUnit = screenCenter2D.minus(Qt.vector2d(objectPicker.screenPoint.x, objectPicker.screenPoint.y)).length()
// Compute the current vector screenCenter2D -> CurrentMousePoint
const mouseVector = Qt.vector2d(mouse.x - screenCenter2D.x, -(mouse.y - screenCenter2D.y))
let offset = (mouseVector.length() - scaleUnit) * sensibility
offset = (offset < 0) ? offset * 3 : offset // Used to make it more sensible when we want to reduce the scale (because the action field is shorter)
if (offset) doRelativeScale(objectPicker.modelMatrix, pickedAxis.times(offset))
return
}
}
}
}
onReleased: {
if(objectPicker && mouse.button === Qt.RightButton) {
resetMenu.updateTypeBeforePopup(objectPicker.gizmoType)
resetMenu.popup(window)
}
}
}
Menu {
id: resetMenu
property int transformType
property string transformTypeToDisplay
function updateTypeBeforePopup(type) {
resetMenu.transformType = type
switch(type) {
case TransformGizmo.Type.POSITION: resetMenu.transformTypeToDisplay = "position"; break
case TransformGizmo.Type.ROTATION: resetMenu.transformTypeToDisplay = "rotation"; break
case TransformGizmo.Type.SCALE: resetMenu.transformTypeToDisplay = "scale"; break
}
}
MenuItem {
text: `Reset ${resetMenu.transformTypeToDisplay}?`
onTriggered: resetATransformType(resetMenu.transformType)
}
}
/***** GIZMO'S BASIC COMPONENTS *****/
Transform {
id: gizmoDisplayTransform
scale: root.gizmoScale * (camera.position.minus(gizmoDisplayTransform.translation)).length()
}
Entity {
id: centerSphereEntity
components: [centerSphereMesh, centerSphereMaterial, frontLayerComponent]
SphereMesh {
id: centerSphereMesh
radius: 0.04
rings: 8
slices: 8
}
PhongMaterial {
id: centerSphereMaterial
property color base: "white"
ambient: base
shininess: 0.2
}
}
// AXIS GIZMO INSTANTIATOR => X, Y and Z
NodeInstantiator {
model: 3
Entity {
id: axisContainer
property int axis : {
switch(index) {
case 0: return TransformGizmo.Axis.X
case 1: return TransformGizmo.Axis.Y
case 2: return TransformGizmo.Axis.Z
}
}
property color baseColor: {
switch(axis) {
case TransformGizmo.Axis.X: return "#e63b55" // Red
case TransformGizmo.Axis.Y: return "#83c414" // Green
case TransformGizmo.Axis.Z: return "#3387e2" // Blue
}
}
property real lineRadius: 0.015
// SCALE ENTITY
Entity {
id: scaleEntity
Entity {
id: axisCylinder
components: [cylinderMesh, cylinderTransform, scaleMaterial, frontLayerComponent]
CylinderMesh {
id: cylinderMesh
length: 0.5
radius: axisContainer.lineRadius
rings: 2
slices: 16
}
Transform {
id: cylinderTransform
matrix: {
const offset = cylinderMesh.length/2 + centerSphereMesh.radius
const m = Qt.matrix4x4()
switch(axis) {
case TransformGizmo.Axis.X: {
m.translate(Qt.vector3d(offset, 0, 0))
m.rotate(90, Qt.vector3d(0,0,1))
break
}
case TransformGizmo.Axis.Y: {
m.translate(Qt.vector3d(0, offset, 0))
break
}
case TransformGizmo.Axis.Z: {
m.translate(Qt.vector3d(0, 0, offset))
m.rotate(90, Qt.vector3d(1,0,0))
break
}
}
return m
}
}
}
Entity {
id: axisScaleBox
components: [cubeScaleMesh, cubeScaleTransform, scaleMaterial, scalePicker, frontLayerComponent]
CuboidMesh {
id: cubeScaleMesh
property real edge: 0.07
xExtent: edge
yExtent: edge
zExtent: edge
}
Transform {
id: cubeScaleTransform
matrix: {
const offset = cylinderMesh.length + centerSphereMesh.radius
const m = Qt.matrix4x4()
switch(axis) {
case TransformGizmo.Axis.X: {
m.translate(Qt.vector3d(offset, 0, 0))
m.rotate(90, Qt.vector3d(0,0,1))
break
}
case TransformGizmo.Axis.Y: {
m.translate(Qt.vector3d(0, offset, 0))
break
}
case TransformGizmo.Axis.Z: {
m.translate(Qt.vector3d(0, 0, offset))
m.rotate(90, Qt.vector3d(1,0,0))
break
}
}
return m
}
}
}
PhongMaterial {
id: scaleMaterial
ambient: baseColor
}
TransformGizmoPicker {
id: scalePicker
mouseController: mouseHandler
gizmoMaterial: scaleMaterial
gizmoBaseColor: baseColor
gizmoAxis: axis
gizmoType: TransformGizmo.Type.SCALE
onPickedChanged: {
this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) // Save the current transformations
root.pickedChanged(picker.isPressed) // Used to prevent camera transformations
}
}
}
// POSITION ENTITY
Entity {
id: positionEntity
components: [coneMesh, coneTransform, positionMaterial, positionPicker, frontLayerComponent]
ConeMesh {
id: coneMesh
bottomRadius : 0.04
topRadius : 0.001
hasBottomEndcap : true
hasTopEndcap : true
length : 0.15
rings : 2
slices : 8
}
Transform {
id: coneTransform
matrix: {
const offset = cylinderMesh.length + centerSphereMesh.radius + 0.4
const m = Qt.matrix4x4()
switch(axis) {
case TransformGizmo.Axis.X: {
m.translate(Qt.vector3d(offset, 0, 0))
m.rotate(-90, Qt.vector3d(0,0,1))
break
}
case TransformGizmo.Axis.Y: {
m.translate(Qt.vector3d(0, offset, 0))
break
}
case TransformGizmo.Axis.Z: {
m.translate(Qt.vector3d(0, 0, offset))
m.rotate(90, Qt.vector3d(1,0,0))
break
}
}
return m
}
}
PhongMaterial {
id: positionMaterial
ambient: baseColor
}
TransformGizmoPicker {
id: positionPicker
mouseController: mouseHandler
gizmoMaterial: positionMaterial
gizmoBaseColor: baseColor
gizmoAxis: axis
gizmoType: TransformGizmo.Type.POSITION
onPickedChanged: {
// this.modelMatrix = copyMatrix(objectTransform.matrix) // Save the current transformations
this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) // Save the current transformations
root.pickedChanged(picker.isPressed) // Used to prevent camera transformations
}
}
}
// ROTATION ENTITY
Entity {
id: rotationEntity
components: [torusMesh, torusTransform, rotationMaterial, rotationPicker, frontLayerComponent]
TorusMesh {
id: torusMesh
radius: cylinderMesh.length + 0.25
minorRadius: axisContainer.lineRadius
slices: 8
rings: 32
}
Transform {
id: torusTransform
matrix: {
const scaleDiff = 2*torusMesh.minorRadius + 0.01 // Just to make sure there is no face overlapping
const m = Qt.matrix4x4()
switch(axis) {
case TransformGizmo.Axis.X: m.rotate(90, Qt.vector3d(0,1,0)); break
case TransformGizmo.Axis.Y: m.rotate(90, Qt.vector3d(1,0,0)); m.scale(Qt.vector3d(1-scaleDiff, 1-scaleDiff, 1-scaleDiff)); break
case TransformGizmo.Axis.Z: m.scale(Qt.vector3d(1-2*scaleDiff, 1-2*scaleDiff, 1-2*scaleDiff)); break
}
return m
}
}
PhongMaterial {
id: rotationMaterial
ambient: baseColor
}
TransformGizmoPicker {
id: rotationPicker
mouseController: mouseHandler
gizmoMaterial: rotationMaterial
gizmoBaseColor: baseColor
gizmoAxis: axis
gizmoType: TransformGizmo.Type.ROTATION
onPickedChanged: {
this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) // Save the current transformations
root.pickedChanged(picker.isPressed) // Used to prevent camera transformations
}
}
}
}
}
}