Qt Quick 3D Physics - カスタムシェイプの例

さまざまなシェイプの使用例です。

このサンプルは、複数のリジッドボディメッシュをロードしてスポーンし、それらをアニメーションさせるデモです。シーンは、サイコロタワー、テーブルクロス、カップ、一握りのサイコロで構成されています。カップは、スポーンしたサイコロを集めて、サイコロタワーに入れるようにアニメーションします。サイコロはテーブルクロスの上に転がり落ちます。

環境

いつものように、PhysicsWorldView3D があります。View3D には、ライトプローブをセットアップする環境があります:

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

テクスチャ

スカイボックス、テーブルクロス、サイコロの数字に使う4つのテクスチャを定義します:

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.ShadowMapQualityHigh
    pcfFactor: 0.1
    shadowBias: 1
}

テーブルクロス

テーブルクロスを追加します。テーブルクロスは、織物テクスチャを持つモデルと、衝突用の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"
    }
}

カップ

コップは、コリジョンシェイプとして、モデルとTriangleMeshShape を持つDynamicRigidBody として定義します。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
        }
    }
}

タワー

TowerはStaticRigidBody 、ModelとTriangleMeshShape

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とRepeater3D 。 ComponentにはDynamicRigidBodyConvexMeshShape 、Modelが含まれます。位置、色、スケール、メッシュソースは、ダイスごとにランダムに生成されます。

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
            receivesShadows: false
            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()
        }
    }
}

アニメーション

サイコロをカップからサイコロタワーに移動させるために、カップをアニメーションさせ、上に移動させてからひっくり返します。アニメーションが物理シミュレーションと同期していることを確認するために、PhysicsWorldonFrameDone シグナルに接続したAnimationController を使用します。シミュレーションの各フレームの後、経過したタイムステップでアニメーションを進行させます。

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
}

ファイル

画像:

© 2025 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.