- Qt 6.5
- Qt Quick 3D Physics
- Qt Quick 3D Physics Examples and Tutorials
- Qt Quick 3D Physics - Custom Shapes Example
- Qt Quick 3D Physics | Commercial or GPLv3

# Qt Quick 3D Physics - Custom Shapes 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 {
         width: 1280
         height: 720
         visible: true
         title: qsTr("Qt Quick 3D Physics - Custom Shapes")

         PhysicsWorld {
             id: physicsWorld
             running: true
             typicalLength: 2
             enableCCD: true
             maximumTimestep: 20
             scene: viewport.scene
         }

         View3D {
             id: viewport
             anchors.fill: parent

             environment: SceneEnvironment {
                 clearColor: "white"
                 backgroundMode: SceneEnvironment.SkyBox
                 antialiasingMode: SceneEnvironment.MSAA
                 antialiasingQuality: SceneEnvironment.High
                 lightProbe: proceduralSky
             }

             Texture {
                 id: proceduralSky
                 textureData: ProceduralSkyTextureData {
                     sunLongitude: -115
                 }
             }

             Texture {
                 id: weaveNormal
                 source: "maps/weave.png"
                 scaleU: 200
                 scaleV: 200
                 generateMipmaps: true
                 mipFilter: Texture.Linear
             }

             Texture {
                 id: numberNormal
                 source: "maps/numbers-normal.png"
             }

             Texture {
                 id: numberFill
                 source: "maps/numbers.png"
                 generateMipmaps: true
                 mipFilter: Texture.Linear
             }

             Node {
                 id: scene
                 scale: Qt.vector3d(2, 2, 2)
                 PerspectiveCamera {
                     id: camera
                     position: Qt.vector3d(-45, 25, 60)
                     eulerRotation: Qt.vector3d(-6, -33, 0)
                     clipFar: 1000
                     clipNear: 0.1
                 }

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

                 StaticRigidBody {
                     position: Qt.vector3d(-15, -8, 0)
                     id: tablecloth

                     Model {
                         geometry: HeightFieldGeometry {
                             id: tableclothGeometry
                             extents: Qt.vector3d(150, 20, 150)
                             source: "maps/cloth-heightmap.png"
                             smoothShading: false
                         }
                         materials: PrincipledMaterial {
                             baseColor: "#447722"
                             roughness: 0.8
                             normalMap: weaveNormal
                             normalStrength: 0.7
                         }
                     }

                     collisionShapes: HeightFieldShape {
                         id: hfShape
                         extents: tableclothGeometry.extents
                         source: "maps/cloth-heightmap.png"
                     }
                 }

                 DynamicRigidBody {
                     id: diceCup
                     isKinematic: true
                     mass: 0
                     property vector3d bottomPos: Qt.vector3d(11, 6, 0)
                     property vector3d topPos: Qt.vector3d(11, 45, 0)
                     property vector3d unloadPos: Qt.vector3d(0, 45, 0)
                     position: bottomPos
                     kinematicPivot: Qt.vector3d(0, 6, 0)
                     kinematicPosition: bottomPos
                     collisionShapes: TriangleMeshShape {
                         id: cupShape
                         source: "meshes/simpleCup.mesh"
                     }
                     Model {
                         source: "meshes/cup.mesh"
                         materials: PrincipledMaterial {
                             baseColor: "#cc9988"
                             roughness: 0.3
                             metalness: 1
                         }
                     }
                 }

                 StaticRigidBody {
                     id: diceTower
                     x: -4
                     Model {
                         id: testModel
                         source: "meshes/tower.mesh"
                         materials: [
                             PrincipledMaterial {
                                 baseColor: "#ccccce"
                                 roughness: 0.3
                             },
                             PrincipledMaterial {
                                 id: glassMaterial
                                 baseColor: "#aaaacc"
                                 transmissionFactor: 0.95
                                 thicknessFactor: 1
                                 roughness: 0.05
                             }
                         ]
                     }
                     collisionShapes: TriangleMeshShape {
                         id: triShape
                         source: "meshes/tower.mesh"
                     }
                 }

                 Component {
                     id: diceComponent

                     DynamicRigidBody {
                         id: thisBody
                         function randomInRange(min, max) {
                             return Math.random() * (max - min) + min
                         }

                         function restore() {
                             reset(initialPosition, eulerRotation)
                         }

                         scale: Qt.vector3d(scaleFactor, scaleFactor, scaleFactor)
                         eulerRotation: Qt.vector3d(randomInRange(0, 360),
                                                    randomInRange(0, 360),
                                                    randomInRange(0, 360))

                         property vector3d initialPosition: Qt.vector3d(11 + 1.5 * Math.cos(index/(Math.PI/4)),
                                                                        diceCup.bottomPos.y + index * 1.5,
                                                                        0)
                         position: initialPosition

                         property real scaleFactor: randomInRange(0.8, 1.4)
                         property color baseCol: Qt.hsla(randomInRange(0, 1),
                                                         randomInRange(0.6, 1.0),
                                                         randomInRange(0.4, 0.7),
                                                         1.0)

                         collisionShapes: ConvexMeshShape {
                             id: diceShape
                             source: Math.random() < 0.25 ? "meshes/icosahedron.mesh"
                                   : Math.random() < 0.5 ? "meshes/dodecahedron.mesh"
                                   : Math.random() < 0.75 ? "meshes/octahedron.mesh"
                                                          : "meshes/tetrahedron.mesh"
                         }

                         Model {
                             id: thisModel
                             source: diceShape.source
                             materials: PrincipledMaterial {
                                 metalness: 1.0
                                 roughness: randomInRange(0.2, 0.6)
                                 baseColor: baseCol
                                 emissiveMap: numberFill
                                 emissiveFactor: Qt.vector3d(1, 1, 1)
                                 normalMap: numberNormal
                                 normalStrength: 0.75
                             }
                         }
                     }
                 }

                 Repeater3D {
                     id: dicePool
                     model: 25
                     delegate: diceComponent
                     function restore() {
                         for (var i = 0; i < count; i++) {
                             objectAt(i).restore()
                         }
                     }
                 }

                 Connections {
                     target: physicsWorld
                     property real totalAnimationTime: 7500
                     function onFrameDone(timeStep) {
                         let progressStep = timeStep / totalAnimationTime
                         animationController.progress += progressStep
                         if (animationController.progress >= 1) {
                             animationController.completeToEnd()
                             animationController.reload()
                             animationController.progress = 0
                         }
                     }
                 }

                 AnimationController {
                     id: animationController
                     animation: SequentialAnimation {
                         PauseAnimation { duration: 2500 }
                         PropertyAnimation {
                             target: diceCup
                             property: "kinematicPosition"
                             to: diceCup.topPos
                             duration: 2500
                         }
                         ParallelAnimation {
                             PropertyAnimation {
                                 target: diceCup
                                 property: "kinematicEulerRotation.z"
                                 to: 130
                                 duration: 1500
                             }
                             PropertyAnimation {
                                 target: diceCup
                                 property: "kinematicPosition"
                                 to: diceCup.unloadPos
                                 duration: 1500
                             }
                         }
                         PauseAnimation { duration: 1000 }
                         ParallelAnimation {
                             PropertyAnimation {
                                 target: diceCup
                                 property: "kinematicEulerRotation.z"
                                 to: 0
                                 duration: 1500
                             }
                             PropertyAnimation {
                                 target: diceCup
                                 property: "kinematicPosition"
                                 to: diceCup.topPos
                                 duration: 1500
                             }
                         }
                         PropertyAnimation { target: diceCup; property: "kinematicPosition"; to: diceCup.bottomPos; duration: 1500 }
                         PauseAnimation { duration: 2000 }
                         ScriptAction { script: dicePool.restore() }
                     }
                 }
             } // scene
         } // View3D

         WasdController {
             keysEnabled: true
             controlledObject: camera
             speed: 0.2
         }
     }