本页内容

Qt Quick 3D 物理 - 复合形状示例

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

一张风景截图,其中包含多个方形边框和环形图案,它们相互连接在一起

本示例演示了如何使用多个碰撞形状来创建用于碰撞检测的复杂物体。该场景由一个绿色的静态平面和一系列相互连接的连杆组成。初始时,模拟功能处于禁用状态。经过一段时间或当用户按下空格键时,模拟将开始。 此时将开始播放动画,使最左侧和最右侧的连杆水平来回移动。

设置

与往常一样,我们需要添加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 ”,以便我们可以通过动画对其进行控制。由于它是运动学对象,我们需要设置“kinematicPosition ”和“kinematicRotation ”属性。我们通过动画化“kinematicPosition ”属性来对其进行动画控制。其余连杆以实例形式创建,彼此之间留有一定间距。

为了获得完全遵循物理模拟的平滑动画,我们使用了一个AnimationController ,并将它连接到PhysicsWorld 上的onFrameDone 信号。这样,如果出现任何导致模拟变慢的帧丢失,动画也会相应地减慢。我们使用一个SequentialAnimation ,其中包含四个NumberAnimation ,来驱动最左侧和最右侧的环形部件来回移动。以下是该动画的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
        }
    }
}

有趣的部分在于 Mesh 和 Capsule Links 文件内部的实现。让我们分别来看看它们。

DynamicRigidBody {
    id: root
    scale: Qt.vector3d(100, 100, 100)
    property color color: "white"
    PrincipledMaterial {
        id: _material
        baseColor: root.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"
        }
    ]
}

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

如果启用调试模式并仔细观察,碰撞形状就是这样组合成复合碰撞形状的:

DynamicRigidBody {
    id: root
    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(root.w / 100, root.len / 100, root.w / 100)
            eulerRotation.z: 90
            y: -root.len / 2
        }
        Model {
            materials: material3
            source: "#Cylinder"
            scale: Qt.vector3d(root.w / 100, root.len / 100, root.w / 100)
            eulerRotation.z: 90
            y: root.len / 2
        }
        Model {
            materials: material3
            source: "#Cylinder"
            scale: Qt.vector3d(root.w / 100, root.len / 100, root.w / 100)
            x: root.len / 2
        }
        Model {
            materials: material3
            source: "#Cylinder"
            scale: Qt.vector3d(root.w / 100, root.len / 100, root.w / 100)
            x: -root.len / 2
        }
        Model {
            materials: material3
            source: "#Sphere"
            scale: Qt.vector3d(root.w / 100, root.w / 100, root.w / 100)
            x: -root.len / 2
            y: -root.len / 2
        }
        Model {
            materials: material3
            source: "#Sphere"
            scale: Qt.vector3d(root.w / 100, root.w / 100, root.w / 100)
            x: -root.len / 2
            y: root.len / 2
        }
        Model {
            materials: material3
            source: "#Sphere"
            scale: Qt.vector3d(root.w / 100, root.w / 100, root.w / 100)
            x: root.len / 2
            y: -root.len / 2
        }
        Model {
            materials: material3
            source: "#Sphere"
            scale: Qt.vector3d(root.w / 100, root.w / 100, root.w / 100)
            x: root.len / 2
            y: root.len / 2
        }
    }
    collisionShapes: [
        CapsuleShape {
            y: -root.len / 2
            height: root.len
            diameter: root.w
        },
        CapsuleShape {
            y: root.len / 2
            height: root.len
            diameter: root.w
        },
        CapsuleShape {
            x: -root.len / 2
            eulerRotation.z: 90
            height: root.len
            diameter: root.w
        },
        CapsuleShape {
            x: root.len / 2
            eulerRotation.z: 90
            height: root.len
            diameter: root.w
        }
    ]
}

“胶囊连接体”是一个动态刚体,其中包含一些共享相同材质的模型。该连接体由多个圆柱体和球体组成。与“网格连接体”类似,我们这里也有一个碰撞形状列表。不过这次,每个形状都是一个“胶囊形状”。

如果启用调试模式并仔细观察,就会发现碰撞形状正是这样组合成复合碰撞形状的。

一张3D渲染图的截图,显示了两个通过方形框架连接的环及其刚体

文件:

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