Three.js中实现对InstanceMesh的碰撞检测
1. 概述
之前的文章提到,在Three.js中使用InstanceMesh来实现性能优化,可以实现单个Mesh的拾取功能
那,能不能实现碰撞检测呢?肯定是可以的,不过Three.js中并没有直接的API可以实现对InstanceMesh的碰撞检测,需要手动实现
回顾本文的描述的Three.js的场景前提:
- 使用InstanceMesh来构建数量众多的桥柱,这些柱子都是圆柱且材质相同
- 使用一个初始圆柱和一系列的变化矩阵,构建了这个场景
- 有的桥柱是直立的,有的桥柱是倾斜的
本文所采用的方法如下:
- 场景初始加载时,通过初始圆柱和变换矩阵,计算每个桥柱的三维包围盒从而计算二维包围盒(XZ平面上)
- 每一帧分为两轮检测,第一次为粗检测,第二次为细检测
- 每一帧计算待碰撞物体(假设为船)的三维包围盒从而计算二维包围盒(XZ平面上)
- 粗检测阶段,判断桥柱的二维包围盒和船的二维包围盒是否相交,相交则进入细检测阶段,否则判定不相交
- 细检测阶段,将船的包围盒(假设为长方体)的顶点进行逆变换,变换矩阵为待检测的这个桥柱的变换矩阵,求出逆变换后的长方体的六个顶点在XZ平面上的最大多边形,判断这个多边形是否于初始柱子的二维包围盒是否相交
详细内容如下
2. 初始场景加载
在场景加载时,通过初始圆柱和变换矩阵,计算每个桥柱的三维包围盒从而计算XZ平面上的二维包围盒
for (let index = 0; index < matrixList.length; index++) {
const matrix = matrixList[index];
const positionAttribute = geo.getAttribute("position") as THREE.BufferAttribute;
const vertices = positionAttribute.array;
const box = new THREE.Box3().setFromBufferAttribute(positionAttribute);
const points = new Array<THREE.Vector3>();
for (let i = 0; i < vertices.length; i += 3) {
const vertex = new THREE.Vector3(vertices[i], vertices[i + 1], vertices[i + 2]);
vertex.applyMatrix4(matrix);
points.push(vertex);
}
box.setFromPoints(points);
box2dList.push(new THREE.Box2(new THREE.Vector2(box.min.x, box.min.z), new THREE.Vector2(box.max.x, box.max.z)));
}
3. 粗检测
粗检测函数较为简单,判断桥柱的二维包围盒和船的二维包围盒是否相交
function roughDetectionCollided(shipBox2d: THREE.Box2, pillarBox2d: THREE.Box2) {
return shipBox2d.intersectsBox(pillarBox2d);
}
注意,此处使用的是包围盒(矩形),而桥柱在XZ平面上应是圆形,在精度要求较高时应使用圆形判断而不是矩形
function testBoxBox(pillarBox: THREE.Box2, shipBox: THREE.Box2) {
const pillarBoxCenter = pillarBox.getCenter(new THREE.Vector2());
const pillarBoxSize = pillarBox.getSize(new THREE.Vector2());
const circle = new SAT.Circle(new SAT.Vector(pillarBoxCenter.x, pillarBoxCenter.y), pillarBoxSize.x / 2);
const box = new SAT.Polygon(new SAT.Vector(), [
new SAT.Vector(shipBox.min.x, shipBox.min.y),
new SAT.Vector(shipBox.max.x, shipBox.min.y),
new SAT.Vector(shipBox.max.x, shipBox.max.y),
new SAT.Vector(shipBox.min.x, shipBox.max.y)
]);
return SAT.testPolygonCircle(box, circle);
}
- 注:这里使用了SAT库进行二维碰撞检测,地址:SAT.js (jriecken.github.io)
4. 细检测
细检测函数较为复杂,将船的包围盒(假设为长方体)的顶点进行逆变换,变换矩阵为待检测的这个桥柱的变换矩阵,求出逆变换后的长方体的六个顶点在XZ平面上的最大多边形,判断这个多边形是否于初始柱子的二维包围盒是否相交
function fineDetectionCollided(shipPosList: Array<THREE.Vector3>, pillarBox: THREE.Box2, matrix: THREE.Matrix4) {
const shipPosMatrixedList = new Array<THREE.Vector3>();
const shipPosListScalared = shipPosList.map(vector3 => vector3.clone().multiplyScalar(1000));
for (let i = 0; i < shipPosListScalared.length; i++) {
const transformedVector = new THREE.Vector3().copy(shipPosListScalared[i]).applyMatrix4(matrix.clone().invert());
shipPosMatrixedList.push(transformedVector);
}
const points = shipPosMatrixedList.map((pos) => new Point(pos.x, pos.z));
const selectedPoints: Point[] = [];
const maxArea: number[] = [0];
const result: Point[] = [];
findLargestPolygon(points, selectedPoints, maxArea, result);
const sortedPoints = sortPointsInCounterClockwiseOrder(result);
const satShipPolygon = new SAT.Polygon(new SAT.Vector(), [
new SAT.Vector(sortedPoints[0].x, sortedPoints[0].y),
new SAT.Vector(sortedPoints[1].x, sortedPoints[1].y),
new SAT.Vector(sortedPoints[2].x, sortedPoints[2].y),
new SAT.Vector(sortedPoints[3].x, sortedPoints[3].y),
new SAT.Vector(sortedPoints[4].x, sortedPoints[4].y),
new SAT.Vector(sortedPoints[5].x, sortedPoints[5].y),
]);
const pillarBoxCenter = pillarBox.getCenter(new THREE.Vector2());
const pillarBoxSize = pillarBox.getSize(new THREE.Vector2());
const circle = new SAT.Circle(new SAT.Vector(pillarBoxCenter.x, pillarBoxCenter.y), pillarBoxSize.x / 2);
return SAT.testPolygonCircle(satShipPolygon, circle);
}
5. 碰撞检测
最后,在场景每一帧更新时,调用碰撞检测函数,碰撞检测函数则是先调用粗检测函数,粗检测相交后再调用细检测函数
function detectionCollided() {
const ship = scene.getObjectByName(ModelName.Ship);
const collidedIndex = new Array<number>();
if (!ship) return collidedIndex;
const shipBox3d = new THREE.Box3().setFromObject(ship);
const shipBox2d = new THREE.Box2().setFromPoints([new THREE.Vector2(shipBox3d.min.x, shipBox3d.min.z), new THREE.Vector2(shipBox3d.max.x, shipBox3d.max.z)]);
box2dList.forEach((pillarBox2d, i) => {
if (roughDetectionCollided(shipBox2d, pillarBox2d)) {
const matrix = matrixList[i]
const positionAttribute = geo.getAttribute("position") as THREE.BufferAttribute;
const points = new Array<THREE.Vector3>();
const vertices = positionAttribute.array
for (let j = 0; j < vertices.length; j += 3) {
const vertex = new THREE.Vector3(vertices[j], vertices[j + 1], vertices[j + 2]);
points.push(vertex);
}
const box3d = new THREE.Box3().setFromPoints(points);
const box2d = new THREE.Box2().setFromPoints([new THREE.Vector2(box3d.min.x, box3d.min.z), new THREE.Vector2(box3d.max.x, box3d.max.z)])
const minPoint = shipBox3d.min;
const maxPoint = shipBox3d.max;
const shipPos = [
new THREE.Vector3(minPoint.x, minPoint.y, minPoint.z),
new THREE.Vector3(minPoint.x, minPoint.y, maxPoint.z),
new THREE.Vector3(minPoint.x, maxPoint.y, minPoint.z),
new THREE.Vector3(minPoint.x, maxPoint.y, maxPoint.z),
new THREE.Vector3(maxPoint.x, minPoint.y, minPoint.z),
new THREE.Vector3(maxPoint.x, minPoint.y, maxPoint.z),
new THREE.Vector3(maxPoint.x, maxPoint.y, minPoint.z),
new THREE.Vector3(maxPoint.x, maxPoint.y, maxPoint.z)
];
if (Math.abs(pillarBox2d.max.x - pillarBox2d.min.x - pillarBox2d.max.y + pillarBox2d.min.y) < 1e-10) {
if (testBoxBox(pillarBox2d, shipBox2d)) collidedIndex.push(i);
} else if (fineDetectionCollided(shipPos, box2d, matrix)) {
collidedIndex.push(i);
}
}
});
return collidedIndex;
}
6. 进一步优化
在实测中,上述这种方式运行起来还算流畅,主要是因为细检测虽然消耗性能但是只执行少数几次,粗检测则几乎只是比大小,参考下面的Three.js中Box2.js的源码:
intersectsBox( box ) {
// using 4 splitting planes to rule out intersections
return box.max.x < this.min.x || box.min.x > this.max.x ||
box.max.y < this.min.y || box.min.y > this.max.y ? false : true;
}
这里提出三个优化方向:
- 使用Web Worker来开启新线程进行计算,将计算过程抽离主线程,保证绘制、交互的流畅
- 使用空间划分,如BVH,将包围盒进行划分,能有效减少碰撞检测时的检测次数,而不必每个包围盒都检测一次
- 使用OBB进行简化代码,Three.js中支持OBB,和上述代码中采用的AABB式包围盒相比,OBB式包围盒更好地支持矩阵变换
7. 参考资料
[1] SAT.js (jriecken.github.io)
[2] InstancedMesh – three.js docs (threejs.org)
[3] Three.js使用InstancedMesh实现性能优化 - 当时明月在曾照彩云归 - 博客园 (cnblogs.com)
Three.js中实现对InstanceMesh的碰撞检测的更多相关文章
- js中实现对checkbox选中和取消
可以使用 element.attr('checked','checked') 来进行选中.但是不能使用 element.attr('checked','false') 来取消选中. 必须通过以下方式: ...
- 在应用程序中实现对NandFlash的操作
以TC58NVG2S3ETA00 为例: 下面是它的一些物理参数: 图一 图二 图三 图四 图五 图6-0 图6-1 说明一下,在图6-1中中间的那个布局表可以看做是实际的NandFlash一页数据的 ...
- C++中实现对map按照value值进行排序 - 菜鸟变身记 - 51CTO技术博客
C++中实现对map按照value值进行排序 - 菜鸟变身记 - 51CTO技术博客 C++中实现对map按照value值进行排序 2012-03-15 15:32:36 标签:map 职场 休闲 排 ...
- Android平台中实现对XML的三种解析方式
本文介绍在Android平台中实现对XML的三种解析方式. XML在各种开发中都广泛应用,Android也不例外.作为承载数据的一个重要角色,如何读写XML成为Android开发中一项重要的技能. 在 ...
- Python中实现对list做减法操作介绍
Python中实现对list做减法操作介绍 这篇文章主要介绍了Python中实现对list做减法操作介绍,需要的朋友可以参考下 问题描述:假设我有这样两个list, 一个是list1,list1 = ...
- WPF: 在 MVVM 设计中实现对 ListViewItem 双击事件的响应
ListView 控件最常用的事件是 SelectionChanged:如果采用 MVVM 模式来设计 WPF 应用,通常,我们可以使用行为(如 InvokeCommandAction)并结合命令来实 ...
- ios中实现对UItextField,UITextView等输入框的字数限制
本文转载至 http://blog.sina.com.cn/s/blog_9bf272cf01013lsd.html 2011-10-05 16:48 533人阅读 评论(0) 收藏 举报 1. ...
- 在jQuery EasyUI中实现对DataGrid进行编辑
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/ ...
- C#中实现对Excel特定文本的搜索
打开Excel的VBA帮助,查看Excel的对象模型,很容易找到完成这个功能需要的几个集合和对象: Application.Workbooks. Workbook.Worksheets还有Worksh ...
- Android中实现对/system/bin/surfaceflinger进程进行拦截和注入
对于Android for arm上的so注入(inject)和挂钩(hook),网上已有牛人给出了代码inject.由于实现中的ptrace函数是依赖于平台的,所以不经改动只能用于arm平台.本文将 ...
随机推荐
- 2023年最新sentinel-dashbord部署安装(保姆级别)
目录 Sentinel-dashboard安装下载 前景提要 一. 构建环境 二.下载安装与配置 1.进入百度搜索:Sentinel 或访问地址:面向云原生微服务的高可用流控防护组件 2.进入git主 ...
- 使用EasyExcel对excel数据进行相似度判断
@Data public class ExeclDto { /** * execl表 */ private String filename; /** * 需要匹配的工作表名 */ private St ...
- 3 大数据实战系列-spark shell分析日志
1 准备数据源 文件格式: 访问时间\t用户ID\t[查询词]\t该URL在返回结果中的排名\t用户点击的顺序号\t用户点击URL 数据文件越大越好,至少100万行 2 启动任务 ./spark-sh ...
- CDI的概念理解
1.CDI是什么?目的和作用是什么? 概念(是什么):是JavaEE 6标准中一个规范, 作用(干什么): 它提供了Java EE平台上服务注入的组件管理核心,简化应该是CDI的目标,让一切都可以被注 ...
- 知识图谱之《海贼王-ONEPICE》领域图谱项目实战(含码源):数据采集、知识存储、知识抽取、知识计算、知识应用、图谱可视化、问答系统(KBQA)等
知识图谱之<海贼王-ONEPICE>领域图谱项目实战(含码源):数据采集.知识存储.知识抽取.知识计算.知识应用.图谱可视化.问答系统(KBQA)等 实体关系可视化页面可视化页面尝鲜 1. ...
- bitfield
bitfield 作用 位域修改 溢出控制 原理 通过对redis字符串二进制形式进行操作,通过改变其值的作用 更具体 将一个Redis字符串看作是一个由二进制位组成的数组. 并能对变长位宽和任意没有 ...
- Linux学习环境搭建(VMware虚拟机安装Linux)
企业现状 目前绝大多数企业运维人员的工作环境都是Windows下通过SSH工具(如XShell等)远程连接千百里外的服务器进行管理和维护的. 而且学Linux运维,99.9%知识与硬件无关,用虚拟机足 ...
- 论文解读(Moka‑ADA)《Moka‑ADA: adversarial domain adaptation with model‑oriented knowledge adaptation for cross‑domain sentiment analysis》
Note:[ wechat:Y466551 | 可加勿骚扰,付费咨询 ] 论文信息 论文标题:Moka‑ADA: adversarial domain adaptation with model‑o ...
- 从Element日期组件源码中学到的两个工具方法
最近翻到 ElementUI 的日期组件源码,看到一些处理日期的工具方法,挺有意思,平常没有注意到,特此记录下来. 获取当前日期的前一天,后一天 export const prevDate = fun ...
- vite 找不到依赖模块:[plugin:vite:dep-pre-bundle]
问题描述: 运行项目时,出现[plugin:vite:dep-pre-bundle] 错误.这种问题一般为依赖的包未正常配置相关字段,导致vite无法找到包的入口. 遇到这种模块内.找不到引用模块的, ...