threejs入门三,使用cannonjs实现物理效果

118 阅读2分钟

安装cannon-es

yarn add cannon-es

import * as CANNON from 'cannon-es'

创建物理世界

const world = CANNON.world({
  gravity: new CANNON.Vec3(0, -9.8, 0) // 重力加速度
})

创建地面和小球的碰撞箱,并添加到物理世界运行,最后在渲染

const shpereWorld = new CANNON.Sphere(1) // 创建物理小球的形状
const shpereBody = new CANNON.Body({
  shape: shpereWorld, // 形状
  mass: 2, // 质量
  // 材质,多个物体只有都设置默认弹力和摩擦力,才能生效
  material: new CANNON.Material({
    // 默认弹性
    restitution: 0.8, 
    // 默认摩擦力
    friction: 1.0 
  }),
  // 初始位置
  position: new CANNON.Vec3(0, 10, 0) 
})
// 添加到物理世界运行
world.addBody(shpereBody)

// 物理地板
const planeWorld = new CANNON.Plane()
const planeBody = new CANNON.Body({
  mass: 0,
  material: new CANNON.Material({
    restitution: 0.6, // 默认弹性
    friction: 1.0 // 默认摩擦力
  }),
  position: new CANNON.Vec3(0, 0, 0),
  shape: planeWorld,
})
planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2)
world.addBody(planeBody)

// 时钟实例
const clock = new THREE.Clock()
// 渲染
function animate() {
  requestAnimationFrame(animate);
  const lastTime = clock.getDelta()
  controls.update()
  // 更新物理世界.1/60就是60帧,lastTime用于确定每帧更新时间,不传也行,但是可能会导致帧数不稳定
  world.step(1/60, lastTime)
  shpereMesh.position.copy(shpereBody.position)
  renderer.render(scene, camera);
}
animate()

当然,我们也可以封装一下,每次点击创建一个,并使他们从随机位置掉落,这样就能看到碰撞效果

// 创建渲染世界物体
function createMesh() {
  // 几何
  const sphere = new THREE.SphereGeometry(1, 30, 30)
  // 材质
  const sphereMaterial = new THREE.MeshStandardMaterial({
    color: new THREE.Color('#aacc00'),
  })
  // 打开线框模式
  sphereMaterial.wireframe = true
  // 物体
  const shpereMesh = new THREE.Mesh(sphere, sphereMaterial)
  shpereMesh.position.set(0,10,0)
  scene.add(shpereMesh)
  return shpereMesh
}

// 创建物理世界物体
function createBody () {
  const x = Math.random() * 5
  const y = Math.random() * 5
  const shpereWorld = new CANNON.Sphere(1)
  const shpereBody = new CANNON.Body({
    shape: shpereWorld, // 形状
    mass: 2, // 质量
    material: new CANNON.Material({
      restitution: 0.8, // 默认弹性
      friction: 3.0 // 默认摩擦力
    }),
    position: new CANNON.Vec3(x, 10, y)
  })
  shpereBody.addEventListener('collide', (e) => {
    const level = e.contact.getImpactVelocityAlongNormal()
    if(level > 5) {
      // 设置播放时间,为0则是从头开始播放
      sound.currentTime = 0
      // 设置声音根据碰撞强度
      sound.volume = Math.floor(level) / 20
      setTimeout(() => {
        sound.play()
      },0)
    }
  })
  world.addBody(shpereBody)
  return shpereBody
}

// 添加点击事件
const entities = []
function pushShpere() {
  entities.push({
    mesh: createMesh(),
    body: createBody()
  })
}
window.addEventListener('click', pushShpere)

// 更新animate函数,使其对所有添加的物体生效
function animate() {
  requestAnimationFrame(animate);
  const lastTime = clock.getDelta()
  controls.update()
  // 更新物理世界.1/60就是60帧,lastTime用于确定每帧更新时间,不传也行,但是可能会导致帧数不稳定
  world.step(1/60, lastTime)
  // shpereMesh.position.copy(shpereBody.position)
  entities.forEach(entity => {
    // 更新渲染世界物体位置
    entity.mesh.position.copy(entity.body.position)
    // 更新渲染世界物体旋转矩阵,这样碰撞会更逼真,球也会滚动起来
    entity.mesh.quaternion.copy(entity.body.quaternion)
  })

  renderer.render(scene, camera);
}
animate()

在创建body函数中给物体施加力,不要随机垂直掉落

// 创建物理世界物体
function createBody () {
  const x = Math.random() * 5
  const z = Math.random() * 5
  const shpereWorld = new CANNON.Sphere(1)
  const shpereBody = new CANNON.Body({
    shape: shpereWorld, // 形状
    mass: 2, // 质量
    material: new CANNON.Material({
      restitution: 0.8, // 默认弹性
      friction: 3.0 // 默认摩擦力
    }),
    position: new CANNON.Vec3(0, 10, 0)
  })
  shpereBody.addEventListener('collide', (e) => {
    const level = e.contact.getImpactVelocityAlongNormal()
    if(level > 5) {
      // 设置播放时间,为0则是从头开始播放
      sound.currentTime = 0
      // 设置声音根据碰撞强度
      sound.volume = Math.floor(level) / 20
      setTimeout(() => {
        sound.play()
      },0)
    }
  })
  world.addBody(shpereBody)
  
  // 施加力
  shpereBody.applyLocalForce(
    new CANNON.Vec3(x * 10, 0, z * 10), // 施加的力的向量
    new CANNON.Vec3(0.5, 0.5, 0.5),// 施加力在物体上的位置
  )
  
  return shpereBody
}