Qt Quick 3D Physics - Raycaster Example

Demonstrates continuous ray intersection queries using Raycaster.

Raycaster intersecting physics bodies

The example sweeps a Raycaster across a static box, a dynamic sphere, and the floor. The ray changes color when it reports an intersection, and a yellow marker shows the scene-space hit position.

Configuring the Ray

The Raycaster is a child of a semi-transparent dynamic body. Its excludeParentBody property determines whether that body can be reported as the closest hit:

 Raycaster {
     id: ray
     enabled: enabledCheckBox.checked
     direction: Qt.vector3d(0, -1, 0)
     maxDistance: distanceSlider.value
     includeStatic: staticCheckBox.checked
     includeDynamic: dynamicCheckBox.checked
     excludeParentBody: excludeParentCheckBox.checked
     excludedBodies: excludeSphereCheckBox.checked ? [sphereBody] : []

     Model {
         readonly property real rayLength: ray.isColliding ? ray.hit.distance : ray.maxDistance

         visible: ray.enabled
         source: "#Cube"
         y: -rayLength / 2
         scale: Qt.vector3d(0.025, rayLength / 100, 0.025)
         materials: PrincipledMaterial {
             baseColor: ray.isColliding ? "#24a148" : "#da1e28"
             lighting: PrincipledMaterial.NoLighting
         }
     }
 }

The control panel enables or disables the ray, selects static and dynamic bodies, and changes the maximum query distance. Two controls leave bodies out of the result: one toggles parent exclusion, and one names the blue sphere in excludedBodies, so the ray passes through it and reports the floor instead. The sweep can also be paused to set the ray angle manually:

 Frame {
     anchors.left: parent.left
     anchors.top: parent.top
     anchors.margins: 10

     background: Rectangle {
         color: "#c0c0c0"
         border.color: "#202020"
     }

     ColumnLayout {
         Label {
             text: qsTr("Raycaster")
             font.bold: true
         }
         Label {
             color: !ray.enabled ? "#525252" : ray.isColliding ? "#106b2d" : "#a2191f"
             text: !ray.enabled ? qsTr("Disabled") : ray.isColliding ? qsTr("Hit detected") : qsTr("No hit")
         }
         CheckBox {
             id: enabledCheckBox
             text: qsTr("Enabled")
             checked: true
         }
         CheckBox {
             id: staticCheckBox
             text: qsTr("Include static bodies")
             checked: true
         }
         CheckBox {
             id: dynamicCheckBox
             text: qsTr("Include dynamic bodies")
             checked: true
         }
         CheckBox {
             id: excludeParentCheckBox
             text: qsTr("Exclude parent body")
             checked: true
         }
         CheckBox {
             id: excludeSphereCheckBox
             text: qsTr("Exclude the blue sphere")
         }
         CheckBox {
             id: sweepCheckBox
             text: qsTr("Sweep ray")
             checked: true
         }
         Label {
             text: qsTr("Ray angle: %1°").arg(rayOrigin.eulerRotation.z.toFixed(0))
         }
         Slider {
             id: angleSlider
             Layout.preferredWidth: 220
             enabled: !sweepCheckBox.checked
             from: -25
             to: 25
             stepSize: 1
         }
         Label {
             text: qsTr("Maximum distance: %1").arg(distanceSlider.value.toFixed(0))
         }
         Slider {
             id: distanceSlider
             Layout.preferredWidth: 220
             from: 0
             to: 600
             value: 350
             stepSize: 10
         }
         Label {
             visible: ray.isColliding
             text: qsTr("Hit distance: %1").arg(ray.hit.distance.toFixed(1))
         }
     }
 }

Files: