Cannon Tutorial 物理引擎
cannon.js引入
1 | npm install cannon-es |
模块引入:
1 | import * as CANNON from 'cannon-es' |
World容器与Body实体
cannon.js必须要创建一个World实例,
相当于ThreeJs中必须存在一个Scene实例一样,
World实例用于管理所有物体、约束、碰撞等物理元素
1 | const world = new CANNON.World({ |
CANNON.Body表示某个物体所对应的物理属性,
CANNON.Body和Three.Mesh组合在一起,构成一个具有物理属性的物体
- CANNON 里
- Mesh 表
1 | const planeBody = new CANNON.Body({ |
将Body添加到world中之后,
需要根据body每帧计算的值更新对应mesh的属性值:
1 | const stepTime = 1/60 |
根据这段代码,plane是一个质量为1的无限延长的平面,
在向下的重力作用下,plane将会下坠:
物体碰撞
我们将平面水平放置(沿x轴旋转90度),作为静态物体,
并且将平面的尺寸设置为30*30:
1 | const planeBody = new CANNON.Body({ |
创建一个方形物体:
(注意创建方形物体时,vec3的属性表示距物体中心点的位置)
1 | const boxGeo = new THREE.BoxGeometry(3, 3, 3) |
再创建一个球体:
1 | const sphereGeo = new THREE.SphereGeometry(3) |
线性阻尼值的范围在0-1之间,
值越大代表摩擦力越大:
1 | sphereBody.linearDamping = 0.3 |
自旋角速度,自旋阻尼:
1 | boxBody.angularVelocity.set(10,0,0,0) |
顺便一提,自旋角整得太大,方块会被创飞
物理材质
不同物理材质的物体接触时也会有不同的运动轨迹,
也就是给每种物理材质赋予独有的一组运动属性,
下面分别定义了平面、立方体、球体的材质
1 | const planePhysMat = new CANNON.Material() |
将材质和对应的Body联系在一起:
1 | const planeBody = new CANNON.Body({ |
不同材质间相互作用,才能赋予运动以独特的物理属性,
因此需要将不同的材质联系起来,
下面的代码将立方体与平面的摩擦力设置为0,
将球体与平面的弹跳系数设置为0.9
1 | const planeBoxContactMat = new CANNON.ContactMaterial( |
GLTF模型的碰撞
这个部分踩坑无数,bug叠bug,难绷
要使物体间产生碰撞效果,首先要为碰撞物创建碰撞网格(Body),
如何对GLTF模型创建碰撞网格,
需要用到 CANNON.Trimesh
我想要实现的效果是:一把钥匙模型从半空掉落到地板上
使用CANNON.Trimesh,需要用到vertex和index两个数组,
这两个数据源从gltf模型上都能取到:
1 | loader.load('assets/key.glb',gltf=>{ |
上面的代码里,将position和index数据直接取了出来,
放入Trimesh中构建实体,
但是还是不行,钥匙直接穿过平面掉落。
chatGPT告诉我,之所以会出这个问题,
是由于使用scale对模型进行缩小,
但物体实际的position坐标依旧没有改变,
我用Trimesh返回的顶点坐标信息创建了一个网格物体,
发现它说的没错,顶点坐标依旧保持gltf模型刚被引入的尺寸:
因此需要对原本的vertex进行手动缩放
1 | loader.load('assets/key.glb',gltf=>{ |
手动缩放后,Shape和物体原本网格应该保持一致,
试了一下,模型还是无限向下掉(shiiiiiiiit)
这时我隐约觉得可能不是我用于构建TriMesh的数据有问题,
于是我引用了一个普通盒子模型的vertex和index,
发现Trimesh果然还是往下掉,和顶点、索引数据无关。
上网搜了一下,发现原来这是CANNON.Trimesh的一个官方bug:
CANNON.Trimesh构建的不规则Shape只能和Plane/Sphere类型的Shape进行碰撞
好吧……于是我把碰撞平面的Shape类型从Box改成了Plane,
终于成功嘞
1 | const planeGeo = new THREE.PlaneGeometry(12,12) |