Revision control

++ // Copyright (C) 2023 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ import QtCore
++ import QtQuick
++ import QtTest
++ import QtQuick3D
++ import QtQuick3D.Physics
++ import QtQuick3D.Physics.TestUtils
++
++ Item {
++ width: 640
++ height: 480
++ visible: true
++
++ PhysicsWorld {
++ id: world
++ gravity: Qt.vector3d(0, -9.81, 0)
++ running: true
++ forceDebugDraw: true
++ typicalLength: 1
++ typicalSpeed: 10
++ minimumTimestep: 15
++ maximumTimestep: 15
++ scene: viewport.scene
++ property real elapsedTime: 0
++ }
++
++ Connections {
++ target: world
++ function onFrameDone(timeStep) {
++ world.elapsedTime += timeStep
++ }
++ }
++
++ View3D {
++ id: viewport
++ anchors.fill: parent
++
++ environment: SceneEnvironment {
++ clearColor: "#151a3f"
++ backgroundMode: SceneEnvironment.Color
++ }
++
++ PerspectiveCamera {
++ id: camera1
++ position: Qt.vector3d(0, 5, 10)
++ eulerRotation: Qt.vector3d(-20, 0, 0)
++ clipFar: 50
++ clipNear: 0.01
++ }
++
++ DirectionalLight {
++ eulerRotation.x: -45
++ eulerRotation.y: 45
++ }
++
++
++ Node {
++ x: -5
++
++ TriggerBody {
++ position: Qt.vector3d(0, 3, 0)
++ collisionShapes: BoxShape {
++ extents: Qt.vector3d(1, 2, 1)
++ }
++
++ onBodyEntered: (body) => {
++ body.triggered = true;
++ boxCentre.simulationEnabled = false;
++ boxCentreLow.simulationEnabled = true;
++ collisionSphereLeft.simulationEnabled = false;
++ boxRight.simulationEnabled = true;
++ }
++ }
++
++ DynamicRigidBody {
++ id: collisionSphereLeft
++ massMode: DynamicRigidBody.CustomDensity
++ density: 1000
++ position: Qt.vector3d(0, 6, 0)
++ sendTriggerReports: true
++ simulationEnabled: false
++ property bool triggered: false
++
++ collisionShapes: SphereShape {
++ diameter: 1
++ }
++ Model {
++ source: "#Sphere"
++ materials: PrincipledMaterial {
++ baseColor: collisionSphereLeft.triggered ? "yellow" : "red"
++ }
++ scale: Qt.vector3d(0.01, 0.01, 0.01)
++ }
++ }
++ }
++
++
++ Node {
++ x: 0
++ DynamicRigidBody {
++ id: boxCentreLow
++ isKinematic: true
++ simulationEnabled: false
++ position: Qt.vector3d(0, -1, 0)
++ kinematicPosition: position
++ collisionShapes: BoxShape {
++ extents: Qt.vector3d(2,1,2)
++ }
++ Model {
++ source: "#Cube"
++ scale: Qt.vector3d(2, 1, 2).times(0.01)
++ eulerRotation: Qt.vector3d(0, 90, 0)
++ materials: PrincipledMaterial {
++ baseColor: "green"
++ }
++ }
++ receiveContactReports: true
++ sendContactReports: true
++ property bool hasCollided: false
++
++ onBodyContact: (body, positions, impulses, normals) => {
++ hasCollided = true
++ }
++ }
++
++ TriggerBody {
++ position: Qt.vector3d(0, 3, 0)
++ collisionShapes: BoxShape {
++ extents: Qt.vector3d(1, 2, 1)
++ }
++
++ onBodyEntered: (body) => {
++ body.triggered = true;
++ collisionSphereRight.simulationEnabled = true;
++ }
++ }
++
++ StaticRigidBody {
++ id: boxCentre
++ position: Qt.vector3d(0, 4.5, 0)
++ collisionShapes: BoxShape {
++ extents: Qt.vector3d(2,1,2)
++ }
++ Model {
++ source: "#Cube"
++ scale: Qt.vector3d(2, 1, 2).times(0.01)
++ eulerRotation: Qt.vector3d(0, 90, 0)
++ materials: PrincipledMaterial {
++ baseColor: "blue"
++ }
++ }
++ }
++
++ DynamicRigidBody {
++ id: collisionSphereCenter
++ massMode: DynamicRigidBody.CustomDensity
++ density: 1000
++ position: Qt.vector3d(0, 6, 0)
++ sendTriggerReports: true
++ sendContactReports: true
++ property bool triggered: false
++
++ collisionShapes: SphereShape {
++ diameter: 1
++ }
++ Model {
++ source: "#Sphere"
++ materials: PrincipledMaterial {
++ baseColor: collisionSphereCenter.triggered ? "yellow" : "red"
++ }
++ scale: Qt.vector3d(0.01, 0.01, 0.01)
++ }
++ }
++ }
++
++ Node {
++ id: triggerNode
++ x: 5
++
++ TriggerBody {
++ id: triggerBox
++ position: Qt.vector3d(0, 3, 0)
++ collisionShapes: BoxShape {
++ extents: Qt.vector3d(1, 2, 1)
++ }
++
++ onBodyEntered: (body) => {
++ body.triggered = true;
++ collisionSphereLeft.simulationEnabled = true;
++ collisionSphereRight.simulationEnabled = false;
++ }
++ }
++
++ DynamicRigidBody {
++ id: collisionSphereRight
++ massMode: DynamicRigidBody.CustomDensity
++ density: 1000
++ position: Qt.vector3d(0, 6, 0)
++ sendTriggerReports: true
++ property bool triggered: false
++
++ collisionShapes: SphereShape {
++ id: sphereShape
++ diameter: 1
++ }
++ Model {
++ source: "#Sphere"
++ materials: PrincipledMaterial {
++ baseColor: collisionSphereRight.triggered ? "yellow" : "red"
++ }
++ scale: Qt.vector3d(0.01, 0.01, 0.01)
++ }
++ }
++
++ StaticRigidBody {
++ id: boxRight
++ simulationEnabled: false
++ position: Qt.vector3d(0, 0, 0)
++ collisionShapes: BoxShape {
++ extents: Qt.vector3d(2,1,2)
++ }
++ Model {
++ source: "#Cube"
++ scale: Qt.vector3d(2, 1, 2).times(0.01)
++ eulerRotation: Qt.vector3d(0, 90, 0)
++ materials: PrincipledMaterial {
++ baseColor: "blue"
++ }
++ }
++ }
++ }
++
++ Node {
++ id: disabledNode
++ x: 10
++
++ // PhysX only rejects forces and impulses on a body with its simulation disabled
++ // in checked builds, and dereferences the simulation object such a body does not
++ // have in release ones, so make sure we never pass them on.
++ DynamicRigidBody {
++ id: disabledBody
++ simulationEnabled: false
++ position: Qt.vector3d(0, 6, 0)
++ collisionShapes: SphereShape {
++ diameter: 1
++ }
++ property bool commandsSent: false
++
++ function sendCommands() {
++ applyCentralForce(Qt.vector3d(0, 1000, 0));
++ applyForce(Qt.vector3d(0, 1000, 0), Qt.vector3d(0.1, 0, 0));
++ applyTorque(Qt.vector3d(0, 1000, 0));
++ applyCentralImpulse(Qt.vector3d(0, 1000, 0));
++ applyImpulse(Qt.vector3d(0, 1000, 0), Qt.vector3d(0.1, 0, 0));
++ applyTorqueImpulse(Qt.vector3d(0, 1000, 0));
++ commandsSent = true;
++ }
++ }
++ }
++ }
++
++ PhysicsTestCase {
++ name: "trigger right"
++ goalReached: collisionSphereRight.triggered
++ }
++
++ PhysicsTestCase {
++ name: "trigger left"
++ goalReached: collisionSphereLeft.triggered
++ }
++
++ PhysicsTestCase {
++ name: "trigger center"
++ goalReached: collisionSphereCenter.triggered
++ }
++
++ PhysicsTestCase {
++ name: "trigger right box"
++ goalReached: collisionSphereCenter.triggered
++ }
++
++ PhysicsTestCase {
++ name: "trigger center low box"
++ goalReached: boxCentreLow.hasCollided
++ }
++
++ Timer {
++ interval: 100
++ running: true
++ onTriggered: disabledBody.sendCommands()
++ }
++
++ PhysicsTestCase {
++ name: "disabled body ignores commands"
++ // The body must still be where it started: not simulated, and not moved by any of
++ // the forces and impulses applied to it.
++ goalReached: disabledBody.commandsSent && world.elapsedTime > 500
++ && disabledBody.position.y === 6
++ }
++ }