【可视化学习】46-cannon-es的简单使用(一)
发表于:2023-09-26 |

前言

本篇文章主要介绍下cannon-es的简单使用,cannon-es是一个物理引擎,可以实现一些简单的物理效果,比如重力,碰撞等等。

官方文档

https://pmndrs.github.io/cannon-es/docs/modules.html#BODY_TYPES

基本demo

初始化代码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<template>
<div></div>
</template>

<script setup>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";


// 初始化3D场景
const scene = new THREE.Scene();
// 初始化相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 3;

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// 渲染
let clock = new THREE.Clock();
function animate() {
let delta = clock.getDelta();
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}

animate();
</script>

<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

canvas {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
}
</style>

创建three中的平面和小球

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 创建一个球体几何体
const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32);
// 创建一个球体材质
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// 创建一个球体网格
const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
// 将网格添加到3D场景
scene.add(sphereMesh);

// 创建一个平面几何体
// const planeGeometry = new THREE.PlaneGeometry(10, 10);
const planeGeometry = new THREE.BoxGeometry(10, 0.2, 10);
// 创建一个平面材质
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
// 创建一个平面网格
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
// x轴旋转90度
planeMesh.rotation.x = 0.1;
// 将网格添加到3D场景
scene.add(planeMesh);

效果图

创建物体世界效果

安装并导入

安装
1
npm i cannon-es
导入
1
import * as CANNON from "cannon-es";

创建物体世界

1
2
3
4
// 初始化物理世界
const world = new CANNON.World();
// 设置重力
world.gravity.set(0, -9.82, 0);

创建物体

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 sphereShape = new CANNON.Sphere(0.5);
// 创建一个刚体
const sphereBody = new CANNON.Body({
mass: 1,
shape: sphereShape,
position: new CANNON.Vec3(0, 5, 0),
});
// 将刚体添加到物理世界
world.addBody(sphereBody);

// 创建一个物理世界的平面
// const planeShape = new CANNON.Plane();
const planeShape = new CANNON.Box(new CANNON.Vec3(5, 0.1, 5));
// 创建一个刚体
const planeBody = new CANNON.Body({
// mass: 0,
shape: planeShape,
position: new CANNON.Vec3(0, 0, 0),
type: CANNON.Body.STATIC,
});
planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), 0.1);

// 将刚体添加到物理世界
world.addBody(planeBody);

animate中更新

1
2
3
4
5
6
7
8
9
10
11
function animate() {
let delta = clock.getDelta();
world.step(1 / 60, delta);
// 更新球体网格的位置和旋转
sphereMesh.position.copy(sphereBody.position);
sphereMesh.quaternion.copy(sphereBody.quaternion);

controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}

cannon材质

材质与摩擦

这里我创建了一个平面,两个立方体,平面设置0.7的friction(摩擦系数),然后给两个小立方体设置了不同的friction(摩擦系数),我将其中一个设为了0,相当于没有摩擦,此时在略微倾斜的平面上就会有一个因为摩擦不动,另外一个因为没有摩擦所以会滑下去。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
<template>
<div></div>
</template>

<script setup>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as CANNON from "cannon-es";

// 初始化物理世界
const world = new CANNON.World();
// 设置重力
world.gravity.set(0, -9.82, 0);

// 初始化3D场景
const scene = new THREE.Scene();
// 初始化相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 5, 10);

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// 设置立方体材质
const boxMaterialCon = new CANNON.Material("boxMaterial");
boxMaterialCon.friction = 0.7;

// 创建一个物理世界的平面
// const planeShape = new CANNON.Plane();
const planeShape = new CANNON.Box(new CANNON.Vec3(5, 0.1, 5));
// 创建一个刚体
const planeBody = new CANNON.Body({
// mass: 0,
shape: planeShape,
position: new CANNON.Vec3(0, 0, 0),
type: CANNON.Body.STATIC,
material: boxMaterialCon,
});
planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), 0.1);

// 将刚体添加到物理世界
world.addBody(planeBody);

// 创建一个平面几何体
// const planeGeometry = new THREE.PlaneGeometry(10, 10);
const planeGeometry = new THREE.BoxGeometry(10, 0.2, 10);
// 创建一个平面材质
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
// 创建一个平面网格
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
// x轴旋转90度
planeMesh.rotation.x = 0.1;
// 将网格添加到3D场景
scene.add(planeMesh);

let phyMeshes = [];
let meshes = [];

// 创建物理立方体
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(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);

// 创建第二个物理立方体
// const boxShape2 = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
// 设置立方体材质
const boxSlipperyMaterial = new CANNON.Material("boxSlipperyMaterial");
boxSlipperyMaterial.friction = 0;

// 创建一个刚体
const boxBody2 = new CANNON.Body({
shape: boxShape,
position: new CANNON.Vec3(1, 5, 0),
mass: 1,
material: boxSlipperyMaterial,
});
// 将刚体添加到物理世界
world.addBody(boxBody2);
phyMeshes.push(boxBody2);

// 创建立方体几何体
// const boxGeometry2 = new THREE.BoxGeometry(1, 1, 1);
// // 创建立方体材质
// const boxMaterial2 = new THREE.MeshBasicMaterial({ color: 0x0000ff });
// 创建立方体网格
const boxMesh2 = new THREE.Mesh(boxGeometry, boxMaterial);
// 将网格添加到3D场景
scene.add(boxMesh2);
meshes.push(boxMesh2);

// 渲染
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);
}

animate();
</script>

<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

canvas {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
}
</style>

材质与弹性

这里我创建了3个立方体,分别给第一个立方体设置了弹性系数为1,摩擦系数为0,第二个摩擦系数为0,第三个则没设置,然后给接触材质设置了弹性系数为0.1,摩擦系数为0

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
<template>
<div></div>
</template>

<script setup>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as CANNON from "cannon-es";

// 初始化物理世界
const world = new CANNON.World();
// 设置重力
world.gravity.set(0, -9.82, 0);

// 初始化3D场景
const scene = new THREE.Scene();
// 初始化相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 5, 10);

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// 设置立方体材质
const boxMaterialCon = new CANNON.Material("boxMaterial");
boxMaterialCon.friction = 0.7;
boxMaterialCon.restitution = 1;

// 创建一个物理世界的平面
// const planeShape = new CANNON.Plane();
const planeShape = new CANNON.Box(new CANNON.Vec3(5, 0.1, 5));
// 创建一个刚体
const planeBody = new CANNON.Body({
// mass: 0,
shape: planeShape,
position: new CANNON.Vec3(0, 0, 0),
type: CANNON.Body.STATIC,
material: boxMaterialCon,
});
planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), 0.1);

// 将刚体添加到物理世界
world.addBody(planeBody);

// 创建一个平面几何体
// const planeGeometry = new THREE.PlaneGeometry(10, 10);
const planeGeometry = new THREE.BoxGeometry(10, 0.2, 10);
// 创建一个平面材质
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
// 创建一个平面网格
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
// x轴旋转90度
planeMesh.rotation.x = 0.1;
// 将网格添加到3D场景
scene.add(planeMesh);

let phyMeshes = [];
let meshes = [];

// 创建物理立方体
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(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);

// 创建第二个物理立方体
// const boxShape2 = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
// 设置立方体材质
const boxSlipperyMaterial = new CANNON.Material("boxSlipperyMaterial");
boxSlipperyMaterial.friction = 0;

// 创建一个刚体
const boxBody2 = new CANNON.Body({
shape: boxShape,
position: new CANNON.Vec3(1, 5, 0),
mass: 1,
material: boxSlipperyMaterial,
});
// 将刚体添加到物理世界
world.addBody(boxBody2);
phyMeshes.push(boxBody2);

// 创建立方体几何体
// const boxGeometry2 = new THREE.BoxGeometry(1, 1, 1);
// // 创建立方体材质
// const boxMaterial2 = new THREE.MeshBasicMaterial({ color: 0x0000ff });
// 创建立方体网格
const boxMesh2 = new THREE.Mesh(boxGeometry, boxMaterial);
// 将网格添加到3D场景
scene.add(boxMesh2);
meshes.push(boxMesh2);

// 创建第三个物理立方体
// const boxShape3 = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
// 设置立方体材质
const boxBouncyMaterial = new CANNON.Material("boxBouncyMaterial");
// boxBouncyMaterial.friction = 0.1;
// boxBouncyMaterial.restitution = 1;

const boxBody3 = new CANNON.Body({
shape: boxShape,
position: new CANNON.Vec3(2, 5, 0),
mass: 1,
material: boxBouncyMaterial,
});
// 将刚体添加到物理世界
world.addBody(boxBody3);
phyMeshes.push(boxBody3);

// 创建立方体几何体
// const boxGeometry3 = new THREE.BoxGeometry(1, 1, 1);
// // 创建立方体材质
// const boxMaterial3 = new THREE.MeshBasicMaterial({ color: 0xff0000 });
// 创建立方体网格
const boxMesh3 = new THREE.Mesh(boxGeometry, boxMaterial);
// 将网格添加到3D场景
scene.add(boxMesh3);
meshes.push(boxMesh3);

// 定义接触材质
const material3toplane = new CANNON.ContactMaterial(
boxMaterialCon,
boxBouncyMaterial,
{
friction: 0,
restitution: 0.1,
}
);
// 将接触材质添加到物理世界
world.addContactMaterial(material3toplane);

// 渲染
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);
}

animate();
</script>

<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

canvas {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
}
</style>

碰撞与事件

碰撞与碰撞组

接下来我们通过一个案例来了解碰撞和碰撞组的概念

基础代码

首先我们还是一样,创建我们的基础代码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<template>
<div></div>
</template>

<script setup>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

// 初始化3D场景
const scene = new THREE.Scene();
// 初始化相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 5, 10);

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;



// 渲染
let clock = new THREE.Clock();
function animate() {
let delta = clock.getDelta();
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}

animate();
</script>

<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

canvas {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
}
</style>

物理世界代码

接下来,我们和之前一样,进行物理世界的物体创建

导入并初始化
1
2
3
4
5
6
import * as CANNON from "cannon-es";

// 初始化物理世界
const world = new CANNON.World();
// 设置重力
world.gravity.set(0, -9.82, 0);
创建一个立方体当作平面使用
  1. 添加一个物理世界和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);
  2. animate种添加world每一帧
    1
    2
    3
    4
    5
    6
    7
    function animate() {
    let delta = clock.getDelta();
    world.step(1 / 60, delta);
    controls.update();
    renderer.render(scene, camera);
    requestAnimationFrame(animate);
    }
    效果图
创建立方体
  1. 关联物理世界物体和three物体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    let 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);
    }
  2. 创建立方体

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

    效果图

  3. 创建小球

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

    效果图

  4. 创建圆柱

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

    效果图

  5. 给立方体添加一个速度

    1
    2
    // 设置立方体的初始速度
    boxBody.velocity.set(2, 0, 0);
  6. 设置组别
    首先定义一下组别

    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
    9
    const 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
    8
    const 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
2
3
4
5
boxBody.addEventListener("collide", (e) => {
console.log("碰撞了", e);
let impactStrength = e.contact.getImpactVelocityAlongNormal();
console.log("impactStrength", impactStrength);
});

完整代码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
<template>
<div></div>
</template>

<script setup>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as CANNON from "cannon-es";

// 初始化物理世界
const world = new CANNON.World();
// 设置重力
world.gravity.set(0, -9.82, 0);

// 初始化3D场景
const scene = new THREE.Scene();
// 初始化相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 5, 10);

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// 设置碰撞组,数值要用2的幂
const GROUP1 = 1;
const GROUP2 = 2;
const GROUP3 = 4;
const GROUP4 = 8;

// 设置立方体材质
const boxMaterialCon = new CANNON.Material("boxMaterial");
boxMaterialCon.friction = 0;
boxMaterialCon.restitution = 1;

// 创建一个物理世界的平面
// const planeShape = new CANNON.Plane();
const planeShape = new CANNON.Box(new CANNON.Vec3(5, 0.1, 5));
// 创建一个刚体
const 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,
});
// planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), 0.1);

// 将刚体添加到物理世界
world.addBody(planeBody);

// 创建一个平面几何体
// const planeGeometry = new THREE.PlaneGeometry(10, 10);
const planeGeometry = new THREE.BoxGeometry(10, 0.2, 10);
// 创建一个平面材质
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
// 创建一个平面网格
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
// x轴旋转90度
// planeMesh.rotation.x = 0.1;
// 将网格添加到3D场景
scene.add(planeMesh);

let phyMeshes = [];
let meshes = [];

// 创建物理立方体
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, 5, 0),
mass: 1,
material: boxMaterialCon,
collisionFilterGroup: GROUP1,
collisionFilterMask: GROUP1 | GROUP2 | GROUP3 | GROUP4,
});
// 将刚体添加到物理世界
world.addBody(boxBody);
phyMeshes.push(boxBody);

boxBody.addEventListener("collide", (e) => {
console.log("碰撞了", e);
let impactStrength = e.contact.getImpactVelocityAlongNormal();
console.log("impactStrength", impactStrength);
});

// 创建立方体几何体
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);

// 创建物理球
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,
collisionFilterGroup: GROUP2,
collisionFilterMask: GROUP1 | GROUP4,
});
// 将刚体添加到物理世界
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);

// 创建物理圆柱体
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,
});
// 将刚体添加到物理世界
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);

// 设置立方体的初始速度
// boxBody.velocity.set(2, 0, 0);

// 渲染
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);
}

animate();
</script>

<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

canvas {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
}
</style>

碰撞休眠

基于上面的代码添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 设置物理世界允许休眠
world.allowSleep = true;
// 设置立方体允许休眠
boxBody.allowSleep = true;
// 设置立方体如果速度小于0.1,则休眠
boxBody.sleepSpeedLimit = 0.5;
// 设置立方体如果速度小于0.1之后,1秒后进入休眠状态
boxBody.sleepTimeLimit = 1;

// 监听休眠事件
boxBody.addEventListener("sleepy", (e) => {
console.log("即将进入休眠", e);
});
boxBody.addEventListener("sleep", (e) => {
console.log("进入休眠", e);
});

效果图

形状组合

组合原理

这里没什么好讲的,主要来说一下组合胶囊体的position的设置
示意图
圆柱的高度为1.5,中心位置是(0,0),然后一半的高度就是0.75,所以我们就把球的y值设置为0.75和-0.75

组合完整代码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
<template>
<div></div>
</template>

<script setup>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as CANNON from "cannon-es";

// 初始化物理世界
const world = new CANNON.World();
// 设置重力
world.gravity.set(0, -9.82, 0);

// 初始化3D场景
const scene = new THREE.Scene();
// 初始化相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 0, 10);
camera.lookAt(0, 0, 0);

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

let phyMeshes = [];
let meshes = [];

// 设置碰撞组,数值要用2的幂
const GROUP1 = 1;
const GROUP2 = 2;
const GROUP3 = 4;

// 设置立方体材质
const boxMaterialCon = new CANNON.Material("boxMaterial");
boxMaterialCon.friction = 0.2;
boxMaterialCon.restitution = 0.1;

// 创建一个物理世界的平面
// const planeShape = new CANNON.Plane();
const planeShape = new CANNON.Box(new CANNON.Vec3(5, 0.1, 5));
// 创建一个刚体
const 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,
});
// planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), 0.1);

// 将刚体添加到物理世界
world.addBody(planeBody);

// 创建一个平面几何体
// const planeGeometry = new THREE.PlaneGeometry(10, 10);
const planeGeometry = new THREE.BoxGeometry(10, 0.2, 10);
// 创建一个平面材质
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
// 创建一个平面网格
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
// x轴旋转90度
// planeMesh.rotation.x = 0.1;
// 将网格添加到3D场景
scene.add(planeMesh);

// 创建由2个球加1个圆柱体组成的胶囊体
// 创建body
const capsuleBody = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 5, 0),
material: boxMaterialCon,
collisionFilterGroup: GROUP2,
collisionFilterMask: GROUP1 | GROUP2 | GROUP3,
});
// 创建一个球体几何体
const sphereShape = new CANNON.Sphere(0.5);
// 创建一个圆柱体几何体
const cylinderShape = new CANNON.Cylinder(0.5, 0.5, 1.5, 20);
// 将球体和圆柱体添加到body
capsuleBody.addShape(sphereShape, new CANNON.Vec3(0, 0.75, 0));
capsuleBody.addShape(cylinderShape, new CANNON.Vec3(0, 0, 0));
capsuleBody.addShape(sphereShape, new CANNON.Vec3(0, -0.75, 0));

capsuleBody.velocity.set(2, 0, 0);
// 将body添加到物理世界
world.addBody(capsuleBody);
phyMeshes.push(capsuleBody);

// 创建胶囊体网格
const capsuleGeometry = new THREE.CylinderGeometry(0.5, 0.5, 1.5, 20);
const capsuleMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const capsuleMesh = new THREE.Mesh(capsuleGeometry, capsuleMaterial);
capsuleMesh.position.copy(capsuleBody.position);
capsuleMesh.quaternion.copy(capsuleBody.quaternion);

// 创建一个球体
const sphereGeometry = new THREE.SphereGeometry(0.5, 20, 20);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff });
const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphereMesh.position.set(0, 0.75, 0);
capsuleMesh.add(sphereMesh);
const sphereMesh2 = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphereMesh2.position.set(0, -0.75, 0);
capsuleMesh.add(sphereMesh2);

scene.add(capsuleMesh);
meshes.push(capsuleMesh);


// 渲染
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);
}

animate();
</script>

<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

canvas {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
}
</style>

组合效果

效果图

模型运用物理引擎

注意:模型无法和立方体碰撞,这是一个bug,等待修复,因此我们暂时使用平面来和模型碰撞

核心代码及讲解

使用CANNON.Trimesh将模型的position和index传进去作为形状即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// 设置trimeshbody
const trimeshShape = new CANNON.Trimesh(
lion.geometry.attributes.position.array,
lion.geometry.index.array
);
let trimeshBody = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 2, 0),
material: boxMaterialCon,
collisionFilterGroup: GROUP2,
collisionFilterMask: GROUP1 | GROUP2 | GROUP3,
// shape: trimeshShape,
});
trimeshBody.addShape(trimeshShape);

完整代码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
<template>
<div></div>
</template>

<script setup>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as CANNON from "cannon-es";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
// 初始化物理世界
const world = new CANNON.World();
// 设置重力
world.gravity.set(0, -9.82, 0);

// 初始化3D场景
const scene = new THREE.Scene();
// 初始化相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 0, 10);
camera.lookAt(0, 0, 0);

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

let phyMeshes = [];
let meshes = [];

// 设置碰撞组,数值要用2的幂
const GROUP1 = 1;
const GROUP2 = 2;
const GROUP3 = 4;
const GROUP4 = 8;

// 设置立方体材质
const boxMaterialCon = new CANNON.Material("boxMaterial");
boxMaterialCon.friction = 0.2;
boxMaterialCon.restitution = 0.1;

// 创建一个平面
const planeShape1 = new CANNON.Plane();
// 创建一个刚体
const planeBody1 = new CANNON.Body({
mass: 0,
shape: planeShape1,
position: new CANNON.Vec3(0, 0, 0),
type: CANNON.Body.STATIC,
material: boxMaterialCon,
collisionFilterGroup: GROUP1,
collisionFilterMask: GROUP1 | GROUP2 | GROUP3,
});
planeBody1.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
// 将刚体添加到物理世界
world.addBody(planeBody1);

// 创建一个物理世界的平面
// const planeShape = new CANNON.Plane();
const planeShape = new CANNON.Box(new CANNON.Vec3(5, 0.1, 5));
// 创建一个刚体
const 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,
});
// planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), 0.1);

// 将刚体添加到物理世界
world.addBody(planeBody);

// 创建一个平面几何体
// const planeGeometry = new THREE.PlaneGeometry(10, 10);
const planeGeometry = new THREE.BoxGeometry(10, 0.2, 10);
// 创建一个平面材质
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
// 创建一个平面网格
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
// x轴旋转90度
// planeMesh.rotation.x = 0.1;
// 将网格添加到3D场景
scene.add(planeMesh);

// 创建由2个球加1个圆柱体组成的胶囊体
// 创建body
const capsuleBody = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 5, 0),
material: boxMaterialCon,
collisionFilterGroup: GROUP2,
collisionFilterMask: GROUP1 | GROUP2 | GROUP3,
});
// 创建一个球体几何体
const sphereShape = new CANNON.Sphere(0.5);
// 创建一个圆柱体几何体
const cylinderShape = new CANNON.Cylinder(0.5, 0.5, 1.5, 20);
// 将球体和圆柱体添加到body
capsuleBody.addShape(sphereShape, new CANNON.Vec3(0, 0.75, 0));
capsuleBody.addShape(cylinderShape, new CANNON.Vec3(0, 0, 0));
capsuleBody.addShape(sphereShape, new CANNON.Vec3(0, -0.75, 0));

capsuleBody.velocity.set(1, 0, 0);
// 将body添加到物理世界
world.addBody(capsuleBody);
phyMeshes.push(capsuleBody);

// 创建胶囊体网格
const capsuleGeometry = new THREE.CylinderGeometry(0.5, 0.5, 1.5, 20);
const capsuleMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const capsuleMesh = new THREE.Mesh(capsuleGeometry, capsuleMaterial);
capsuleMesh.position.copy(capsuleBody.position);
capsuleMesh.quaternion.copy(capsuleBody.quaternion);
scene.add(capsuleMesh);
meshes.push(capsuleMesh);

// 创建一个球体
const sphereGeometry = new THREE.SphereGeometry(0.5, 20, 20);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff });
const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphereMesh.position.set(0, 0.75, 0);
capsuleMesh.add(sphereMesh);
const sphereMesh2 = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphereMesh2.position.set(0, -0.75, 0);
capsuleMesh.add(sphereMesh2);

// 加载模型
const loader = new GLTFLoader();
loader.load("./model/lion.gltf", (gltf) => {
// console.log(gltf);
let lion = gltf.scene.children[0];
console.log(lion);
lion.material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
scene.add(lion);
meshes.push(lion);

// 设置trimeshbody
const trimeshShape = new CANNON.Trimesh(
lion.geometry.attributes.position.array,
lion.geometry.index.array
);
let trimeshBody = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 2, 0),
material: boxMaterialCon,
collisionFilterGroup: GROUP2,
collisionFilterMask: GROUP1 | GROUP2 | GROUP3,
// shape: trimeshShape,
});
trimeshBody.addShape(trimeshShape);

// 添加到物理世界
world.addBody(trimeshBody);
phyMeshes.push(trimeshBody);
});

// 渲染
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);
}

animate();
</script>

<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

canvas {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
}
</style>

效果

效果图

添加力

基础代码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<template>
<div></div>
</template>

<script setup>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as CANNON from "cannon-es";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { Body } from "cannon-es";
// 初始化物理世界
const world = new CANNON.World();
// 设置重力
world.gravity.set(0, -9.82, 0);

// 初始化3D场景
const scene = new THREE.Scene();
// 初始化相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 0, 10);
camera.lookAt(0, 0, 0);

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

let phyMeshes = [];
let meshes = [];

// 设置碰撞组,数值要用2的幂
const GROUP1 = 1;
const GROUP2 = 2;
const GROUP3 = 4;
const GROUP4 = 8;

// 设置立方体材质
const boxMaterialCon = new CANNON.Material("boxMaterial");
boxMaterialCon.friction = 0;
boxMaterialCon.restitution = 0.1;

// 创建一个物理世界的平面
// const planeShape = new CANNON.Plane();
const planeShape = new CANNON.Box(new CANNON.Vec3(5, 0.1, 5));
// 创建一个刚体
const 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,
});
// planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), 0.1);

// 将刚体添加到物理世界
world.addBody(planeBody);

// 创建一个平面几何体
// const planeGeometry = new THREE.PlaneGeometry(10, 10);
const planeGeometry = new THREE.BoxGeometry(10, 0.2, 10);
// 创建一个平面材质
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
// 创建一个平面网格
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
planeMesh.position.y = -0.1;
// x轴旋转90度
// planeMesh.rotation.x = 0.1;
// 将网格添加到3D场景
scene.add(planeMesh);

// 创建坐标系
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

// 创建物理球
const sphereShape = new CANNON.Sphere(0.5);
const sphereBody = new CANNON.Body({
mass: 1,
shape: sphereShape,
position: new CANNON.Vec3(0, 5, 0),
material: boxMaterialCon,
collisionFilterGroup: GROUP2,
collisionFilterMask: GROUP1 | GROUP2 | GROUP3,
});
sphereBody.quaternion.setFromAxisAngle(new CANNON.Vec3(0, 0, 1), Math.PI);
world.addBody(sphereBody);
phyMeshes.push(sphereBody);

// 创建3D球
const sphereGeometry = new THREE.SphereGeometry(0.5, 8, 8);
const sphereMaterial = new THREE.MeshBasicMaterial({
color: 0xff0000,
wireframe: true,
});
const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphereMesh.position.y = 5;
meshes.push(sphereMesh);
scene.add(sphereMesh);

// 渲染
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);
}

animate();
</script>

<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

canvas {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
}
</style>

此时我们搞出来了一个线框球
效果图

applyForce

1
2
3
4
// 监听点击事件
window.addEventListener("click", () => {
sphereBody.applyForce(new CANNON.Vec3(10, 0, 0), new CANNON.Vec3(0, -0.5, 0));
});

applyLocalForce

为了展示效果的不同我们在基础代码中已经将球进行了反转180度

1
2
3
4
5
6
window.addEventListener("click", () => {
sphereBody.applyLocalForce(
new CANNON.Vec3(10, 0, 0),
new CANNON.Vec3(0, -0.5, 0)
);
});

同样的,我们可以使用applyImpulse方法

applyImpulse

但是需要注意的是他需要乘上我们设置的帧率

1
2
3
4
5
6
7
8
 sphereBody.applyImpulse(
new CANNON.Vec3(10 * (1 / 60), 0, 0),
new CANNON.Vec3(0, -0.5, 0)
);
sphereBody.applyLocalImpulse(
new CANNON.Vec3(10 * (1 / 60), 0, 0),
new CANNON.Vec3(0, -0.5, 0)
);

applyTorque

添加扭力

1
sphereBody.applyTorque(new CANNON.Vec3(0, 0, -10));

cannon约束

lock约束

基础平面代码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<template>
<div></div>
</template>

<script setup>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as CANNON from "cannon-es";
// 初始化物理世界
const world = new CANNON.World();
// 设置重力
world.gravity.set(0, -9.82, 0);

// 初始化3D场景
const scene = new THREE.Scene();
// 初始化相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 0, 10);
camera.lookAt(0, 0, 0);

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

let phyMeshes = [];
let meshes = [];

// 设置立方体材质
const boxMaterialCon = new CANNON.Material("boxMaterial");
boxMaterialCon.friction = 0;
boxMaterialCon.restitution = 0.1;

// 创建一个物理世界的平面
// const planeShape = new CANNON.Plane();
const planeShape = new CANNON.Box(new CANNON.Vec3(5, 0.1, 5));
// 创建一个刚体
const planeBody = new CANNON.Body({
// mass: 0,
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);
planeMesh.position.y = -0.1;
// 将网格添加到3D场景
scene.add(planeMesh);

// 渲染
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);
}

animate();
</script>

<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

canvas {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
}
</style>

效果图

添加立方体

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
26
// 循环十个并排的立方体
// 创建一个立方体几何体
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
// 创建一个立方体材质
const boxMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });

const boxShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
for (let i = 0; i < 10; i++) {
// 创建一个立方体网格
const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial);
boxMesh.position.x = i;
boxMesh.position.y = 1;
meshes.push(boxMesh);
scene.add(boxMesh);

// 创建一个刚体
const boxBody = new CANNON.Body({
mass: 1,
shape: boxShape,
position: new CANNON.Vec3(i + 0.5 * i, 1, 0),
material: boxMaterialCon,
});
// 将刚体添加到物理世界
world.addBody(boxBody);
phyMeshes.push(boxBody);
}

添加lock约束

1
2
3
4
5
6
7
8
9
10
let previousBody;
for (let i = 0; i < 10; i++) {
// ... 此处省略代码
// 将刚体添加到上一个刚体的约束中
if (previousBody) {
const constraint = new CANNON.LockConstraint(boxBody, previousBody);
world.addConstraint(constraint);
}
previousBody = boxBody;
}

解除lock约束

有添加自然可以解除

1
2
3
4
5
6
7
8
9
10
11
let previousBody;
for (let i = 0; i < 10; i++) {
// ... 此处省略代码
// 将刚体添加到上一个刚体的约束中
if (previousBody) {
const constraint = new CANNON.LockConstraint(boxBody, previousBody);
world.addConstraint(constraint);
world.removeConstraint(constraint);
}
previousBody = boxBody;
}

这样就可以接触约束了,效果和没添加约束时一样

point约束

这里保持基础平面代码不变

创建扁平立方体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 创建扁平立方体
const boxShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.05));

// 循环创建10个立方体
for (let i = 0; i < 10; i++) {
const boxBody = new CANNON.Body({
mass: i == 0 ? 0 : 1,
type: i == 0 ? CANNON.Body.STATIC : CANNON.Body.DYNAMIC,
shape: boxShape,
position: new CANNON.Vec3(0, 15 - i * 1.1, 0),
material: boxMaterialCon,
});
world.addBody(boxBody);
phyMeshes.push(boxBody);

const boxGeometry = new THREE.BoxGeometry(1, 1, 0.1);
const boxMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial);
boxMesh.position.y = 15 - i * 1.1;
scene.add(boxMesh);
meshes.push(boxMesh);
}

添加point约束

这里我说一下参数为什么是这几个数字,首先点到点需要4个参数,分别是两个刚体和两个点,这里我们使用的是同一个刚体,所以第一个参数和第三个参数都是同一个刚体,第二个参数是当前刚体的点,第四个参数是上一个刚体的点,如图所示,我们用的是两个刚体相邻的那四个点位位置对应中心线段的那两个点
效果图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let previousBody;
for (let i = 0; i < 10; i++) {
// ... 此处省略代码
if (i > 0) {
const constraint = new CANNON.PointToPointConstraint(
boxBody,
new CANNON.Vec3(-0.5, 0.55, 0),
previousBody,
new CANNON.Vec3(-0.5, -0.55, 0)
);
world.addConstraint(constraint);
const constraint2 = new CANNON.PointToPointConstraint(
boxBody,
new CANNON.Vec3(0.5, 0.55, 0),
previousBody,
new CANNON.Vec3(0.5, -0.55, 0)
);
world.addConstraint(constraint2);
}

previousBody = boxBody;
}

效果图

添加小球撞击测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
window.addEventListener("click", () => {
// 创建一个球体
const sphereShape = new CANNON.Sphere(0.5);
const sphereBody = new CANNON.Body({
mass: 1,
shape: sphereShape,
position: new CANNON.Vec3(0, 8, 3),
material: boxMaterialCon,
});
sphereBody.velocity.set(0, 0, -10);
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);
sphereMesh.position.y = 8;
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
const sphereShape = new CANNON.Sphere(0.5);
let previousBody;
for (let i = 0; i < 10; i++) {
let sphereBody = new CANNON.Body({
mass: i == 0 ? 0 : 1,
type: i == 0 ? CANNON.Body.STATIC : CANNON.Body.DYNAMIC,
shape: sphereShape,
position: new CANNON.Vec3(0, 15 - i * 1.2, 0),
material: boxMaterialCon,
});
world.addBody(sphereBody);
phyMeshes.push(sphereBody);

const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphereMesh.position.y = 15 - i * 1.2;
scene.add(sphereMesh);
meshes.push(sphereMesh);
}

效果图

添加距离约束

参数1.2就是他们的距离

1
2
3
4
5
6
7
8
9
10
if (i > 0) {
// 创建距离约束
let constraint = new CANNON.DistanceConstraint(
previousBody,
sphereBody,
1.2
);
world.addConstraint(constraint);
}
previousBody = sphereBody;

效果图

添加小球撞击测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
window.addEventListener("click", () => {
// 创建一个球体
const sphereShape = new CANNON.Sphere(0.5);
const sphereBody = new CANNON.Body({
mass: 1,
shape: sphereShape,
position: new CANNON.Vec3(0, 8, 3),
material: boxMaterialCon,
});
sphereBody.velocity.set(0, 0, -10);
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);
sphereMesh.position.y = 8;
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
23
24
25
26
const rows = 15;
const cols = 15;

const bodies = {
// rows-cols: body
};
const boxGeometry = new THREE.SphereGeometry(0.3, 8, 8);
const boxMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const particleShape = new CANNON.Particle();
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
const body = new CANNON.Body({
mass: 0.5,
shape: particleShape,
position: new CANNON.Vec3(i - cols * 0.5, 10, j - rows * 0.5),
});
world.addBody(body);
bodies[`${i}-${j}`] = body;
phyMeshes.push(body);

const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial);
boxMesh.position.set(i - cols * 0.5, 10, j - rows * 0.5);
meshes.push(boxMesh);
scene.add(boxMesh);
}
}
添加距离约束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
const body = bodies[`${i}-${j}`];
if (i > 0) {
const body2 = bodies[`${i - 1}-${j}`];
const constraint = new CANNON.DistanceConstraint(body, body2, 1);
world.addConstraint(constraint);
}
if (j > 0) {
const body2 = bodies[`${i}-${j - 1}`];
const constraint = new CANNON.DistanceConstraint(body, body2, 1);
world.addConstraint(constraint);
}
}
}
创建小球

这里我设置了球质量为0,这样就不会掉落

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 创建物理球体
const sphereShape = new CANNON.Sphere(5);
const sphereBody = new CANNON.Body({
mass: 0,
shape: sphereShape,
position: new CANNON.Vec3(0, 0, 0),
material: boxMaterialCon,
});
world.addBody(sphereBody);
phyMeshes.push(sphereBody);

// 创建球体几何体
const sphereGeometry = new THREE.SphereGeometry(5, 32, 32);
// 创建球体材质
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// 创建球体网格
const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphereMesh.position.set(0, 0, 0);
meshes.push(sphereMesh);
scene.add(sphereMesh);

弹簧约束

保持基础平面代码不变

创建静态球体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 创建1个固定的静态球体
const sphereShape = new CANNON.Sphere(0.2);
const sphereBody = new CANNON.Body({
mass: 0,
shape: sphereShape,
position: new CANNON.Vec3(0, 10, 0),
type: CANNON.Body.STATIC,
});
world.addBody(sphereBody);
phyMeshes.push(sphereBody);

const sphereGeometry = new THREE.SphereGeometry(0.2, 32, 32);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphereMesh.position.copy(sphereBody.position);
meshes.push(sphereMesh);
scene.add(sphereMesh);

效果图

创建立方体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 创建1个立方体
const boxShape = new CANNON.Box(new CANNON.Vec3(1, 1, 0.3));
const boxBody = new CANNON.Body({
mass: 1,
shape: boxShape,
position: new CANNON.Vec3(0, 6, 0),
});
world.addBody(boxBody);
phyMeshes.push(boxBody);

const boxGeometry = new THREE.BoxGeometry(2, 2, 0.6);
const boxMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff });
const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial);
boxMesh.position.copy(boxBody.position);
meshes.push(boxMesh);
scene.add(boxMesh);

效果图

创建弹簧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 创建1个弹簧拉住立方体
const spring = new CANNON.Spring(sphereBody, boxBody, {
// 弹簧的长度
restLength: 5,
// 弹簧的刚度
stiffness: 100,
// 弹簧的阻尼
damping: 1,
// 弹簧的锚点
localAnchorA: new CANNON.Vec3(0, 0, 0),
localAnchorB: new CANNON.Vec3(-1, 1, 0),
});

// 通过计算每一step之前获取弹簧的作用力,并且应用弹簧的作用力
world.addEventListener("preStep", () => {
// 应用弹簧的作用力
spring.applyForce();
});

破坏约束

保持基础平面代码不变

创建距离约束小球

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
26
27
28
// 创建15个距离约束的小球
for (let i = 0; i < 15; i++) {
const sphereShape = new CANNON.Sphere(0.45);
const sphereBody = new CANNON.Body({
mass: i == 0 ? 0 : 1,
shape: sphereShape,
position: new CANNON.Vec3(0, 15 - i * 0.9, 0),
});
world.addBody(sphereBody);
phyMeshes.push(sphereBody);

const sphereGeometry = new THREE.SphereGeometry(0.45, 32, 32);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphereMesh.position.copy(sphereBody.position);
meshes.push(sphereMesh);
scene.add(sphereMesh);

// 创建距离约束
if (i > 0) {
const constraint = new CANNON.DistanceConstraint(
sphereBody,
phyMeshes[i - 1],
0.9
);
world.addConstraint(constraint);
}
}

效果图

点击添加撞击小球

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
window.addEventListener("click", () => {
// 创建一个球体
const sphereShape = new CANNON.Sphere(0.45);
const sphereBody = new CANNON.Body({
mass: 1,
shape: sphereShape,
position: new CANNON.Vec3(5, 10, 0),
});
world.addBody(sphereBody);
phyMeshes.push(sphereBody);
sphereBody.velocity.set(-50, 0, 0);

const sphereGeometry = new THREE.SphereGeometry(0.45, 32, 32);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphereMesh.position.copy(sphereBody.position);
meshes.push(sphereMesh);
scene.add(sphereMesh);
});

删除约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 在每次世界模拟完一个时间步之后,判断约束力度的绝对值大小,如果大于1000,就删除约束
world.addEventListener("postStep", () => {
// 循环判断世界约束
for (let i = 0; i < world.constraints.length; i++) {
const constraint = world.constraints[i];
// 获取约束力度的绝对值大小
let multiplier = Math.abs(constraint.equations[0].multiplier);

if (multiplier > 1000) {
// 删除约束
world.removeConstraint(constraint);
}
}
});

效果逼真度

默认是10,可以自己设置高数值,来触发更逼真的物理引擎效果,但是会消耗更多的性能

1
world.solver.iterations = 10;

铰链约束

创建物体

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 创建固定的body
const fixedBody = new CANNON.Body({
mass: 0,
position: new CANNON.Vec3(0, 10, 0),
});
// 设置box形状
const fixedShape = new CANNON.Box(new CANNON.Vec3(2.5, 2.5, 0.25));
fixedBody.addShape(fixedShape);
// 设置body类型为静态
fixedBody.type = CANNON.Body.STATIC;
// 将body添加到物理世界
world.addBody(fixedBody);
phyMeshes.push(fixedBody);

//threejs的box
const fixedMesh = new THREE.Mesh(
new THREE.BoxGeometry(5, 5, 0.5),
new THREE.MeshBasicMaterial({ color: 0x00ff00 })
);
fixedMesh.position.copy(fixedBody.position);
fixedMesh.quaternion.copy(fixedBody.quaternion);
meshes.push(fixedMesh);
scene.add(fixedMesh);

// 创建移动的body
const moveBody = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 4, 0),
});
// 设置box形状
moveBody.addShape(fixedShape);
// 将body添加到物理世界
world.addBody(moveBody);
phyMeshes.push(moveBody);

//threejs的box
const moveMesh = new THREE.Mesh(
new THREE.BoxGeometry(5, 5, 0.5),
new THREE.MeshBasicMaterial({ color: 0x00ff00 })
);
moveMesh.position.copy(moveBody.position);
moveMesh.quaternion.copy(moveBody.quaternion);
meshes.push(moveMesh);
scene.add(moveMesh);

效果图

添加铰链约束

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

1
2
3
4
5
6
7
8
9
// 创建铰链约束
const hingeConstraint = new CANNON.HingeConstraint(fixedBody, moveBody, {
pivotA: new CANNON.Vec3(0, -3, 0),
pivotB: new CANNON.Vec3(0, 3, 0),
axisA: new CANNON.Vec3(1, 0, 0),
axisB: new CANNON.Vec3(1, 0, 0),
// collideConnected: true,
});
world.addConstraint(hingeConstraint);

效果图

添加力测试铰链

1
2
3
4
5
6
7
8
9

window.addEventListener("click", () => {
//创建一个力
const force = new CANNON.Vec3(0, 0, -100);
// 应用力
moveBody.applyForce(force, moveBody.position);

});

添加马达测试铰链

1
2
3
4
5
6
7
8
9
10
window.addEventListener("click", () => {
// //创建一个力
// const force = new CANNON.Vec3(0, 0, -100);
// // 应用力
// moveBody.applyForce(force, moveBody.position);
// 启用马达
hingeConstraint.enableMotor();
// 设置马达速度
hingeConstraint.setMotorSpeed(10);
});

结语

本篇文章就先到这里,更多内容敬请期待,大家一起做大做强

上一篇:
【可视化学习】47-cannon-es的简单使用(二)
下一篇:
编辑器组件封装(二)