说明

这里主要实现物理世界中的自由落体和物体间的碰撞

学习本篇文章前,请事先了解或者学习一下three的内容,方便理解,这里不会教大家创建一个基础的three3d场景,直接就是进行物理世界的搭建

下载依赖包

npm i cannon-es

引入依赖包

import * as CANNON from "cannon-es";

搭建物理世界

1.实例化一个物理世界,并且设置重力加速度

// CANNON.World创建物理世界对象
const world = new CANNON.World();
// 设置物理世界的重力加速度
world.gravity.set(0, -9.8, 0)

2.创建碰撞体的形状和位置

说明:这里为什么说的是形状,因为cannon本身并不会真正去渲染出一个物体,而是利用three创建物体,并进行渲染,但是此时创建的物体是没有任何物理效果的,所以就必须将物理世界中的碰撞体与three中的物体关联起来,变成一个整体,从而实现物理效果,就是在three的物体表面附着一层碰撞形状,从而实现碰撞的检测

比如创建一个球的形状

const bodyShape = new CANNON.Sphere(0.3);
// 设置碰撞体规则
const body = new CANNON.Body({
    mass: 1, // 质量,如果为0表示静止不动
    position: new CANNON.Vec3(0, 2, 0), //位置
    shape: bodyShape,//碰撞体的几何体形状
});

3.将物体添加到物理世界中

world.addBody(body);

4.在动画函数中更新物理世界

// 更新物理世界
world.step(1 / 60)
// 渲染循环中,同步物理世界与网格世界
mesh.position.copy(body.position);

完整代码

这里作者增加了一个地面,并且设置了地面与球体间的摩擦系数和弹性系数,这里以vue3框架

<template>
  <div id="canvas">

  </div>
</template>

<script setup lang="js">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import * as CANNON from "cannon-es";

import { onMounted } from "vue"

onMounted(() => {

  const width = document.documentElement.clientWidth
  const height = document.documentElement.clientHeight

  // 创建3d场景
  const scene = new THREE.Scene()
  // 环境光
  const light = new THREE.AmbientLight("#ffffff", 0.2)
  scene.add(light)
  // 添加一个点光源
  const pointLight = new THREE.PointLight("#ffffff", 100, 100)
  pointLight.position.set(5, 5, 5)
  scene.add(pointLight)


  // CANNON.World创建物理世界对象
  const world = new CANNON.World();
  // 设置物理世界的重力加速度
  world.gravity.set(0, -9.8, 0)
  // 创建碰撞体的形状
  // 球
  const bodyShape = new CANNON.Sphere(0.3);
  // 地面
  const bodyFloor = new CANNON.Plane()
  // 设置碰撞体规则
  const body = new CANNON.Body({
    mass: 1,
    position: new CANNON.Vec3(0, 2, 0),
    shape: bodyShape,//碰撞体的几何体形状
  });
  const body2 = new CANNON.Body({
    mass: 0,
    position: new CANNON.Vec3(0, 0, 0),
    shape: bodyFloor,//碰撞体的几何体形状
  });
  // 设置刚体的旋转
  body2.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2)

  // 创建球体和地面的材质,并设置弹性系数
  const ballMaterial = new CANNON.Material("ballMaterial");
  const floorMaterial = new CANNON.Material("floorMaterial");
  // 为球体和地面的碰撞体设置材质
  body.material = ballMaterial; // 球体的碰撞体
  body2.material = floorMaterial; // 地面的碰撞体
  const contactMaterial = new CANNON.ContactMaterial(
    ballMaterial,
    floorMaterial,
    {
      friction: 0.3, // 摩擦系数
      restitution: 0.5, // 弹性系数,这里再次强调以保持一致性
    }
  );

  // 添加接触材料对到物理世界
  world.addContactMaterial(contactMaterial);
  // 在物理世界添加物体
  world.addBody(body);
  world.addBody(body2);



  // 创建相机
  const camera = new THREE.PerspectiveCamera(45, width / height, 1, 1000)
  camera.position.z = 7
  camera.position.y = 3
  camera.position.x = 3

  // 创建球
  const boxGeometry = new THREE.SphereGeometry(0.3)
  const material = new THREE.MeshPhongMaterial({ color: "#e67e22" })
  const mesh = new THREE.Mesh(boxGeometry, material)
  mesh.position.set(0, 2, 0)
  scene.add(mesh)
  // 创建地面
  const planeGeometry = new THREE.PlaneGeometry(10, 10)
  const materialfloor = new THREE.MeshPhongMaterial({ color: "#95a5a6", side: THREE.DoubleSide })
  const meshfloor = new THREE.Mesh(planeGeometry, materialfloor)
  meshfloor.position.set(0, 0, 0)
  meshfloor.rotation.x = -Math.PI / 2
  scene.add(meshfloor)

  // 创建渲染器
  const renderer = new THREE.WebGLRenderer({ antialias: true })
  // 调整渲染器大小
  renderer.setSize(width, height)
  // 添加动画
  renderer.setAnimationLoop(animate)
  document.querySelector("#canvas").appendChild(renderer.domElement)
  // 添加轨道控制器
  const controls = new OrbitControls(camera, renderer.domElement);

  // 动画函数
  function animate(time) {
    // 更新物理世界
    world.step(1 / 60)
    // 渲染循环中,同步物理世界与网格世界
    mesh.position.copy(body.position);
    meshfloor.position.copy(body2.position);
    controls.update()
    renderer.render(scene, camera);

  }
})
</script>

<style scoped></style>