// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick import QtQuick3D import QtQuick3D.Physics import QtQuick3D.Helpers import QtQuick.Controls import QtQuick.Layouts Window { id: appWindow width: 1280 height: 720 visible: true title: qsTr("PhysX Physics Demo") PhysicsWorld { id: physicsWorld gravity: Qt.vector3d(0, -9.81, 0) running: false forceDebugDraw: true minimumTimestep: minTimestepSlider.value maximumTimestep: maxTimestepSlider.value typicalLength: 1 typicalSpeed: 10 scene: viewport.scene } View3D { id: viewport anchors.fill: parent environment: SceneEnvironment { antialiasingMode: SceneEnvironment.MSAA } focus: true Keys.onSpacePressed: { console.log("toggle physics") physicsWorld.running = !physicsWorld.running } Node { id: scene1 PerspectiveCamera { id: camera1 position: Qt.vector3d(-2, 1, 5) eulerRotation : Qt.vector3d(-20, -20, 0) clipFar: 50 clipNear: 0.01 } DirectionalLight { eulerRotation: Qt.vector3d(-45, 45, 0) castsShadow: true brightness: 1 shadowMapQuality: Light.ShadowMapQualityVeryHigh shadowBias: 0.05 pcfFactor: 0.005 } TriggerBody { id: movingArea position: Qt.vector3d(4, -0.25, 1) onBodyEntered: (body) => { if (body.hasOwnProperty('inArea')) body.inArea = true; } onBodyExited: (body) => { if (body.hasOwnProperty('inArea')) body.inArea = false; } Model { source: "#Cube" scale: Qt.vector3d(0.02, 0.015, 0.02) materials: PrincipledMaterial { baseColor: Qt.rgba(0, 0, 1, 0.2) alphaMode: PrincipledMaterial.Blend } } SequentialAnimation { running: true paused: !physicsWorld.running loops: -1 NumberAnimation { target: movingArea property: "position.x" duration: 5000 from: 4 to: -4 easing.type: Easing.InOutQuad } NumberAnimation { target: movingArea property: "position.x" duration: 5000 from: -4 to: 4 easing.type: Easing.InOutQuad } } collisionShapes: BoxShape { extents: Qt.vector3d(2, 1.5, 2) } } DynamicRigidBody { id: impeller position: Qt.vector3d(-3, -0.5, 0) isKinematic: true receiveContactReports: true SequentialAnimation { running: physicsWorld.running loops: Animation.Infinite; NumberAnimation {target: impeller; property: "kinematicPosition.x"; from: -3; to: 3; duration: 2000 } NumberAnimation {target: impeller; property: "kinematicPosition.x"; from: 3; to: -3; duration: 2000 } } Model { source: "#Sphere" materials: PrincipledMaterial { baseColor: "yellow" } scale: Qt.vector3d(0.01, 0.01, 0.01) } collisionShapes: SphereShape { diameter: 1 } onBodyContact: (body, positions, impulses, normals) => { for (var normal of normals) { let force = normal.times(-0.001); body.applyCentralImpulse(force); } } } StaticRigidBody { id: floor position: Qt.vector3d(0, -1, 0) eulerRotation: Qt.vector3d(-90, 0, 0) collisionShapes: PlaneShape {} Model { source: "#Rectangle" scale: Qt.vector3d(5, 5, 1) materials: PrincipledMaterial { baseColor: "green" } castsShadows: false receivesShadows: true } } StaticRigidBody { id: leftWall eulerRotation: Qt.vector3d(0, 90, 0) position: Qt.vector3d(-15, 3, 0) collisionShapes: PlaneShape { scale: Qt.vector3d(0.2, 0.2, 0.2) } } StaticRigidBody { id: rightWall position: Qt.vector3d(15, 3, 0) eulerRotation: Qt.vector3d(0, 270, 0) collisionShapes: PlaneShape { scale: Qt.vector3d(0.2, 0.2, 0.2) } } StaticRigidBody { id: backWall position: Qt.vector3d(0, 3, -15) collisionShapes: PlaneShape { scale: Qt.vector3d(0.2, 0.2, 0.2) } } StaticRigidBody { id: frontWall position: Qt.vector3d(0, 3, 10) eulerRotation: Qt.vector3d(0, 180, 0) collisionShapes: PlaneShape { scale: Qt.vector3d(0.2, 0.2, 0.2) } } } Node { id: shapeSpawner property var instancesBoxes: [] property var instancesSpheres: [] function createBox() { var component = Qt.createComponent("box.qml"); let box = component.createObject(scene1, {y: 3}); instancesBoxes.push(box); if (box === null) { // Error Handling console.log("Error creating object"); } } function deleteBox() { if (instancesBoxes.length > 0) { let box = instancesBoxes.pop(); box.destroy(); } } function updateBoxes() { while (numberBoxes.value > instancesBoxes.length) { createBox(); } while (numberBoxes.value < instancesBoxes.length) { deleteBox(); } updateLocks(); } function doImpulse() { for (var i = 0; i < instancesBoxes.length; i++) { let box = instancesBoxes[i]; box.applyCentralImpulse(Qt.vector3d(0,0.01,0)); } for (i = 0; i < instancesSpheres.length; i++) { let sphere = instancesSpheres[i]; sphere.applyCentralImpulse(Qt.vector3d(0,0.01,0)); } } //// Sphere function createSphere() { var component = Qt.createComponent("sphere.qml"); let sphere = component.createObject(scene1, {y: 3}); instancesSpheres.push(sphere); if (sphere === null) { // Error Handling console.log("Error creating object"); } } function deleteSphere() { if (instancesSpheres.length > 0) { let sphere = instancesSpheres.pop(); sphere.destroy(); } } function updateSpheres() { while (numberSpheres.value > instancesSpheres.length) { createSphere(); } while (numberSpheres.value < instancesSpheres.length) { deleteSphere(); } updateLocks(); } function updateLocks() { var i; for (i = 0; i < instancesSpheres.length; i++) { let sphere = instancesSpheres[i]; sphere.angularAxisLock = (lockAngularX.checked ? DynamicRigidBody.LockX : 0) | (lockAngularY.checked ? DynamicRigidBody.LockY : 0) | (lockAngularZ.checked ? DynamicRigidBody.LockZ : 0); sphere.linearAxisLock = (lockLinearX.checked ? DynamicRigidBody.LockX : 0) | (lockLinearY.checked ? DynamicRigidBody.LockY : 0) | (lockLinearZ.checked ? DynamicRigidBody.LockZ : 0); } for (i = 0; i < instancesBoxes.length; i++) { let box = instancesBoxes[i]; box.angularAxisLock = (lockAngularX.checked ? DynamicRigidBody.LockX : 0) | (lockAngularY.checked ? DynamicRigidBody.LockY : 0) | (lockAngularZ.checked ? DynamicRigidBody.LockZ : 0); box.linearAxisLock = (lockLinearX.checked ? DynamicRigidBody.LockX : 0) | (lockLinearY.checked ? DynamicRigidBody.LockY : 0) | (lockLinearZ.checked ? DynamicRigidBody.LockZ : 0); } } function setKinematic() { for (var i = 0; i < instancesSpheres.length; i++) { let sphere = instancesSpheres[i]; sphere.isKinematic = isKinematicCheck.checked; } for (i = 0; i < instancesBoxes.length; i++) { let box = instancesBoxes[i]; box.isKinematic = isKinematicCheck.checked; } } } } WasdController { keysEnabled: true property bool controllingUnit: false controlledObject: camera1 speed: 0.02 shiftSpeed: 0.2 Keys.onPressed: (event)=> { if (keysEnabled) handleKeyPress(event); if (event.key === Qt.Key_Space) { physicsWorld.running = !physicsWorld.running } else if (event.key === Qt.Key_H) { physicsWorld.forceDebugDraw = !physicsWorld.forceDebugDraw } else if (event.key === Qt.Key_J) { floor.eulerRotation.x = (360 + floor.eulerRotation.x - 1) % 360 } else if (event.key === Qt.Key_K) { floor.eulerRotation.x = floor.eulerRotation.x + 1 % 360 } } Keys.onReleased: (event)=> { if (keysEnabled) handleKeyRelease(event) } } ColumnLayout { Label { text: "Number of boxes: " + numberBoxes.value } Slider { id: numberBoxes focusPolicy: Qt.NoFocus from: 0 to: 20 value: 0 stepSize: 1 onMoved: { shapeSpawner.updateBoxes() } } Label { text: "Number of spheres: " + numberSpheres.value } Slider { id: numberSpheres focusPolicy: Qt.NoFocus from: 0 to: 20 value: 0 stepSize: 1 onMoved: { shapeSpawner.updateSpheres() } } CheckBox { id: lockAngularX text: qsTr("Lock angular X") onCheckedChanged: shapeSpawner.updateLocks() } CheckBox { id: lockAngularY text: qsTr("Lock angular Y") onCheckedChanged: shapeSpawner.updateLocks() } CheckBox { id: lockAngularZ text: qsTr("Lock angular Z") onCheckedChanged: shapeSpawner.updateLocks() } CheckBox { id: lockLinearX text: qsTr("Lock linear X") onCheckedChanged: shapeSpawner.updateLocks() } CheckBox { id: lockLinearY text: qsTr("Lock linear Y") onCheckedChanged: shapeSpawner.updateLocks() } CheckBox { id: lockLinearZ text: qsTr("Lock linear Z") onCheckedChanged: shapeSpawner.updateLocks() } CheckBox { id: isKinematicCheck text: qsTr("Is Kinematic") onCheckedChanged: shapeSpawner.setKinematic() } Button { text: "Add impulse" onClicked: shapeSpawner.doImpulse() } Label { text: "Min timestep: " + minTimestepSlider.value } Slider { id: minTimestepSlider focusPolicy: Qt.NoFocus from: 1 to: 100 value: 5 } Label { text: "Max timestep: " + maxTimestepSlider.value } Slider { id: maxTimestepSlider focusPolicy: Qt.NoFocus from: 1 to: 100 value: 50 } Label { text: "Debug view (forced): " + physicsWorld.forceDebugDraw } } }