Qt Quick 3D Physics - Cannon Example

// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick3D
import QtQuick3D.Physics
import QtQuick3D.Helpers
import QtQuick.Controls
import QtQuick.Layouts

Window {
    id: appWindow
    width: 800
    height: 600
    visible: true
    title: qsTr("Qt Quick 3D Physics - Cannon")

    PhysicsWorld {
        scene: viewport.scene
    }

    View3D {
        id: viewport
        width: parent.width
        height: parent.height
        focus: true

        environment: SceneEnvironment {
            antialiasingMode: SceneEnvironment.MSAA
            backgroundMode: SceneEnvironment.Color
            clearColor: "#f0f0f0"
        }

        PerspectiveCamera {
            id: camera
            position: Qt.vector3d(-4000, 5000, 10000)
            eulerRotation: Qt.vector3d(-20, -20, 0)
            clipFar: 500000
            clipNear: 100
        }

        DirectionalLight {
            eulerRotation: Qt.vector3d(-45, 45, 0)
            castsShadow: true
            brightness: 1
            shadowMapQuality: Light.ShadowMapQualityVeryHigh
            shadowFactor: 100
        }

        StaticRigidBody {
            eulerRotation: Qt.vector3d(-90, 0, 0)
            collisionShapes: PlaneShape {}
            Model {
                source: "#Rectangle"
                scale: Qt.vector3d(2000, 2000, 1)
                materials: PrincipledMaterial {
                    baseColor: "green"
                }
                castsShadows: false
                receivesShadows: true
            }
        }

        Node {
            id: shapeSpawner
            property var instancesBoxes: []
            property var instancesSpheres: []
            property int stackCount: 0
            property var boxComponent: Qt.createComponent("Box.qml")
            property var sphereComponent: Qt.createComponent("Sphere.qml")

            function createStack(stackZ, numStacks) {
                let size = 10
                let extents = 400

                for (var i = 0; i < size; i++) {
                    for (var j = 0; j < size - i; j++) {
                        let x = j * 2 - size + i
                        let y = i * 2 + 1
                        let z = 5 * (stackZ - numStacks)
                        let center = Qt.vector3d(x, y, z).times(0.5 * extents)
                        let box = boxComponent.incubateObject(shapeSpawner, {
                                                                  "position": center,
                                                                  "xyzExtents": extents
                                                              })
                        instancesBoxes.push(box)
                    }
                }
            }

            function createBall(position, forward) {
                var diameter = 600
                var speed = 20000
                let settings = {
                    "position": position,
                    "sphereDiameter": diameter
                }
                let sphere = sphereComponent.createObject(shapeSpawner, settings)
                sphere.setLinearVelocity(forward.times(speed))
                instancesSpheres.push(sphere)

                if (sphere === null) {
                    console.log("Error creating object")
                }
            }

            function reset() {
                // Only run method if previous stack has been created fully
                for (var i = 0; i < instancesBoxes.length; i++)
                    if (!instancesBoxes[i].object)
                        return

                instancesSpheres.forEach(sphere => {
                                             sphere.collisionShapes = []
                                             sphere.destroy()
                                         })
                instancesBoxes.forEach(box => {
                                           box.object.collisionShapes = []
                                           box.object.destroy()
                                       })
                instancesSpheres = []
                instancesBoxes = []

                for (var stackI = 0; stackI < stackSlider.value; stackI++) {
                    shapeSpawner.createStack(stackI, stackSlider.value)
                }
            }
        }
        Crosshair {
            id: crossHair
            anchors.centerIn: parent
        }
    }

    Component.onCompleted: {
        shapeSpawner.reset()
    }

    WasdController {
        speed: 100
        controlledObject: camera
        Keys.onPressed: event => {
                            handleKeyPress(event)
                            if (event.key === Qt.Key_Space) {
                                shapeSpawner.createBall(camera.position,
                                                        camera.forward)
                            }
                        }
        Keys.onReleased: event => {
                             handleKeyRelease(event)
                         }
    }

    Frame {
        background: Rectangle {
            color: "#c0c0c0"
            border.color: "#202020"
        }
        anchors.top: parent.top
        anchors.left: parent.left
        anchors.margins: 10

        ColumnLayout {
            Label {
                text: "No. Stacks: " + stackSlider.value.toFixed(0)
            }
            Slider {
                id: stackSlider
                focusPolicy: Qt.NoFocus
                from: 1
                to: 9
                value: 4
                stepSize: 1
                snapMode: Slider.SnapOnRelease
            }
            Button {
                id: resetButton
                Layout.alignment: Qt.AlignHCenter
                text: "Reset scene"
                onClicked: shapeSpawner.reset()
            }
            Button {
                id: fireButton
                Layout.alignment: Qt.AlignHCenter
                text: "Fire!"
                onClicked: shapeSpawner.createBall(camera.position,
                                                   camera.forward)
            }
        }
    }
}