Hi, I got a react component that uses Three.js for displaying a globe consisting of an earthmesh and a cloudmesh. I have a function (const) called animate, that rotates the globe and updates the TrackingBallControls, which I use additionally. I also got a useState called selectedValue that can take different values. Whenever the selectedValue changes I want to stop the current animation and start a new one afterwards in order to be able to access changed values inside the animate function. Whenever I change the selectedValue the animation stops and is being called again as expected. However the rotation of the earthMesh as well as the controls are no longer being displayed. When logging the rotation of the earthMesh I’m seing the value being updated, but the animation is just not being shown/updated. I’m really desperate already because I just can’t seem to find the root cause of this and especially no solution. Can anyone tell me what im doing wrong here? Thank you in Advance!
import React, { useState, useEffect, useRef } from 'react';
import axios from 'axios';
import './MainComponent.css';
import * as THREE from 'three';
import map from './assets/earth_living.jpg';
import bumpMap from './assets/elev_bump_8k.jpg';
import earth_clouds from './assets/fair_clouds_8k.jpg';
import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls';
import CoordinateStorage from './CoordinateStorage';
import {Grid, Box} from '@mui/material';
const MainComponent = ({selectedTopic}) => {
const [selectedValue, setSelectedValue] = useState(selectedTopic);
const containerRef = useRef(); //Container für das 3D Model
const modelCreatedRef = useRef(false);
const earthMeshRef = useRef();
const animationRef = useRef(null);
var scene, camera, renderer, controls, canvas, material, cloudMaterial, light;
var cloudGeometry, cloudMesh;
var animationSpeed = 0.0005;
useEffect(() => {
setSelectedValue(selectedTopic);
removeExistingLocations();
}, [selectedTopic]);
/**
* Creates the 3D Model of the earth
*/
const setup3DModel = () => {
material = new THREE.MeshStandardMaterial();
material.map = new THREE.TextureLoader().load(map)
material.bumpMap = new THREE.TextureLoader().load(bumpMap);
material.bumpScale = 0.2;
cloudMaterial = new THREE.MeshPhongMaterial();
cloudMaterial.map = new THREE.TextureLoader().load(earth_clouds);
cloudMaterial.transparent = true;
cloudMaterial.opacity = 0.3;
cloudGeometry = new THREE.SphereGeometry(1.01, 32, 32);
cloudMesh = new THREE.Mesh(cloudGeometry, cloudMaterial);
light = new THREE.PointLight(0xffffff, 1);
light.position.set(5, 0, 0);
scene = new THREE.Scene();
scene.background = new THREE.Color(0x282c34);
camera = new THREE.PerspectiveCamera(50, window.innerWidth/window.innerHeight, 1, 1000);
camera.position.z = 2.8;
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth * 0.65, window.innerHeight/1.5);
camera.aspect = window.innerWidth * 0.65/(window.innerHeight/1.5);
camera.updateProjectionMatrix();
window.addEventListener('resize', function() {
renderer.setSize( window.innerWidth * 0.65, window.innerHeight/1.5);
camera.aspect = window.innerWidth * 0.65/(window.innerHeight/1.5);
camera.updateProjectionMatrix();
})
canvas = renderer.domElement;
document.body.appendChild(renderer.domElement);
controls = new TrackballControls(camera, renderer.domElement);
controls.minDistance = 2.1;
controls.maxDistance = 8;
controls.noPan = true;
scene.add(light);
}
setup3DModel();
const animate = () => {
animationRef.current = requestAnimationFrame(animate);
earthMeshRef.current.rotation.y += animationSpeed;
cloudMesh.rotation.y += animationSpeed * 0.85;
light.position.copy(camera.position);
console.log(selectedValue);
controls.update();
renderer.render(scene, camera);
}
const stopAnimation = () => {
cancelAnimationFrame(animationRef.current);
};
/**
* Creates the cloud layer, adds Mouse Listeners to the canvas and defines animation function
*/
const create3DModel = () => {
const cylinderMaterial = new THREE.MeshBasicMaterial();
// Erstelle eine Kugelgeometrie
const geometry = new THREE.SphereGeometry(1, 32, 32);
// erstelle wolken/kugel geometrie
// erstelle eine zylindergeometrie
const cylinderGeometry = new THREE.CylinderGeometry(0.01,0.01,0.5);
// Erstelle ein Mesh-Objekt mit der Geometrie und dem Material
earthMeshRef.current = new THREE.Mesh(geometry, material);
//const cityMesh = new THREE.Mesh(cityGeometry, cityMaterial);
const cylinderMesh = new THREE.Mesh(cylinderGeometry, cylinderMaterial);
//cancel mouse movement when rotating globe
canvas.addEventListener('mousedown', function(event) {
animationSpeed = 0;
})
//right-click listener for restarting the rotation animation after drag event
canvas.addEventListener("contextmenu", function(event){
// Verhindert das Öffnen des Kontextmenüs
event.preventDefault();
animationSpeed = 0.0005;
});
cylinderMesh.position.set(translatedCoordinates[0],translatedCoordinates[1],translatedCoordinates[2]);
scene.add(earthMeshRef.current);
scene.add(cloudMesh);
containerRef.current.appendChild(canvas);
}
useEffect(() => {
if (!modelCreatedRef.current) {
create3DModel();
modelCreatedRef.current = true;
}
}, []);
useEffect(() => {
fetchData();
fetchPrice();
stopAnimation();
animate();
}, [selectedValue]);
useEffect(() => {
const interval = setInterval(() => {
fetchData();
fetchPrice();
}, 5 * 1000); //15 * 60 * 1000 to call every 15 minutes
return () => {
clearInterval(interval);
};
}, []);
return (
<div className='main-class'>
<Grid container>
<Grid item xs={6} ref={containerRef}></Grid>
<Grid item xs={6}>
<div>
<label>{selectedValue}</label>
</div>
</Grid>
</Grid>
</div>
);
}
export default MainComponent;
Here is a codesandbox link, where my problem can be reproduced:
Steps to reproduce:
- Have a look into the console: Option 1 will be logged each frame when the animation started
- Now change the value of the Fitler field above to let’s say “Option 2”
- The animation stops but the value of “Option 2” is still being logged each frame, which means the animate function is being called correctly, but the effects of the value changes inside the animate function are just not being displayed/rendered