自学内容网 自学内容网

Cannon-ES中RigidVehicle的创建与应用:结合Three.js实现车辆动态模拟

前言

在物理引擎与三维图形渲染技术日益融合的今天,Cannon-ES作为一款轻量级的JavaScript物理引擎,为开发者提供了强大的物理模拟功能。其中,RigidVehicle模块更是为车辆动力学模拟提供了强有力的支持。本文旨在深入探讨Cannon-ES中的RigidVehicle,从基本概念到创建使用,再到结合Three.js实现车辆的三维动态模拟,一步步引导读者掌握这一技术。
通过本文的学习,读者将不仅能够理解RigidVehicle的概念和特性,还能掌握其在网页开发中的实际应用。我们将通过丰富的代码示例和详细的效果展示,让读者直观地看到RigidVehicle在车辆模拟中的强大功能。无论是游戏开发、虚拟仿真还是教育演示,RigidVehicle都能成为你手中的得力助手。让我们一起踏上这段探索之旅吧!

1、RigidVehicle

1.1 概念

RigidVehicleCannon-es.js中用于模拟具有物理效果的车辆的对象。它基于刚体动力学原理,通过模拟车辆的悬挂系统、车轮与地面的接触以及车辆的运动学特性,来实现逼真的物理效果。

1.2 RigidVehicle的创建与使用

  1. 创建物理世界:
    在使用RigidVehicle之前,首先需要创建一个物理世界(CANNON.World),并设置重力加速度等参数。
  2. 定义车身刚体:
    车身刚体(chassisBody)是RigidVehicle的基础,它代表了车辆的主体部分。车身刚体通常是一个具有一定质量和形状的刚体。
  3. 创建RigidVehicle对象:
    使用Cannon-es.js提供的RigidVehicle构造函数或相关方法,可以创建一个RigidVehicle对象。在创建过程中,需要传入车身刚体以及其他相关参数,如车辆的轴向索引等。
  4. 添加车轮:
    车轮是RigidVehicle的重要组成部分。在创建RigidVehicle对象后,需要为车辆添加车轮。车轮的添加过程包括定义车轮的形状、质量、连接点等参数,并将车轮添加到车辆上。
  5. 设置车轮的碰撞和物理属性:
    为了模拟车轮与地面的碰撞以及车轮的物理行为,需要为车轮设置碰撞材质(CANNON.Material)和接触材质(CANNON.ContactMaterial)。这些材质决定了车轮与地面之间的摩擦系数、反弹系数等物理参数。
  6. 推进物理模拟:
    在每一帧中调用物理世界的step方法或fixedStep方法,以推进物理模拟。在模拟过程中,RigidVehicle会自动计算车辆的运动状态、车轮与地面的接触力等物理量。

1.3 RigidVehicle的特性与应用

  • 逼真的物理效果:
    RigidVehicle通过模拟车辆的悬挂系统、车轮与地面的接触以及车辆的运动学特性,实现了逼真的物理效果。这使得开发者可以在Web应用中创建具有真实物理行为的车辆模拟。
  • 易于集成:
    Cannon-es.js作为Cannon.js的现代化版本,具有易于集成的特点。它可以与WebGL技术无缝结合,并可以轻松与其他WebGL应用程序(如Three.js)集成。这使得开发者可以在现有的WebGL项目中快速添加物理模拟功能。
    广泛的应用场景:
    RigidVehicle在游戏开发、物理模拟、教育应用等领域具有广泛的应用前景。例如,在游戏开发中,可以使用RigidVehicle来模拟车辆的驾驶和碰撞行为;在物理模拟中,可以使用RigidVehicle来研究车辆的动力学特性;在教育应用中,可以使用RigidVehicle来教授物理学和工程学知识。

2、前置代码准备

2.1 代码

<template>
    <canvas ref="cannonDemo" class="cannonDemo">
    </canvas>
</template>

<script setup>
import { onMounted, ref } from "vue"
import * as THREE from 'three'
import * as CANNON from 'cannon-es'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
const cannonDemo = ref('null')

onMounted(() => {
    const cannonDemoDomWidth = cannonDemo.value.offsetWidth
    const cannonDemoDomHeight = cannonDemo.value.offsetHeight

    // 创建场景
    const scene = new THREE.Scene
    // 创建相机
    const camera = new THREE.PerspectiveCamera( // 透视相机
        45, // 视角 角度数
        cannonDemoDomWidth / cannonDemoDomHeight, // 宽高比 占据屏幕
        0.1, // 近平面(相机最近能看到物体)
        1000, // 远平面(相机最远能看到物体)
    )
    camera.position.set(0, 2, 40)
    // 创建渲染器
    const renderer = new THREE.WebGLRenderer({
        antialias: true, // 抗锯齿
        canvas: cannonDemo.value
    })
    // 设置设备像素比
    renderer.setPixelRatio(window.devicePixelRatio)
    // 设置画布尺寸
    renderer.setSize(cannonDemoDomWidth, cannonDemoDomHeight)

    const light = new THREE.AmbientLight(0x404040, 200); // 柔和的白光
    scene.add(light);

    let meshes = []
    let phyMeshes = []
    const physicsWorld = new CANNON.World()
    // 设置y轴重力
    physicsWorld.gravity.set(0, -9.82, 0)
    
    const planeShap = new CANNON.Plane()
    const planeBody = new CANNON.Body({
        shape: planeShap,
        mass: 0,
        type: CANNON.BODY_TYPES.STATIC,
        position: new CANNON.Vec3(0,0,0)
    })
    planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1,0,0), Math.PI/2)
    physicsWorld.addBody(planeBody)
    phyMeshes.push(planeBody)

    const planeGeometry = new THREE.PlaneGeometry(100,100)
    const planeMaterial = new THREE.MeshBasicMaterial({color: 0xE0E0E0, side: THREE.DoubleSide})

    const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial)
    scene.add(planeMesh)
    meshes.push(planeMesh)


    const axesHelper = new THREE.AxesHelper(30);
    scene.add(axesHelper);

    const updatePhysic = () => { // 因为这是实时更新的,所以需要放到渲染循环动画animate函数中
        physicsWorld.step(1 / 60)
        for (let i = 0; i < phyMeshes.length; i++) {
            meshes[i].position.copy(phyMeshes[i].position)
            meshes[i].quaternion.copy(phyMeshes[i].quaternion)
        }
    }

    // 控制器
    const control = new OrbitControls(camera, renderer.domElement)
    // 开启阻尼惯性,默认值为0.05
    control.enableDamping = true

    // 渲染循环动画
    function animate() {
        // 在这里我们创建了一个使渲染器能够在每次屏幕刷新时对场景进行绘制的循环(在大多数屏幕上,刷新率一般是60次/秒)
        requestAnimationFrame(animate)
        updatePhysic()
        // 更新控制器。如果没在动画里加上,那必须在摄像机的变换发生任何手动改变后调用
        control.update()
        renderer.render(scene, camera)
    }

    // 执行动画
    animate()
})

</script>
<style scoped>
.cannonDemo {
    width: 100vw;
    height: 100vh;
}
</style>

2.2 效果

我们只是创建了一个刚体地板,如下图所示:
请添加图片描述


3、RigidVehicle结合three的使用

3.1 代码

创建车身及车轮代码如下:

    // 创建车身
    const chassisShape = new CANNON.Box(new CANNON.Vec3(5, 0.5, 2))
    const chassisBody = new CANNON.Body({
        mass: 1,
        shape: chassisShape,
    })
    chassisBody.position.set(0, 5, 0)
    // physicsWorld.addBody(chassisBody)
    phyMeshes.push(chassisBody)

    // threejs
    const chassisMesh = new THREE.Mesh(
        new THREE.BoxGeometry(10, 1, 4),
        new THREE.MeshBasicMaterial({
            color: 0xE066FF
        })
    )
    scene.add(chassisMesh)
    meshes.push(chassisMesh)

    // 创建刚性车子
    const vehicle = new CANNON.RigidVehicle({
        chassisBody
    })

    // 创建轮子1
    const wheelShape = new CANNON.Sphere(1.5)
    const wheelBody1 = new CANNON.Body({
        mass: 1,
        shape: wheelShape
    })
    vehicle.addWheel({
        body: wheelBody1,
        position: new CANNON.Vec3(-4, -0.5, 3.5),
        direction: new CANNON.Vec3(0, -1, 0)
    })
    // physicsWorld.addBody(wheelBody1)
    phyMeshes.push(wheelBody1)

    // 创建轮子2
    const wheelBody2 = new CANNON.Body({
        mass: 1,
        shape: wheelShape
    })
    vehicle.addWheel({
        body: wheelBody2,
        position: new CANNON.Vec3(4, -0.5, 3.5),
        direction: new CANNON.Vec3(0, -1, 0)
    })
    // physicsWorld.addBody(wheelBody2)
    phyMeshes.push(wheelBody2)

    // 创建轮子3
    const wheelBody3 = new CANNON.Body({
        mass: 1,
        shape: wheelShape
    })
    vehicle.addWheel({
        body: wheelBody3,
        position: new CANNON.Vec3(4, -0.5, -3.5),
        direction: new CANNON.Vec3(0, -1, 0)
    })
    // physicsWorld.addBody(wheelBody3)
    phyMeshes.push(wheelBody3)

    // 创建轮子4
    const wheelBody4 = new CANNON.Body({
        mass: 1,
        shape: wheelShape
    })
    vehicle.addWheel({
        body: wheelBody4,
        position: new CANNON.Vec3(-4, -0.5, -3.5),
        direction: new CANNON.Vec3(0, -1, 0)
    })
    // physicsWorld.addBody(wheelBody4)
    phyMeshes.push(wheelBody4)

    // threejs轮子1
    const wheelMesh1 = new THREE.Mesh(
        new THREE.SphereGeometry(1.5, 20, 20),
        new THREE.MeshBasicMaterial({
            color: 0x898220
        })
    )
    scene.add(wheelMesh1)
    meshes.push(wheelMesh1)

    // threejs轮子2
    const wheelMesh2 = new THREE.Mesh(
        new THREE.SphereGeometry(1.5, 20, 20),
        new THREE.MeshBasicMaterial({
            color: 0x898220
        })
    )
    scene.add(wheelMesh2)
    meshes.push(wheelMesh2)

    // threejs轮子3
    const wheelMesh3 = new THREE.Mesh(
        new THREE.SphereGeometry(1.5, 20, 20),
        new THREE.MeshBasicMaterial({
            color: 0x898220
        })
    )
    scene.add(wheelMesh3)
    meshes.push(wheelMesh3)

    // threejs轮子4
    const wheelMesh4 = new THREE.Mesh(
        new THREE.SphereGeometry(1.5, 20, 20),
        new THREE.MeshBasicMaterial({
            color: 0x898220
        })
    )
    scene.add(wheelMesh4)
    meshes.push(wheelMesh4)

    // 将车子的约束添加到世界里
    vehicle.addToWorld(physicsWorld)

3.1.2 效果

请添加图片描述

3.2 控制车子移动

写入监听事件,代码如下:

    window.addEventListener('keydown', (event) => {
        if (event.key == 'w') {
            vehicle.setWheelForce(50, 0)
            vehicle.setWheelForce(50, 3)

        }
        if (event.key == 's') {
            vehicle.setWheelForce(-50, 0)
            vehicle.setWheelForce(-50, 3)

        }
        if (event.key == 'a') {
            vehicle.setSteeringValue(Math.PI/4, 0)
            vehicle.setSteeringValue(Math.PI/4, 3)

        }
        if (event.key == 'd') {
            vehicle.setSteeringValue(-Math.PI/4, 0)
            vehicle.setSteeringValue(-Math.PI/4, 3)

        }
    })
    window.addEventListener('keyup', (event) => {
        if (event.key == 'w') {
            vehicle.setWheelForce(0, 0)
            vehicle.setWheelForce(0, 1)

        }
        if (event.key == 's') {
            vehicle.setWheelForce(0, 0)
            vehicle.setWheelForce(0, 1)

        }
        if (event.key == 'a') {
            vehicle.setSteeringValue(0, 0)
            vehicle.setSteeringValue(0, 3)

        }
        if (event.key == 'd') {
            vehicle.setSteeringValue(0, 0)
            vehicle.setSteeringValue(0, 3)

        }
    })

3.2.1 效果

请添加图片描述


4、完整代码

最后给出完整代码如下:

<template>
    <canvas ref="cannonDemo" class="cannonDemo">
    </canvas>
</template>

<script setup>
import { onMounted, ref } from "vue"
import * as THREE from 'three'
import * as CANNON from 'cannon-es'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
const cannonDemo = ref('null')

onMounted(() => {
    const cannonDemoDomWidth = cannonDemo.value.offsetWidth
    const cannonDemoDomHeight = cannonDemo.value.offsetHeight

    // 创建场景
    const scene = new THREE.Scene
    // 创建相机
    const camera = new THREE.PerspectiveCamera( // 透视相机
        45, // 视角 角度数
        cannonDemoDomWidth / cannonDemoDomHeight, // 宽高比 占据屏幕
        0.1, // 近平面(相机最近能看到物体)
        1000, // 远平面(相机最远能看到物体)
    )
    camera.position.set(0, 2, 40)
    // 创建渲染器
    const renderer = new THREE.WebGLRenderer({
        antialias: true, // 抗锯齿
        canvas: cannonDemo.value
    })
    // 设置设备像素比
    renderer.setPixelRatio(window.devicePixelRatio)
    // 设置画布尺寸
    renderer.setSize(cannonDemoDomWidth, cannonDemoDomHeight)

    const light = new THREE.AmbientLight(0x404040, 200); // 柔和的白光
    scene.add(light);

    let meshes = []
    let phyMeshes = []
    const physicsWorld = new CANNON.World()
    // 设置y轴重力
    physicsWorld.gravity.set(0, -9.82, 0)

    const planeShap = new CANNON.Plane()
    const planeBody = new CANNON.Body({
        shape: planeShap,
        mass: 0,
        type: CANNON.BODY_TYPES.STATIC,
        position: new CANNON.Vec3(0, 0, 0)
    })
    planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2)
    physicsWorld.addBody(planeBody)
    phyMeshes.push(planeBody)

    const planeGeometry = new THREE.PlaneGeometry(100, 100)
    const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xE0E0E0, side: THREE.DoubleSide })

    const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial)
    scene.add(planeMesh)
    meshes.push(planeMesh)

    // 创建车身
    const chassisShape = new CANNON.Box(new CANNON.Vec3(5, 0.5, 2))
    const chassisBody = new CANNON.Body({
        mass: 20,
        shape: chassisShape,
    })
    chassisBody.position.set(0, 5, 0)
    // physicsWorld.addBody(chassisBody)
    phyMeshes.push(chassisBody)

    // threejs
    const chassisMesh = new THREE.Mesh(
        new THREE.BoxGeometry(10, 1, 4),
        new THREE.MeshBasicMaterial({
            color: 0xE066FF
        })
    )
    scene.add(chassisMesh)
    meshes.push(chassisMesh)

    // 创建刚性车子
    const vehicle = new CANNON.RigidVehicle({
        chassisBody
    })

    // 创建轮子1
    const wheelShape = new CANNON.Sphere(1.5)
    const wheelBody1 = new CANNON.Body({
        mass: 1,
        shape: wheelShape
    })
    vehicle.addWheel({
        body: wheelBody1,
        position: new CANNON.Vec3(-4, -0.5, 3.5),
        direction: new CANNON.Vec3(0, -1, 0)
    })
    // physicsWorld.addBody(wheelBody1)
    phyMeshes.push(wheelBody1)

    // 创建轮子2
    const wheelBody2 = new CANNON.Body({
        mass: 1,
        shape: wheelShape
    })
    vehicle.addWheel({
        body: wheelBody2,
        position: new CANNON.Vec3(4, -0.5, 3.5),
        direction: new CANNON.Vec3(0, -1, 0)
    })
    // physicsWorld.addBody(wheelBody2)
    phyMeshes.push(wheelBody2)

    // 创建轮子3
    const wheelBody3 = new CANNON.Body({
        mass: 1,
        shape: wheelShape
    })
    vehicle.addWheel({
        body: wheelBody3,
        position: new CANNON.Vec3(4, -0.5, -3.5),
        direction: new CANNON.Vec3(0, -1, 0)
    })
    // physicsWorld.addBody(wheelBody3)
    phyMeshes.push(wheelBody3)

    // 创建轮子4
    const wheelBody4 = new CANNON.Body({
        mass: 1,
        shape: wheelShape
    })
    vehicle.addWheel({
        body: wheelBody4,
        position: new CANNON.Vec3(-4, -0.5, -3.5),
        direction: new CANNON.Vec3(0, -1, 0)
    })
    // physicsWorld.addBody(wheelBody4)
    phyMeshes.push(wheelBody4)

    // threejs轮子1
    const wheelMaterial = new THREE.MeshBasicMaterial({
            color: 0x898220,
            wireframe: true
        })
    const wheelMesh1 = new THREE.Mesh(
        new THREE.SphereGeometry(1.5, 20, 20),
        wheelMaterial
    )
    scene.add(wheelMesh1)
    meshes.push(wheelMesh1)

    // threejs轮子2
    const wheelMesh2 = new THREE.Mesh(
        new THREE.SphereGeometry(1.5, 20, 20),
        wheelMaterial
    )
    scene.add(wheelMesh2)
    meshes.push(wheelMesh2)

    // threejs轮子3
    const wheelMesh3 = new THREE.Mesh(
        new THREE.SphereGeometry(1.5, 20, 20),
        wheelMaterial
    )
    scene.add(wheelMesh3)
    meshes.push(wheelMesh3)

    // threejs轮子4
    const wheelMesh4 = new THREE.Mesh(
        new THREE.SphereGeometry(1.5, 20, 20),
        wheelMaterial
    )
    scene.add(wheelMesh4)
    meshes.push(wheelMesh4)

    // 将车子的约束添加到世界里
    vehicle.addToWorld(physicsWorld)

    window.addEventListener('keydown', (event) => {
        if (event.key == 'w') {
            vehicle.setWheelForce(50, 0)
            vehicle.setWheelForce(50, 3)

        }
        if (event.key == 's') {
            vehicle.setWheelForce(-50, 0)
            vehicle.setWheelForce(-50, 3)

        }
        if (event.key == 'a') {
            vehicle.setSteeringValue(Math.PI/4, 0)
            vehicle.setSteeringValue(Math.PI/4, 3)

        }
        if (event.key == 'd') {
            vehicle.setSteeringValue(-Math.PI/4, 0)
            vehicle.setSteeringValue(-Math.PI/4, 3)

        }
    })
    window.addEventListener('keyup', (event) => {
        if (event.key == 'w') {
            vehicle.setWheelForce(0, 0)
            vehicle.setWheelForce(0, 1)

        }
        if (event.key == 's') {
            vehicle.setWheelForce(0, 0)
            vehicle.setWheelForce(0, 1)

        }
        if (event.key == 'a') {
            vehicle.setSteeringValue(0, 0)
            vehicle.setSteeringValue(0, 3)

        }
        if (event.key == 'd') {
            vehicle.setSteeringValue(0, 0)
            vehicle.setSteeringValue(0, 3)

        }
    })

    const axesHelper = new THREE.AxesHelper(30);
    scene.add(axesHelper);

    const updatePhysic = () => { // 因为这是实时更新的,所以需要放到渲染循环动画animate函数中
        physicsWorld.step(1 / 60)
        for (let i = 0; i < phyMeshes.length; i++) {
            meshes[i].position.copy(phyMeshes[i].position)
            meshes[i].quaternion.copy(phyMeshes[i].quaternion)
        }
    }

    // 控制器
    const control = new OrbitControls(camera, renderer.domElement)
    // 开启阻尼惯性,默认值为0.05
    control.enableDamping = true

    // 渲染循环动画
    function animate() {
        // 在这里我们创建了一个使渲染器能够在每次屏幕刷新时对场景进行绘制的循环(在大多数屏幕上,刷新率一般是60次/秒)
        requestAnimationFrame(animate)
        updatePhysic()
        // 更新控制器。如果没在动画里加上,那必须在摄像机的变换发生任何手动改变后调用
        control.update()
        renderer.render(scene, camera)
    }

    // 执行动画
    animate()
})

</script>
<style scoped>
.cannonDemo {
    width: 100vw;
    height: 100vh;
}
</style>

在学习的路上,如果你觉得本文对你有所帮助的话,那就请关注点赞评论三连吧,谢谢,你的肯定是我写博的另一个支持。


原文地址:https://blog.csdn.net/weixin_44103733/article/details/142685130

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!