首页 > 其他分享 >Three.js中实现对InstanceMesh的碰撞检测

Three.js中实现对InstanceMesh的碰撞检测

时间:2023-09-20 19:14:31浏览次数:41  
标签:const InstanceMesh min max THREE Three 碰撞检测 new SAT

1. 概述

之前的文章提到,在Three.js中使用InstanceMesh来实现性能优化,可以实现单个Mesh的拾取功能

那,能不能实现碰撞检测呢?肯定是可以的,不过Three.js中并没有直接的API可以实现对InstanceMesh的碰撞检测,需要手动实现

回顾本文的描述的Three.js的场景前提:

  • 使用InstanceMesh来构建数量众多的桥柱,这些柱子都是圆柱且材质相同
  • 使用一个初始圆柱和一系列的变化矩阵,构建了这个场景
  • 有的桥柱是直立的,有的桥柱是倾斜的

本文所采用的方法如下:

  1. 场景初始加载时,通过初始圆柱和变换矩阵,计算每个桥柱的三维包围盒从而计算二维包围盒(XZ平面上)
  2. 每一帧分为两轮检测,第一次为粗检测,第二次为细检测
  3. 每一帧计算待碰撞物体(假设为船)的三维包围盒从而计算二维包围盒(XZ平面上)
  4. 粗检测阶段,判断桥柱的二维包围盒和船的二维包围盒是否相交,相交则进入细检测阶段,否则判定不相交
  5. 细检测阶段,将船的包围盒(假设为长方体)的顶点进行逆变换,变换矩阵为待检测的这个桥柱的变换矩阵,求出逆变换后的长方体的六个顶点在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);
}

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)

标签:const,InstanceMesh,min,max,THREE,Three,碰撞检测,new,SAT
From: https://www.cnblogs.com/jiujiubashiyi/p/17718115.html

相关文章

  • Three.js——八、坐标、更改模型原点、移除、显示隐藏模型对象
    世界坐标.getWorldPosition()基础坐标也就是模型的.position属性世界坐标:就是模型资深.position和所有父对象.position累加的坐标用.getWorldPosition()属性需要用三维向量表示摸个坐标后方可读取例如:constgeometry=newTHREE.BoxGeometry(100,100,100);constmaterial......
  • threejs 4纹理
     ......
  • threejs 5材质
     ......
  • 使用threejs创建一个长方体
    //创建设备正方体//x1X轴坐标y1Y轴坐标item设备的信息可以把item嵌入到正方体里面h:高度privateinitQuare1(x1:any,y1:any,item:any,h:any){vargeometry=newTHREE.BoxGeometry(0.5,1,0.5);//确定颜色根据设备的状态动态显示正方体......
  • Three——四、几何体、高光网络材质、锯齿模糊以及GUI库的使用
    Threejs常见几何体简介Three.js常见的几何体:常见的几何体://BoxGeometry:长方体constgeometry=newTHREE.BoxGeometry(100,100,100);//SphereGeometry:球体constgeometry=newTHREE.SphereGeometry(50);//CylinderGeometry:圆柱constgeometry=newTHREE.CylinderGe......
  • Three ways to conditionally remove variables in a dataset
    Method1:proccontentsdata=carsshortout=outds00;run;dataoutds(keep=name);setoutds00;vnam=substr(name,1,1);ifvnamne"M"thenoutput;run;proctransposedata=outdsout=outds01(drop=_name__label_);idname;run;dataoutds02(drop=......
  • CF1335E1 Three Blocks Palindrome (easy version)
    思路发现一个进阶回文序列仅包含三个部分:\(x\)个连续的\(a\),\(y\)个连续的\(b\),\(x\)个连续的\(a\)。对于一个\(a\),我们一定会取最外面的两个\(a\),如果不取,则答案一定不小或不变,所以我们枚举到\(a\)的时候,一定是确定了最外围的两个\(a\)的位置。接下来再枚举\(x\)......
  • 震惊!CSS 也能实现碰撞检测?
    本文,我们将一起学习,使用纯CSS,实现如下所示的动画效果:上面的动画效果,非常有意思,核心有两点:小球随机做X、Y方向的直线运动,并且能够实现碰撞到边界的时候,实现反弹效果小球在碰撞边界的瞬间,颜色发生随机的变化嗯?很有意思的效果。看上去,我们好像使用CSS实现了碰撞检测。然......
  • Threejs用官方提供的编辑器做一个简单的模型
    Threejs有提供一个web端的编辑器制作3D模型,地址是https://threejs.org/editor/,这个打开就可以在线编辑,但是因为比较简陋,所以只能做一些简单的模型用于测试,实际开发中还是用blender这种标准的建模软件来建模,首先打开页面可以看到下面的画面,(是英文版本的,不过单词都比较简单,可以凑合看......
  • Rocky虚拟机(Three Days)用户与组管理与目录/文件权限
    ThreeDays一、用户管理1、概述Linux系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这个账号的身份进入系统用户的账号一方面可以帮助系统管理员对使用系统的用户进行跟踪,并控制他们对系统资源的访问;另一方......