animate function is being called but not displayed

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

It will help if you provide a live example of your program on codepen or a similar platform and explain how to reproduce your issue.

Hi, thanks for your reply. Here is a codesandbox link. I added the steps to reproduce to my question.

I don’t do react but there are some react errors that you probably should fix first.

I see a problem here:

  useEffect(() => {
    //fetchData();
    //fetchPrice();
    stopAnimation();
    animate();
  }, [selectedValue]);

the way your code is written, when you assign animationRef.current from requestAnimationFrame it is the ID of the next call of animate, not the currently executed one, so when you pass it to cancelAnimationFrame you might cancel the scheduled pass, not the current one, or you might cancel the current one but after the next one is scheduled. This might also end up having two animate loops running.

If you comment out

   // stopAnimation();
  //  animate();

and switch from option1 to option2, the earth keeps rotating.

I know deleting those two lines will keep the earth rotation. The problem is that unless you stop the current animation and start a new one. The used values inside the animate function wont be updated. So when commenting out the two lines you just mentioned, you will see that the logging of the selected value inside of the animate function will always stay “Option 1”, regardless of whats actually selected.

I don’t think it has anything to do with animate loop or THREE, it’s a react issue.