本文目录
- 前言
- 1、RigidVehicle
- 1.1 概念
- 1.2 RigidVehicle的创建与使用
- 1.3 RigidVehicle的特性与应用
- 2、前置代码准备
- 2.1 代码
- 2.2 效果
- 3、RigidVehicle结合three的使用
- 3.1 代码
- 3.1.2 效果
- 3.2 控制车子移动
- 3.2.1 效果
- 4、完整代码
前言
在物理引擎与三维图形渲染技术日益融合的今天,
Cannon-ES
作为一款轻量级的JavaScript
物理引擎,为开发者提供了强大的物理模拟功能。其中,RigidVehicle
模块更是为车辆动力学模拟提供了强有力的支持。本文旨在深入探讨Cannon-ES
中的RigidVehicle
,从基本概念到创建使用,再到结合Three.js
实现车辆的三维动态模拟,一步步引导读者掌握这一技术。
通过本文的学习,读者将不仅能够理解RigidVehicle
的概念和特性,还能掌握其在网页开发中的实际应用。我们将通过丰富的代码示例和详细的效果展示,让读者直观地看到RigidVehicle
在车辆模拟中的强大功能。无论是游戏开发、虚拟仿真还是教育演示,RigidVehicle
都能成为你手中的得力助手。让我们一起踏上这段探索之旅吧!
1、RigidVehicle
1.1 概念
RigidVehicle
是Cannon-es.js
中用于模拟具有物理效果的车辆的对象。它基于刚体动力学原理,通过模拟车辆的悬挂系统、车轮与地面的接触以及车辆的运动学特性,来实现逼真的物理效果。
1.2 RigidVehicle的创建与使用
- 创建物理世界:
在使用RigidVehicle
之前,首先需要创建一个物理世界(CANNON.World
),并设置重力加速度等参数。 - 定义车身刚体:
车身刚体(chassisBody
)是RigidVehicle
的基础,它代表了车辆的主体部分。车身刚体通常是一个具有一定质量和形状的刚体。 - 创建
RigidVehicle
对象:
使用Cannon-es.js
提供的RigidVehicle
构造函数或相关方法,可以创建一个RigidVehicle
对象。在创建过程中,需要传入车身刚体以及其他相关参数,如车辆的轴向索引等。 - 添加车轮:
车轮是RigidVehicle
的重要组成部分。在创建RigidVehicle
对象后,需要为车辆添加车轮。车轮的添加过程包括定义车轮的形状、质量、连接点等参数,并将车轮添加到车辆上。 - 设置车轮的碰撞和物理属性:
为了模拟车轮与地面的碰撞以及车轮的物理行为,需要为车轮设置碰撞材质(CANNON.Material
)和接触材质(CANNON.ContactMaterial
)。这些材质决定了车轮与地面之间的摩擦系数、反弹系数等物理参数。 - 推进物理模拟:
在每一帧中调用物理世界的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.offsetWidthconst 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.05control.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)// threejsconst 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})// 创建轮子1const 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)// 创建轮子2const 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)// 创建轮子3const 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)// 创建轮子4const 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轮子1const wheelMesh1 = new THREE.Mesh(new THREE.SphereGeometry(1.5, 20, 20),new THREE.MeshBasicMaterial({color: 0x898220}))scene.add(wheelMesh1)meshes.push(wheelMesh1)// threejs轮子2const wheelMesh2 = new THREE.Mesh(new THREE.SphereGeometry(1.5, 20, 20),new THREE.MeshBasicMaterial({color: 0x898220}))scene.add(wheelMesh2)meshes.push(wheelMesh2)// threejs轮子3const wheelMesh3 = new THREE.Mesh(new THREE.SphereGeometry(1.5, 20, 20),new THREE.MeshBasicMaterial({color: 0x898220}))scene.add(wheelMesh3)meshes.push(wheelMesh3)// threejs轮子4const 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.offsetWidthconst 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)// threejsconst 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})// 创建轮子1const 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)// 创建轮子2const 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)// 创建轮子3const 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)// 创建轮子4const 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轮子1const 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轮子2const wheelMesh2 = new THREE.Mesh(new THREE.SphereGeometry(1.5, 20, 20),wheelMaterial)scene.add(wheelMesh2)meshes.push(wheelMesh2)// threejs轮子3const wheelMesh3 = new THREE.Mesh(new THREE.SphereGeometry(1.5, 20, 20),wheelMaterial)scene.add(wheelMesh3)meshes.push(wheelMesh3)// threejs轮子4const 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.05control.enableDamping = true// 渲染循环动画function animate() {// 在这里我们创建了一个使渲染器能够在每次屏幕刷新时对场景进行绘制的循环(在大多数屏幕上,刷新率一般是60次/秒)requestAnimationFrame(animate)updatePhysic()// 更新控制器。如果没在动画里加上,那必须在摄像机的变换发生任何手动改变后调用control.update()renderer.render(scene, camera)}// 执行动画animate()
})</script>
<style scoped>
.cannonDemo {width: 100vw;height: 100vh;
}
</style>
在学习的路上,如果你觉得本文对你有所帮助的话,那就请关注点赞评论三连吧,谢谢,你的肯定是我写博的另一个支持。