// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick import QtQuick3D Item { id: root required property Node origin required property Camera camera property real xSpeed: 0.1 property real ySpeed: 0.1 property bool xInvert: false property bool yInvert: true property bool mouseEnabled: true property bool panEnabled: true property bool automaticClipping: true property alias acceptedButtons: dragHandler.acceptedButtons readonly property bool inputsNeedProcessing: status.useMouse || status.isPanning implicitWidth: parent.width implicitHeight: parent.height Connections { enabled: root.automaticClipping target: root.camera function onZChanged() { // Adjust near/far values based on distance let distance = root.camera.z if (distance < 1) { root.camera.clipNear = 0.01 root.camera.clipFar = 100 if (root.camera.z === 0) { console.warn("camera z set to 0, setting it to near clip") root.camera.z = root.camera.clipNear } } else if (distance < 100) { root.camera.clipNear = 0.1 root.camera.clipFar = 1000 } else { root.camera.clipNear = 1 root.camera.clipFar = 10000 } } } DragHandler { id: dragHandler target: null enabled: root.mouseEnabled acceptedModifiers: Qt.NoModifier onCentroidChanged: { root.mouseMoved(Qt.vector2d(centroid.position.x, centroid.position.y), false); } onActiveChanged: { if (active) root.mousePressed(Qt.vector2d(centroid.position.x, centroid.position.y)); else root.mouseReleased(Qt.vector2d(centroid.position.x, centroid.position.y)); } } DragHandler { id: ctrlDragHandler target: null enabled: root.mouseEnabled && root.panEnabled acceptedButtons: root.acceptedButtons acceptedModifiers: Qt.ControlModifier onCentroidChanged: { root.panEvent(Qt.vector2d(centroid.position.x, centroid.position.y)); } onActiveChanged: { if (active) root.startPan(Qt.vector2d(centroid.position.x, centroid.position.y)); else root.endPan(); } } PinchHandler { id: pinchHandler target: null enabled: root.mouseEnabled onTranslationChanged: (delta) => { if (!root.panEnabled) return; delta.x = -(delta.x / root.width) * root.camera.z; delta.y = (delta.y / root.height) * root.camera.z; let movement = Qt.vector3d(0, 0, 0) // X Movement let xDirection = root.origin.right movement = movement.plus(Qt.vector3d(xDirection.x * delta.x, xDirection.y * delta.x, xDirection.z * delta.x)); // Y Movement let yDirection = root.origin.up movement = movement.plus(Qt.vector3d(yDirection.x * delta.y, yDirection.y * delta.y, yDirection.z * delta.y)); root.origin.position = root.origin.position.plus(movement) } onScaleChanged: (delta) => { root.camera.z = root.camera.z * (1 / delta) } } TapHandler { acceptedButtons: root.acceptedButtons onTapped: root.forceActiveFocus() // qmllint disable signal-handler-parameters } WheelHandler { id: wheelHandler orientation: Qt.Vertical target: null enabled: root.mouseEnabled acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad onWheel: event => { let delta = -event.angleDelta.y * 0.01; root.camera.z += root.camera.z * 0.1 * delta } } function mousePressed(newPos) { root.forceActiveFocus() status.currentPos = newPos status.lastPos = newPos status.useMouse = true; } function mouseReleased(newPos) { status.useMouse = false; } function mouseMoved(newPos: vector2d) { status.currentPos = newPos; } function startPan(pos: vector2d) { status.isPanning = true; status.currentPanPos = pos; status.lastPanPos = pos; } function endPan() { status.isPanning = false; } function panEvent(newPos: vector2d) { status.currentPanPos = newPos; } FrameAnimation { id: updateTimer running: root.inputsNeedProcessing onTriggered: status.processInput(frameTime * 100) } QtObject { id: status property bool useMouse: false property bool isPanning: false property vector2d lastPos: Qt.vector2d(0, 0) property vector2d lastPanPos: Qt.vector2d(0, 0) property vector2d currentPos: Qt.vector2d(0, 0) property vector2d currentPanPos: Qt.vector2d(0, 0) function negate(vector) { return Qt.vector3d(-vector.x, -vector.y, -vector.z) } function processInput(frameDelta) { if (useMouse) { // Get the delta var rotationVector = root.origin.eulerRotation; var delta = Qt.vector2d(lastPos.x - currentPos.x, lastPos.y - currentPos.y); // rotate x var rotateX = delta.x * root.xSpeed * frameDelta if (root.xInvert) rotateX = -rotateX; rotationVector.y += rotateX; // rotate y var rotateY = delta.y * -root.ySpeed * frameDelta if (root.yInvert) rotateY = -rotateY; rotationVector.x += rotateY; root.origin.setEulerRotation(rotationVector); lastPos = currentPos; } if (isPanning) { let delta = currentPanPos.minus(lastPanPos); delta.x = -delta.x delta.x = (delta.x / root.width) * root.camera.z * frameDelta delta.y = (delta.y / root.height) * root.camera.z * frameDelta let velocity = Qt.vector3d(0, 0, 0) // X Movement let xDirection = root.origin.right velocity = velocity.plus(Qt.vector3d(xDirection.x * delta.x, xDirection.y * delta.x, xDirection.z * delta.x)); // Y Movement let yDirection = root.origin.up velocity = velocity.plus(Qt.vector3d(yDirection.x * delta.y, yDirection.y * delta.y, yDirection.z * delta.y)); root.origin.position = root.origin.position.plus(velocity) lastPanPos = currentPanPos } } } }