3Js Raycasting + OrbitControls + Camera Rotation on a Globe

I would like for the user to be able to hover over the little spheres inside the countries to receive information.

Using a raycaster seems to be the first step here, but it does not trigger upon mouse move. Also, it is not pointing where the mouse is. I believe this has something to do with the camera being rotated.

This is less important because I think I can figure it out, but it’s a bug in the code snippet below so I’m mentioning it:

I also would like for my globe to be rotating constantly, unless the user is controlling it with OrbitControls. Right now, when I try to use OrbitControls (dragging the mouse), the globe just zooms in and out- why is that? Does this have to do with the camera being updated constantly in d3.timer()?

I think this is the part of the code that’s most important.

d3.timer( function ( t ) {
        theta += 0.1;

        camera.position.x = 100 * Math.sin( THREE.MathUtils.degToRad( theta ) );
        camera.position.y = 100 * Math.sin( THREE.MathUtils.degToRad( theta ) );
        //camera.position.z = 100 * Math.cos( THREE.MathUtils.degToRad( theta ) );
        camera.lookAt( scene.position );
        camera.updateMatrixWorld();

        controls.update();
        raycaster.setFromCamera( pointer, camera );
        const intersects = raycaster.intersectObjects( earthPivot.children, false );

        if ( intersects.length > 0 ) {

                if ( INTERSECTED != intersects[ 0 ].object ) {

                    console.log(intersects[0].object.name)
                    INTERSECTED = intersects[ 0 ].object;

                }

            } else {

                INTERSECTED = null;

            }

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

Please take a look at this snippet.

<div id='container'> </div>
<script src="https://d3js.org/d3.v6.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>
<script src="https://unpkg.com/d3-array@1"></script>
<script src="https://unpkg.com/d3-collection@1"></script>
<script src="https://unpkg.com/d3-dispatch@1"></script>
<script src="https://unpkg.com/d3-request@1"></script>
<script src="https://unpkg.com/d3-timer@1"></script>
<script type='module'>
import * as THREE from "https://unpkg.com/[email protected]/build/three.module.js";
import { OrbitControls } from "https://unpkg.com/[email protected]/examples/jsm/controls/OrbitControls.js";

var width = 650;
var height = 650;
var radius = 168,
    scene = new THREE.Scene(),
    camera = new THREE.PerspectiveCamera( 100, width / height, 1, 1000 ),
    renderer = new THREE.WebGLRenderer( { alpha: true } ),
    container = document.getElementById( 'container' ),
    controls,
    raycaster;

const pointer = new THREE.Vector2();
let INTERSECTED;
raycaster = new THREE.Raycaster();
container.addEventListener( 'mousemove', pointerMove );
let theta = 0;

scene.background = new THREE.Color( "rgb(20,20,20)" );

camera.position.set( 0, 0, 300 );
camera.lookAt( 0, 0, 0 );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( width, height );
container.appendChild( renderer.domElement );

let earthPivot = new THREE.Group();

d3.json( "https://raw.githubusercontent.com/alessiogmonti/alessiogmonti.github.io/master/Pages/Index/dataFranceModified.json", function ( error, topology ) {

    if ( error ) throw error;
  
  /// NO NEED TO LOOK AT ////////////////////////////////////////////
    var countries = [];
    var cones = [];
    for ( var i = 0; i < topology.objects.countries.geometries.length; i ++ ) {

        var rgb = [];
        var newcolor;
        for ( var j = 0; j < 3; j ++ ) {
            rgb.push( Math.floor( Math.random() * 255 ) );
            newcolor = 'rgb(' + rgb.join( ',' ) + ')';
        }

        var mesh = wireframe( topojson.mesh( topology, topology.objects.countries.geometries[ i ] ), new THREE.LineBasicMaterial( { color: newcolor, linewidth: 5 } ) );
        countries.push( mesh );
        scene.add( mesh );

        mesh.geometry.computeBoundingBox();
        var center = new THREE.Vector3();
        mesh.geometry.boundingBox.getCenter( center );

        mesh.add( earthPivot );
        let height = 1;
        const geometry = new THREE.SphereGeometry( height, 50, 36 );
        const material = new THREE.MeshBasicMaterial( { color: newcolor } );
        const cone = new THREE.Mesh( geometry, material );
        cone.position.copy( center );
        cone.position.setLength( radius + height );
        cone.name = topology.objects.countries.geometries[ i ].properties[ 'name' ];
        cones.push( cone );
        earthPivot.add( cone );

  /// NO NEED TO LOOK AT //////////////////////////////////////////////////////////////
    }


    controls = new OrbitControls( camera, renderer.domElement );

    const helper = new THREE.CameraHelper( camera );
    scene.add( helper );

    const axesHelper = new THREE.AxesHelper( 50 );
    scene.add( axesHelper );

    d3.timer( function ( t ) {
        theta += 0.1;

        camera.position.x = 100 * Math.sin( THREE.MathUtils.degToRad( theta ) );
        camera.position.y = 100 * Math.sin( THREE.MathUtils.degToRad( theta ) );
        //camera.position.z = 100 * Math.cos( THREE.MathUtils.degToRad( theta ) );
        camera.lookAt( scene.position );
        camera.updateMatrixWorld();

        controls.update();

    // this code was to look at the interaction between pointer and camera
        // const material = new THREE.LineBasicMaterial( { color: 0x0000ff } );
        // const geometry = new THREE.BufferGeometry().setFromPoints( [pointer,camera.position] );
        // const line = new THREE.Line( geometry, material );
        // scene.add(line)

        raycaster.setFromCamera( pointer, camera );
        const intersects = raycaster.intersectObjects( earthPivot.children, false );

        if ( intersects.length > 0 ) {

                if ( INTERSECTED != intersects[ 0 ].object ) {

                    console.log(intersects[0].object.name)
                    INTERSECTED = intersects[ 0 ].object;

                }

            } else {

                INTERSECTED = null;

            }

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

// Converts a point [longitude, latitude] in degrees to a THREE.Vector3.
function vertex( point ) {

    var lambda = point[ 0 ] * Math.PI / 180,
        phi = point[ 1 ] * Math.PI / 180,
        cosPhi = Math.cos( phi );
    return new THREE.Vector3(
        radius * cosPhi * Math.cos( lambda ),
        radius * cosPhi * Math.sin( lambda ),
        radius * Math.sin( phi )
    );

}

function pointerMove( event ){

    pointer.x = ( event.clientX / width ) * 2 - 1;
    pointer.y = - ( event.clientY / height ) * 2 + 1;

}

// Converts a GeoJSON MultiLineString in spherical coordinates to a THREE.LineSegments.
function wireframe( multilinestring, material ) {

    var geometry = new THREE.BufferGeometry();
    var pointsArray = new Array();
    multilinestring.coordinates.forEach( function ( line ) {

        d3.pairs( line.map( vertex ), function ( a, b ) {

            pointsArray.push( a, b );

        } );

    } );
    geometry.setFromPoints( pointsArray );
    return new THREE.LineSegments( geometry, material );

}
</script>