Empty Array of Intersects Found in Three.js Raycasting

I have been experimenting with a WebGL panorama cube using the example found here:

My goal is to determine which cube the user clicks on, and I learned that Raycaster can help with this. Following the documentation, I implemented the following function:

    function onMouseDown( event ) {
        event.preventDefault();

        var mouseVector = new THREE.Vector3(
            ( event.clientX / window.innerWidth ) * 2 - 1,
          - ( event.clientY / window.innerHeight ) * 2 + 1,
            1 );

        //projector.unprojectVector( mouseVector, camera );
        mouseVector.unproject( camera );
        var raycaster = new THREE.Raycaster( camera.position, mouseVector.sub( camera.position ).normalize() );

        // create an array containing all objects in the scene with which the ray intersects
        var intersects = raycaster.intersectObjects( scene.children );
        console.log(intersects);
        if (intersects.length>0){
            console.log("Intersected object:", intersects.length);
            intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff );
        }
        // ...

However, I am always getting an empty result for intersects. My scene is defined as

scene = new THREE.Scene();

and includes a skybox:

var skyBox = new THREE.Mesh( new THREE.CubeGeometry( 1, 1, 1 ), materials );
skyBox.applyMatrix( new THREE.Matrix4().makeScale( 1, 1, - 1 ) );
scene.add( skyBox );

I have searched for solutions to similar issues but haven't been able to apply them to my specific case. Any guidance would be greatly appreciated.

Answer №1

Consider implementing the following into your material definition:

var materials = new THREE.SomeMaterial({
    /* other settings */,
    side: THREE.DoubleSide
});

If you want Raycaster to intersect back-faces, ensure that the side property is set to either THREE.BackSide or THREE.DoubleSide. Despite scaling inverting the face direction, it's the vertex order that matters for the Raycaster.

Diving Deeper

The code snippet below illustrates how a ray projected from the center of a skybox, inverted by a -Z scale, may appear.

The altered box looks peculiar due to the -Z scaling, causing mismatches with the normals. However, this is not the focus here.

The green arrow denotes the original ray, while the red arrow shows what happens to the ray inside the Mesh.raycast function, applying the inverse of the object's world matrix to the ray but not its geometry. This presents a separate issue.

The key point here is that within Mesh.raycast, there's no impact on the vertex/index order. Therefore, when checking the mesh's triangles, they retain their original order. For a typical BoxGeometry/BoxBufferGeometry, this implies the faces all facing outward from the geometric origin.

Hence, despite any transformation matrix effects on the rays, they still attempt to intersect the back-face of those triangles, necessitating the material to be set as THREE.DoubleSide (the default). Alternatively, it could be set to

THREE.BackSide</code, though the -Z scale might disrupt this).</p>

<p>By clicking the raycast buttons without setting the -Z scaled box to <code>THREE.DoubleSide
(default), you will encounter 0 intersections. Click the "Set THREE.DoubleSide" button and retry—the intersection should now occur.

var renderer, scene, camera, controls, stats;

var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 35,
NEAR = 1,
FAR = 1000,
  ray1, ray2, mesh;

function populateScene(){
var cubeGeo = new THREE.BoxBufferGeometry(10, 10, 10),
cubeMat = new THREE.MeshPhongMaterial({ color: "red", transparent: true, opacity: 0.5 });
mesh = new THREE.Mesh(cubeGeo, cubeMat);
  mesh.applyMatrix( new THREE.Matrix4().makeScale( 1, 1, -1 ) );
  mesh.updateMatrixWorld(true);
scene.add(mesh);
  
  var dir = new THREE.Vector3(0.5, 0.5, 1);
  dir.normalize();
  ray1 = new THREE.Ray(new THREE.Vector3(), dir);
  
  var arrow1 = new THREE.ArrowHelper(ray1.direction, ray1.origin, 20, 0x00ff00);
  scene.add(arrow1);
  
  var inverseMatrix = new THREE.Matrix4();
  inverseMatrix.getInverse(mesh.matrixWorld);
  
  ray2 = ray1.clone();
  ray2.applyMatrix4(inverseMatrix);
  
  var arrow2 = new THREE.ArrowHelper(ray2.direction, ray2.origin, 20, 0xff0000);
  scene.add(arrow2);
}

function init() {
document.body.style.backgroundColor = "slateGray";

renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });

document.body.appendChild(renderer.domElement);
document.body.style.overflow = "hidden";
document.body.style.margin = "0";
document.body.style.padding = "0";

scene = new THREE.Scene();

camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
camera.position.z = 50;
scene.add(camera);

controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.dynamicDampingFactor = 0.5;
controls.rotateSpeed = 3;

var light = new THREE.PointLight(0xffffff, 1, Infinity);
camera.add(light);

stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0';
document.body.appendChild(stats.domElement);

resize();
window.onresize = resize;

populateScene();

animate();
  
  var rayCaster = new THREE.Raycaster();
  document.getElementById("greenCast").addEventListener("click", function(){
    rayCaster.ray.copy(ray1);
    alert(rayCaster.intersectObject(mesh).length + " intersections!");
  });
  document.getElementById("redCast").addEventListener("click", function(){
    rayCaster.ray.copy(ray2);
    alert(rayCaster.intersectObject(mesh).length + " intersections!");
  });
  document.getElementById("setSide").addEventListener("click", function(){
    mesh.material.side = THREE.DoubleSide;
    mesh.material.needsUpdate = true;
  });
}

function resize() {
WIDTH = window.innerWidth;
HEIGHT = window.innerHeight;
if (renderer && camera && controls) {
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
controls.handleResize();
}
}

function render() {
renderer.render(scene, camera);
}

function animate() {
requestAnimationFrame(animate);
render();
controls.update();
stats.update();
}

function threeReady() {
init();
}

(function () {
function addScript(url, callback) {
callback = callback || function () { };
var script = document.createElement("script");
script.addEventListener("load", callback);
script.setAttribute("src", url);
document.head.appendChild(script);
}

addScript("https://threejs.org/build/three.js", function () {
addScript("https://threejs.org/examples/js/controls/TrackballControls.js", function () {
addScript("https://threejs.org/examples/js/libs/stats.min.js", function () {
threeReady();
})
})
})
})();
body{
  text-align: center;
}
<input id="greenCast" type="button" value="Cast Green">
<input id="redCast" type="button" value="Cast Red">
<input id="setSide" type="button" value="Set THREE.DoubleSide">

Answer №2

If you're looking for a simpler way to calculate the ray from your camera, consider using this method:

THREE.Raycaster.prototype.setFromCamera( Vector2, Camera );

All you need to do is define your mouse coordinates as a Vector2, then pass them to the Raycaster. This method takes care of handling the complexity of intersecting the frustrum with the camera's ray and should help resolve your issue.

(It's worth noting that the raycaster only intersects faces that directly face the ray. However, in the case of an inverted SkyBox where the geometry faces point inside the box, intersections are still possible when the camera is within the box. Alternatively, if the box is too far away, it may exceed the raycaster's default far value.)

function onMouseDown( event ) {

    event.preventDefault();

    var mouseVector = new THREE.Vector2(
         event.clientX / window.innerWidth * 2 - 1,
        -event.clientY / window.innerHeight * 2 + 1
    );

    var raycaster = new THREE.Raycaster;
    raycaster.setFromCamera( mouseVector, camera );

    var intersects = raycaster.intersectObjects( scene.children );
        
    console.log(intersects);
        
    if( intersects.length > 0 ){

        console.log( "Intersected object:", intersects[ 0 ] );
        intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff );

    }

}

Answer №3

When utilizing the raycaster.intersectObjects() function within Three.js, it is vital to supply the targeted objects in array format. Previously, the method was used as

const intersects = raycaster.intersectObjects(planeMesh);
. However, for proper functionality and to avoid an empty array being returned, it is crucial to enclose the object or objects within an additional pair of square brackets like so:
const intersects = raycaster.intersectObjects([planeMesh]);
. This adjustment of encapsulating the object reference within an array enables the raycaster to accurately detect intersections and prevents returning an empty intersection array.

https://i.sstatic.net/1gSoW.png

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

Looking to add some movement to your website? Learn how to make an image track your mouse pointer in a specific section of your webpage

I'm just starting out with web design and javascript, so please be patient. My goal is to have an image follow the mouse pointer only when it's within a specific section of my website. I've managed to make the image track the mouse cursor ...

Generate a Report in Embedded Mode using the PowerBI API

Attempting to generate a new report using data from an embedded view, but encountering continuous "This content isn't available" messages and receiving a 403 error from the reportEmbed.min.js during rendering. While able to create and save reports suc ...

Revamping a design using Mustache js

I've successfully implemented mustache js to render a template using data from an API, but now I face a challenge in updating the same template at a later time. Within my template, there is a list structured like this: template.html <div id="temp ...

The output of the Node.js crypto.pbkdf2 function may not match the result obtained from CryptoJS.PBKDF

Currently, I am utilizing PBKDF2 on both the frontend with CryptoJS and the backend with Node.js. Despite using the identical salt, algorithm, number of iterations, and password, the derived keys are turning out to be different. Below is the code snippet ...

Encountering an issue with getDerivedStateFromProps in React where an error states: Unable to retrieve property 'setState' of null

Just found out that componentWillReceiveProps is deprecated and now we should be using the getDerivedStateFromProps lifecycle method. You can find more information about it at this link. This is how I'm implementing it: class Main extends Component ...

Using JQuery to encapsulate the contents of a variable

I am working with a variable that stores HTML retrieved from an HTML5 SQL database. When I use the .append() method to add it to a DOM element, everything works as expected. However, I want to wrap the HTML contents before appending them, so I tried using ...

Creating textures using a-frame's rendering capabilities

I have been trying to create a similar setup to this demo... ... as an a-frame component. However, I am encountering a problem where the quad, meant to display the "monitored" camera feed, is showing a blank white screen. It turns black when I enter VR mo ...

Is there a way to turn off data sorting on the x-axis of a d3 line chart?

I am looking to create a line graph similar to the example shown here. My JSON data is structured as follows: [ { "timeStamp": "23:33:58", "usage": 90 }, { "timeStamp": "00:04:03", "usage": 94 }, { ...

Where can the data be found within an AngularJS HTTP POST request?

Can someone help me with this issue? I'm trying to send data to my server using an AngularJS button that triggers a function with an HTTP post request. However, I can't seem to locate where the data is stored in the request. Here is the code for ...

Tips on utilizing ajax to load context without needing to refresh the entire page

As a beginner in AJAX, I have some understanding of it. However, I am facing an issue on how to refresh the page when a new order (order_id, order_date, and order_time) is placed. I came across some code on YouTube that I tried implementing, but I'm n ...

Unspecified checkbox selection with Vue.js

Recently delving into the world of Vue, I've been grappling with how to display a nested list visually. In this list, each item should feature a triple-state checkbox system: When a child item is checked, the parent item's checkbox should becom ...

Typing the url in the address bar when using Angular's ui-router

Two distinct states are present: .state('test', { url: '/test', templateUrl: 'js/angular/views/test.html', controller: 'UserController', }) .state('test.list', { url: ' ...

Replace all instances of the same word with the word "and" using regular expressions

If we look at the sentence below: Blue shirt blue hat Can regular expressions be used to detect 2 matching words and replace the second one with and so it reads: Blue shirt and hat Now, let's tackle a more complex example. In this case, we nee ...

Authentication error: The API data retrieval was disrupted due to an unexpected termination of input

I encountered an error while trying to fetch data from an API using JavaScript. <button onclick="event.preventDefault(); getOdds()">Print odds</button> JS function getOdds() { const sportsKey = prompt("Enter the sports key:&q ...

Increase the dimensions of the jQuery modal confirmation box

I am working on a jQuery confirmation dialog that displays some details for the user. My goal is to have the dialog adjust its dimensions after the user clicks "YES" to show a smaller text like "Please wait...". How can I dynamically change the size of the ...

Tips for making a React/Socket.IO app accessible from external sources

Trying to get my React app (built with Node.js and Socket.IO) running locally and accessible from external sources has been a challenge for me. I've managed to expose port 3000 using ngrok tunneling, but my socket connection is listening on port 3001. ...

NextJS not maintaining state for current user in Firebase

I'm working on an app utilizing firebase and nextjs. I've set up a login page, but when I try to retrieve the current user, it returns undefined. This issue began a few days ago while working in react native as well - initially, it was related to ...

Using JavaScript, adding an element to an array results in the array returning a value of 1

When I try to push a string into an array and then return the array, I am getting unexpected result of "1". Upon closer inspection, it seems that the array is being successfully created but before returning it, only "1" is being returned. Here is the snip ...

How can I combine jQuery "this" along with a CSS selector?

Currently, I am working on a script that will allow me to toggle the visibility of a section within a div. I have three of these divs with hidden sections and I am trying to find a way to control all three using a single function. This is what my code loo ...

How to iteratively process JSON array using JavaScript?

I am currently attempting to iterate through the JSON array provided below: { "id": "1", "msg": "hi", "tid": "2013-05-05 23:35", "fromWho": "<a href="/cdn-cgi/l/email-pro ...