简介

  • 碰撞检测在three.js开发中是很常见的。最常见的两种方式实现:
  1. 使用.Raycaste()在物体的各个顶点发出射线,计算是否和其他物体相交。
  2. 使用.Box3在物体上创建包围盒,计算两个物体包围盒是否相交。
  • 本节介绍第一种碰撞检测法。

Raycaster

origin — 光线投射的原点向量。
direction — 射线的方向向量,应该归一化。
near — 所有返回的结果应该比 near 远。near不能为负,默认值为0。
far — 所有返回的结果应该比 far 近。far 不能小于 near,默认值为无穷大。
Raycaster(origin, direction, near, far)
  • 射线检测法缺点也比较明显,当物体的中心在另一个物体内部时,是不能够检测到碰撞的。而且当两个物体能够互相穿过,且有较大部分重合时,检测效果也不理想。

开始实现

基础模板

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>学习</title>
  </head>
  <body>
    <canvas id="c2d" class="c2d" width="1000" height="500"></canvas>
    <script type="module">
      import * as THREE from './file/three.js-dev/build/three.module.js'

      const canvas = document.querySelector('#c2d')
      // 渲染器
      const renderer = new THREE.WebGLRenderer({ canvas })

      const fov = 40 // 视野范围
      const aspect = 2 // 相机默认值 画布的宽高比
      const near = 0.1 // 近平面
      const far = 10000 // 远平面
      // 透视投影相机
      const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
      camera.position.set(0, 6, 5)
      camera.lookAt(0, 0, 0)

      // 场景
      const scene = new THREE.Scene()
      {
        // 灯光
        const color = 0xffffff
        const intensity = 1
        const light = new THREE.DirectionalLight(color, intensity)
        light.position.set(-1, 10, 4)
        scene.add(light)
      }

      // 立方体
      const boxWidth = 1
      const boxHeight = 1
      const boxDepth = 1
      const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth)

      const material = new THREE.MeshPhongMaterial({
        color: 0x6688aa
      })
      const cube = new THREE.Mesh(geometry, material)
      cube.position.x = -1
      scene.add(cube)

      const material2 = new THREE.MeshPhongMaterial({
        color: 0x6688aa
      })
      const cube2 = new THREE.Mesh(geometry, material2)
      cube2.position.x = 1
      scene.add(cube2)
      
      // 渲染
      function render() {
        renderer.render(scene, camera)
        requestAnimationFrame(render)
      }
      requestAnimationFrame(render)
    </script>
  </body>
</html>

image.png

添加键盘控制

      document.addEventListener(
        'keydown',
        (e) => {
          var ev = e || window.event
          switch (ev.keyCode) {
            case 87:
              cube.position.z -= 0.05
              break
            case 83:
              cube.position.z += 0.05
              break
            case 65:
              cube.position.x -= 0.05
              break
            case 68:
              cube.position.x += 0.05
              break
            default:
              break
          }
        },
        false
      )

1.gif

创建检测方法

  1. 获取移动物体中心点的世界坐标。
  2. 获取移动物体的所有顶点坐标。
  3. 循环所有顶点坐标,根据移动物体的变换矩阵转换顶点坐标。
  4. 通过Raycaster()从中心点的世界坐标向转换后的顶点坐标,发出射线检测要碰撞的物体。
  5. 比较射线的长度是否小于物体的长度。
    注意本节使用R135版本,几何对象都是继承的BufferGeometry是没有顶点数组对象(.vertices)
  function onIntersect() {
    // 声明一个变量用来表示是否碰撞
    let bool = false

    // .position 对象局部位置
    // .clone() 复制一个新的三维向量
    // 网格中心 世界坐标
    const centerCoord = cube.position.clone()
    // 获取网格中 几何对象的顶点对象
    const position = cube.geometry.attributes.position
    // 顶点三维向量
    const vertices = []
    // .count 矢量个数
    for (let i = 0; i < position.count; i++) {
      // .getX() 获取给定索引的矢量的第一维元素
      vertices.push(new THREE.Vector3(position.getX(i), position.getY(i), position.getZ(i)))
    }

    for (let i = 0; i < vertices.length; i++) {
      // .matrixWorld 物体的世界坐标变换 -- 物体旋转、位移 的四维矩阵
      // .applyMatrix4() 将该向量乘以四阶矩阵
      // 获取世界坐标下 网格变换后的坐标
      let vertexWorldCoord = vertices[i].clone().applyMatrix4(cube.matrixWorld)

      // .sub(x) 从该向量减去x向量
      // 获得由中心指向顶点的向量
      var dir = vertexWorldCoord.clone().sub(centerCoord)

      // .normalize() 将该向量转换为单位向量
      // 发射光线 centerCoord 为投射的原点向量  dir 向射线提供方向的方向向量
      let raycaster = new THREE.Raycaster(centerCoord, dir.clone().normalize())

      // 放入要检测的 物体cube2,返回相交物体
      let intersects = raycaster.intersectObjects([cube2], true)

      if (intersects.length > 0) {
        // intersects[0].distance:射线起点与交叉点之间的距离(交叉点:射线和模型表面交叉点坐标)
        // dir.length():几何体顶点和几何体中心构成向量的长度
        // intersects[0].distance小于dir.length() 表示物体相交
        if (intersects[0].distance < dir.length()) {
          bool = true
        }
      }
    }
    return bool
  }
  • 在键盘监听事件中添加网格颜色修改。
    if (onIntersect()) {
        cube.material.color.set('yellow')
    } else {
        cube.material.color.set(0x6688aa)
    }

1.gif

  • 这种方式存在很多小问题,在需要物理引擎这一类操作的时候,建议大家还是直接使用第三方插件。如官方使用的ammo.js等。
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐