1. 概述
之前的文章提到,在Three.js中使用InstanceMesh来实现性能优化,可以实现单个Mesh的拾取功能
那,能不能实现碰撞检测呢?肯定是可以的,不过Three.js中并没有直接的API可以实现对InstanceMesh的碰撞检测,需要手动实现
回顾本文的描述的Three.js的场景前提:
- 使用InstanceMesh来构建数量众多的桥柱,这些柱子都是圆柱且材质相同
- 使用一个初始圆柱和一系列的变化矩阵,构建了这个场景
- 有的桥柱是直立的,有的桥柱是倾斜的
本文所采用的方法如下:
- 场景初始加载时,通过初始圆柱和变换矩阵,计算每个桥柱的三维包围盒从而计算二维包围盒(XZ平面上)
- 每一帧分为两轮检测,第一次为粗检测,第二次为细检测
- 每一帧计算待碰撞物体(假设为船)的三维包围盒从而计算二维包围盒(XZ平面上)
- 粗检测阶段,判断桥柱的二维包围盒和船的二维包围盒是否相交,相交则进入细检测阶段,否则判定不相交
- 细检测阶段,将船的包围盒(假设为长方体)的顶点进行逆变换,变换矩阵为待检测的这个桥柱的变换矩阵,求出逆变换后的长方体的六个顶点在XZ平面上的最大多边形,判断这个多边形是否于初始柱子的二维包围盒是否相交
详细内容如下
2. 初始场景加服务器托管网载
在场景加载时,通过初始圆柱和变换矩阵,计算每个桥柱的三维包围盒从而计算XZ平面上的二维包围盒
for (let index = 0; index ();
for (let i = 0; i
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, pillarBox: THREE.Box2, matrix: THREE.Matrix4) {
const shipPosMatrixedList = new Array();
const shipPosListScalared = shipPosList.map(vector3 => vector3.clone().multiplyScalar(1000));
for (let i = 0; i 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(sortedPoin服务器托管网ts[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();
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();
const vertices = positionAttribute.array
for (let j = 0; j
6. 进一步优化
在实测中,上述这种方式运行起来还算流畅,主要是因为细检测虽然消耗性能但是只执行少数几次,粗检测则几乎只是比大小,参考下面的Three.js中Box2.js
的源码:
intersectsBox( box ) {
// using 4 splitting planes to rule out intersections
return box.max.x this.max.x ||
box.max.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)
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
相关推荐: MongoDB简介3、MongoDB特点一、MongoDB安装(docker方式)二、MongoDB安装(普通方式)
目录 1、NoSQL概述 2、什么是MongoDB 3、MongoDB特点 一、MongoDB安装(docker方式) 二、MongoDB安装(普通方式) 三、MongoDB 概念解析 1、NoSQL概述 NoSQL(NoSQL = Not Only SQL)…