Single-node interaction with three.js

In this demonstration, you can observe two clickable particles that both respond to clicks. I am seeking a way to detect clicks on particles without removing them from the scene, similar to this:

if (intersects.length>0){
    if(intersects[0].object.type == "Points"){
        intersects[0].object.material.color.setHex( Math.random() * 0xffffff );
    }
}

I prioritize using particles because they maintain a consistent size relative to the camera, eliminating the need for a secondary orthographic camera. Ultimately, my goal is to achieve something reminiscent of the ARK Starmap from Star Citizen, where systems remain consistently sized and clickable.

$(function(){
    if ( ! Detector.webgl ) Detector.addGetWebGLMessage();

    var stats;

    var camera, controls, scene, renderer;

    var group;
    var objectControls;
    var particleSystem, raycaster;
    var mouse = new THREE.Vector2();
    var projector = new THREE.Projector();

    function init() {

        var width = window.innerWidth;
        var height = window.innerHeight;

        camera = new THREE.PerspectiveCamera(50, width / height, 1, 10000000);
        camera.position.z = 1500;
        camera.position.y = 100;

        scene = new THREE.Scene();

        renderer = new THREE.WebGLRenderer();
        renderer.setClearColor("black");
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.autoClear = false;

        var container = document.getElementById('container');
        container.appendChild(renderer.domElement);

        controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableZoom = false;
        controls.minDistance = 50;
        controls.maxDistance = 500000;

        var loader = new THREE.TextureLoader();
        loader.load("textures/systempoint.png", function(texture) {
            particles = new THREE.Geometry(),
            pMaterial = new THREE.PointsMaterial({ map: texture, size: 32, transparent: true, sizeAttenuation: false });

            particle = new THREE.Vector3(400, 300, 300);
            particles.vertices.push(particle);

            particle = new THREE.Vector3(300, 400, 300);
            particles.vertices.push(particle);

            // create the particle system
            particleSystem = new THREE.Points(particles, pMaterial);
            particleSystem.sortParticles = false;
            particleSystem.name = "systems";
            scene.add(particleSystem);
        });

        //////////////////////////////////////////////////////////////////////////
        // Lights
        //////////////////////////////////////////////////////////////////////////

        light = new THREE.AmbientLight(0x666666);
        scene.add(light);

        var params = { recursive: true };
        objectControls = new ObjectControls(camera, params);

        stats = new Stats();
        stats.domElement.style.position = 'absolute';
        stats.domElement.style.top = '0px';
        stats.domElement.style.zIndex = 100;
        container.appendChild(stats.domElement);

        window.addEventListener('resize', onWindowResize, false);

        console.log(scene);
    }

    function onWindowResize() {
        var width = window.innerWidth;
        var height = window.innerHeight;
        camera.aspect = width / height;
        camera.updateProjectionMatrix();
        renderer.setSize(width, height);
    }

    var lastTimeMsec = null;
    var nowMsec = null;

    function animate() {
        requestAnimationFrame(animate);
        //controls.update(); // required if controls.enableDamping = true, or if controls.autoRotate = true
        stats.update();
        objectControls.update();
        render();

    }

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

    $(document).mousedown(function(e) {
        e.preventDefault();
        var mouseVector = new THREE.Vector3(
            (e.clientX / window.innerWidth) * 2 - 1,
            -(e.clientY / window.innerHeight) * 2 + 1,
            1
        );

        mouseVector.unproject(camera);
        var raycaster = new THREE.Raycaster(camera.position, mouseVector.sub(camera.position).normalize());
        raycaster.params.Points.threshold = 15;

        var intersects = raycaster.intersectObjects(scene.children);

        if (intersects.length > 0) {
            if (intersects[0].object.type == "Points") {
                console.log(intersects[0].object.position);
                intersects[0].object.material.color.setHex(Math.random() * 0xffffff);
            }
        }
    });

    init();
    animate();
});

Answer №1

It seems you misunderstood the change in color as an indication that both particles were being clicked on ... actually, the color change is applied to the material shared between the particles, not the particles themselves.

I took the time to fix your fiddle, and it was quite a challenge (but I did learn something along the way).

Check out the updated fiddle here

Here are some important points to note:

  • I switched to a non-minified version of three.js for easier troubleshooting.
  • The raycaster does support the Points object, and I adjusted the threshold for better click accuracy on particles.
  • Each particle now has its own unique color and uses THREE.VertexColors.
  • I cleaned up the code using the Raycaster feature.
  • You'll see code for Sprites in there, but they are no longer necessary as matching against particles in a Points object is now possible with three.js.
  • I added code to utilize the userData property for storing custom data related to clicked items.

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

Maximizing the potential of typescript generics in Reactjs functional components

I have a component within my react project that looks like this: import "./styles.css"; type InputType = "input" | "textarea"; interface ContainerProps { name: string; placeholder: string; as: InputType; } const Conta ...

Guidelines for filling an Express handlebars template and sending it via email

I am currently utilizing node.js, express, express-handlebars, and nodemailer for my project. My objective is to populate an HBS template with content and style it as an email before sending it out. The end result can be viewed below (first rendered in the ...

The scrollTop feature fails to function properly following an Axios response

I'm currently facing a challenge with creating a real-time chat feature using Laravel, Vue.js, Pusher, and Echo. The issue arises while implementing the following 3 methods: created() { this.fetchMessages(); this.group = $('#group') ...

What could be causing the ng-submit to fail to trigger the $scope.submit function?

I am currently working on my App.js file which contains the following code snippet: var app = angular.module('githubApp', []); In addition to that, I have a githubAppController with the following code block: app.controller('githubContro ...

Looking for the MIME file type requirement for Excel or spreadsheets?

Values in the 2nd column indicate the file MIME type. https://i.sstatic.net/qPcQD.png I am looking to create a condition that will be true for all of the above file MIME types. Specifically, I need a condition for all Excel files. This is what I attempte ...

Error message: Trying to call the prepare() function on a null variable within the CRUD system

I have implemented a CRUD system on my website. The main page (avisos.php) displays a table with all the existing records. When a user clicks on the "ADD NEW RECORD" button, the following script is triggered: $("#btn-add").click(function(){ $(".co ...

How to use jQuery to hide list items after a certain threshold

$('li[data-number=4]').after().hide(); <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <ul> <li data-number="0"">1</li> <li data-number="1">2</li> ...

What is the process for assigning a value to a property?

I am currently developing an Angular application that utilizes reactive forms. I need to retrieve the values of dynamically created fields within a form group. Here is the code I have implemented: this.formSaisieSecondary = this.fb.group({ ...

Guide to using AJAX to send a select tag value to a PHP script on the current page

Issue I am facing a problem where I need to utilize the select tag in order to choose a value and then send that value via an ajax call to a PHP script embedded within the same page. In my code, I have implemented the select tag and upon selecting a valu ...

jQuery Hide/Show Not Working as Expected

I am currently developing a Single Page Application using jQuery along with Semantic and Bootstrap for the UI. I have encountered an issue where jQuery is struggling to hide and show two elements on the same level, even though it works fine elsewhere in th ...

How can I create an input field that only reveals something when a specific code is entered?

I am currently developing a website using HTML, and I would like to create an admin section that requires a password input in order to access. After consulting resources on w3schools, I attempted the following code: <button onclick="checkPassword()" i ...

Transforming complex mathematical equations into executable code using the node.js platform

Looking to implement a mathematical formula found at the following link: https://en.wikipedia.org/wiki/Necklace_(combinatorics)#Number_of_bracelets into node.js for calculating the total number of distinct ring sequences that can be created with n length ...

Firebase functions are producing null values when called

I'm encountering an issue where the Functions function is returning null when I call it to retrieve user information. Can you provide a solution? workspace/functions/src/index.ts exports.getUser = functions.https.onCall((data, context) => { adm ...

Customize the asset path location in Webpack

I am currently working on a Vue application that utilizes Webpack for code minimization. Is there a method to dissect the chunks and adjust the base path from which webpack retrieves the assets? For example, by default webpack fetches assets from "/js" o ...

Determining the Width of a DIV Dynamically with CSS or LESS Depending on the Number of Siblings

One of my challenges involves a parent DIV with a width set to 100%. Dynamically, this parent DIV is filled with numerous children DIVs. I am trying to calculate and assign their widths using only the calc method in CSS or LESS. This is because the flex ...

Leveraging Webpack and Jest for seamless module importing in a development project

I've been working on a Node project and utilizing imports and exports extensively. To package the frontend code, I opted for Webpack. However, it seems to require running as common JS. Moreover, Jest is being used in my project, which led me to spec ...

Vue.js: Issue with updating list in parent component when using child component

I'm encountering an issue when utilizing a child component, the list fails to update based on a prop that is passed into it. When there are changes in the comments array data, the list does not reflect those updates if it uses the child component < ...

What is the best way to display a loading screen while awaiting the rendering process in ReactJS?

Recently, I created a CSS-animated "loading" element for my web page. However, I want it to only be visible when data is being loaded or rendered. Do you have any suggestions? I'm quite new to this and could use some guidance. This is how I implement ...

Storing selected elements from an array into a database using Laravel and Ajax

I have a query. I am working on saving arrays into a database but before doing so, I need to split the data and only save specific values. My approach involves using Ajax to pass the data to the controller. Important note: The array sets can be more than ...

When Vue.js is compiled, it removes a script tag

I'm currently learning Vue.js and encountering an issue that I can't seem to resolve. My project is utilizing Vue CLI 3, and within my code I am inserting a custom tag (not a component) in my template with a script tag inside it. <ra type="ou ...