Cannon-es.js之HingeConstraint铰链约束案例
本文目录
- 前言
- 最终效果
- 推力效果
- 马达效果
- 1、HingeConstraint
- 1.1 概念
- 1.2 功能特点
- 1.3 使用场景
- 2、前置代码准备
- 2.1 代码
- 2.2 效果
- 3、HingeConstraint的使用
- 3.1 代码
- 3.2 效果
- 3.3 点击生成力
- 3.4 马达效果
- 4、完整代码
前言
在3D物理引擎的领域中,
cannon-es
以其高效、轻量级和易于集成的特性,成为了WebGL
环境中实现逼真物理模拟的重要工具。今天,我们将聚焦于cannon-es
中的HingeConstraint
,这一强大的铰链约束功能。
HingeConstraint
允许我们模拟两个刚体之间的铰链连接,从而实现复杂的旋转和交互效果。无论是模拟门的开关、杠杆的运作,还是创建机械臂的关节,HingeConstraint
都能提供精确且高效的物理模拟。
在本文中,我们将通过前置代码准备、HingeConstraint
的具体使用以及完整代码展示,带你领略这一约束的神奇魅力。我们还将探索如何通过点击生成力以及添加马达效果,进一步丰富物理模拟的交互性和趣味性。无论你是物理引擎的新手,还是经验丰富的开发者,本文都将为你提供一份详尽的指南,帮助你更好地掌握cannon-es
中的HingeConstraint
。
最终效果
推力效果
马达效果
1、HingeConstraint
1.1 概念
HingeConstraint
,即铰链约束,在cannon-es
物理引擎中用于模拟两个刚体之间的铰链连接。这种约束允许两个刚体在一定范围内相对旋转,类似于现实中的门轴或铰链。
1.2 功能特点
- 旋转限制:
HingeConstraint
可以限制两个刚体之间的相对旋转角度,从而模拟出类似于门、杠杆等物体的运动效果。 - 力传递:通过铰链约束,可以将力从一个刚体传递到另一个刚体,实现复杂的物理交互效果。
- 参数可调:在
Cannon-es.js
中,HingeConstraint
提供了多个参数来调整约束的行为,如旋转轴、旋转范围、摩擦力等。
1.3 使用场景
- 门和窗:模拟门或窗的开启和关闭效果,通过调整旋转轴和旋转范围来实现。
- 杠杆和吊桥:模拟杠杆或吊桥的运动效果,通过铰链约束实现物体的平衡和旋转。
- 机械臂:在机器人或机械臂的模拟中,使用铰链约束来连接各个关节,实现复杂的运动轨迹。
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, 30)// 创建渲染器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 boxShape = new CANNON.Box(new CANNON.Vec3(0.5, 2.5, 2.5))const boxBody = new CANNON.Body({shape: boxShape,mass: 0,type: CANNON.BODY_TYPES.STATIC,position: new CANNON.Vec3(0, 5, 0)})physicsWorld.addBody(boxBody)phyMeshes.push(boxBody)const boxGeometry = new THREE.BoxGeometry(1, 5, 5)const boxMaterial = new THREE.MeshBasicMaterial({color: 0xff0000})const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial)scene.add(boxMesh)meshes.push(boxMesh)const boxShape1 = new CANNON.Box(new CANNON.Vec3(0.5, 2.5, 2.5))const boxBody1 = new CANNON.Body({shape: boxShape1,position: new CANNON.Vec3(0, -1, 0)})physicsWorld.addBody(boxBody1)phyMeshes.push(boxBody1)const boxGeometry1 = new THREE.BoxGeometry(1, 5, 5)const boxMaterial1 = new THREE.MeshBasicMaterial({color: 0x0000ff, wireframe: true})const boxMesh1 = new THREE.Mesh(boxGeometry1, boxMaterial1)scene.add(boxMesh1)meshes.push(boxMesh1)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、HingeConstraint的使用
3.1 代码
// 创建铰链约束const hingeConstraint = new CANNON.HingeConstraint(boxBody, boxBody1, {pivotA: new CANNON.Vec3(0, -3, 0),pivotB: new CANNON.Vec3(0, 3, 0),axisA: new CANNON.Vec3(0, 0, 1),axisB: new CANNON.Vec3(0, 0, 1),collideConnected: true // 两个物体是否会碰撞})physicsWorld.addConstraint(hingeConstraint)
3.2 效果
可以看到,我们添加铰链后蓝色线框方块被拉住不会往下掉。
3.3 点击生成力
我们添加点击事件,点一下就给一个x
轴正方的力,代码如下:
window.addEventListener('click', () => {// 点击创建力const force = new CANNON.Vec3(100,0,0)// 应用力boxBody1.applyForce(force, boxBody1.position)})
效果如下:
由于我们设置了collideConnected: true // 两个物体是否会碰撞
所以蓝色线框方块不能够继续往上旋转。
3.4 马达效果
我们将collideConnected: false // 两个物体是否会碰撞
设置成false
,或者直接删除这行代码默认就是false
,我们再添加马达效果的代码如下:
window.addEventListener('click', () => {// 点击创建力// const force = new CANNON.Vec3(100,0,0)// // 应用力// boxBody1.applyForce(force, boxBody1.position)// 启用马达hingeConstraint.enableMotor()// 设置马达速度hingeConstraint.setMotorSpeed(10)})
效果如下:
可以看到我们点击后蓝色线框方块开始了旋转。我们可以利用这个做成车轮在转动的效果。
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, 30)// 创建渲染器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 boxShape = new CANNON.Box(new CANNON.Vec3(0.5, 2.5, 2.5))const boxBody = new CANNON.Body({shape: boxShape,mass: 0,type: CANNON.BODY_TYPES.STATIC,position: new CANNON.Vec3(0, 5, 0)})physicsWorld.addBody(boxBody)phyMeshes.push(boxBody)const boxGeometry = new THREE.BoxGeometry(1, 5, 5)const boxMaterial = new THREE.MeshBasicMaterial({color: 0xff0000})const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial)scene.add(boxMesh)meshes.push(boxMesh)const boxShape1 = new CANNON.Box(new CANNON.Vec3(0.5, 2.5, 2.5))const boxBody1 = new CANNON.Body({shape: boxShape1,mass: 1,position: new CANNON.Vec3(0, -1, 0)})physicsWorld.addBody(boxBody1)phyMeshes.push(boxBody1)const boxGeometry1 = new THREE.BoxGeometry(1, 5, 5, 5, 5)const boxMaterial1 = new THREE.MeshBasicMaterial({color: 0x0000ff, wireframe: true})const boxMesh1 = new THREE.Mesh(boxGeometry1, boxMaterial1)scene.add(boxMesh1)meshes.push(boxMesh1)// 创建铰链约束const hingeConstraint = new CANNON.HingeConstraint(boxBody, boxBody1, {pivotA: new CANNON.Vec3(0, -3, 0),pivotB: new CANNON.Vec3(0, 3, 0),axisA: new CANNON.Vec3(0, 0, 1),axisB: new CANNON.Vec3(0, 0, 1),collideConnected: false // 两个物体是否会碰撞})physicsWorld.addConstraint(hingeConstraint)window.addEventListener('click', () => {// 点击创建力// const force = new CANNON.Vec3(100,0,0)// // 应用力// boxBody1.applyForce(force, boxBody1.position)// 启用马达hingeConstraint.enableMotor()// 设置马达速度hingeConstraint.setMotorSpeed(10)})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>
在学习的路上,如果你觉得本文对你有所帮助的话,那就请关注点赞评论三连吧,谢谢,你的肯定是我写博的另一个支持。