Cannon-ES中RaycastVehicle的深入探索与实践

前言

在三维物理引擎的世界里,Cannon-ES以其轻量级、高效和易于集成的特点,赢得了众多开发者的青睐。而RaycastVehicle,作为Cannon-ES中用于模拟复杂车辆运动的核心类,更是为开发者提供了强大的车辆动力学模拟功能。从简单的汽车驾驶到复杂的赛车游戏,RaycastVehicle都能轻松应对。
本文将带您深入探索RaycastVehicle的概念、核心特性和应用场景。我们将从理论到实践,逐步揭示RaycastVehicle的奥秘。通过详细的代码示例和效果展示,您将看到RaycastVehicle如何在Cannon-ES的物理世界中模拟出逼真的车辆运动。此外,我们还将探讨如何监听和施加力,以实现更加丰富的车辆交互效果。
无论您是Cannon-ES的新手,还是已经有一定经验的开发者,本文都将为您提供宝贵的参考和启示。让我们一起踏上这段探索之旅,共同领略RaycastVehicle的魅力吧!

1、RaycastVehicle

Cannon-es中,RaycastVehicle是一个重要的概念,它允许开发者在物理世界中模拟复杂的车辆运动。

1.1 概念

RaycastVehicleCannon-es中用于模拟车辆运动的类。它通过使用射线投射(Raycasting)技术来确定车辆与地面的接触点,并据此计算车辆的物理行为。这种技术使得RaycastVehicle能够处理各种复杂的地形和物理交互。

1.2 核心特性

  1. 射线投射技术:RaycastVehicle利用射线投射来确定车辆与地面的接触点。这些射线从车辆底部的多个点发出,并沿着车辆的运动方向投射到地面上。通过检测这些射线与地面的交点,RaycastVehicle能够计算出车辆的支撑点和摩擦力等物理参数。
  2. 多轮支持:RaycastVehicle支持多轮车辆模拟,这意味着它可以处理具有多个轮子的车辆。每个轮子都可以独立地进行射线投射和物理计算,从而更准确地模拟车辆的运动。
  3. 物理行为模拟:RaycastVehicle能够模拟车辆的多种物理行为,包括加速、减速、转向、碰撞等。这些行为都是基于物理原理进行计算的,因此能够呈现出逼真的车辆运动效果。

1.3 应用场景

RaycastVehicleCannon-es中具有广泛的应用场景,特别是在需要模拟车辆运动的3D物理模拟中。以下是一些典型的应用场景:

  • 游戏开发:在游戏开发中,RaycastVehicle可以用于模拟玩家的车辆控制。通过模拟车辆的物理行为,游戏可以提供更加逼真的驾驶体验。
  • 虚拟现实:在虚拟现实应用中,RaycastVehicle可以用于模拟用户在虚拟环境中的车辆驾驶。这可以增强用户的沉浸感和交互体验。
  • 物理模拟:在物理模拟领域,RaycastVehicle可以用于研究和分析车辆在不同条件下的运动特性。这有助于优化车辆设计、提高车辆性能以及确保车辆的安全性。

2、前置代码准备

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

我们也是初始化了整个场景,并且放了平面。


3、RaycastVehicle的使用

3.1 代码

创建悬架车架及轮子,代码如下:

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

    // 创建视图车身
    let chassisMesh = new THREE.Mesh(
        new THREE.BoxGeometry(4,1,2),
        new THREE.MeshBasicMaterial({color: 0x0000ff})
    )
    scene.add(chassisMesh)
    meshes.push(chassisMesh)

    // 创建复杂车架
    const vehicle = new CANNON.RaycastVehicle({
        chassisBody: chassisBody
    })

    // 车轮配置
    const wheelOptions = {
        // 半径
        radius: 0.5,
        // 车轮垂直哪个轴
        directionLocal: new CANNON.Vec3(0, -1, 0),
        // 设置悬架刚度
        suspensionStiffness: 30,
        // 设置悬架的休息长度
        suspensionRestLength: 0.5,
        // 设置车轮的滑动摩擦力
        frictionSlip: 1.5,
        // 悬架拉伸阻尼
        dampingRelaxation: 2.3,
        // 悬架压缩阻尼
        dampingCompression: 4.3,
        // 最大悬架力
        maxSuspensionForce: 100000,
        // 设置最大的悬架变化
        maxSuspensionTravel: 0.3,
        // 设置车轮的转向轴
        axleLocal: new CANNON.Vec3(0,0,1)
    }

    // 添加
    vehicle.addWheel({
        ...wheelOptions,
        // 车轮位置
        chassisConnectionPointLocal: new CANNON.Vec3(-1,0,1)
    })
    vehicle.addWheel({
        ...wheelOptions,
        // 车轮位置
        chassisConnectionPointLocal: new CANNON.Vec3(-1,0,-1)
    })
    vehicle.addWheel({
        ...wheelOptions,
        // 车轮位置
        chassisConnectionPointLocal: new CANNON.Vec3(1,0,1)
    })
    vehicle.addWheel({
        ...wheelOptions,
        // 车轮位置
        chassisConnectionPointLocal: new CANNON.Vec3(1,0,-1)
    })

    vehicle.addToWorld(physicsWorld)

    // 车轮形状
    const wheelShape = new CANNON.Cylinder(0.5,0.5,0.2,20);
    const wheelGeometry = new THREE.CylinderGeometry(0.5,0.5,0.2,20)
    const wheelMaterial = new THREE.MeshBasicMaterial({color: 0x888888})

    let wheelBodies = []
    for(let i = 0; i < vehicle.wheelInfos.length; i++) {
        const wheel = vehicle.wheelInfos[i]
        const cylinderBody = new CANNON.Body({
            mass: 0,
            shape: wheelShape
        })
        cylinderBody.position.copy(wheel.chassisConnectionPointWorld)
        cylinderBody.quaternion.copy(chassisBody.quaternion)
        // physicsWorld.addBody(cylinderBody)
        phyMeshes.push(cylinderBody)
        wheelBodies.push(cylinderBody)

        const cylinderMesh = new THREE.Mesh(wheelGeometry, wheelMaterial)
        cylinderMesh.rotation.x = -Math.PI/2
        const wheelObj = new THREE.Object3D()
        wheelObj.add(cylinderMesh)
        scene.add(wheelObj)
        meshes.push(wheelObj)
    }

physicsWorld.addEventListener('postStep', () => {
    for(let i = 0; i < vehicle.wheelInfos.length; i++) {
        vehicle.updateWheelTransform(i)
        const t = vehicle.wheelInfos[i].worldTransform
        const wheelBody = wheelBodies[i]
        wheelBody.position.copy(t.position)
        wheelBody.quaternion.copy(t.quaternion)

    }
})

3.2 效果

请添加图片描述


4、监听施加力

4.1 代码

我们监听事件以便给车子施加力,代码如下:

window.addEventListener('keydown', (event) => {
        if (event.key == 'w') {
            vehicle.applyEngineForce(-100, 0)
            vehicle.applyEngineForce(-100, 1)

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

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

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

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

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

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

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

        }
    })

4.2 效果

请添加图片描述

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你华还是你华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值