Qt Quick 3D Physics - 复合形状示例

演示如何使用复合碰撞形状。

此示例演示了如何使用多个碰撞形状来创建用于碰撞检测的复杂对象。场景由一个绿色静态平面和一系列相互连接的链接组成。开始时,模拟是关闭的。一段时间后或当用户按下空格键时,模拟将启动。动画开始时,最左边和最右边的链接会水平前后移动。

设置

像往常一样,我们需要添加PhysicsWorld

PhysicsWorld {
    id: physicsWorld
    enableCCD: true
    maximumTimestep: 20
    scene: viewport.scene
}

我们进行常规设置,包括环境、摄像机和灯光:

environment: SceneEnvironment {
    antialiasingMode: SceneEnvironment.MSAA
    backgroundMode: SceneEnvironment.Color
    clearColor: "lightblue"
}

focus: true

PerspectiveCamera {
    id: camera
    position: Qt.vector3d(0, 900, 1500)
    eulerRotation: Qt.vector3d(-10, 0, 0)
    clipFar: 15500
    clipNear: 1
}

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

物理对象

我们有常规的静态平面:

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

然后我们创建链接实例。

MeshLink {
    id: leftLink
    isKinematic: true
    property vector3d startPos: Qt.vector3d(-6 * viewport.ringDistance,
                                            viewport.ringY,
                                            0)
    property vector3d startRot: Qt.vector3d(90, 0, 0)
    kinematicPosition: startPos
    position: startPos
    kinematicEulerRotation: startRot
    eulerRotation: startRot
    color: "red"
}

CapsuleLink {
    position: Qt.vector3d(-5 * viewport.ringDistance, viewport.ringY, 0)
    eulerRotation: Qt.vector3d(90, 0, 0)
}

MeshLink {
    position: Qt.vector3d(-4 * viewport.ringDistance, viewport.ringY, 0)
    eulerRotation: Qt.vector3d(90, 0, 0)
}

MeshLink {
    position: Qt.vector3d(-3 * viewport.ringDistance, viewport.ringY, 0)
    eulerRotation: Qt.vector3d(0, 90, 0)
}

MeshLink {
    position: Qt.vector3d(-2 * viewport.ringDistance, viewport.ringY, 0)
    eulerRotation: Qt.vector3d(90, 0, 0)
}

MeshLink {
    position: Qt.vector3d(-1 * viewport.ringDistance, viewport.ringY, 0)
    eulerRotation: Qt.vector3d(0, 90, 0)
}

CapsuleLink {
    position: Qt.vector3d(0, viewport.ringY, 0)
}

MeshLink {
    position: Qt.vector3d(1 * viewport.ringDistance, viewport.ringY, 0)
    eulerRotation: Qt.vector3d(0, 90, 0)
}

MeshLink {
    position: Qt.vector3d(2 * viewport.ringDistance, viewport.ringY, 0)
    eulerRotation: Qt.vector3d(90, 0, 0)
}

MeshLink {
    position: Qt.vector3d(3 * viewport.ringDistance, viewport.ringY, 0)
    eulerRotation: Qt.vector3d(0, 90, 0)
}

MeshLink {
    position: Qt.vector3d(4 * viewport.ringDistance, viewport.ringY, 0)
    eulerRotation: Qt.vector3d(90, 0, 0)
}

CapsuleLink {
    position: Qt.vector3d(5 * viewport.ringDistance, viewport.ringY, 0)
    eulerRotation: Qt.vector3d(90, 0, 0)
}

MeshLink {
    id: rightLink
    isKinematic: true
    property vector3d startPos: Qt.vector3d(6 * viewport.ringDistance,
                                            viewport.ringY,
                                            0)
    property vector3d startRot: Qt.vector3d(90, 0, 0)
    kinematicPosition: startPos
    position: startPos
    kinematicEulerRotation: startRot
    eulerRotation: startRot
    color: "red"
}

左边的第一个链接的isKinematic 属性设置为true ,这样我们就可以通过动画来控制它。由于它是一个运动对象,我们需要设置kinematicPositionkinematicRotation 属性。我们通过动画kinematicPosition 属性来制作动画。其他链接是实例化的,它们之间有一定的间距。

为了获得与物理仿真完全一致的流畅动画,我们使用了一个AnimationController ,并将其连接到PhysicsWorld 上的onFrameDone 信号。这样,如果有任何帧丢失导致仿真速度减慢,动画也会相应减慢。我们使用带有四个NumberAnimationSequentialAnimation 来前后移动最左和最右的圆环。这是动画的 QML 代码:

Connections {
    target: physicsWorld
    property real totalAnimationTime: 12000
    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 {
        NumberAnimation {
            target: leftLink
            property: "kinematicPosition.x"
            to: 3 * viewport.ringDistance
            from: -6 * viewport.ringDistance
            easing.type: Easing.InOutCubic
            duration: 1000
        }
        NumberAnimation {
            target: leftLink
            property: "kinematicPosition.x"
            from: 3 * viewport.ringDistance
            to: -6 * viewport.ringDistance
            easing.type: Easing.InOutCubic
            duration: 1000
        }
        NumberAnimation {
            target: rightLink
            property: "kinematicPosition.x"
            to: -3 * viewport.ringDistance
            from: 6 * viewport.ringDistance
            easing.type: Easing.InOutCubic
            duration: 1000
        }
        NumberAnimation {
            target: rightLink
            property: "kinematicPosition.x"
            from: -3 * viewport.ringDistance
            to: 6 * viewport.ringDistance
            easing.type: Easing.InOutCubic
            duration: 1000
        }
    }
}

有趣的是网格和胶囊链接文件中的内容。让我们逐一查看。

DynamicRigidBody {
    scale: Qt.vector3d(100, 100, 100)
    property color color: "white"
    PrincipledMaterial {
        id: _material
        baseColor: color
        metalness: 1.0
        roughness: 0.5
    }

    Model {
        source: "meshes/ring.mesh"
        materials: [_material]
    }

    collisionShapes: [
        ConvexMeshShape {
            source: "meshes/segmentedRing_001.mesh"
        },
        ConvexMeshShape {
            source: "meshes/segmentedRing_002.mesh"
        },
        ConvexMeshShape {
            source: "meshes/segmentedRing_003.mesh"
        },
        ConvexMeshShape {
            source: "meshes/segmentedRing_004.mesh"
        },
        ConvexMeshShape {
            source: "meshes/segmentedRing_005.mesh"
        },
        ConvexMeshShape {
            source: "meshes/segmentedRing_006.mesh"
        },
        ConvexMeshShape {
            source: "meshes/segmentedRing_007.mesh"
        },
        ConvexMeshShape {
            source: "meshes/segmentedRing_008.mesh"
        },
        ConvexMeshShape {
            source: "meshes/segmentedRing_009.mesh"
        },
        ConvexMeshShape {
            source: "meshes/segmentedRing_010.mesh"
        },
        ConvexMeshShape {
            source: "meshes/segmentedRing_011.mesh"
        },
        ConvexMeshShape {
            source: "meshes/segmentedRing_012.mesh"
        }
    ]
}

网格链接是一个具有模型和材质的动态刚体。模型从网格文件中加载网格。我们还有一个碰撞形状列表,这些形状组合在一起形成一个复合形状,用于碰撞检测。每个形状都是从源文件中加载网格的凸面网格形状。凸面形状基本上是指形状内任意两点之间的连线始终位于形状内部的形状。

如果我们在调试模式下仔细观察,就会发现碰撞形状是如何形成复合碰撞形状的:

DynamicRigidBody {
    property real len: 170
    property real w: 17
    PrincipledMaterial {
        id: material3
        baseColor: "yellow"
        metalness: 1.0
        roughness: 0.5
    }
    Node {
        opacity: 1
        Model {
            materials: material3
            source: "#Cylinder"
            scale: Qt.vector3d(w / 100, len / 100, w / 100)
            eulerRotation.z: 90
            y: -len / 2
        }
        Model {
            materials: material3
            source: "#Cylinder"
            scale: Qt.vector3d(w / 100, len / 100, w / 100)
            eulerRotation.z: 90
            y: len / 2
        }
        Model {
            materials: material3
            source: "#Cylinder"
            scale: Qt.vector3d(w / 100, len / 100, w / 100)
            x: len / 2
        }
        Model {
            materials: material3
            source: "#Cylinder"
            scale: Qt.vector3d(w / 100, len / 100, w / 100)
            x: -len / 2
        }
        Model {
            materials: material3
            source: "#Sphere"
            scale: Qt.vector3d(w / 100, w / 100, w / 100)
            x: -len / 2
            y: -len / 2
        }
        Model {
            materials: material3
            source: "#Sphere"
            scale: Qt.vector3d(w / 100, w / 100, w / 100)
            x: -len / 2
            y: len / 2
        }
        Model {
            materials: material3
            source: "#Sphere"
            scale: Qt.vector3d(w / 100, w / 100, w / 100)
            x: len / 2
            y: -len / 2
        }
        Model {
            materials: material3
            source: "#Sphere"
            scale: Qt.vector3d(w / 100, w / 100, w / 100)
            x: len / 2
            y: len / 2
        }
    }
    collisionShapes: [
        CapsuleShape {
            y: -len / 2
            height: len
            diameter: w
        },
        CapsuleShape {
            y: len / 2
            height: len
            diameter: w
        },
        CapsuleShape {
            x: -len / 2
            eulerRotation.z: 90
            height: len
            diameter: w
        },
        CapsuleShape {
            x: len / 2
            eulerRotation.z: 90
            height: len
            diameter: w
        }
    ]
}

胶囊连接体是一种动态刚体,有些模型采用相同的材料。该链接由多个圆柱体和球体组成。与网格链接一样,我们也有一个碰撞形状列表。这次,每个形状都是一个胶囊形状。

如果我们在调试模式下仔细观察,就会发现碰撞形状是如何形成复合碰撞形状的。

文件:

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