Qt Quick 3D Physics - Custom Shapes Example▲
This example demonstrates loading and spawning several rigid body meshes as well as animating them. The scene consists of a dice tower, a weave, a cup and a handful of dices. The cup is animated to collect spawning dices and put them in the dice tower. The dices will then roll down and out on the weave.
QML▲
This is the full qml code for the example:
Window
{
width
:
1280
height
:
720
visible
:
true
title
:
qsTr("QtQuick3DPhysics Custom Shapes"
)
DynamicsWorld {
id
:
physicsWorld
running
:
true
typicalLength
:
2
enableCCD
:
true
}
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
, 20
, 60
)
eulerRotation
:
Qt.vector3d(-
6
, -
13
, 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
)
heightMap
:
"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
heightMap
:
"maps/cloth-heightmap.png"
}
}
DynamicRigidBody {
id
:
diceCup
isKinematic
:
true
mass
:
0
property
vector3d
restPos
:
Qt.vector3d(11
, 6
, 0
)
position
:
restPos
pivot
:
Qt.vector3d(0
, 6
, 0
)
collisionShapes
:
TriangleMeshShape {
id
:
cupShape
meshSource
:
"meshes/simpleCup.mesh"
}
Model {
source
:
"meshes/cup.mesh"
materials
:
PrincipledMaterial {
baseColor
:
"#cc9988"
roughness
:
0.3
metalness
:
1
}
}
Behavior
on
eulerRotation.z {
NumberAnimation
{
duration
:
1500
}
}
Behavior
on
position
{
PropertyAnimation
{
duration
:
1500
}
}
}
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
meshSource
:
"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
)),
5
+
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
meshSource
:
Math.random() &
lt; 0.25 ? "meshes/icosahedron.mesh"
:
Math.random() &
lt; 0.5 ? "meshes/dodecahedron.mesh"
:
Math.random() &
lt; 0.75 ? "meshes/octahedron.mesh"
:
"meshes/tetrahedron.mesh"
}
Model {
id
:
thisModel
source
:
diceShape.meshSource
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 (
let i =
0
;
i &
lt;
count;
i++
) {
objectAt
(
i).restore
(
)
}
}
}
SequentialAnimation
{
running
:
physicsWorld.running
PauseAnimation
{
duration
:
1500
}
ScriptAction
{
script
:
diceCup.position =
Qt.vector3d(4
, 45
, 0
) }
PauseAnimation
{
duration
:
1500
}
ScriptAction
{
script
: {
diceCup.
eulerRotation.
z =
130
;
diceCup.
position =
Qt.vector3d
(
0
,
45
,
0
) }
}
PauseAnimation
{
duration
:
3000
}
ScriptAction
{
script
: {
diceCup.
eulerRotation.
z =
0
;
diceCup.
position =
Qt.vector3d
(
4
,
45
,
0
) }
}
PauseAnimation
{
duration
:
1500
}
ScriptAction
{
script
:
diceCup.position =
diceCup.restPos }
PauseAnimation
{
duration
:
2000
}
ScriptAction
{
script
:
dicePool.restore() }
loops
:
Animation.Infinite
}
}
// scene
}
// View3D
WasdController {
keysEnabled
:
true
controlledObject
:
camera
speed
:
0.2
}
}
Enivronment▲
As usual it contains a DynamicsWorld and a View3D. In the View3D we have our environment which sets up a lightprobe:
environment
:
SceneEnvironment {
clearColor
:
"white"
backgroundMode
:
SceneEnvironment.SkyBox
antialiasingMode
:
SceneEnvironment.MSAA
antialiasingQuality
:
SceneEnvironment.High
lightProbe
:
proceduralSky
}
Textures▲
We define four textures which will be used for the skybox, the weave and the numbers on the dice:
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
}
Scene▲
We have a Node which contains our scene with the camera and a directional light:
id
:
scene
scale
:
Qt.vector3d(2
, 2
, 2
)
PerspectiveCamera {
id
:
camera
position
:
Qt.vector3d(-
45
, 20
, 60
)
eulerRotation
:
Qt.vector3d(-
6
, -
13
, 0
)
clipFar
:
1000
clipNear
:
0.1
}
DirectionalLight {
eulerRotation
:
Qt.vector3d(-
45
, 25
, 0
)
castsShadow
:
true
brightness
:
1
shadowMapQuality
:
Light.ShadowMapQualityVeryHigh
}
Weave▲
We add the weave which is a StaticRigidBody consisting of a model with a weave texture and a HeightFieldShape for collision.
StaticRigidBody {
position
:
Qt.vector3d(-
15
, -
8
, 0
)
id
:
tablecloth
Model {
geometry
:
HeightFieldGeometry {
id
:
tableclothGeometry
extents
:
Qt.vector3d(150
, 20
, 150
)
heightMap
:
"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
heightMap
:
"maps/cloth-heightmap.png"
}
}
Cup▲
We define the cup as a DynamicRigidBody with a Model and a TriangleMeshShape as the collision shape. It has a Behavior on the eulerRotation and position properties as these are part of an animation.
DynamicRigidBody {
id
:
diceCup
isKinematic
:
true
mass
:
0
property
vector3d
restPos
:
Qt.vector3d(11
, 6
, 0
)
position
:
restPos
pivot
:
Qt.vector3d(0
, 6
, 0
)
collisionShapes
:
TriangleMeshShape {
id
:
cupShape
meshSource
:
"meshes/simpleCup.mesh"
}
Model {
source
:
"meshes/cup.mesh"
materials
:
PrincipledMaterial {
baseColor
:
"#cc9988"
roughness
:
0.3
metalness
:
1
}
}
Behavior
on
eulerRotation.z {
NumberAnimation
{
duration
:
1500
}
}
Behavior
on
position
{
PropertyAnimation
{
duration
:
1500
}
}
}
Tower▲
The tower is just a StaticRigidBody with a Model and a TriangleMeshShape for collision.
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
meshSource
:
"meshes/tower.mesh"
}
}
Dices▲
To generate the dices we use a Component and a Repeater3D. The Component contains a DynamicRigidBody with a ConvexMeshShape and a Model. The position, color, scale and mesh source are randomly generated for each die.
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
)),
5
+
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
meshSource
:
Math.random() &
lt; 0.25 ? "meshes/icosahedron.mesh"
:
Math.random() &
lt; 0.5 ? "meshes/dodecahedron.mesh"
:
Math.random() &
lt; 0.75 ? "meshes/octahedron.mesh"
:
"meshes/tetrahedron.mesh"
}
Model {
id
:
thisModel
source
:
diceShape.meshSource
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 (
let i =
0
;
i &
lt;
count;
i++
) {
objectAt
(
i).restore
(
)
}
}
}
Animation▲
To make the dices move from the cup to the dice tower we animate the cup and move it up and then tip it over. This is done using a SequentialAnimation:
SequentialAnimation
{
running
:
physicsWorld.running
PauseAnimation
{
duration
:
1500
}
ScriptAction
{
script
:
diceCup.position =
Qt.vector3d(4
, 45
, 0
) }
PauseAnimation