4

How to implement two-finger pinch gesture handling for Qt3D Camera's FOV?

There are FirstPersonCameraController and OrbitCameraController camera controllers which handles mouse/touch pad events. The latter even have zoomLimit property, but its meaning is not what I need to zoom scene (from inside a cubemap, camera position is fixed to (0, 0, 0)). I use the former one. It correctly handles mouse drag and single-finger touch events, but not deals with two-finger pinch-like gesture.

Can I customize in simple way PinchArea to interact with Qt3D's Camera? Or Qt Quick's API are orthogonal in this sense to Qt3D's API?

Tomilov Anatoliy
  • 15,657
  • 10
  • 64
  • 169

2 Answers2

1

Use the pinchUpdated event of PinchArea to find information about the Pinch: according to the doc

The pinch parameter provides information about the pinch gesture, including the scale, center and angle of the pinch. These values reflect changes only since the beginning of the current gesture, and therefore are not limited by the minimum and maximum limits in the pinch property.

So you should be able to do something like:

Camera {
    id: myCamera
}

PinchArea {
    onPinchUpdated: {
        myCamera.fieldOfView = pinch.scale*someFactor
    } 
}

This can be done in any custom QML you have that has access to the Pinch and the Camera. If it is a custom script, you can alwazs pass the camera as a property

property Camera myCamera
Basile Perrenoud
  • 4,039
  • 3
  • 29
  • 52
1

I ended up with combination of PinchArea and MouseArea:

import QtQml 2.2

import QtQuick 2.9
import QtQuick.Scene3D 2.0

import Qt3D.Core 2.9
import Qt3D.Extras 2.9
import Qt3D.Render 2.9
import Qt3D.Input 2.1
import Qt3D.Logic 2.0

import PanoEntity 1.0
import Utility 1.0

Item {
    property url panorama
    onPanoramaChanged: {
        if (panorama.toString().length !== 0) {
            if (panoEntity.setPanorama(panorama)) {
                basicCamera.position = Qt.vector3d(0.0, 0.0, 0.0)
                basicCamera.upVector = Qt.vector3d(0.0, 0.0, 1.0)
                basicCamera.viewCenter = panoEntity.pano.dir
                pinchArea.updateFov(defaultFov)
            }
        }
    }

    property Image previewImage

    property real fovMin: 20.0
    property real defaultFov: 60.0
    property real fovMax: 90.0

    property real sensitivity: 1.5
    property real pinchSensitivity: 0.5

    Scene3D {
        id: scene3d

        anchors.fill: parent

        aspects: ["render", "input", "logic"]
        cameraAspectRatioMode: Scene3D.AutomaticAspectRatio // basicCamera.aspectRatio = width / height
        multisample: true

        Entity {
            Camera {
                id: basicCamera

                projectionType: CameraLens.PerspectiveProjection
                nearPlane: 0.1
                farPlane: 2.0 * Math.SQRT2
            }

            RenderSettings {
                id: renderSettings

                renderPolicy: scene3d.visible ? RenderSettings.Always : RenderSettings.OnDemand

                ForwardRenderer {
                    camera: basicCamera

                    clearColor: "transparent"
                }
            }

            InputSettings {
                id: inputSettings
            }

            components: [renderSettings, inputSettings]

            PanoEntity {
                id: panoEntity

                MemoryBarrier {
                    waitFor: MemoryBarrier.All
                }
            }
        }
    }
    PinchArea {
        id: pinchArea

        anchors.fill: parent

        function updateFov(newFov) {
            if (newFov > fovMax) {
                newFov = fovMax
            } else if (newFov < fovMin) {
                newFov = fovMin
            }
            var eps = 1.0 / 60.0
            if (Math.abs(basicCamera.fieldOfView - newFov) > eps) {
                basicCamera.fieldOfView = newFov
                zoomer.fov = newFov
            }
        }

        function updatePinch(pinch) {
            updateFov(basicCamera.fieldOfView * (1.0 + (pinch.previousScale - pinch.scale) * pinchSensitivity))
        }

        onPinchUpdated: {
            updatePinch(pinch)
        }
        onPinchFinished: {
            updatePinch(pinch)
        }

        MouseArea {
            anchors.fill: parent

            propagateComposedEvents: true

            property point startPoint

            function updateView(mouse) {
                basicCamera.pan((startPoint.x - mouse.x) * sensitivity * basicCamera.fieldOfView / width, Qt.vector3d(0.0, 0.0, 1.0))
                basicCamera.tilt((mouse.y - startPoint.y) * sensitivity * basicCamera.fieldOfView / height)
                startPoint = Qt.point(mouse.x, mouse.y)
            }

            onPressed: {
                startPoint = Qt.point(mouse.x, mouse.y)
            }
            onPositionChanged: {
                updateView(mouse)
            }
            onReleased: {
                updateView(mouse)
            }
        }
    }
    Zoomer {
        id: zoomer

        fovMax: parent.fovMax
        defaultFov: parent.defaultFov
        fovMin: parent.fovMin

        anchors.horizontalCenter: parent.horizontalCenter
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 45

        onFovChanged: {
            pinchArea.updateFov(fov);
        }
    }

    Shortcut {
        context: Qt.WindowShortcut
        enabled: scene3d.visible

        sequence: StandardKey.Save

        onActivated: {
            panoEntity.pano.dir = basicCamera.viewCenter
            panoEntity.pano.up = basicCamera.upVector
            panoEntity.pano.version = 7
            if (!panoEntity.updatePanorama()) {
                console.log("Unable to update panorama %1".arg(panorama))
            } else if (previewImage && Utility.fileExists(previewImage.source)) {
                scene3d.grabToImage(function(grabResult) {
                    if (!grabResult.saveToFile(Utility.toLocalFile(previewImage.source))) {
                        console.log("Unable save preview to file: %1".arg(previewImage.source))
                    }
                }, Qt.size(512, 512))
            }
        }
    }
}
Tomilov Anatoliy
  • 15,657
  • 10
  • 64
  • 169