Qt Quick 3D - XR 3D Interaction example | Qt Quick 3D | Qt 6.12.0
Description: Demonstrates how to manipulate 3D objects with Qt Quick 3D XR.
Keywords: example

- Qt 6.12
- Qt Quick 3D
- Qt Quick 3D Examples and Tutorials
- Qt Quick 3D - XR 3D Interaction example
- Qt Quick 3D | Commercial or GPLv3

# 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:

     property Model hoveredObject: null
     signal objectPressed(obj: Model, pos: vector3d, direction: vector3d)
     signal moved(pos: vector3d, direction: vector3d)
     signal released()
     signal objectGrabbed(obj: Model)

#### Gadgets

We define an abstract component, `XrGadget`, which 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)
     }

#### Reusable components

We put the `AimController` and the gadgets in the separate sub-project `xr_shared`, so we can reuse them in future examples. (`xr_shared` also includes the `FreeformTeleporter` component from the Freeform Teleportation Example.)

#### 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.RightController
         view: xrView
         enableVirtualMouse: true
         enableThumbstickMove: thumbCheckBox.checked

         onObjectPressed: (obj, pos, dir) => {
             gadgetBox.handlePress(obj, pos, dir)
         }
         onHoveredObjectChanged: {
             gadgetBox.handleHover(hoveredObject)
             hapticFeedback.handleHover(hoveredObject)
         }
         onMoved: (pos, dir) => {
             gadgetBox.handleMove(pos, dir)
         }
         onReleased: {
             gadgetBox.handleRelease()
         }
         onObjectGrabbed: (obj) => {
             if (!grabCheckBox.checked)
                 return
             const gadget = obj as XrGadget
             if (!gadget)
                 startGrab(obj)
             else if (gadget.grabbable)
                 startGrab(gadget.controlledObject)
         }
         Model {
             source: "#Cylinder"
             scale: "0.05, 0.1, 0.05"
             z: 5
             eulerRotation.x: 90
             materials: PrincipledMaterial {
                 baseColor: "black"
             }
         }
         xrCursor: cursor
     }

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

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

Example project @ code.qt.io