Search code examples
javascriptthree.jspointraycasting

Galaxies simulation: Change color of a point and display text on mouseover


I am trying to create a simulation of positions of 4673 of the nearest galaxies.

The galaxies are points.

I want to color a point on mouseover and load the name of the galaxy.

I have spent many days trying to achieve it. I am able to change color, as well as do basic raycasting, however, I am unable to separately raycast/color individual point. All the points are raycasted and colored as a group as seen in the current version.

What should I do to correct this? Thank you so much for your time and patience with a beginner.

Complete code is available here.

Relevant code is included below:

window.addEventListener( "mousemove", onDocumentMouseMove, false );

var selectedObject = null;

function onDocumentMouseMove( event ) {

    event.preventDefault();
    if ( selectedObject ) {

        selectedObject.material.color.set( '#fff' );
        selectedObject = null;

    }

    var intersects = getIntersects( event.layerX, event.layerY );
    if ( intersects.length > 0 ) {

        var res = intersects.filter( function ( res ) {

            return res && res.object;

        } )[ 0 ];

        if ( res && res.object ) {

            selectedObject = res.object;
            selectedObject.material.color.set( '#69f' );

        }

    }

}

var raycaster = new THREE.Raycaster();
var mouseVector = new THREE.Vector3();

function getIntersects( x, y ) {

    x = ( x / window.innerWidth ) * 2 - 1;
    y = - ( y / window.innerHeight ) * 2 + 1;

    mouseVector.set( x, y, 0.5 );
    raycaster.setFromCamera( mouseVector, camera );

    return raycaster.intersectObject( dots, true );

}

Solution

  • First thing to do is to set raycaster.params.Points.threshold equal to the size of your points. This makes it so that the colors of all points change when a user hovers over any point:

    (I increased your point size for ease of hovering):

    <html>
    <head>
      <meta charset="UTF-8">
      <style>
        body { margin: 0; }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
      <script src="https://rawcdn.githack.com/mrdoob/three.js/f32e6f14046b5affabe35a0f42f0cad7b5f2470e/examples/js/controls/TrackballControls.js"></script>
    </head>
    
    <body>
    <script>
    
    // Create an empty scene
    var scene = new THREE.Scene();
    
    // Create a basic perspective camera
    var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
    camera.position.x = 200;
    
    // Create a renderer with Antialiasing
    var renderer = new THREE.WebGLRenderer({antialias:true});
    
    // Configure renderer clear color
    renderer.setClearColor("#000000");
    
    // Configure renderer size
    renderer.setSize( window.innerWidth, window.innerHeight );
    
    // Append Renderer to DOM
    document.body.appendChild( renderer.domElement );
    
    //Add Milky Way
    var dotGeometry = new THREE.Geometry();
    dotGeometry.vertices.push(new THREE.Vector3( 0, 0, 0));
    
    var rawFile = new XMLHttpRequest();
    rawFile.open("GET", "https://rawcdn.githack.com/RiteshSingh/galaxies/9e6a4e54b37647e5a9a1d6f16c017769533fe258/galaxydata.txt", false);
    rawFile.onreadystatechange = function ()
    {
    	if(rawFile.readyState === 4)
    	{
    		if(rawFile.status === 200 || rawFile.status == 0)
    		{
    			var allText = rawFile.responseText;
    			var data = allText.split("\n");
    
    			for (var i = 0; i < 4672; i++) {
    				var parts = data[i].split("\t");
    
    				var D = parts[0];
    				var glon = parts[1]*3.1416/180;
    				var glat = parts[2]*3.1416/180;
    
    				var z = D*Math.sin(glat);
    				var xy = D*Math.cos(glat);
    				var x = xy*Math.cos(glon);
    				var y = xy*Math.sin(glon);
    
    				dotGeometry.vertices.push(new THREE.Vector3( x, y, z));
    			}
    		}
    	}
    }
    rawFile.send(null);
    
    var size = 0.32;
    var dotMaterial = new THREE.PointsMaterial( { size: size } );
    var dots = new THREE.Points( dotGeometry, dotMaterial );
    scene.add( dots );
    
    var controls = new THREE.TrackballControls( camera, renderer.domElement );
    
    // Render Loop
    var render = function () {
      requestAnimationFrame( render );
      controls.update();
      // Render the scene
      renderer.render(scene, camera);
    };
    render();
    
    window.addEventListener( "mousemove", onDocumentMouseMove, false );
    
    var selectedObject = null;
    
    function onDocumentMouseMove( event ) {
    
    	event.preventDefault();
    	if ( selectedObject ) {
    
    		selectedObject.material.color.set( '#fff' );
    		selectedObject = null;
    
    	}
    
    	var intersects = getIntersects( event.layerX, event.layerY );
    	if ( intersects.length > 0 ) {
    
    		var res = intersects.filter( function ( res ) {
    
    			return res && res.object;
    
    		} )[ 0 ];
    
    		if ( res && res.object ) {
    
    			selectedObject = res.object;
    			selectedObject.material.color.set( '#69f' );
    
    		}
    
    	}
    
    }
    
    var raycaster = new THREE.Raycaster();
    raycaster.params.Points.threshold = size;
    var mouseVector = new THREE.Vector3();
    
    function getIntersects( x, y ) {
    
    	x = ( x / window.innerWidth ) * 2 - 1;
    	y = - ( y / window.innerHeight ) * 2 + 1;
    
    	mouseVector.set( x, y, 0.5 );
    	raycaster.setFromCamera( mouseVector, camera );
    
    	return raycaster.intersectObject( dots, true );
    
    }
    
    </script>
    </body>
    </html>

    Then you just need to make it so that only the hovered point changes color:

    <html>
    <head>
      <meta charset="UTF-8">
      <style>
        body { margin: 0; }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
      <script src="https://rawcdn.githack.com/mrdoob/three.js/f32e6f14046b5affabe35a0f42f0cad7b5f2470e/examples/js/controls/TrackballControls.js"></script></script>
    </head>
    
    <body>
    <script>
    
    // Create an empty scene
    var scene = new THREE.Scene();
    
    // Create a basic perspective camera
    var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
    camera.position.x = 200;
    
    // Create a renderer with Antialiasing
    var renderer = new THREE.WebGLRenderer({antialias:true});
    
    // Configure renderer clear color
    renderer.setClearColor("#000000");
    
    // Configure renderer size
    renderer.setSize( window.innerWidth, window.innerHeight );
    
    // Append Renderer to DOM
    document.body.appendChild( renderer.domElement );
    
    //Add Milky Way
    var dotGeometry = new THREE.Geometry();
    dotGeometry.vertices.push();
    
    var colors = [];
    
    var rawFile = new XMLHttpRequest();
    rawFile.open("GET", "https://rawcdn.githack.com/RiteshSingh/galaxies/9e6a4e54b37647e5a9a1d6f16c017769533fe258/galaxydata.txt", false);
    rawFile.onreadystatechange = function ()
    {
    	if(rawFile.readyState === 4)
    	{
    		if(rawFile.status === 200 || rawFile.status == 0)
    		{
    			var allText = rawFile.responseText;
    			var data = allText.split("\n");
    
    			for (var i = 0; i < 4672; i++) {
    				var parts = data[i].split("\t");
    
    				var D = parts[0];
    				var glon = parts[1]*3.1416/180;
    				var glat = parts[2]*3.1416/180;
    
    				var z = D*Math.sin(glat);
    				var xy = D*Math.cos(glat);
    				var x = xy*Math.cos(glon);
    				var y = xy*Math.sin(glon);
    
    				dotGeometry.vertices.push(new THREE.Vector3( x, y, z));
    
    				colors.push(new THREE.Color(0xFF0000));
    			}
    		}
    	}
    }
    rawFile.send(null);
    
    dotGeometry.colors = colors;
    
    var size = 0.32;
    var dotMaterial = new THREE.PointsMaterial({
    	size: size,
    	vertexColors: THREE.VertexColors,
    });
    var dots = new THREE.Points( dotGeometry, dotMaterial );
    scene.add( dots );
    
    var controls = new THREE.TrackballControls( camera, renderer.domElement );
    
    // Render Loop
    var render = function () {
      requestAnimationFrame( render );
      controls.update();
      // Render the scene
      renderer.render(scene, camera);
    };
    render();
    
    window.addEventListener( "mousemove", onDocumentMouseMove, false );
    
    var selectedObject = null;
    
    function onDocumentMouseMove( event ) {
    
    	event.preventDefault();
    	if ( selectedObject ) {
    
    		selectedObject.material.color.set( '#fff' );
    		selectedObject = null;
    
    	}
    
    	var intersects = getIntersects( event.layerX, event.layerY );
    	if ( intersects.length > 0 ) {
    		var idx = intersects[0].index;
    		dots.geometry.colors[idx] = new THREE.Color(0xFFFFFF);
    		dots.geometry.colorsNeedUpdate = true;
    		console.log(idx)
    	}
    }
    
    var raycaster = new THREE.Raycaster();
    raycaster.params.Points.threshold = size;
    var mouseVector = new THREE.Vector3();
    
    function getIntersects( x, y ) {
    
    	x = ( x / window.innerWidth ) * 2 - 1;
    	y = - ( y / window.innerHeight ) * 2 + 1;
    
    	mouseVector.set( x, y, 0.5 );
    	raycaster.setFromCamera( mouseVector, camera );
    
    	return raycaster.intersectObject( dots, true );
    
    }
    
    </script>
    </body>
    </html>

    You'll see the points turn white after the user mouses over them.

    I'll leave it as a pedagogical exercise for you to determine how to turn the points back to red after the mouse exits a given point :)