三.js:“选择工具” 如何检测二维正方形和三维物体的交集

Three.js: "select tool" How to detect the intersection of a 2D square and 3D objects

本文关键字:正方形 二维 三维 检测 选择 js 工具 选择工具 何检测      更新时间:2023-09-26

基本上是我想创建的:

我有一个带有对象的 3D 地图,我想选择屏幕上 2D 框中 x1,y1 到 x2,y2 中的所有对象。

任何想法必须如何完成,因为我对如何开始一无所知。

提前感谢!

prevXprevY是鼠标按下的坐标:

function onDocumentMouseUp(event) {
  event.preventDefault();
  var x = (event.clientX / window.innerWidth) * 2 - 1;
  var y = -(event.clientY / window.innerHeight) * 2 + 1;
  var width = (x - prevX); //* window.innerWidth;
  var height = (y - prevY); //* window.innerHeight;
  var dx = prevX; //* window.innerWidth;
  var dy = prevY; //* window.innerHeight;
  console.log(
    dx + ',' + 
    dy + "," + 
    (dx + width) + "," + 
    (dy + height) + 
    ", width=" + width + 
    ", height=" + height
  );
  var topLeftCorner3D = new THREE.Vector3(dx, dy, 1).unproject(
    camera);
  var topRightCorner3D = new THREE.Vector3(dx + width, dy, 1)
    .unproject(camera);
  var bottomLeftCorner3D = new THREE.Vector3(dx, dy + height,
    1).unproject(camera);
  var bottomRightCorner3D = new THREE.Vector3(dx + width, dy +
    height, 1).unproject(camera);
  var topPlane = new THREE.Plane();
  var rightPlane = new THREE.Plane();
  var bottomPlane = new THREE.Plane();
  var leftPlane = new THREE.Plane();
  topPlane.setFromCoplanarPoints(camera.position,
    topLeftCorner3D, topRightCorner3D);
  rightPlane.setFromCoplanarPoints(camera.position,
    topRightCorner3D, bottomRightCorner3D);
  bottomPlane.setFromCoplanarPoints(camera.position,
    bottomRightCorner3D, bottomLeftCorner3D);
  leftPlane.setFromCoplanarPoints(camera.position,
    bottomLeftCorner3D, topLeftCorner3D);
  //var frustum = new THREE.Frustum( topPlane, bottomPlane, leftPlane, rightPlane, nearPlane, farPlane);
  function isObjectInFrustum(object3D) {
    var sphere = object3D.geometry.boundingSphere;
    var center = sphere.center;
    var negRadius = -sphere.radius;
    if (topPlane.distanceToPoint(center) < negRadius) { return false; }
    if (bottomPlane.distanceToPoint(center) < negRadius) { return false; }
    if (rightPlane.distanceToPoint(center) < negRadius) { return false; }
    if (leftPlane.distanceToPoint(center) < negRadius) { return false; }
    return true;
  }
  var matches = [];
  for (var i = 0; i < window.objects.length; i++) {
    if (isObjectInFrustum(window.objects[i])) {
      window.objects[i].material = window.selectedMaterial;
    }
  }
}

在屏幕空间中与框相交等效于与 3D 空间中的金字塔(透视(或立方体(正交视图(相交。我认为您应该根据2D框定义THREE.Frustum

对于透视相机:

  1. 将屏幕空间框角坐标转换为 3D 矢量(从相机位置到给定点的矢量(

var topLeftCorner3D = new THREE。Vector3( topLeftCorner2D.x, topLeftCorner2D.y, 1 (.unproject( 相机(;

  1. 根据这些点构建 4 个平面(一个平面用于一个盒子侧(。飞机的第三点是相机位置。

topPlane.setFromCoplanarPoints (camera.position, topLeftCorner3D , topRightCorner3D ( rightPlane.setFromCoplanarPoints (camera.position, topRightCorner3D , bottomRightCorner3D ( bottomPlane.setFrom共面点 (camera.position,bottomRightCorner3D , bottomLeftCorner3D ( leftPlane.setFromCoplanarPoints (camera.position, bottomLeftCorner3D , topLeftCorner3D (

    在这些平面
  1. 上构建一个基于视锥体的平面,将相机的近平面和远平面添加到其中。

var 视锥体 = 新的三个。视锥体( topPlane, bottomPlane, leftPlane, rightPlane, nearPlane, farPlane(;

  1. 将视锥体与物体相交

frustum.intersectsBox(object.geometry.boundingBox(

视锥体.相交球体(object.geometry.boundingSphere(

使用 Frustum 对象本身的另一种选择:您可以跳过近平面和远平面,您只需检查对象是否在与 4 个平面接壤的空间中。你可以编写一个像Frustum.intersectSphere()方法一样只有 4 个平面的函数:

function isObjectInFrustum(object3D) {
    var sphere = object3D.geometry.boundingSphere;
    var center = sphere.center;
    var negRadius = - sphere.radius;
    if ( topPlane.distanceToPoint( center )< negRadius ) return false;
    if ( bottomPlane.distanceToPoint( center )< negRadius ) return false;
    if ( rightPlane.distanceToPoint( center )< negRadius ) return false;
    if ( leftPlane.distanceToPoint( center )< negRadius ) return false;
    return true;  
}

您可以使用对象的边界框。 THREE.Geometry包含一个boundingBox属性。默认情况下,这是null,您应该通过直接调用computeBoundingBox()来计算它。

scene.traverse( function ( node ) {
    if ( node.geometry )
         node.geometry.computeBoundingBox();
} );
有了边界框

后,边界框 2D 坐标为(如果"y"是向上轴(:

mesh.geometry.boundingBox.min.x
mesh.geometry.boundingBox.min.z
mesh.geometry.boundingBox.max.x
mesh.geometry.boundingBox.max.z

请参阅 http://threejs.org/docs/#Reference/Core/Geometry

(但是,如果您需要具有非矩形形状的网格的确切结果,则需要进一步计算(

有关比子截锥体选择更准确的方法,请参阅具有三个 js 的选框选择。它的工作原理是将边界框的 8 个角投影到屏幕空间上,然后与屏幕矩形相交。另请参阅此处的第三个更快方法(用 ClojureScript 编写(,该方法将 3D 边界球投影到屏幕空间中的边界圆上,以及前两个方法的实现。另请参阅相关的堆栈溢出问题,该问题尝试使用 GPU 执行屏幕投影和使用读取像素进行拾取(我没有研究这种方法,因为我认为 readpixel 会使渲染管道停滞太多,但如果我错了,请告诉我(。

我在 http://jsbin.com/tamoce/3/做了一个亚截锥体选择的工作示例,但它太不准确了。重要的部分是:

          var rx1 = ( x1 / window.innerWidth ) * 2 - 1;
          var rx2 = ( x2 / window.innerWidth ) * 2 - 1;
          var ry1 = -( y1 / window.innerHeight ) * 2 + 1;
          var ry2 = -( y2 / window.innerHeight ) * 2 + 1;
          var projectionMatrix = new THREE.Matrix4();
          projectionMatrix.makeFrustum( rx1, rx2, ry1, ry2, camera.near, camera.far );
          camera.updateMatrixWorld();
          camera.matrixWorldInverse.getInverse( camera.matrixWorld );
          var viewProjectionMatrix = new THREE.Matrix4();
          viewProjectionMatrix.multiplyMatrices( projectionMatrix, camera.matrixWorldInverse );
          var frustum = new THREE.Frustum();
          frustum.setFromMatrix( viewProjectionMatrix );