前言
本篇文章主要介绍下cannon-es的简单使用,cannon-es是一个物理引擎,可以实现一些简单的物理效果,比如重力,碰撞等等。
官方文档
https://pmndrs.github.io/cannon-es/docs/modules.html#BODY_TYPES
基本demo
初始化代码
1 | <template> |
创建three中的平面和小球
1 | // 创建一个球体几何体 |

创建物体世界效果
安装并导入
安装
1 | npm i cannon-es |
导入
1 | import * as CANNON from "cannon-es"; |
创建物体世界
1 | // 初始化物理世界 |
创建物体
1 | // 创建一个球体 |
animate中更新
1 | function animate() { |
cannon材质
材质与摩擦
这里我创建了一个平面,两个立方体,平面设置0.7的friction(摩擦系数),然后给两个小立方体设置了不同的friction(摩擦系数),我将其中一个设为了0,相当于没有摩擦,此时在略微倾斜的平面上就会有一个因为摩擦不动,另外一个因为没有摩擦所以会滑下去。
1 | <template> |
材质与弹性
这里我创建了3个立方体,分别给第一个立方体设置了弹性系数为1,摩擦系数为0,第二个摩擦系数为0,第三个则没设置,然后给接触材质设置了弹性系数为0.1,摩擦系数为0
1 | <template> |
碰撞与事件
碰撞与碰撞组
接下来我们通过一个案例来了解碰撞和碰撞组的概念
基础代码
首先我们还是一样,创建我们的基础代码
1 | <template> |
物理世界代码
接下来,我们和之前一样,进行物理世界的物体创建
导入并初始化
1 | import * as CANNON from "cannon-es"; |
创建一个立方体当作平面使用
- 添加一个物理世界和three世界的立方体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// 设置立方体材质
const boxMaterialCon = new CANNON.Material("boxMaterial");
boxMaterialCon.friction = 0;
// 创建一个物理世界的平面
const planeShape = new CANNON.Box(new CANNON.Vec3(5, 0.1, 5));
// 创建一个刚体
const planeBody = new CANNON.Body({
shape: planeShape,
position: new CANNON.Vec3(0, -0.1, 0),
type: CANNON.Body.STATIC,
material: boxMaterialCon,
});
// 将刚体添加到物理世界
world.addBody(planeBody);
// 创建一个平面几何体
const planeGeometry = new THREE.BoxGeometry(10, 0.2, 10);
// 创建一个平面材质
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
// 创建一个平面网格
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
// 将网格添加到3D场景
scene.add(planeMesh); - animate种添加world每一帧
1
2
3
4
5
6
7function animate() {
let delta = clock.getDelta();
world.step(1 / 60, delta);
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
创建立方体
关联物理世界物体和three物体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let phyMeshes = [];
let meshes = [];
// 渲染
let clock = new THREE.Clock();
function animate() {
let delta = clock.getDelta();
world.step(1 / 60, delta);
for (let i = 0; i < phyMeshes.length; i++) {
meshes[i].position.copy(phyMeshes[i].position);
meshes[i].quaternion.copy(phyMeshes[i].quaternion);
}
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}创建立方体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 创建物理立方体
const boxShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
// 创建一个刚体
const boxBody = new CANNON.Body({
shape: boxShape,
position: new CANNON.Vec3(-2, 0.5, 0),
mass: 1,
material: boxMaterialCon,
});
// 将刚体添加到物理世界
world.addBody(boxBody);
phyMeshes.push(boxBody);
// 创建立方体几何体
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
// 创建立方体材质
const boxMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// 创建立方体网格
const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial);
// 将网格添加到3D场景
scene.add(boxMesh);
meshes.push(boxMesh);
创建小球
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 创建物理球
const sphereShape = new CANNON.Sphere(0.5);
// 创建一个刚体
const sphereBody = new CANNON.Body({
shape: sphereShape,
position: new CANNON.Vec3(0, 0.5, 0),
mass: 1,
material: boxMaterialCon,
});
// 将刚体添加到物理世界
world.addBody(sphereBody);
phyMeshes.push(sphereBody);
// 创建球几何体
const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32);
// 创建球材质
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff });
// 创建球网格
const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
// 将网格添加到3D场景
scene.add(sphereMesh);
meshes.push(sphereMesh);
创建圆柱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 创建物理圆柱体
const cylinderShape = new CANNON.Cylinder(0.5, 0.5, 1, 32);
// 创建一个刚体
const cylinderBody = new CANNON.Body({
shape: cylinderShape,
position: new CANNON.Vec3(2, 0.5, 0),
mass: 1,
material: boxMaterialCon,
});
// 将刚体添加到物理世界
world.addBody(cylinderBody);
phyMeshes.push(cylinderBody);
// 创建圆柱体几何体
const cylinderGeometry = new THREE.CylinderGeometry(0.5, 0.5, 1, 32);
// 创建圆柱体材质
const cylinderMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
// 创建圆柱体网格
const cylinderMesh = new THREE.Mesh(cylinderGeometry, cylinderMaterial);
// 将网格添加到3D场景
scene.add(cylinderMesh);
meshes.push(cylinderMesh);
给立方体添加一个速度
1
2// 设置立方体的初始速度
boxBody.velocity.set(2, 0, 0);设置组别
首先定义一下组别1
2
3
4
5// 设置碰撞组,数值要用2的幂
const GROUP1 = 1;
const GROUP2 = 2;
const GROUP3 = 4;
const GROUP4 = 8;设置我们的平面立方体为组别1,他接受组别1,2,3,4的碰撞
1
2
3
4
5
6
7
8
9const planeBody = new CANNON.Body({
// mass: 0,
shape: planeShape,
position: new CANNON.Vec3(0, -0.1, 0),
type: CANNON.Body.STATIC,
material: boxMaterialCon,
collisionFilterGroup: GROUP1,
collisionFilterMask: GROUP1 | GROUP2 | GROUP3|GROUP4,
});设置我们的立方体为组别4,他接受组别1,2,3,4的碰撞
1
2
3
4
5
6
7
8
9// 创建一个刚体
const boxBody = new CANNON.Body({
shape: boxShape,
position: new CANNON.Vec3(-2, 0.5, 0),
mass: 1,
material: boxMaterialCon,
collisionFilterGroup: GROUP4,
collisionFilterMask: GROUP1 | GROUP2 | GROUP3 | GROUP4,
});设置小球为组别2,他只接受组别1,4的碰撞
1
2
3
4
5
6
7
8const sphereBody = new CANNON.Body({
shape: sphereShape,
position: new CANNON.Vec3(0, 0.5, 0),
mass: 1,
material: boxMaterialCon,
collisionFilterGroup: GROUP2,
collisionFilterMask: GROUP1 | GROUP4,
});设置圆柱为组别3,他只接受组别1,4的碰撞
1
2
3
4
5
6
7
8
9
10
11// 创建物理圆柱体
const cylinderShape = new CANNON.Cylinder(0.5, 0.5, 1, 32);
// 创建一个刚体
const cylinderBody = new CANNON.Body({
shape: cylinderShape,
position: new CANNON.Vec3(2, 0.5, 0),
mass: 1,
material: boxMaterialCon,
collisionFilterGroup: GROUP3,
collisionFilterMask: GROUP1 | GROUP4,
});从上面的代码可以看出来,我们的圆柱和球是不会碰撞的,事实是否是如此呢,请看下面的效果视频
碰撞事件
碰撞监听
这里我们使用collide事件来监听碰撞,使用e.contact.getImpactVelocityAlongNormal()来获取碰撞强度
1 | boxBody.addEventListener("collide", (e) => { |
完整代码
1 | <template> |
碰撞休眠
基于上面的代码添加
1 | // 设置物理世界允许休眠 |

形状组合
组合原理
这里没什么好讲的,主要来说一下组合胶囊体的position的设置
圆柱的高度为1.5,中心位置是(0,0),然后一半的高度就是0.75,所以我们就把球的y值设置为0.75和-0.75
组合完整代码
1 | <template> |
组合效果

模型运用物理引擎
注意:模型无法和立方体碰撞,这是一个bug,等待修复,因此我们暂时使用平面来和模型碰撞
核心代码及讲解
使用CANNON.Trimesh将模型的position和index传进去作为形状即可
1 |
|
完整代码
1 | <template> |
效果

添加力
基础代码
1 | <template> |
此时我们搞出来了一个线框球
applyForce
1 | // 监听点击事件 |
applyLocalForce
为了展示效果的不同我们在基础代码中已经将球进行了反转180度
1 | window.addEventListener("click", () => { |
同样的,我们可以使用applyImpulse方法
applyImpulse
但是需要注意的是他需要乘上我们设置的帧率
1 | sphereBody.applyImpulse( |
applyTorque
添加扭力
1 | sphereBody.applyTorque(new CANNON.Vec3(0, 0, -10)); |
cannon约束
lock约束
基础平面代码
1 | <template> |

添加立方体
1 | // 循环十个并排的立方体 |
添加lock约束
1 | let previousBody; |
解除lock约束
有添加自然可以解除
1 | let previousBody; |
这样就可以接触约束了,效果和没添加约束时一样
point约束
这里保持基础平面代码不变
创建扁平立方体
1 | // 创建扁平立方体 |
添加point约束
这里我说一下参数为什么是这几个数字,首先点到点需要4个参数,分别是两个刚体和两个点,这里我们使用的是同一个刚体,所以第一个参数和第三个参数都是同一个刚体,第二个参数是当前刚体的点,第四个参数是上一个刚体的点,如图所示,我们用的是两个刚体相邻的那四个点位位置对应中心线段的那两个点
1 | let previousBody; |

添加小球撞击测试
1 | window.addEventListener("click", () => { |
距离约束
保持基础平面代码不变
创建小球体
1 | const sphereShape = new CANNON.Sphere(0.5); |

添加距离约束
参数1.2就是他们的距离
1 | if (i > 0) { |

添加小球撞击测试
1 | window.addEventListener("click", () => { |
距离约束实现布料效果
创建一串小球
1 | const rows = 15; |
添加距离约束
1 | for (let i = 0; i < cols; i++) { |
创建小球
这里我设置了球质量为0,这样就不会掉落
1 | // 创建物理球体 |
弹簧约束
保持基础平面代码不变
创建静态球体
1 | // 创建1个固定的静态球体 |

创建立方体
1 | // 创建1个立方体 |

创建弹簧
1 | // 创建1个弹簧拉住立方体 |
破坏约束
保持基础平面代码不变
创建距离约束小球
1 | // 创建15个距离约束的小球 |

点击添加撞击小球
1 | window.addEventListener("click", () => { |
删除约束
1 | // 在每次世界模拟完一个时间步之后,判断约束力度的绝对值大小,如果大于1000,就删除约束 |
效果逼真度
默认是10,可以自己设置高数值,来触发更逼真的物理引擎效果,但是会消耗更多的性能
1 | world.solver.iterations = 10; |
铰链约束
创建物体
1 | // 创建固定的body |

添加铰链约束
这里面的参数就是铰链的位置,可以自己调整pivotA是相对于fixedBody铰链的中心点,pivotB是相对于moveBody铰链的中心点,是3的原因是2.5(半径)+0.5(间隔的一半)得到,axisA是相对于中心点的方向,axisB是相对于中心点的方向
1 | // 创建铰链约束 |

添加力测试铰链
1 |
|
添加马达测试铰链
1 | window.addEventListener("click", () => { |
结语
本篇文章就先到这里,更多内容敬请期待,大家一起做大做强