使用物理动画库
npm install --save cannon (旧的库)
npm install --save cannon-es@0.15.1 使用es库(新的库)
对单个物体创建物理碰撞及施加力
import CANNON from 'cannon'
/**
* 原理就是在物理世界部分创建相同的物体将计算的值给Threejs的物体
*/
// 物理世界部分
const world = new CANNON.World()
/* 设置重力x,y,z(y = -9.82 是地球重力) */
world.gravity.set(0, -9.82, 0)
// --创建物理世界材料,并设置两种材料碰撞的参数
/*参数名随便*/
const defaultMaterial = new CANNON.Material('default')
const defaultContactMaterial = new CANNON.ContactMaterial(
defaultMaterial, /* 要碰撞的材料1 */
defaultMaterial, /* 要碰撞的材料2 */
{
friction: 0.1,
restitution: 0.7
}
)
/* 将材料添加到物理世界 */
world.addContactMaterial(defaultContactMaterial)
/* 设置世界碰撞的默认材料 【设置世界基本碰撞材料就可以不单个使用】 */
world.defaultContactMaterial = defaultContactMaterial
// --创建物理世界物体
const sphereShape = new CANNON.Sphere(0.5)
/* 创建球体body,定位,物体 */
const sphereBody = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 3, 0),
shape: sphereShape,
material: defaultMaterial /* 球体的碰撞材料 | C */
})
/* 给物体的一点加力 | L */
sphereBody.applyLocalForce(
new CANNON.Vec3(150, 0, 0), /* 要应用的力矢量 */
new CANNON.Vec3(0, 0, 0) /* 施加力的身体局部点 */
)
/* 在世界中添加身体是球体的body */
world.addBody(sphereBody)
// --创建物理地面并旋转
const floorShape = new CANNON.Plane()
const floorBody = new CANNON.Body()
floorBody.mass = 0
/* 地板的碰撞材料 | C */
floorBody.material = defaultMaterial
floorBody.addShape(floorShape)
/* 沿着x轴旋转半圈 */
floorBody.quaternion.setFromAxisAngle(
new CANNON.Vec3(-1, 0, 0),
Math.PI * 0.5
)
// 在更新动画中推进物理世界
const clock = new THREE.Clock()
const oldElapsedTime = 0
const tick = () => {
const elapsedTime = clock.getElapsedTime()
const deltaTime = elapsedTime - oldElapsedTime
oldElapsedTime = elapsedTime
/* 模拟风吹的力[对世界点施加力] */
sphereBody.applyForce(
new CANNON.Vec3(-0.5, 0, 0),
sphereBody.position
)
/* 此时物理世界的物体值已经在运动 * /
world.setp(1/60, deltaTime , 3)
/* 将物理世界物体的值复制给threejs物体 */
sphere.position.copy(sphereBody.position)
}
tick()
对多个物体添加物理碰撞及施加力
// 环境贴图
const environmentMapTexture = ...??
// 创建一个声音,物体碰撞的时候发出声音
const hitSound = new Audio('/sounds/hit.mp3')
const playHitSound = (collision) => {
/* 获取冲击力,冲击力大的才发出声音 */
const impactStrength = collision.contact.getImpactVelocityAlongNormal()
if(impactStrength > 1.5) {
hitSound.volume = Math.random()
hitSound.currentTime = 0
hitSound.play()
}
}
const world = new CANNON.World()
world.gravity.set(0, -9.82, 0)
// 优化性能
world.broadphase = new CANNON.SAPBroadphase(world)
world.allowSleep = true /* 让没有碰撞的物体休眠,不检测,提升很大性能 */
// 用来保存所有球体的数组
const objectsToUpdate = []
const sphereGeometry = new THREE.SphereBufferGeometry(1, 20, 20)
const sphereMaterial = new THREE.MeshStandardMaterial({
metalness: 0.3,
roughness: 0.4,
envMap: environmentMapTexture
})
// utils 创建球体
const createSphere = (raduis, position) => {
// Three.js mesh
const mesh = new THREE.Mesh(sphereGeometry, sphereMaterial)
mesh.scale.set(raduis, raduis, raduis)
mesh.castShadow = true
mesh.position.copy(position)
scene.add(mesh)
// CANNON.js body
const shape = new CANNON.Sphere(raduis)
const body = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 3, 0),
shape,
material: defaultMaterial
})
body.position.copy(position)
world.addBody(body)
// save in objects to update(保存更新对象到数组中)
objectsToUpdate.push({
mesh,
body
})
}
createSphere(0.5, {x: 0, y: 3, y: 0})
// 创建盒子,物理盒子threejs中创建盒子不同
// 物理盒子值需要提供长宽高的一般(因为他是从盒子中心点开始计算的)
const boxGeometry = new THREE.BoxBufferGeometry(1, 1, 1)
const boxMaterial = new THREE.MeshStandardMaterial({
metalness: 0.3,
roughness: 0.4,
envMap: environmentMapTexture
})
const createBox = (width, height, depth, position) => {
// Three.js mesh
const mesh = new THREE.Mesh(boxGeometry, boxMaterial)
mesh.scale.set(width, height, depth)
mesh.castShadow = true
mesh.position.copy(position)
scene.add(mesh)
// CANNON.js body(物理盒子只需要长宽高的一半)
const box = new CANNON.Box(new CANNON.Vec3(width/2, height/2, depth/2))
const body = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 3, 0),
box,
material: defaultMaterial
})
body.position.copy(position)
// 添加碰撞事件,碰撞的时候发出声音
body.addEventListener('collide', playHitSound)
world.addBody(box)
// save in objects to update(保存更新对象到数组中)
objectsToUpdate.push({
mesh,
body
})
}
// 在gui控制面板中添加按钮创建随机小球
const gui = new dat.GUI()
const debugObject = {}
debugObject.createShpere = () => {
createSphere(
Math.random() * 0.5,
{
x: (Math.random() - 0.5) * 3,
y: 3,
y: (Math.random() - 0.5) * 3
}
)
}
debugObject.createBox = () => {
createBox(
Math.random(),
Math.random(),
Math.random(),
{
x: (Math.random() - 0.5) * 3,
y: 3,
y: (Math.random() - 0.5) * 3
}
)
}
// 删除操作
debugObject.reset = () => {
for(let object of objectsToUpdate) {
object.body.removeEventListener('collide', playHitSound)
world.removeBody(object.body)
scene.remove(object.mesh)
}
}
gui.add(debugObject, 'createShpere')
gui.add(debugObject, 'createBox')
gui.add(debugObject, 'reset')
// 在更新动画中为所有要更新的对象创建物理碰撞
const tick = () => {
for(let object of objectsToUpdate) {
// 拷贝物理定位
object.mesh.position.copy(object.body.position)
// 拷贝物理局部旋转(立方体物理碰撞有旋转)
object.mesh.quaternion.copy(object.body.quaternion)
}
}