0

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: https://codesandbox.io/s/earth-rotation-fcp210?file=/src/MainComponent.js

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

Tried: Stopping and restarting the animation in order to get the updated value of "SelectedValue" inside of my animate function

Expected: The current animation being canceled, a new one being started and the updated value of selectedValue being logged to the console.

Result: the updated value of selectedValue being logged to the console but the effects of the animation not being displayed/rendered.

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
Harrihase
  • 1
  • 2
  • Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. – Community May 24 '23 at 14:14

0 Answers0