Detecting onclick on a model in three.js: A simple guide

I am curious if there is a method in three.js that allows users to click on a model (such as an imported gltf file from Blender) and trigger another action upon the click, such as displaying a pop-up box or an image.

I attempted to search for this feature on the official three.js website, but all I found was the raycaster class. However, my understanding is that this only works with objects that have a mesh, which means it only works with models created in three.js itself using geometry properties.

I also came across tutorials utilizing threex.domevents, but I couldn't figure out how to use it with models imported from Blender. When I tried implementing it, my models vanished from the screen.

Is there another solution available? Or is it simply not possible to achieve this functionality?

Your help would be greatly appreciated. Thank you very much.

<!DOCTYPE html>
    <html>
      <head>
        <meta charset=UTF-8 />
        <link rel="stylesheet" type="text/css" href="styles/mycss.css" />
      </head>
      <body>
        <script src="three.min.js"></script>
        <script src="GLTFLoader.js"></script>
        <script src="OrbitControls.js"></script>
        <script src="threex.domevents.js"></script>
            <script>
                // JavaScript code here
            </script>
      </body>
    </html>

The car model can be downloaded from:

I am learning how to implement this feature based on tutorials I found on YouTube.

Answer №1

In the realm of 3D, the solution lies in utilizing Raycaster instead of traditional methods used in a 2D environment. Here's an illustrative snippet of code to demonstrate this concept:

var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();

function detectClicks( event ) {

    event.preventDefault();

    mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
    mouse.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;

    raycaster.setFromCamera( mouse, camera );

    var hits = raycaster.intersectObjects( targets ); 

    if ( hits.length > 0 ) {
        
        hits[0].object.execCallback();
    
    }

}

This setup allows for executing a callback function within a 3D object upon interaction.

Answer №2

If you want to accomplish this task, raycaster is the way to go. By navigating through the gltf model and converting it into a mesh, you can achieve the desired effect.

loader.load("nameOfObject.gltf", function (gltf) {
    gltf.scene.traverse(function (child) {
        if (child.isMesh) {
            let m = child;
            m.receiveShadow = true;
            m.castShadow = true;
            m.material.flatShading = true;
            sceneMeshes.push(m);
        }
        if (child.isLight) {
            let l = child;
            l.castShadow = true;
            l.shadow.bias = -0.003;
            l.shadow.mapSize.width = 2048;
            l.shadow.mapSize.height = 2048;
        }
    });
    scene.add(gltf.scene);

    sceneMeshes.push(gltf.scene);
}, (xhr) => {
    console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
}, (error) => {
    console.log(error);
});

Following that, utilize the raycaster on the created mesh.

function onDocumentMouseDown( event ) {
    const mouse = {
        x: (event.clientX / renderer.domElement.clientWidth) * 2 - 1,
        y: -(event.clientY / renderer.domElement.clientHeight) * 2 + 1,
    };
    raycaster.setFromCamera(mouse, camera);

    const intersects = raycaster.intersectObjects(sceneMeshes, false);

    const color = Math.random() * 0xffffff;

    if (intersects.length > 0) {
        let n = new THREE.Vector3();
        n.copy(intersects[0].face.normal);
        n.transformDirection(intersects[0].object.matrixWorld);
        const selectedObject = intersects[0];
        alert('this is a Drink');
        
    }
}

Answer №3

After hours of trying to crack the problem, my persistence paid off.

Despite my initial struggle, I found a simple solution by adding a cube with 100% transparency on top of my model and using a standard raycast method, which surprisingly did the trick.

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

Navigating through every node in an XML document

Can anyone assist with JavaScript? I have a XML file and need to loop through all node elements using JavaScript, not JQUERY. The goal is to print all node names and their values. Here is the XML file: <?xml version="1.0" encoding="UTF-8"?> <bo ...

Utilizing JavaScript to manage sections within a dropdown menu

When dealing with this particular HTML code, there is a feature where a list item includes options such as all, a, b, c, and d. If the user selects 'All', it should restrict them from choosing any other items. However, if they do not choose &apos ...

Guide to displaying two messages in a single toast with react-toastify

Is there a way to have only one toast message display when the user clicks on the favorite button, showing either "Added to Fav" or "Removed From Fav"? I am currently able to display two separate toasts with different messages. ...

Issues arise when submitting the form on a web page with an MVC 4 partial view, causing the page

Scenario: I am currently working on a C#/MVC 4 project where I have a view that includes a partial view. The main view consists of a form with a submit button, and the partial view is initially hidden but can be displayed by selecting a checkbox. Problem: ...

Generate SVG components without displaying them

Is there a way to generate a custom SVG graphic through a function without the need to attach it to any element? Can I simply create an empty selection and return that instead? Here is my current implementation: function makeGraphic(svgParent) { retur ...

Guide on exporting a reducer from a Node module built on React Redux

I am currently working on a project that requires importing a component from an external node module. Both the project and the component utilize React and Redux, and I intend for them to share the same store. Below is a snippet of the reducer code for the ...

Obtaining HTML data with ajax within a WordPress post

Greetings! I've been putting together a website and I'm eager to include a unique timeline in each of my posts. I'm utilizing WordPress for this project. However, since the timeline I want to insert will vary from post to post, I am unable t ...

What steps can I take to remove the dark areas in my see-through material?

While displaying a 3D shoe model in the browser using three.js, I encountered an issue with a half-transparent material set for the heel. The problem arises when I use the orbitControls in the project and rotate the model to a certain angle, which then rev ...

Is it possible for a skybox to consist of just one image, or are multiple images necessary?

When searching for skybox images (e.g. google images), I noticed that the results often show single images in a sideways cross pattern. However, most three.js examples (such as this one) feature loading 6 separate images. It seems odd to have to cut up a ...

Tips for successfully passing multiple variables to the onload function within an Angular ng-include

I am facing a challenge in my Angular application where I need to pass two variables to a template. Here is what I have been trying: <div ng-include="'myTemplate.html'" onload="{obj: someObject, value: value}"></div> Unfortunately, ...

What are the steps for setting up jScroll?

As a newcomer to JS & jQuery, I appreciate your patience. I've been working on creating a dynamic <ul> list using JS, and I'm happy that it's finally coming together. Now, my next step is to incorporate infinite scrolling into my ...

Create a custom chrome browser extension designed specifically for sharing posts on

I'm working on creating a basic chrome extension that features an icon. When the icon is clicked, I want the official Twitter window to pop up (similar to what you see here). One common issue with existing extensions is that the Twitter window remains ...

AngularJS is patiently awaiting the completion of the translation rendering process

I implemented the ngCloak directive to my body element on the page and every angular-related element, but it appears that it is not working with AngularJS translate. The translate variables show up on the page initially and then get translated after a se ...

Passing JSON data from an ASP.NET controller to a view

My issue arises when a web page is loaded and triggers a controller action to retrieve data based on user selection. I am trying to return this data as a JSON object, but it appears as a single string within the HTML page. The basic structure of the contro ...

Bringing back a Mongoose Aggregate Method to be Utilized in Angular

I'm having trouble returning an aggregate function to Angular and encountering errors along the way. I would really appreciate some assistance with identifying the mistake I am making. The specific error message I receive is Cannot read property &apos ...

Connection between the view and the router's backbone

In my opinion, it is important to maintain isolation between the router and view in data-driven programming paradigm. They should only communicate with each other through model changes that they both subscribe to. However, I have noticed that different on ...

Tips for dynamically generating ng-change within ng-repeat

I am facing an issue with a repeated div that contains a dropdown menu with three options: 'single', 'double', and 'matrix'. Depending on the selected value, the corresponding div should be displayed. However, when the ng-repe ...

Retrieve class property in Angular by its name

How can I retrieve an array from a class like the one below without using a switch statement, dictionary, or other collection to look up the name passed into the method? export class ProcessOptions { Arm = [{ name: 'Expedited Review ("ER") ...

"Experience the magic of Datepicker jQuery with just a click

Does anyone know how to capture the click event when a day is selected using jQuery Datepicker? Is it possible, and if so, how can I achieve this? https://i.sstatic.net/ZG4r8.png Here is the HTML code snippet: Date From : <input type="text" ...

Utilize string paths for images instead of requires when resolving img src and background-image URLs

I have successfully implemented image loading with webpack using the file loader, but only when I require or import images. I am curious about how create-react-app achieves the functionality where everything in the public folder is accessible, for example, ...