Revision control
+ // Copyright (C) 2026 The Qt Company Ltd.
+ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+ import QtQuick
+ import QtQuick.Controls
+ import QtQuick.Layouts
+ import QtQuick3D
+ import QtQuick3D.Physics
+
+ Window {
+ id: window
+ width: 800
+ height: 600
+ visible: true
+ title: qsTr("Qt Quick 3D Physics - Raycaster")
+
+ PhysicsWorld {
+ scene: view3D.scene
+ gravity: Qt.vector3d(0, 0, 0)
+ }
+
+ View3D {
+ id: view3D
+ anchors.fill: parent
+
+ environment: SceneEnvironment {
+ antialiasingMode: SceneEnvironment.MSAA
+ backgroundMode: SceneEnvironment.Color
+ clearColor: "#f0f0f0"
+ }
+
+ PerspectiveCamera {
+ position: Qt.vector3d(0, 300, 750)
+ eulerRotation.x: -20
+ clipFar: 2000
+ }
+
+ DirectionalLight {
+ eulerRotation: Qt.vector3d(-45, 35, 0)
+ castsShadow: true
+ brightness: 1.2
+ shadowFactor: 50
+ shadowMapQuality: Light.ShadowMapQualityVeryHigh
+ }
+
+ StaticRigidBody {
+ eulerRotation.x: -90
+ collisionShapes: PlaneShape {}
+
+ Model {
+ source: "#Rectangle"
+ scale: Qt.vector3d(12, 8, 1)
+ materials: PrincipledMaterial {
+ baseColor: "green"
+ roughness: 0.8
+ }
+ castsShadows: false
+ receivesShadows: true
+ }
+ }
+
+ StaticRigidBody {
+ position: Qt.vector3d(-130, 60, 0)
+ collisionShapes: BoxShape {
+ extents: Qt.vector3d(90, 120, 90)
+ }
+
+ Model {
+ source: "#Cube"
+ scale: Qt.vector3d(0.9, 1.2, 0.9)
+ materials: PrincipledMaterial {
+ baseColor: "#d94141"
+ }
+ }
+ }
+
+ DynamicRigidBody {
+ id: sphereBody
+ position: Qt.vector3d(130, 75, 0)
+ isKinematic: true
+ kinematicPosition: position
+ collisionShapes: SphereShape {
+ diameter: 100
+ }
+
+ Model {
+ source: "#Sphere"
+ materials: PrincipledMaterial {
+ baseColor: "#3d70b2"
+ roughness: 0.4
+ }
+ }
+ }
+
+ DynamicRigidBody {
+ position: Qt.vector3d(0, 310, 0)
+ isKinematic: true
+ kinematicPosition: position
+ collisionShapes: SphereShape {
+ diameter: 100
+ }
+
+ Model {
+ source: "#Sphere"
+ castsShadows: false
+ materials: PrincipledMaterial {
+ baseColor: "#7193b5"
+ opacity: 0.3
+ alphaMode: PrincipledMaterial.Blend
+ roughness: 0.35
+ }
+ }
+
+ Node {
+ id: rayOrigin
+ y: 70
+ // The slider holds the angle, whether the sweep or the user moves it.
+ eulerRotation.z: angleSlider.value
+
+ SequentialAnimation {
+ running: ray.enabled && sweepCheckBox.checked
+ loops: Animation.Infinite
+ NumberAnimation {
+ target: angleSlider
+ property: "value"
+ from: -25
+ to: 25
+ duration: 3000
+ easing.type: Easing.InOutSine
+ }
+ NumberAnimation {
+ target: angleSlider
+ property: "value"
+ from: 25
+ to: -25
+ duration: 3000
+ easing.type: Easing.InOutSine
+ }
+ }
+
+ //! [raycast]
+ 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
+ }
+ }
+ }
+ //! [raycast]
+ }
+ }
+
+ Model {
+ visible: ray.enabled && ray.isColliding
+ source: "#Sphere"
+ position: ray.hit.position
+ scale: Qt.vector3d(0.16, 0.16, 0.16)
+ materials: PrincipledMaterial {
+ baseColor: "#f1c21b"
+ lighting: PrincipledMaterial.NoLighting
+ }
+ }
+ }
+
+ //! [controls]
+ 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))
+ }
+ }
+ }
+ //! [controls]
+ }