1. 引言

碰撞检测是三维场景中常见的需求,Three.js是常用的前端三维JavaScript库,本文就如何在Three.js中进行碰撞检测进行记述

主要使用到的方法有:

  • 射线法Raycaster
  • 包围盒bounding box
  • 物理引擎Cannon.js

2. Raycaster

Raycaster用于进行raycasting(光线投射), 光线投射用于进行鼠标拾取(在三维空间中计算出鼠标移过了什么物体)

在某些情况下也能用于初略的碰撞检测

示例如下:

<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,
body,
canvas {
height: 100%;
width: 100%;
margin: 0;
}
</style> </head> <body>
<canvas id="canvas"></canvas> <script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three/build/three.module.js",
"three/addons/": "https://unpkg.com/three/examples/jsm/"
}
}
</script> <script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import Stats from 'three/addons/libs/stats.module.js' const scene = new THREE.Scene(); const raycaster = new THREE.Raycaster();
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x0000ff });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube); // 创建性能监视器
let stats = new Stats();
// 将监视器添加到页面中
document.body.appendChild(stats.domElement) const canvas = document.querySelector('#canvas');
const camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight, 0.1, 100000);
camera.position.set(0, 0, 10); // 添加环境光
const ambient = new THREE.AmbientLight("#FFFFFF");
ambient.intensity = 5;
scene.add(ambient);
// 添加平行光
const directionalLight = new THREE.DirectionalLight("#FFFFFF");
directionalLight.position.set(0, 0, 0);
directionalLight.intensity = 16;
scene.add(directionalLight); // 添加Box
const box = new THREE.BoxGeometry(1, 1, 1);
const boxMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const boxMesh = new THREE.Mesh(box, boxMaterial);
boxMesh.position.set(6, 0, 0);
scene.add(boxMesh); const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector('#canvas'),
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight, false) const controls = new OrbitControls(camera, renderer.domElement); function animate() {
// 更新帧数
stats.update() boxMesh.position.x -= 0.01; cube.material.color.set(0x0000ff); raycaster.set(boxMesh.position, new THREE.Vector3(-1, 0, 0).normalize());
const intersection = raycaster.intersectObject(cube);
if (intersection.length > 0) {
if (intersection[0].distance < 0.5) {
intersection[0].object.material.color.set(0xff0000);
}
} raycaster.set(boxMesh.position, new THREE.Vector3(1, 0, 0).normalize());
const intersection2 = raycaster.intersectObject(cube);
if (intersection2.length > 0) {
if (intersection2[0].distance < 0.5) {
intersection2[0].object.material.color.set(0xff0000);
}
} requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
</script>
</body> </html>

可以看到,两个立方体在刚接触时和要分开时检测出了碰撞,但是在两个立方体接近重合时却没检测出碰撞

这是因为Raycaster使用的是一根射线来检测,射线需要起点和方向,上述例子中将起点设为绿色立方体的中心,当绿色立方体中心在蓝色立方体内时,就检测不出碰撞了

另外,射线是需要方向的,上述示例中设置为检测左右两个方向,然而方向是难以穷举的,太多的Raycaster也严重损耗性能

所以说,Raycaster在某些情况下也能用于初略的碰撞检测,然而问题是显著的

3. bounding box

bounding box,在Three.js中为Box3类,表示三维空间中的一个轴对齐包围盒(axis-aligned bounding box,AABB)

利用bounding box,可以用来检测物体是否相交(即,碰撞)

示例如下(和Raycaster部分的代码相比只修改了animate函数):

<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,
body,
canvas {
height: 100%;
width: 100%;
margin: 0;
}
</style> </head> <body>
<canvas id="canvas"></canvas> <script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three/build/three.module.js",
"three/addons/": "https://unpkg.com/three/examples/jsm/"
}
}
</script> <script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import Stats from 'three/addons/libs/stats.module.js' const scene = new THREE.Scene(); const raycaster = new THREE.Raycaster();
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x0000ff });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube); // 创建性能监视器
let stats = new Stats();
// 将监视器添加到页面中
document.body.appendChild(stats.domElement) const canvas = document.querySelector('#canvas');
const camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight, 0.1, 100000);
camera.position.set(0, 0, 10); // 添加环境光
const ambient = new THREE.AmbientLight("#FFFFFF");
ambient.intensity = 5;
scene.add(ambient);
// 添加平行光
const directionalLight = new THREE.DirectionalLight("#FFFFFF");
directionalLight.position.set(0, 0, 0);
directionalLight.intensity = 16;
scene.add(directionalLight); // 添加Box
const box = new THREE.BoxGeometry(1, 1, 1);
const boxMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const boxMesh = new THREE.Mesh(box, boxMaterial);
boxMesh.position.set(6, 0, 0);
scene.add(boxMesh); const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector('#canvas'),
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight, false) const controls = new OrbitControls(camera, renderer.domElement); function animate() {
// 更新帧数
stats.update() boxMesh.position.x -= 0.02; const cubeBox = new THREE.Box3().setFromObject(cube);
const boxMeshBox = new THREE.Box3().setFromObject(boxMesh);
cubeBox.intersectsBox(boxMeshBox) ? cube.material.color.set(0xff0000) : cube.material.color.set(0x0000ff); requestAnimationFrame(animate);
renderer.render(scene, camera);
} animate();
</script>
</body> </html>

可以看到,在Three.js中使用bounding box来检测碰撞效果还可以,当然,AABB这种bounding box是将物体用一个立方体或长方体包围起来,如果物体的形状很不规则,那么使用bounding box来检测碰撞可能是不够精细的,比如下面这个例子:

示例中绿色立方体还没撞到蓝色锥体,但是bounding box已经检测出碰撞

所以,利用bounding box来检测物体是否相交是大体可行的

4. Cannon.js

Cannon.js是一个3d物理引擎,它能实现常见的碰撞检测,各种体形,接触,摩擦和约束功能

这里笔者想借助物理引擎来实现碰撞检测,当然,其他的物理引擎(如,Ammo.js,Oimo.js等)也是可以的

使用Cannon.js进行两个Cube的碰撞检测示例如下:

<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,
body,
canvas {
height: 100%;
width: 100%;
margin: 0;
}
</style> </head> <body>
<canvas id="canvas"></canvas> <script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three/build/three.module.js",
"three/addons/": "https://unpkg.com/three/examples/jsm/"
}
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.js"></script> <script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import Stats from 'three/addons/libs/stats.module.js' const scene = new THREE.Scene();
const world = new CANNON.World() // 创建性能监视器
let stats = new Stats();
// 将监视器添加到页面中
document.body.appendChild(stats.domElement) const canvas = document.querySelector('#canvas');
const camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight, 0.1, 100000);
camera.position.set(0, 0, 10); // 添加环境光
const ambient = new THREE.AmbientLight("#FFFFFF");
ambient.intensity = 5;
scene.add(ambient);
// 添加平行光
const directionalLight = new THREE.DirectionalLight("#FFFFFF");
directionalLight.position.set(0, 0, 0);
directionalLight.intensity = 16;
scene.add(directionalLight); // 创建第一个Cube的Three.js模型
const cubeGeometry1 = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial1 = new THREE.MeshBasicMaterial({ color: 0x0000ff });
const cube1 = new THREE.Mesh(cubeGeometry1, cubeMaterial1);
scene.add(cube1); // 创建第一个Cube的Cannon.js刚体
const cubeShape1 = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
const cubeBody1 = new CANNON.Body({ mass: 1, shape: cubeShape1 });
cubeBody1.position.set(1, 0, 0);
world.addBody(cubeBody1); // 创建第二个Cube的Three.js模型
const cubeGeometry2 = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial2 = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube2 = new THREE.Mesh(cubeGeometry2, cubeMaterial2);
scene.add(cube2); // 创建第二个Cube的Cannon.js刚体
const cubeShape2 = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
const cubeBody2 = new CANNON.Body({ mass: 1, shape: cubeShape2 });
cubeBody2.position.set(-1, 0, 0);
world.addBody(cubeBody2); // 监听碰撞事件
cubeBody2.addEventListener("collide", function (e) {
cube2.material.color.set(0xff0000);
}); const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector('#canvas'),
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight, false) const controls = new OrbitControls(camera, renderer.domElement); function animate() {
// 更新帧数
stats.update() world.step(1 / 60); cubeBody1.position.x -= 0.02; // 更新Three.js模型的位置
cube1.position.copy(cubeBody1.position);
cube1.quaternion.copy(cubeBody1.quaternion);
cube2.position.copy(cubeBody2.position);
cube2.quaternion.copy(cubeBody2.quaternion); requestAnimationFrame(animate);
renderer.render(scene, camera);
} animate();
</script>
</body> </html>

至于精确性呢,使用Cannon.js也是不错的,示例如下:

看上去,使用Cannon.js的效果是相当不错的,在追求效果的情况下使用物理引擎是不错的选择,当然,增加的编码成本、计算开销也是不少

5. 参考资料

[1] Raycaster – three.js docs (three3d.cn)

[2] Box3 – three.js docs (threejs.org)

[3] schteppe/cannon.js: A lightweight 3D physics engine written in JavaScript. (github.com)

[4] Three.js - 物体碰撞检测(二十六) - 掘金 (juejin.cn)

[5] Three.js 进阶之旅:物理效果-碰撞和声音 - 掘金 (juejin.cn)

[6] pmndrs/cannon-es: A lightweight 3D physics engine written in JavaScript. (github.com)

[7] Cannon.js -- 3d物理引擎_cannon-es_acqui~Zhang的博客-CSDN博客

Three.js中实现碰撞检测的更多相关文章

  1. 5.0 JS中引用类型介绍

    其实,在前面的"js的六大数据类型"文章中稍微说了一下引用类型.前面我们说到js中有六大数据类型(五种基本数据类型 + 一种引用类型).下面的章节中,我们将详细讲解引用类型. 1. ...

  2. 【repost】JS中的异常处理方法分享

    我们在编写js过程中,难免会遇到一些代码错误问题,需要找出来,有些时候怕因为js问题导致用户体验差,这里给出一些解决方法 js容错语句,就是js出错也不提示错误(防止浏览器右下角有个黄色的三角符号,要 ...

  3. JS中给正则表达式加变量

    前不久同事询问我js里面怎么给正则中添加变量的问题,遂写篇博客记录下.   一.字面量 其实当我们定义一个字符串,一个数组,一个对象等等的时候,我们习惯用字面量来定义,例如: var s = &quo ...

  4. js中几种实用的跨域方法原理详解(转)

    今天研究js跨域问题的时候发现一篇好博,非常详细地讲解了js几种跨域方法的原理,特分享一下. 原博地址:http://www.cnblogs.com/2050/p/3191744.html 下面正文开 ...

  5. 关于js中的this

    关于js中的this this是javascript中一个很特别的关键字,也是一种很复杂的机制,学习this的第一步就是要明白this既不指向函数自身也不指向函数的词法作用域,this实际上是函数被调 ...

  6. 表值函数与JS中split()的联系

    在公司用云平台做开发就是麻烦 ,做了很多功能或者有些收获,都没办法写博客,结果回家了自己要把大脑里面记住的写出来. split()这个函数我们并不陌生,但是当前台有许多字段然后随意勾选后的这些参数传递 ...

  7. JS中 call() 与apply 方法

    1.方法定义 call方法: 语法:call([thisObj[,arg1[, arg2[,   [,.argN]]]]]) 定义:调用一个对象的一个方法,以另一个对象替换当前对象. 说明: call ...

  8. 在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查

    Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用JavaScript开发并且能够用在Node.JS环境中,易于使用,支持多SQL方言(dialect),.它当前支持M ...

  9. 分析js中的constructor 和prototype

    在javascript的使用过程中,constructor 和prototype这两个概念是相当重要的,深入的理解这两个概念对理解js的一些核心概念非常的重要. 我们在定义函数的时候,函数定义的时候函 ...

  10. 如何在Node.js中合并两个复杂对象

    通常情况下,在Node.js中我们可以通过underscore的extend或者lodash的merge来合并两个对象,但是对于像下面这种复杂的对象,要如何来应对呢? 例如我有以下两个object: ...

随机推荐

  1. golang调用sdl2,键盘和鼠标事件

    golang调用sdl2,键盘和鼠标事件 win10 x64下测试成功,其他操作系统下不保证成功. 采用的是syscall方式,不是cgo方式. 见地址 代码用golang编写.代码如下: packa ...

  2. Alist云盘视频加密助手:支持云盘视频文件加密与在线播放,不用再担心视频文件被和谐了!

    在当前娱乐资源丰富的时代,人们每天都在接触各种视频资源.然而,网盘限速.版权审核.视频分级.少儿不宜等问题经常让人感到困扰.如何在保护隐私的前提下,让视频存储和分享变得更加便捷.安全呢?分享一款实用的 ...

  3. python -- pandas常见的一些行、列操作方法(感兴趣的,可以跟着一起练练手)

    这篇文章分享一下pandas常见的一些行.列操作方法,比较基础,感兴趣的童鞋可以看看. 首先,我们用 "random.seed(int i)" 生成一组测试数据. 对于random ...

  4. IntelliJ IDEA上手这一篇就够了,从入门到上瘾

    前言 每次换电脑,最最最头疼的事情莫过于安装各种软件和搭建开发环境.这算是不想换电脑的一个原因吧(最主要还是穷).除非是电脑坏了开不了机或者点一下卡一下,真不想换电脑.每次换电脑都得折腾好久. 趁着这 ...

  5. drf多方式登录接口(手机号、邮箱、验证码)登录

    题目 ##### 3 多方式登录接口#### -使用auth的user表扩写 -用户名+密码 -手机号+密码 -邮箱+密码 -签发token逻辑,放在序列化类中写 方式一: serializer.py ...

  6. .netcore中的虚拟文件EmbeddedFile

    以前一直比较好奇像swagger,cap,skywalking等组件是如何实现引用一个dll即可在网页上展示界面的,难道这么多html,js,css等都是硬编码写死在代码文件中的?后面接触apb里面也 ...

  7. 【C#/.NET】xUnit和Moq实现TDD

    ​  目录 前置条件 Moq xUnit TDD 实践 创建项目 红灯 绿灯 重构 单元测试一些最佳实践 总结 前置条件 Moq 安装Moq包 Install-Package Moq Moq是一个Mo ...

  8. Solon 成为信通院可信开源社区、可信开源项目

    自2021年9月17日成立以来,可信开源社区共同体共有五批新成员加入.在4月21日"OSCAR开源生态建设论坛"上,可信开源社区共同体又迎来2位正式成员和6位预备成员,Solon ...

  9. global average pooling

    首先需要对深度网络中常见的pooling方式,以及全连接层有大致的了解.(此处略过不提.) paper: Network in Network fully connected layer 的缺点 在N ...

  10. 教程 | Datavines 自定义数据质量检查规则(Metric)

    Metric 是 Datavines 中一个核心概念,一个 Metric 表示一个数据质量检查规则,比如空值检查和表行数检查都是一个规则.Metric 采用插件化设计,用户可以根据自己的需求来实现一个 ...