Search code examples
javascriptreactjsthree.jsparameter-passingreact-class-based-component

Using data passed from parent to child classes for adding points on globe


I am working on a fun project, to help boost my knowledge and understanding of ReactJS coupled with ThreeJS, but have run into an issue for passing in data from the parent class to the functions in the child class. Right now my project looks as follows: App.js

import React, {Component} from 'react';

import {Globe, Building, BuildingBody} from './components';
import styles from './App.module.css';
import { fetchData } from './assets/api';

class App extends Component {
    state = {
      data: {},
      state: '',
      isMounted: true,
  }

  componentDidMount() {
      const fetchedData = fetchData();
      this.setState({ data: fetchedData});
      
  }

  handleStateChange = (state) => {
      const fetchedData = fetchData(state);
      this.setState({data: fetchedData, state: state})
  }
  render() {
    const { data, state, isMounted=true } = this.state;
    return (
      <div className={styles.container}>
        <div className={styles.dataContainer}>
          <Building handleStateChange={this.handleStateChange.bind(this)}/>
          <BuildingBody data = {data} state={state} />
        </div>
        <Globe data = {data} state={state}/>
      </div>
    );
  }
}

export default App;

My ThreeJS portion looks like this: Globe.jsx

import React, { Component } from 'react';
import * as THREE from 'three';
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls';

import earthmap from '../../assets/images/earthmap4k.jpg';
import earthbump from '../../assets/images/earthbump4k.jpg';
import earthspec from '../../assets/images/earthspec4k.jpg';
import particle from '../../assets/images/particle.jpg';
import clouds from '../../assets/images/earthhiresclouds4K.jpg';

class Globe extends Component {
    constructor(props) {
        super (props);
        this.state = {
            state: props.state,
            data: props.data
        }
    }
    
    componentDidMount() {
        console.log(this.state.data)
        console.log(this.state.state)
        this.createScene();
        this.addSceneObjects();
        this.startAnimation();
        window.addEventListener('resize', this.handleWindowResize);
    }

    componentWillUnmount() {
        window.removeEventListener('resize', this.handleWindowResize);
        window.cancelAnimationFrame(this.requestID);
        this.controls.dispose();
    }

    createScene = () => {
        this.scene = new THREE.Scene();
        this.camera = new THREE.PerspectiveCamera( 75, 720 / 480, 0.1, 1000 );
        this.controls = new OrbitControls(this.camera, this.mount);
        this.controls.enableZoom = false
        
        this.renderer = new THREE.WebGLRenderer({ antialiasing: true });
        this.renderer.setSize( 720, 480 );

        this.mount.appendChild( this.renderer.domElement );
        this.camera.position.z = 20;
    };

    addSceneObjects = () => {
        this.addLight();
        this.addEarth();
        this.addCoord();
    };

    addLight = () => {
        const lights = [];
        lights[0] = new THREE.PointLight(0xffffff, .3, 0);
        lights[1] = new THREE.PointLight(0xffffff, .4, 0);
        lights[2] = new THREE.PointLight(0xffffff, .7, 0);
        lights[3] = new THREE.AmbientLight( 0x706570 );

        lights[0].position.set(0, 200, 0);
        lights[1].position.set(200, 100, 400);
        lights[2].position.set(-200, -200, -50);

        this.scene.add(lights[0]);
        this.scene.add(lights[1]);
        this.scene.add(lights[2]);
        this.scene.add(lights[3]);
    };

    addEarth = () => {
        const earthMap = new THREE.TextureLoader().load( earthmap );
        const earthBumpMap = new THREE.TextureLoader().load( earthbump);
        const earthSpecMap = new THREE.TextureLoader().load( earthspec);

        const earthGeometry = new THREE.SphereGeometry( 10, 32, 32 );
        const earthMaterial = new THREE.MeshPhongMaterial({
            map: earthMap,
            bumpMap: earthBumpMap,
            bumpScale: 0.10,
            specularMap: earthSpecMap,
            specular: new THREE.Color('grey')
        });

        this.earthSphere = new THREE.Mesh( earthGeometry, earthMaterial );
        this.scene.add( this.earthSphere );

        const earthGeo = new THREE.SphereGeometry(10, 36, 36 );
        const cloudsTexture = THREE.ImageUtils.loadTexture( clouds );
        const materialClouds = new THREE.MeshLambertMaterial({
            color: 0xffffff, 
            map: cloudsTexture, 
            transparent:true, 
            opacity:0.4
        });

        this.earthClouds = new THREE.Mesh( earthGeo, materialClouds );
        this.earthClouds.scale.set( 1.015, 1.015, 1.015 );
        this.earthSphere.add( this.earthClouds );
    };

    addCoord = () => {
        this.particleMat = new THREE.PointsMaterial({
            color: 'rgb(255, 255, 255)',
            size: 0.25,
            //map: new THREE.TextureLoader().load(particle),
            //transparent: true,
            //blending: THREE.AdditiveBlending,
            //depthWrite: false
        });

        this.particleGeo = new THREE.SphereGeometry(10, 64, 64);

        this.particleGeo.vertices.forEach(function(vertex) {
            const lat = 31.688372;
            const lon = -106.405023;
            const radius = 10;
            const phi   = (90-lat)*(Math.PI/180);
            const theta = (lon+180)*(Math.PI/180);
            vertex.x = -((radius) * Math.sin(phi)*Math.cos(theta));
            vertex.z = ((radius) * Math.sin(phi)*Math.sin(theta));
            vertex.y = ((radius) * Math.cos(phi));
        });

        this.particleSystem = new THREE.Points(
            this.particleGeo,
            this.particleMat
        );

        this.particleSystem.name = 'particleSystem';

        this.earthClouds.add(this.particleSystem);
    };

    startAnimation = () => {
        this.earthSphere.rotation.y += 0.0009;
        //this.earthClouds.rotation.y += 0.001;
        this.requestID = window.requestAnimationFrame(this.startAnimation);
        //requestAnimationFrame(this.startAnimation);
    
        this.controls.update();

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

    handleWindowResize = () => {
        const width = this.mount.innerWidth;
        const height = this.mount.innerHeight;

        this.renderer.setSize(width, height);
        this.camera.aspect = width / height;

        this.camera.updateProjectionMatrix();
    };

    render() {
        console.log(this.props.data)
        console.log(this.props.state)
        return (
        <>
            <div ref={ref => (this.mount = ref)} data = {this.props.data} state = {this.props.state}>
                
            </div>
        </>
        )
    }
}

export default Globe;

The data from the App.js does get passed into the render() function of my Globe.jsx. However, I am wanting to take that data and pass it into the Globe.jsx body for populating the points using

this.particleGeo.vertices.forEach(function(vertex) {
            const lat = **INSERT DATA.latitude**;
            const lon = **INSERT DATA.longitude**;
            const radius = 10;
            const phi   = (90-lat)*(Math.PI/180);
            const theta = (lon+180)*(Math.PI/180);
            vertex.x = -((radius) * Math.sin(phi)*Math.cos(theta));
            vertex.z = ((radius) * Math.sin(phi)*Math.sin(theta));
            vertex.y = ((radius) * Math.cos(phi));
        });

but I am currently lost in accomplishing this task. I know it is possible to access the data in a component which is a function, but when attempting to convert my Globe.jsx to a function instead of a class, my code breaks (after changing this.state to variables/lets/etc.). If you all could please help me out, I would appreciate it.


Solution

  • You can access props with this.props inside functions:

    addCoord = () => {
        const data = this.props.data;
       ....
    }