Detecting mouse interactions in Three.js using raycasting and intersect testing

I am completely new to Three.js and JavaScript in general. I have been experimenting with mouse event interactions, but so far, I haven't been successful. In my attempt to make it work, I found some JavaScript code on a webpage (https://threejs.org/examples/#webgl_interactive_cubes) that I modified to suit my needs.

However, I encountered an issue that puzzles me. Whenever I refresh the page, the cube automatically turns red, even though it's supposed to change color only when clicked. Interestingly, if I reduce the size of the cube, this problem goes away.

After running console.log(INTERSECTIONS), I discovered that upon refreshing the page, the value is '5,' whereas every click afterwards yields a value of '1.' I expected the value to be '0' upon refresh since no clicking has actually occurred yet. To handle this, I introduced an additional conditional statement:

if (intersects.length > 4)
      {
     INTERSECTED.material.emissive.setHex( 0x111111 );
      }

While this partially resolves the issue, I am still curious as to why it happens in the first place?

How can I simplify this code further to create a basic template for future mouse click events? For instance, attempting to make the cube rotate upon click doesn't seem to work at the moment.

The current code is displayed below:

<script src="js/three.js"></script>
<script>

  var camera, scene, raycaster, renderer;
  var mouse = new THREE.Vector2(), INTERSECTED;
  var radius = 100;
  init();
  animate();

  function init() {

    container = document.createElement( 'div' );
    document.body.appendChild( container );

    var info = document.createElement( 'div' );
    info.style.position = 'absolute';
    info.style.top = '10px';
    info.style.width = '100%';
    info.style.textAlign = 'center';


    camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 10000 );
    scene = new THREE.Scene();

    scene.add( new THREE.AmbientLight( 0xffffff, 0.2 ) );

    var light = new THREE.DirectionalLight( 0xffffff, 2 );

    light.position.set( 30, 10, 1 ).normalize();
    scene.add( light );

    var cubeGeometry = new THREE.BoxGeometry(20,20,20);
    var cubeMaterial = new THREE.MeshLambertMaterial({color: 0x999999, wireframe: false});  
    var object = new THREE.Mesh(cubeGeometry, cubeMaterial);

      object.position.x = 0;  
      object.position.y = 0;  
      object.position.z = 0;

      scene.add( object );



    raycaster = new THREE.Raycaster();
    renderer = new THREE.WebGLRenderer();
    renderer.setClearColor( 0xf0f0f0 );
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize( window.innerWidth, window.innerHeight );
    renderer.sortObjects = false;
    container.appendChild(renderer.domElement);


    document.addEventListener( 'mousedown', onDocumentMouseDown, false );   
    window.addEventListener( 'resize', onWindowResize, false );

  }
  function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize( window.innerWidth, window.innerHeight );
  }
  function onDocumentMouseDown( event ) {
    event.preventDefault();
    mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
    mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
  }

  function animate() {
    requestAnimationFrame( animate );
    render();
  }

  camera.position.x = -30;  
  camera.position.y = 30;  
  camera.position.z = 30;  
  camera.lookAt( scene.position );
  camera.updateMatrixWorld();

  function render() 
  {
    // find intersections
    raycaster.setFromCamera( mouse, camera );
    var intersects = raycaster.intersectObjects( scene.children );
    if ( intersects.length > 0 ) {
      if ( INTERSECTED != intersects[ 0 ].object ) {
        if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );
        INTERSECTED = intersects[ 0 ].object;
        INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
        INTERSECTED.material.emissive.setHex( 0xff0000 );
         console.log(intersects.length);
      }
    } else {
      if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );
      INTERSECTED = null;
    }
    renderer.render( scene, camera );

  }
</script>

Answer №1

By initializing var mouse = new THREE.Vector2(), you are creating a vector with default values {x:0, y:0} (thus positioning your mouse in the center of the screen). The scene is then set up using the init() function, including placing the cube, followed by rendering initiated with animate().

With the mouse positioned at the center of the screen, during each render within the render() function, intersection checks are performed. This means that while the mouse remains in the middle of the screen, positive intersection results will be obtained. Moving the mouse outside of the cube will result in a change of mouse position and thus a negative intersection result.

If your goal is to interact with the mouse click, it would be more suitable to transfer the block of code responsible for checking intersections from render() to onDocumentMouseDown(event).

// Code block for finding intersections
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( scene.children );

if ( intersects.length > 0 ) {
  if ( INTERSECTED != intersects[ 0 ].object ) {
    if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );
    
    INTERSECTED = intersects[ 0 ].object;
    INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
    INTERSECTED.material.emissive.setHex( 0xff0000 );
    console.log(intersects.length);
  }
} else {
  if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );
  
  INTERSECTED = null;
}

This way, intersection checks will only occur upon clicking on the scene.

Check out this jsfiddle example for reference.

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

Exploring Strategies for Debugging JavaScript with Emphasis on Asynchronous Callbacks

Within my current Backbone/jQuery/CoffeeScript project, I have noticed that a function (Backbone.Collection.fetch()) is being called multiple times, sometimes with varying numbers. This issue seems to be related to timing, possibly due to the numerous nest ...

Tips on retrieving values from input files using jQuery

Is there a way to retrieve the value of an input type using jQuery? Specifically, when I add a file to the input file, I want to dynamically add more input types with the file's value. Currently, I am using the following code: $('#show_input&apo ...

When first logging in, React context may initially return as undefined, but it functions correctly upon refreshing the

Currently, I am in the process of learning React Context and implementing it on a Higher Order Component (HOC) to maintain user login status throughout my application. Upon successful login, I extract the user's name from the context and display it in ...

Transferring information between an ASP web form page and an Angular2 application

Currently, I am working on a project that involves dealing with a legacy WebForms system. The system is gradually being updated to Angular 2, but the transition is happening incrementally. In order to effectively integrate information from the legacy sect ...

Adding shadows to a GLTF model in THREE.JS - How do I achieve this effect?

Just starting out with Three.js and I'm trying to figure out how to make my GLTF model cast a shadow on itself. I've been experimenting with different approaches, but not sure if I'm on the right track. The light is set as a PointLight and t ...

Expanding the iWebKit Page Slider with Javascript

Does anyone have a suggestion for a page slider that mimics the look of native apps on iWebKit? I'm looking for something in Javascript that is simple to integrate and will provide a smooth transition between pages. ...

byte sequence displays as binary data (angular + express)

I've been working on pulling files from a back-end Node.js / Express server and displaying them in an Angular front-end. Despite trying different methods, I keep facing the same issue - the data displayed at the front-end appears as a bytestring. Even ...

Retrieve data from a JSON file using Ajax and jQuery

I have a JSon file that contains information about some matches: [{ "id": "2719986", "orario": "00:30", "casa": "Bahia", "trasferta": "Internacional" } , { "id": "2719991", "orario": "02:00", "casa": "Palmeiras", "trasferta": "Botafogo RJ" }] I'v ...

Experiencing frequent rerendering in React following the incorporation of socket io functionality

Currently working on a project similar to Omegle, take a look at some of the code below focusing on the useEffect functions. const Messanger =(props)=>{ let socket = props.socket; let intro; const myId = socket.id; const [readOnly,setReadOnly] = useSta ...

Creating a dropdown menu utilizing JavaScript/jQuery arrays

Looking to create an HTML select menu using a JavaScript array where the keys are used as values for options. The challenge is when entering numbers as keys, they should be accepted in the code. a([selected="No of rooms", 1="1", 2="2", 3="3", 4="4", 5="5" ...

Send responses to contact information via email

My website is being developed on repl.it, using their HTML/CSS/JS server which doesn't support PHP. This poses a challenge as I need PHP for submitting contact responses via email. Is there an alternative solution using Node.js that would allow me to ...

Ways to resolve the issue of data-bs-target not functioning properly in Bootstrap version 5

While viewing the Job screen in the following image, I attempted to click on "Personal," but it remained stuck on the Job screen. ...

Utilizing FabricJS to create a canvas with synchronized scrolling to the window

In the process of developing an app, I am utilizing the Html2canvas library to capture a screenshot of the body. Once the screenshot is taken, it is displayed in a canvas component using the fabricJS framework. The canvas is set to have the same height and ...

I would like to terminate the property when processing it on a mobile device

<div class="col-sm-24 embed-responsive"> <iframe width="100%" src="http://www.youtube.com/embed/${item.snippet.resourceId.videoId}"></iframe> </div> .embed-responsive iframe { height: 185px; margin-top: -4%; paddin ...

How to import variables into asynchronous JavaScript?

Currently, I am attempting to pass some variables into a script that I am loading. Below is an example of how I currently have it set up: window.myApp = { id: '' }; I am including the javascript like this: (function() { var s = documen ...

Show a random number within a Bootstrap tooltip

I am trying to make a bootstrap popover display a random number every time it is clicked, but my current implementation is only showing the initial number generated and does not update on subsequent clicks. Below is my code snippet: <button type=&qu ...

How to Compare Dates in an Array of MongoDB Objects with Varied Date Formats

In the frontend, I am utilizing a datepicker that sends a date through an AJAX Request. The date is in the format 'YYYY-MM-DD'. I need to compare this date with dates in an array retrieved from a MongoDB collection. These dates include a Timezone ...

What is the best way to bring in a variable initialized by an IIFE from a JavaScript file into a TypeScript class?

I'm currently working towards integrating the steelseries.js library (found at https://github.com/HanSolo/SteelSeries-Canvas) into a Grafana plugin built with React. It's quite a complex task, but I'm up for the challenge. Right now, my ma ...

Tips for creating a global variable by combining form input and an API request in React

Currently, I am in the process of constructing a simple login page for my React application. The way it works is that when a user submits a form to log in, if the entered username and password match what's stored in the database, an object is created ...

Using NodeJS to merge properties from an array of objects with varying ranges

Apologies if the title is confusing, but allow me to clarify. Within a UI table, users have the ability to select date ranges in the following format: Monday - {in:"13:00:00",out:"13:59:59"} Tuesday - [{in:"13:00:00",out:"13:59:59"},{in:"14:00:00",out:"1 ...