Qt Quick 3D - XR 3D Interaction example

Demonstrates how to manipulate 3D objects with Qt Quick 3D XR.

This example shows how to create components that move and resize 3D objects in a scene. It uses the same ray-picking strategy as the xr_input example.

Controller input

In this example, we create a reusable component named AimController. This performs ray picking to select a Model, and optionally allows the user to grab the selected Model and move it around.

AimController defines signals that allows us to implement custom interactions:

signal objectPressed(obj: Model, pos: vector3d, direction: vector3d)
signal objectHovered(obj: Model)
signal moved(pos: vector3d, direction: vector3d)
signal released()

Gadgets

We define an abstract component, XrGadget, that has two functions: handleControllerPress and handleControllerMove. In C++, these functions would have been virtual. Since this example is implemented completely in QML, we instead emit signals that can be handled in sub-components.

For example, TranslateGadget moves the controlled object along the gadget's axis based on the onMoved signal:

onMoved: (pos, dir) => {
    let moveDirection = delta.normalized()
    let mapped_axis = controlledObject.mapDirectionToScene(axisDirection).normalized()
    let dot = mapped_axis.dotProduct(moveDirection)
    let offset = mapped_axis.times(delta.length() * dot)
    controlledObject.position = originalPos.plus(offset)
}

Tying it all together

We define a component GadgetBox that keeps track of which object is selected and shows a translucent box around the selected object, in addition to showing gadgets around the object. When a selected object is pressed, the GadgetBox will cycle between the three different types of gadgets (translate, rotate, and resize).

In main.qml we react to the signals from the AimController and call the functions in GadgetBox:

AimController {
    id: rightAim
    controller: XrController.ControllerRight

    onObjectPressed: (obj, pos, dir) => {
        gadgetBox.handlePress(obj, pos, dir)
    }
    onObjectHovered: (obj) => {
        gadgetBox.handleHover(obj)
        hapticFeedback.handleHover(obj)
    }
    onMoved: (pos, dir) => {
        gadgetBox.handleMove(pos, dir)
    }
    onReleased: {
        gadgetBox.handleRelease()
    }

    grabMoveEnabled: !gadgetBox.gadgetActive
}

We also provide haptic feedback when the hovered object or gadget changes:

XrHapticFeedback {
    id: hapticFeedback
    controller: XrHapticFeedback.RightController
    hapticEffect: XrSimpleHapticEffect {
        amplitude: 0.5
        duration: 30
        frequency: 3000
    }
    property Model prevObj: null
    function handleHover(obj: Model) {
        if (obj && obj !== prevObj)
            start()
        prevObj = obj
    }
}

Example project @ code.qt.io

© 2024 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.