Exploring the globe with 3D raycasting, orbit controls, and dynamic camera rotation

It would be great if users could hover over the small spheres in different countries to get more information.

Initially, I thought using a raycaster would help achieve this, but it's not responding to mouse movements as expected. It seems like the issue might be related to the camera rotation.

This code snippet is crucial:

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 review this particular piece of code.

<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/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="4d39255f28280d7d637c7f7a637d">[email protected]</a>/build/three.module.js";
import { OrbitControls } from "https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="6a1e02180f0f2a5a445b58257d78407b79727676737c79090c12404852530c015153"><span class="__cf_email__" data-cfemail="96f2ee64f3f39069aaaa8b868db289d6feffdddcfaa9fd01fcfefdfdfd0077fbfedc6bffbffebebec07548494a3e52efefe10cd0dededece"></span>[email protected]</a>/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( new THREE.Vector3() );
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;
  
  /// NOT RELEVANT ////////////////////////////////////////////
    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 );

  /// NOT RELEVANT //////////////////////////////////////////////////////////////
    }


    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( new THREE.Vector3() );
        camera.updateMatrixWorld();

        controls.update();
    
    // pointer-camera interaction
        // const material = new THREE.LineBasicMaterial( { color: 0x0000ff } );
        // const geometry = new THREE.BufferGeometry().setFromPoints( [pointer,camera.position] );
        // const line = new THREE.LineSegments( 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 );
    } );
} );

// Convert a point [longitude, latitude] 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;

}

// Convert GeoJSON MultiLineString into spherical coordinates to 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>

Answer №1

This piece of code is designed to control rotation speed, potentially assisting with mouse events:

 var timer = new THREE.Clock();
 function animate() {
 requestAnimationFrame(animate);
 var deltaTime = timer.getDelta();
 var speed = .003;
 var xspeed = 0;
 var yspeed = 0;
 if (mixer !== null) mixer.update(deltaTime);
 // This section ensures the Earth rotates constantly
 if (earthPivot) earthPivot.rotation.y += speed;
 earthPivot.rotation.x = xspeed;
 earthPivot.rotation.y = yspeed;

 renderer.render(scene, camera);

 function handleMouseMove(event) {
 xspeed = (event.clientX / width) * 2 - 1;
 yspeed = -(event.clientY / height) * 2 + 1;
 }
 

You may want to consider converting the render into a separate function for better organization.

Answer №2

Hey there! I encountered a similar issue when working with globe visualization.

To resolve this, make sure to include the following code in your mousemove event:

var canvasBounds = renderer.domElement.getBoundingClientRect();
mousenew.x = ((clientX - canvasBounds.left) / (canvasBounds.right - canvasBounds.left)) * 2 - 1;
mousenew.y = -((clientY - canvasBounds.top) / (canvasBounds.bottom - canvasBounds.top)) * 2 + 1;

If you'd like a more detailed explanation of the code above, check out this helpful article:

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

tips on incorporating chosen while still enabling text input

I am currently utilizing chosen to showcase a single selection from a lengthy array of choices. In addition, I am seeking the functionality to enable users to submit a choice that is not listed among the available options. This customization is specifica ...

Error encountered: Unable to access undefined properties while attempting to make an API call to AirTable using NextJS version 13.4

Currently in the process of learning how to use the App router with NextJS 13.4, I encountered an issue while attempting to make an external API call. Even though I am getting all the data correctly from Airtable, Next throws an error that disrupts my try ...

Text alignment issues cause animation to vanish

Utilizing particles.js, I set up a full-screen particle effect by specifying the animation to be full-screen with height: 100vh;. This worked perfectly as intended. However, when attempting to add text on top of the particle animation and center it vertica ...

Encountering RangeError with the Number() function on my express.js server

I'm working with an HTML form that looks like this: <form class="" action="/" method="post"> <input type="text" name="num1" placeholder="First Number"> <input type= ...

Encountered difficulties while attempting to set up React.js

Why am I encountering difficulties installing React? I managed to create a node module file, but it is being automatically deleted. I ran the command npx create-react-app my-app. Below is a screenshot. Aborting installation. npm install --no-audit --save ...

Is the button failing to direct you to the intended destination?

I'm facing an issue with a button tied to a JavaScript function using onClick(); My interface allows me to ban players on a game server, but when I select anyone here: https://i.stack.imgur.com/kcE1t.png, it always selects wartog for some reason. In ...

The background color of the active tab is updated upon loading the page

I have attempted to modify this code to change the background color of li tag on click. It successfully changes the background color when hovering or clicking, but unfortunately reverts back to the default color upon page refresh. I am looking for a soluti ...

Attempting to integrate a three.js OBJLoader within an HTML canvas

My issue is quite straightforward: I attempted to connect a three.js script with an HTML canvas, but I was unsuccessful and now I'm unsure how to proceed. Here is the code I have (I've already loaded the necessary scripts in the HTML head): wi ...

Excessive image display | HTML and CSS synergize

I am having some trouble with my image gallery. Each image is displayed as a vertical column and has zooming effects when hovered over. Clicking on an image should show it in full screen with a caption. The problem arises when dealing with images that are ...

What is the best way to display a string state value in a React component?

I need assistance with displaying a state value that is in string format. Despite my efforts, I have not been successful in finding a solution and am feeling quite frustrated. Can anyone provide some guidance? ...

Guide on setting up the installation process of Gulp jshint with npm?

Having trouble with the installation of JSHint. Can anyone point out what I might be doing incorrectly? This is the command I am using: npm install --save-dev gulp-jshint gulp-jscs jshint-stylish Getting the following error message: "[email protect ...

Encountering the error 'node' getProperty of undefined while trying to retrieve data from an array stored in my state variable

Hello, I am currently developing an app that retrieves images from Instagram using axios. I have successfully stored the image files in an array named 'posts' within my state. Looping through this array to display each image is not an issue for m ...

How to determine if an Angular list has finished rendering

I am facing an issue where I have a large array that is being loaded into a ul list using ng-repeat in Angular. The loading of the list takes too long and I want to display a loader while it's loading, but hide it only when the ul list is fully render ...

Make a request to the local API in a React Native mobile app by sending a GET request

Currently, I am working on a mobile application using React Native and utilizing SQL Server and NodeJS. The API for this project is located in my localhost. Upon running the application on an emulator, I encountered an issue when attempting to make a reque ...

Comparison Between Object and Array

When inputting the values {2,4,45,50} in the console, 50 is displayed. However, assigning these values to a variable results in an error: var a = {2,4,45,50} Uncaught SyntaxError: Unexpected number Can you explain this concept? ...

What methods can be used to troubleshoot an issue of an AngularJS function not being called

I am facing an issue with a dynamic table I have created. The rows and action buttons are generated dynamically when the user clicks on the Devices accordion. However, there seems to be a problem with the function expandCollapseCurrent(). Whenever the use ...

Loop through the v-for based on the input number that was selected

I'm looking to accomplish the following: if a user selects input X (a number between 1 and 10), I want to render a specific vue component X times. It seems to work if I use v-for n in 10, but not when I use variables. <template> <div> ...

Ways to delete a property from an element that was originally included with jquery

On my jsp page, I am implementing a jQuery autocomplete feature for a text field with the id "mytextfield". jQuery(function(){ $("#mytextfield").autocomplete("popuppages/listall.jsp"); }); Sometimes, based on user input and pr ...

Is there a method to retrieve all Class elements without using a for-loop?

I am trying to retrieve all elements with a specific class using document.getElementsByClassName(); <body> <div class="circle" id="red"></div> <div class="circle" id="blue"></div> <div class="circle" id="yell ...

React: Selecting Component Name Based on Provided Property

My goal is to dynamically load an icon component in React passed as a prop from the parent component. Dashboard (parent): import { Button } from './components'; function App() { return ( <div className="app"> <d ...