3D 探索 - Collision detection

May 11

碰撞检测

事情都要从在搭建的场景中来回穿墙开始说起😒(粗略的总结)

碰撞检测 顾名思义就是通过计算物体之间的距离来判断物体是否相交

问题

  • 被检测物体结构复杂
  • 被检测物体数量庞大

简化物体结构

即通过简单的矩形,圆形,立方体,球来包裹模型

为什么使用简单图形

很方便,很简单

  • 矩形的相交,即检查边是否相交
  • 圆的相交,即圆心之间的距离和半径对比

包围盒

  • AABB 轴对齐包围盒
  • OBB 转向包围盒

包围体

  • 圆柱
  • 胶囊

简化物体数量

即通过一些数据结构来减少需要参与检测的物体

四叉树(2D)

quadtree-js

简单理解就是

  • 判断:给定一个区域最大物体数 MAX,将当前区域物体数与 MAX 进行比较
  • 分割:若区域内物体数量大于 Max,则通过 x y 轴将区域分为四个象限(区域)
  • 递归:对分割出来的区域不断的进行 判断、分割,最终将区域分割完成

这样只需要先判定区域再进行碰撞判断即可,有效的减少了物体数量

八叉树(3D)

三维 ‘四叉树’

等等(待探究)


ThreeJS 中的碰撞检测例子

Raycaster (射线)

定义一个原点,朝着一个方向发出射线,然后计算路程中是否有相交面。

ThreeJS Raycaster 例子

// 伪代码 -> 拾取被射到的物体

// 被检测的物体
const targets = []; 
const raycaster = new THREE.Raycaster(); 

// 设置 原点 方向
raycaster.setFromCamera(原点, camera); 

// 获取被拾取的物体
const intersects = rayCaster.intersectObjects(targets, false); 

if (intersects.length > 0) { 
  // 一些操作
}

Octree (八叉树)

胶囊体(模拟人) + 八叉树

// 伪代码 -> player 在 sceneGroup 里不撞墙

// 场景
const sceneGroup = {}
const worldOctree = new Octree();
worldOctree.fromGraphNode(sceneGroup);

// 模拟小人
const player = new Capsule(
  new THREE.Vector3(0, 1, 0), 
  new THREE.Vector3(0, 8, 0), 
  1
); 

// 获取相交结果
const result = worldOctree.capsuleIntersect(player); 
if (result) {
  // 一些操作
}

CannonJs(物理引擎)

cannon-es

建造一个脱离渲染世界的物理世界,设置重力、摩擦力、惯性等物理性质
将物理世界的物体运动坐标映射到 ThreeJS 中

import * as CANNON from 'cannon-es'

// 物理世界
const world = new CANNON.World({ gravity: new CANNON.Vec3(0, -9.8, 0) })

// 预检
world.broadphase = new CANNON.SAPBroadphase(world)

// 物理世界的材质(摩擦力,回弹),区别于渲染材质(map, emssiveMap)
const defaultMaterial = new CANNON.Material('default')
world.addContactMaterial(
  new CANNON.ContactMaterial(defaultMaterial, defaultMaterial, {
    friction: 0,
    restitution: 0.7,
  }),
)

const boxBody = new CANNON.Body({
  mass: 0,
  shape: new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)),
  position: new CANNON.Vec3(2, 0.5, 0),
})

const sphereBody = new CANNON.Body({
  shape: new CANNON.Sphere(0.5),
  mass: 1,
  position: new CANNON.Vec3(0, 3, 0),
  material: defaultMaterial,
})

// 给一个 力
sphereBody.applyLocalForce(new CANNON.Vec3(0, 100, 0), new CANNON.Vec3(0, 0, 0))

world.addBody(boxBody)
world.addBody(sphereBody)


function animate() {
  window.requestAnimationFrame(animate)
  world.fixedStep()

  // 伪 ,拿坐标属性
  ThreeJSMeshObject.position.copy(boxBody.position)
}

animate()