Three.js: "Identifying Tool" How to recognize the overlap between a 2D square and 3D elements

I am looking to achieve the following:

I have a 3D map with objects, and I need to select all objects that fall within a specific 2D box defined by coordinates x1,y1 and x2,y2 on my screen.

I am unsure of how to approach this task and would appreciate any guidance or ideas on how to get started.

Thank you in advance!

prevX and prevY represent the coordinates of the mouse click:

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

Answer №1

When a box intersects in the screen space, it is like intersecting a pyramid (perspective) or a cube (orthogonal view) in 3D space. To achieve this, you can create a THREE.Frustum based on your 2D box.

For a perspective camera:

  1. Convert the corner coordinates of the screen-space box to 3D vectors (a vector from the camera position to the given point).

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

  1. Create 4 planes using these points (one plane for each side of the box). The camera position serves as the third point of the 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 )

  1. Construct a Frustum based on these planes, then add the camera's near and far planes to them.

var frustum = new THREE.Frustum( topPlane, bottomPlane, leftPlane, rightPlane, nearPlane, farPlane);

  1. Perform an intersection between the frustum and the objects.

frustum.intersectsBox(object.geometry.boundingBox)

or

frustum.intersectsSphere(object.geometry.boundingSphere)

Instead of directly utilizing the Frustum object, you could skip the near and far planes and simply check if the object lies within the space outlined by your 4 planes. You can write a function similar to the Frustum.intersectSphere() method with only 4 planes:

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

Answer №2

To determine the locations of objects, utilize their bounding boxes. The THREE.Geometry object comes with a boundingBox property that is initially set to null. You must calculate it yourself by invoking computeBoundingBox().

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

After obtaining the bounding boxes, you can access the 2D coordinates within them (assuming 'y' represents the UP axis):

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

Refer to for more information.

(Note: For meshes with irregular shapes, additional computations may be necessary to achieve precise results)

Answer №3

If you're looking for a more precise approach to subfrustum selection, check out the technique outlined in this article on marquee selection with three js. This method involves projecting the 8 corners of bounding boxes onto the screen space and then intersecting them with a screen rectangle. For an even faster alternative, take a look at this implementation (written in ClojureScript) that projects 3D bounding spheres onto bounding circles in screen space. You can also find implementations of both methods in the provided link. Additionally, there is a related question on Stack Overflow discussing using GPU for screen projection and picking with readpixels. I haven't delved into this method personally as I suspect it may cause rendering pipeline delays, but feel free to explore further and share your insights.

If you want to see a practical example of subfrustum selection, check out my demo at http://jsbin.com/tamoce/3/. However, please note that the accuracy of this particular implementation is not optimal. The crucial snippet of code from the example is:

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

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Jquery toggle exhibits a delayed response time

I have implemented a jQuery toggle, which can be viewed here: http://jsfiddle.net/Rua4j/ Currently, the hidden content is shown only when I click the toggle button. However, I would like it to also appear when I click on the title. How can I achieve this ...

Retrieve information from various tables in a SQLite database using Node.js

Is there a method to retrieve all data from multiple tables in a database? Currently, I have managed to fetch all data from a single table: router.get('/', function (req, res, next) { db.serialize(function () { db.all('SELECT id, name ...

How can you generate a Base64 string with Node.js?

I recently utilized the html2pdf npm package to generate a base64 string of a PDF file and then sent it to my Node.js server. I used Nodemailer to send this PDF as an email attachment by configuring the mailOptions object like so: let mailOptions ...

Surprising block of text suddenly popped up on the browser screen while I was in the middle of working on

Currently delving into the world of MERN stack and working on a simple project. Everything was going smoothly on localhost until out of nowhere, some garbled text appeared on the screen, hindering my progress. I'm completely stumped as to what this my ...

Exporting modules in node.js is driving me crazy. Just four lines of code and I can't figure out what's wrong

Currently, I am delving into the realm of node.js through an online course on Udemy. However, I've run into a perplexing issue that seems to defy logic and reason. The lesson revolves around accessing external files in node.js using module.exports. I ...

Tips on invoking PHP functions using JavaScript, Ajax issues with no output

After several attempts, I'm still struggling to transfer the data from my database (stored in my PHP file) to my JavaScript program. The ultimate goal is to fetch the query results and use them to generate a graph within my JavaScript file. Although ...

Sending an image from a NodeJS socket server to another server: The ultimate guide

Hello there, I'm currently working on a new project and I could really use your input on a challenge I'm dealing with. Here's the situation: I have a web server running a socket.io module. The server is listening on port 3012 and streaming ...

Making a REST API call with an ID parameter using Angular

I am currently working on an Angular application that interacts with a REST API. The data fetched from the API is determined based on the customer ID, for example: api/incident?customer_id=7. I am unsure of how to incorporate this into the API URL and serv ...

After converting my HTML elements to JSX in Next.js, I am experiencing issues with my CSS files not being applied

Hey there, I'm currently working on a website using Next.js. I've converted the HTML elements of a page to JSX elements but for some reason, the CSS of the template isn't showing up. I've double-checked all the paths and even updated th ...

Activate a modal component in ReactJS when a click event occurs

I'm having trouble integrating ReactStrap Modal into my NavBar and I haven't found a solution yet. I created a handler function to be triggered on a click event, but I can't figure out how to call my login component from this handler functio ...

"Learn the steps to access a JSON file directly from a URL within a Next.js

My goal is to upload a JSON file to the server from a URL, open it, parse it, and display its features on Google Maps. However, I am encountering an error with the "fs" library preventing me from opening the file. Below is the code: "use client" ...

Content in React Router may fail to load when refreshing the page, navigating forward, or manually

I recently started using React and I'm getting familiar with React Router. I divided my application into two main routes because each uses a different style: the Splash path is for when the user first enters, which contains the Splash screen, Login, a ...

Code executing twice instead of once in Javascript

Having trouble with a script in my demo below. When I enter "first input", submit, then click on the returned "first input", it alerts once as intended. However, upon refresh, if I enter "first input", submit, then add "second input", submit, and finally c ...

AWS Amplify-hosted Nuxt applications are resilient to errors during the build process

My website is built using Nuxt js and hosted on AWS Amplify. I've encountered a major issue where the website still gets generated successfully even when there's a failure in the nuxt generate command (like a JavaScript error in my code). Below i ...

Guide on deploying Google App Script add-ons on Google Workspace Marketplace

Recently delving into Google App Script, I've taken my first steps in coding within the platform. Utilizing the deploy option provided by Google App Script, I successfully launched my app. However, upon deployment, I encountered difficulty locating my ...

Utilize a button to apply styles to a Span element dynamically generated with JavaScript

I am attempting to spin a span generated using JavaScript when a button is clicked. The class is added to the span, but the spinning effect does not apply. <p>Click the button to create a SPAN element.</p> <button onclick="myFunction ...

Unable to resize react-split-pane dimensions

I recently integrated the react-split-pane plugin into my project, but I am encountering some issues with its functionality. Even though I have tested react-split-pane versions 0.1.68, 0.1.66, and 0.1.64, none of them seem to work as expected in my applic ...

"Revamping the text style of input materials in React JS: A step-by

How can I modify the style of the text field component in Material UI? Specifically, I would like to change the appearance of the text that the user enters. I have attempted to use the Material API, but it has not provided the desired results. Here is an ...

Transform any falsy values within a JavaScript object into an empty string

Looking for a solution to transform all undefined, NaN, etc. values in a Javascript object into an empty string for better definition. For example: javascriptObject = convertUndefined(javascriptObject) Seeking something that can achieve this functionalit ...

Is it possible to swap out a color, such as black, in textured images?

Looking to create transparent clouds on a high-resolution planet image. Unfortunately, only have a JPG file but hoping to find a way to remove the black background. Here's the image in question: ...