Qt Quick 3D 物理引擎 - 自定义形状示例

演示了使用不同形状的应用。

此示例演示了加载和生成多个刚体网格以及对他们进行动画处理。场景包括一个骰塔、桌布、一个杯子,和一些骰子。杯子会进行动画处理来收集生成的骰子并将它们放入骰塔中,然后骰子会从骰塔滚下并在桌布上滚动。

环境

一如既往,我们有一个PhysicsWorld和一个View3D。在View3D中,我们有我们的环境,该环境设置了灯光探针。

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
}

场景

我们有一个节点包含了我们的场景,其中包含摄像头和一个方向光。

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
}

桌布

我们添加了桌布,它是一个由一个带有编织纹理的模型和一个用于碰撞的HeightFieldShape组成的StaticRigidBody

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"
    }
}

杯子

我们定义杯子为一个具有模型和用作碰撞形状的TriangleMeshShapeDynamicRigidBody。它在其eulerRotationposition属性上有行为,因为这些属性属于动画的一部分。

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
        }
    }
}

这座塔只是一个具有TriangleMeshShape用于碰撞的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
        source: "meshes/tower.mesh"
    }
}

骰子

为了生成骰子,我们使用了一个组件和Repeater3D。该组件包含了一个具有ConvexMeshShape和模型的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)),
                                                       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()
        }
    }
}

动画

为了将骰子从杯子移动到骰子塔,我们通过动画使杯子向上移动,然后将其倾斜。为确保动画与物理模拟保持同步,我们使用了一个AnimationController,并将其连接到PhysicsWorldonFrameDone信号。在每一帧模拟之后,我们使用经过的时间步进行动画推进。

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() }
    }
}

控制器

最后添加了一个WasdController,以便能够使用键盘控制相机。

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

文件

图像

© 2024 Qt公司有限公司。此文档中包含的贡献的文档版权属于各自的拥有者。所提供的文档根据自由软件基金会发布的GNU Free Documentation License版本1.3的条款进行许可。Qt及相关标志是芬兰及/或全球的Qt公司有限公司的商标。所有其他商标均属于其各自的所有者。