Qt Quick 3D Physics - Joints Example

Demonstrates using joints in a physics scene.

This example demonstrates using the joints available in Quick 3D Physics. Most joint types (DistanceJoint, PrismaticJoint, RevoluteJoint, and SphericalJoint) inherit from FlexibleJoint to support soft constraints via stiffness and damping. Additionally, D6Joint provides fully configurable 6-degrees-of-freedom constraints, while FixedJoint completely locks all degrees of freedom.

The scene is a typical scene with a PhysicsWorld, a View3D with a PerspectiveCamera and a DirectionalLight:

 Window {
     width: 640
     height: 480
     visible: true
     title: qsTr("Qt Quick 3D Physics - Joints")

     PhysicsWorld {
         scene: viewport.scene
         running: true
     }

     View3D {
         id: viewport
         anchors.fill: parent

         environment: SceneEnvironment {
             clearColor: "#d6dbdf"
             backgroundMode: SceneEnvironment.Color
         }

         PerspectiveCamera {
             position: Qt.vector3d(0, 600, 700)
             eulerRotation: Qt.vector3d(-30, 0, 0)
             clipFar: 5000
             clipNear: 1
         }

         DirectionalLight {
             eulerRotation.x: -45
             eulerRotation.y: 45
             castsShadow: true
             brightness: 1
             shadowFactor: 50
             shadowMapQuality: Light.ShadowMapQualityHigh
         }

         Rope {
             eulerRotation: Qt.vector3d(0, 0, -90)
             position: Qt.vector3d(0, 500, 0)
         }

         SoftRope {
             eulerRotation: Qt.vector3d(0, 0, -90)
             position: Qt.vector3d(100, 500, 0)
         }

         Prismatic {
             id: prismatic
             position: Qt.vector3d(-250, 100, 0)
         }

         Revolute {
             id: revolute
             position: Qt.vector3d(200, 200, 100)
         }

         Spring {
             eulerRotation: Qt.vector3d(0, 0, 0)
             position: Qt.vector3d(0, 1, -400)
         }


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

         FrameAnimation {
             id: animator
             running: true
             onTriggered: {
                 prismatic.jointRotation.z += 1
                 revolute.jointRotation.x += 0.95
             }
         }
     }
 }

There is a StaticRigidBody with a PlaneShape to act as the floor. There are several custom QML objects (Rope, SoftRope, Spring, Prismatic, and Revolute) demonstrating different joint configurations.

 Rope {
     eulerRotation: Qt.vector3d(0, 0, -90)
     position: Qt.vector3d(0, 500, 0)
 }

 SoftRope {
     eulerRotation: Qt.vector3d(0, 0, -90)
     position: Qt.vector3d(100, 500, 0)
 }

 Prismatic {
     id: prismatic
     position: Qt.vector3d(-250, 100, 0)
 }

 Revolute {
     id: revolute
     position: Qt.vector3d(200, 200, 100)
 }

 Spring {
     eulerRotation: Qt.vector3d(0, 0, 0)
     position: Qt.vector3d(0, 1, -400)
 }

The Rope object is a series of capsules and a sphere that are connected with spherical joints for the capsules and a fixed joint for the sphere.

 Node {
     id: root

     SphericalJoint {
         bodyB: shape0
         positionA: root.position
         positionB: Qt.vector3d(-25, 0, 0)
     }

     SphericalJoint {
         bodyA: shape0
         bodyB: shape1
         positionA: Qt.vector3d(25, 0, 0)
         positionB: Qt.vector3d(-25, 0, 0)
     }

     SphericalJoint {
         bodyA: shape1
         bodyB: shape2
         positionA: Qt.vector3d(25, 0, 0)
         positionB: Qt.vector3d(-25, 0, 0)
     }

     SphericalJoint {
         bodyA: shape2
         bodyB: shape3
         positionA: Qt.vector3d(25, 0, 0)
         positionB: Qt.vector3d(-25, 0, 0)
     }

     SphericalJoint {
         bodyA: shape3
         bodyB: shape4
         positionA: Qt.vector3d(25, 0, 0)
         positionB: Qt.vector3d(-25, 0, 0)
     }

     SphericalJoint {
         bodyA: shape4
         bodyB: shape5
         positionA: Qt.vector3d(25, 0, 0)
         positionB: Qt.vector3d(-25, 0, 0)
     }

     FixedJoint {
         bodyA: shape5
         bodyB: sphere
         positionA: Qt.vector3d(25, 0, 0)
         positionB: Qt.vector3d(0, 0, 0)
     }

     // Neighboring links touch exactly at their joints, so each body ignores collisions
     // with its immediate neighbors to avoid fighting the joint with contact forces.
     DynamicRigidBody {
         id: shape0
         position: Qt.vector3d(25, 0, 0)
         filterGroup: 0
         filterIgnoreGroups: 0b0000010
         collisionShapes: CapsuleShape {
             diameter: 10
             height: 40
         }
         Model {
             geometry: CapsuleGeometry {
                 diameter: 10
                 height: 40
             }
             materials: PrincipledMaterial {
                 baseColor: "blue"
             }
         }
     }

     DynamicRigidBody {
         id: shape1
         position: Qt.vector3d(75, 0, 0)
         filterGroup: 1
         filterIgnoreGroups: 0b0000101
         collisionShapes: CapsuleShape {
             diameter: 10
             height: 40
         }
         Model {
             geometry: CapsuleGeometry {
                 diameter: 10
                 height: 40
             }
             materials: PrincipledMaterial {
                 baseColor: "blue"
             }
         }
     }

     DynamicRigidBody {
         id: shape2
         position: Qt.vector3d(125, 0, 0)
         filterGroup: 2
         filterIgnoreGroups: 0b0001010
         collisionShapes: CapsuleShape {
             diameter: 10
             height: 40
         }
         Model {
             geometry: CapsuleGeometry {
                 diameter: 10
                 height: 40
             }
             materials: PrincipledMaterial {
                 baseColor: "blue"
             }
         }
     }

     DynamicRigidBody {
         id: shape3
         position: Qt.vector3d(175, 0, 0)
         filterGroup: 3
         filterIgnoreGroups: 0b0010100
         collisionShapes: CapsuleShape {
             diameter: 10
             height: 40
         }
         Model {
             geometry: CapsuleGeometry {
                 diameter: 10
                 height: 40
             }
             materials: PrincipledMaterial {
                 baseColor: "blue"
             }
         }
     }

     DynamicRigidBody {
         id: shape4
         position: Qt.vector3d(225, 0, 0)
         filterGroup: 4
         filterIgnoreGroups: 0b0101000
         collisionShapes: CapsuleShape {
             diameter: 10
             height: 40
         }
         Model {
             geometry: CapsuleGeometry {
                 diameter: 10
                 height: 40
             }
             materials: PrincipledMaterial {
                 baseColor: "blue"
             }
         }
     }

     DynamicRigidBody {
         id: shape5
         position: Qt.vector3d(275, 0, 0)
         filterGroup: 5
         filterIgnoreGroups: 0b1010000
         collisionShapes: CapsuleShape {
             diameter: 10
             height: 40
         }
         Model {
             geometry: CapsuleGeometry {
                 diameter: 10
                 height: 40
             }
             materials: PrincipledMaterial {
                 baseColor: "blue"
             }
         }
     }

     DynamicRigidBody {
         id: sphere
         position: Qt.vector3d(300, 0, 0)
         scale: Qt.vector3d(0.5, 0.5, 0.5)
         filterGroup: 6
         filterIgnoreGroups: 0b0100000
         collisionShapes: SphereShape {}
         Model {
             source: "#Sphere"
             materials: PrincipledMaterial {
                 baseColor: "blue"
             }
         }
     }
 }

The SoftRope object demonstrates how flexible joints use stiffness and damping properties to create spring-like soft connections.

 Node {
     id: root

     property real stiffness : 15000
     property real damping : 500

     SphericalJoint {
         bodyB: shape0
         positionA: root.position
         positionB: Qt.vector3d(-25, 0, 0)
     }

     PrismaticJoint {
         bodyA: shape0
         bodyB: shape1
         positionA: Qt.vector3d(25, 0, 0)
         positionB: Qt.vector3d(-25, 0, 0)
         stiffness: root.stiffness
         damping: root.damping
         lowerLimit: -20
         upperLimit: 0
     }

     SphericalJoint {
         bodyA: shape1
         bodyB: shape2
         positionA: Qt.vector3d(25, 0, 0)
         positionB: Qt.vector3d(-25, 0, 0)
     }

     PrismaticJoint {
         bodyA: shape2
         bodyB: shape3
         positionA: Qt.vector3d(25, 0, 0)
         positionB: Qt.vector3d(-25, 0, 0)
         stiffness: root.stiffness
         damping: root.damping
         lowerLimit: -20
         upperLimit: 0
     }

     FixedJoint {
         bodyA: shape3
         bodyB: sphere
         positionA: Qt.vector3d(25, 0, 0)
         positionB: Qt.vector3d(0, 0, 0)
     }

     // Neighboring links touch exactly at their joints, so each body ignores collisions
     // with its immediate neighbors to avoid fighting the joint with contact forces.
     DynamicRigidBody {
         id: shape0
         position: Qt.vector3d(25, 0, 0)
         filterGroup: 0
         filterIgnoreGroups: 0b00010
         collisionShapes: CapsuleShape {
             diameter: 10
             height: 40
         }
         Model {
             geometry: CapsuleGeometry {
                 diameter: 10
                 height: 40
             }
             materials: PrincipledMaterial {
                 baseColor: "blueviolet"
             }
         }
     }

     DynamicRigidBody {
         id: shape1
         position: Qt.vector3d(75, 0, 0)
         filterGroup: 1
         filterIgnoreGroups: 0b00101
         collisionShapes: CapsuleShape {
             diameter: 10
             height: 40
         }
         Model {
             geometry: CapsuleGeometry {
                 diameter: 10
                 height: 40
             }
             materials: PrincipledMaterial {
                 baseColor: "blueviolet"
             }
         }
     }

     DynamicRigidBody {
         id: shape2
         position: Qt.vector3d(125, 0, 0)
         filterGroup: 2
         filterIgnoreGroups: 0b01010
         collisionShapes: CapsuleShape {
             diameter: 10
             height: 40
         }
         Model {
             geometry: CapsuleGeometry {
                 diameter: 10
                 height: 40
             }
             materials: PrincipledMaterial {
                 baseColor: "blueviolet"
             }
         }
     }

     DynamicRigidBody {
         id: shape3
         position: Qt.vector3d(175, 0, 0)
         filterGroup: 3
         filterIgnoreGroups: 0b10100
         collisionShapes: CapsuleShape {
             diameter: 10
             height: 40
         }
         Model {
             geometry: CapsuleGeometry {
                 diameter: 10
                 height: 40
             }
             materials: PrincipledMaterial {
                 baseColor: "blueviolet"
             }
         }
     }

     DynamicRigidBody {
         id: sphere
         position: Qt.vector3d(200, 0, 0)
         scale: Qt.vector3d(0.5, 0.5, 0.5)
         filterGroup: 4
         filterIgnoreGroups: 0b01000
         collisionShapes: SphereShape {}

         Model {
             source: "#Sphere"
             materials: PrincipledMaterial {
                 baseColor: "blueviolet"
             }
         }
     }
 }

The Spring object shows how to use D6Joint to constrain motion across 6 degrees of freedom with custom linear and angular spring parameters.

 Node {
     id: root

     DynamicRigidBody {
         id: baseStand
         position: Qt.vector3d(0, 0, 0)

         collisionShapes: BoxShape {
             extents: Qt.vector3d(50, 10, 50)
         }

         Model {
             source: "#Cube"
             scale: Qt.vector3d(0.50, 0.10, 0.50)

             materials: PrincipledMaterial {
                 baseColor: "darkslategray"
             }
         }
     }

     DynamicRigidBody {
         id: floatingSphere
         position: Qt.vector3d(0, 102.5, 0)

         collisionShapes: SphereShape {
             diameter: 20
         }

         Model {
             source: "#Sphere"
             // scale: Qt.vector3d(0.2, 0.2, 0.2)
             materials: PrincipledMaterial {
                 baseColor: "cornflowerblue"
                 roughness: 0.2
             }
         }
     }

     // D6 Joint acting as a soft spring wire
     D6Joint {
         bodyA: baseStand
         bodyB: floatingSphere

         positionA: Qt.vector3d(0, 2.5, 0)
         positionB: Qt.vector3d(0, -100, 0)

         xMotion: D6Joint.Locked
         yMotion: D6Joint.Locked
         zMotion: D6Joint.Locked

         // Allow omnidirectional rotation
         twistMotion: D6Joint.Limited
         swingMotionY: D6Joint.Limited
         swingMotionZ: D6Joint.Limited

         twistLimitLower: -0.07    // ~-4 degrees in radians
         twistLimitUpper: 0.07     // ~+4 degrees in radians
         swingLimitAngleY: 0.087   // ~5 degrees in radians
         swingLimitAngleZ: 0.087   // ~5 degrees in radians

         angularStiffness: 2550000.0
         angularDamping: 2500.0
     }
 }

The Prismatic object consists of two bars locked in a prismatic joint along the x-axis of both of them. With this joint the smaller rod can move freely along this x-axis until it reaches its upper and lower constraint limit.

 Node {
     id: root
     property vector3d jointRotation : Qt.vector3d(0, 0, 90)

     PrismaticJoint {
         bodyA: prismaticBoxA
         bodyB: prismaticBoxB
         lowerLimit: -200
         upperLimit: 0
         positionA: Qt.vector3d(100, 0, 0)
         positionB: Qt.vector3d(-100, 0, 0)
     }

     DynamicRigidBody {
         id: prismaticBoxA
         position: Qt.vector3d(0, 200, 0)
         eulerRotation: root.jointRotation
         kinematicPosition: Qt.vector3d(0, 200, 0)
         kinematicEulerRotation: root.jointRotation
         isKinematic: true
         scale: Qt.vector3d(2, 0.5, 0.5)
         collisionShapes: BoxShape {}
         Model {
             source: "#Cube"
             materials: PrincipledMaterial {
                 baseColor: "yellow"
             }
         }
     }

     DynamicRigidBody {
         id: prismaticBoxB
         position: Qt.vector3d(200 * Math.cos(root.jointRotation.z * Math.PI / 180),
                                200 + 200 * Math.sin(root.jointRotation.z * Math.PI / 180),
                                0)
         eulerRotation: root.jointRotation
         scale: Qt.vector3d(2, 0.4, 0.4)
         collisionShapes: BoxShape {}
         Model {
             source: "#Cube"
             materials: PrincipledMaterial {
                 baseColor: "blue"
             }
         }
     }
 }

The Revolute object consists of two bars rotated by 90 degrees and locked in a revolute joint.

 Node {
     id: root
     property vector3d jointRotation : Qt.vector3d(0, 90, 0)

     RevoluteJoint {
         bodyA: revoluteBoxA
         bodyB: revoluteBoxB
         positionA: Qt.vector3d(100, 0, 0)
         positionB: Qt.vector3d(100, 0, 0)
         orientationB: Quaternion.fromEulerAngles(0, 0, 90)
         angularLimitLower: -Math.PI / 4
         angularLimitUpper: Math.PI / 4
         enableAngularLimit: true
     }

     DynamicRigidBody {
         id: revoluteBoxA
         position: Qt.vector3d(0, 0, 0)
         eulerRotation: root.jointRotation
         kinematicPosition: Qt.vector3d(0, 0, 0)
         kinematicEulerRotation: root.jointRotation
         isKinematic: true
         scale: Qt.vector3d(2, 0.5, 0.5)
         collisionShapes: BoxShape {}
         Model {
             source: "#Cube"
             materials: PrincipledMaterial {
                 baseColor: "yellow"
             }
         }
     }

     DynamicRigidBody {
         id: revoluteBoxB
         position: Qt.vector3d(0, 100, -100)
         eulerRotation: Qt.vector3d(0, 90, -90)
         scale: Qt.vector3d(2, 0.5, 0.5)
         collisionShapes: BoxShape {}
         Model {
             source: "#Cube"
             materials: PrincipledMaterial {
                 baseColor: "blue"
             }
         }
     }
 }

Going back to the main scene there is a FrameAnimation object that rotates the kinematic prismatic and revolute joints.

 FrameAnimation {
     id: animator
     running: true
     onTriggered: {
         prismatic.jointRotation.z += 1
         revolute.jointRotation.x += 0.95
     }
 }

With all these moving parts we can see how the different joints interact in a physics scene.

Files: