Three.js: инструмент выбора Как обнаружить пересечение 2D-квадрата и 3D-объектов

В основном, что я хочу создать:

У меня есть 3D-карта с объектами, я хочу выбрать все объекты, которые находятся в 2D-поле от x1, y1 до x2, y2 на моем экране.

Любые идеи, как это должно быть сделано, потому что я не знаю, как начать.

Заранее спасибо!

prevX и prevY - это координата мыши вниз:

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;
    }
  }
}

person Captain Obvious    schedule 04.12.2014    source источник


Ответы (3)


Пересечение прямоугольника в экранном пространстве эквивалентно пересечению пирамиды (перспектива) или куба (ортогональный вид) в трехмерном пространстве. Я думаю, вы должны определить THREE.Frustum на основе вашего 2D-бокса.

Для перспективной камеры:

  1. преобразовать координаты угла окна в пространстве экрана в 3D-векторы (вектор от положения камеры до заданной точки)

var topLeftCorner3D = new THREE.Vector3(topLeftCorner2D.x, topLeftCorner2D.y, 1).unproject(camera);

  1. построим по этим точкам 4 плоскости (по одной плоскости на одну сторону коробки). Третья точка плоскости — это положение камеры.

TopPlane.SetFromcoplanarpoints (Camera.Position, TopleftCorner3D, TOPRightcorner3D) RightPlane.SetFromcornernArappoints (Camera.position, TOPRightcorner3D, DoteRightcorner3D) Noneplane.etromcorner3D) (Camera.Position, BendRightcorner3D, undeleftcorner3D) (камера.

  1. Постройте усеченный конус на основе этих плоскостей, добавив к ним ближнюю и дальнюю плоскости камеры.

var frustum = new THREE.Frustum(верхняя плоскость, нижняя плоскость, левая плоскость, правая плоскость, ближняя плоскость, дальняя плоскость);

  1. Пересечение усеченного конуса с объектами

frustum.intersectsBox(object.geometry.boundingBox)

or

frustum.intersectsSphere(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;  
}
person elcsiga    schedule 05.12.2014
comment
Выглядит отлично, пойду пробовать! - person Captain Obvious; 05.12.2014
comment
Не могли бы вы предоставить 6 самолетов, которые мне нужны, и как их построить, потому что я не понимаю. Спасибо - person Captain Obvious; 07.12.2014
comment
я не могу заставить его работать, вы не против привести пример jsbin? Я обновил свой ответ тем, что у меня есть - person Captain Obvious; 08.12.2014

Вы можете использовать ограничивающие рамки объектов. THREE.Geometry содержит свойство boundingBox. Это null по умолчанию, вы должны вычислить его, вызвав computeBoundingBox() напрямую.

scene.traverse( function ( node ) {
    if ( node.geometry )
         node.geometry.computeBoundingBox();
} );

После того, как у вас есть ограничивающие рамки, 2D-координаты ограничивающей рамки (если «y» является осью UP):

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.

(однако, если вам нужен точный результат для сеток непрямоугольной формы, вам потребуются дополнительные вычисления)

person elcsiga    schedule 04.12.2014
comment
Спасибо, завтра проверю. Однако я не уверен, что это то, что я хочу? Допустим, у меня есть поддельный прямоугольник в моем окне от (100 100) до (200 200), как я могу проверить, какие 3D-объекты видны в этом прямоугольнике на моем экране? - person Captain Obvious; 05.12.2014
comment
Извините, я проигнорировал, что у вас есть 2d-бокс в пространстве экрана. В этом случае, я думаю, вам следует попробовать «отбор усеченной пирамиды». См.: stackoverflow.com/questions/16396874/ - person elcsiga; 05.12.2014

Чтобы узнать о более точном методе, чем выбор под усеченным конусом, см. выбор бегущей строки с тремя js . Он работает, проецируя 8 углов ограничивающих рамок на пространство экрана, а затем пересекая прямоугольник экрана. см. также здесь третий более быстрый метод (написанный на ClojureScript), который проецирует ограничивающую трехмерную сферу на ограничивающий круг в пространстве экрана, а также реализации первых двух методов. См. также соответствующий вопрос о переполнении стека, который пытается использовать GPU для выполнить проецирование экрана и выбор с помощью пикселов чтения (я не изучал этот метод, так как предполагаю, что пиксели чтения слишком сильно остановят конвейер рендеринга, но дайте мне знать, если я ошибаюсь).

Я сделал рабочий пример выбора подусеченной пирамиды по адресу 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 );
person emh    schedule 10.05.2016
comment
Хорошо, однако, когда я пробую демоверсию, она кажется не очень точной? - person Captain Obvious; 10.05.2016
comment
@CaptainObvious Это правда. Для точного, но совершенно другого метода, который не использует усеченную вершину, см. blog.tempt3d.com/2013/11/marquee-selection-with-threejs.html . - person emh; 11.05.2016
comment
Спасибо, действительно то, что я искал, я проверю - person Captain Obvious; 11.05.2016
comment
@CaptainObvious Кстати, я реализовал 3 разных метода на github.com/emnh/rts/blob/master/src.client/game/client/ , но это ClojureScript, а не JavaScript. Я реализовал метод subfrustum, который, как и вы, я обнаружил слишком неточным, хотя и быстрым, и я реализовал второй метод, который я связал в предыдущем комментарии, который немного медленный, поскольку он проецирует все 8 углов ограничивающей рамки в пространство экрана, а затем находит ограничивающий прямоугольник и третий быстрый метод, который проецирует ограничивающую трехмерную сферу на ограничивающий круг в пространстве экрана. Я выбрал третий способ для своей игры. - person emh; 11.05.2016
comment
@emh Я попробовал решение subfrustum на трехмерной сфере (github.com/dataarts/webgl-globe) для визуализации землетрясений и это очень неточно. Есть ли в настоящее время лучшее решение или по-прежнему рекомендуется использовать выделение бегущей строки? Спасибо за любую помощь. - person vicR; 29.12.2018
comment
@vicR Я не помню, к какому решению я пришел, какое-то время не участвовал в этом проекте. Проверьте github.com/emnh/rts/blob /master/src.client/game/client/. IIRC В итоге я проецировал каждую сферу, ограничивающую 3D-объект, на круг экрана, а затем пересекал его прямоугольником выбора. Также сделано решение для графического процессора, я думаю, в engine2_selection.cljs. Извините за поздний ответ. - person emh; 09.11.2019