Qt Quick 3D Physics - Custom Shapes Example
Qt Quick 3D物理-自定义形状示例
Demonstrates using different shapes.
演示如何使用不同的形状。
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:
这是示例的完整qml代码:
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() < 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.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 < 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:
像往常一样,它包含DynamicsWorld和View3D。在View3D中,我们的环境设置了一个光探针:
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.
我们添加了一个weave,它是一个StaticRigidBody,由一个具有weave纹理的模型和一个用于碰撞的HeightFieldShape组成。
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,并将模型和三角形网格形状定义为碰撞形状。它在eulerRotation和position属性上具有行为,因为它们是动画的一部分。
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,带有一个模型和一个用于碰撞的三角形网格形状。
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.
为了生成骰子,我们使用组件和Repeater3D。组件包含一个带有凸面网格形状的DynamicRigidBody和一个模型。位置、颜色、比例和网格源是为每个模具随机生成的。
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() < 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.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 < 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完成的:
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
}
Controller
控制器
Finally a WasdController is added to be able to control the camera using a keyboard:
最后,添加了WasdController,可以使用键盘控制摄像机:
WasdController {
keysEnabled: true
controlledObject: camera
speed: 0.2
}
Files:
- customshapes/CMakeLists.txt
- customshapes/customshapes.pro
- customshapes/main.cpp
- customshapes/main.qml
- customshapes/qml.qrc
- customshapes/resources.qrc
Images:
- customshapes/maps/cloth-heightmap.png
- customshapes/maps/numbers-normal.png
- customshapes/maps/numbers.png
- customshapes/maps/weave.png
© 2022 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.