1

Basically, I want to access my 3D model element as well as want to control the camera and other things like mesh, animation etc from another component. I'm using pure threeJs and react for this. I've added pure threejs code in the componentDidMount part of the react App class and I want to control suppose the camera part from componentDidMount from another component named CollapseButton. How can I access those camera,scene,materials from CollapseButton? Also when I click a button from CollapseButton I want to do a specific task with the threeJs part declared in the componentDidMount.

In short: From CollapseButton I want to click a button and do a specific task on the pure threeJs part declared in ComponentDidMount. Click a button defined in CollapseButton> Call a function or do something on componentDidmount/threejs part

Here is my App.js:

// ... App.js
import React from "react";
import ReactDOM from "react-dom";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import './App.css';
import CollapseButton from './CollapseButton'
class App extends React.Component {
  componentDidMount() {
    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    this.mount.appendChild(renderer.domElement);

    const sizes = {
      width: window.innerWidth,
      height: window.innerHeight,
    };

    //Creating scene
    var scene = new THREE.Scene();

    //creating camera
    var camera = new THREE.PerspectiveCamera(
      75,
      window.innerWidth / window.innerHeight,
      0.1,
      1000
    );
    camera.position.x = 100;
    camera.position.y = 20;
    camera.position.z = 2;
    camera.lookAt(0, -10, 0);

    window.addEventListener("resize", () => {
      // Update sizes
      sizes.width = window.innerWidth;
      sizes.height = window.innerHeight;

      // Update camera
      camera.aspect = sizes.width / sizes.height;
      camera.updateProjectionMatrix();

      // Update renderer
      renderer.setSize(sizes.width, sizes.height);
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    });

    //loader
    let mesh;
    const gltfLoader = new GLTFLoader();
    gltfLoader.load("LeftBankUnitBoxes.glb", handle_load);
    const texture = new THREE.TextureLoader();
    const grass = texture.load("/textures/Grass.PNG");
    function handle_load(gltf) {
      mesh = gltf.scene;
      mesh.traverse((child) => {
        // if (child.material && child.material.name === "M_UnitBox_349") {
        //     child.material.map=grass

        //     // console.log(child.material)

        //   }
        if (child.material) {
          child.material.map = grass;
          // child.material.metalness=0.8
          // child.material.roughness=0.2
          // child.material = new THREE.MeshStandardMaterial({
          //   map: grass,
          //   metalness:1,
          //   roughness:10,
          //   metalnessMap:asphalt,
          //   envMap:cubeTextureLoader
          //   // transparent:true,
          //   // opacity: 0.2,
          // });
          // console.log(child.material)
        }
      });

      mesh.position.y = -50;
      scene.add(mesh);
    }

    function call(){
      console.log("Calling log")
    }

    //creating controls
    const orbit = new OrbitControls(camera, renderer.domElement);
    orbit.maxPolarAngle = Math.PI / 2;
    // orbit.autoRotate = true;
    orbit.update();

    //creating lights

    var Dirlight = new THREE.DirectionalLight("#ffffff", 1);
    var ambient = new THREE.AmbientLight("#ffffff", 1);
    Dirlight.position.set(-50, 20, 0).normalize();

    Dirlight.castShadow = true;
    scene.add(Dirlight);

    scene.add(ambient);

    //animate
    var animate = function () {
      requestAnimationFrame(animate);

      renderer.render(scene, camera);
      orbit.update();
    };
    animate();
  }

  render() {
    return <div ref={(ref) => (this.mount = ref)} />;
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <>
    <App />
    <div className="Collapse">
      <CollapseButton call={call}/>
    </div>
  </>,
  rootElement
);
export default App;

Here is my CollapseButton:

import React, { useState } from 'react'
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
import { Button } from '@mui/material';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';

export default function Collapse({call}) {
    const [icon,setIcon]=useState(false)

    const show=()=>{
        //handleRender()
        call()
        setIcon(!icon)
    }
  return (
    <>
        <Button style={{maxWidth: '30px', maxHeight: '30px', minWidth: '30px', minHeight: '30px'}} onClick={show}>
            {icon?<ArrowBackIosIcon style={{color:'white',fontSize:24}}/>:<ArrowForwardIosIcon  style={{color:'white',fontSize:24,fontWeight:'bold'}} />}
        </Button>
        
    </>
  )
}

I tried to pass a function from ComponentDidMount to CollapseButton which may called when I click a button from CollapseButton but couldn't find any way to pass the function.

Tanjil Hossain
  • 79
  • 1
  • 10

2 Answers2

1

You can store a reference to your threeJS component in a React ref and expose it to your collapse button as a prop (or by a React context if you plan to use it in more than one component)

As a prop:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.objectRef = React.createRef();
  }

  componentDidMount() {
    // ...three.js code
    this.objectRef.current = myObject;
  }

  render() {
    return (
     <>
       <div ref={(ref) => (this.mount = ref)} />
       <div className="Collapse">
         <CollapseButton call={call} object={objectRef} />
       </div>
     </>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <App />,
  rootElement
);
export default App;
export default function Collapse({ call, object }) {
  // ...

    const show=()=>{
        const myObject = object.current; // Get the object
        // Perform the task here

        call()
        setIcon(!icon)
    }

  // ...
}

Using context:

const ThreeContext = React.createContext({});

class App extends React.Component {
  constructor(props) {
    super(props);
    this.objectRef = React.createRef();
  }

  componentDidMount() {
    // ...three.js code
    this.objectRef.current = myObject;
  }

  render() {
    return (
     <ThreeContext.Provider value={{ object: this.objectRef }}>
       <div ref={(ref) => (this.mount = ref)} />
       <div className="Collapse">
         <CollapseButton call={call} />
       </div>
     </ThreeContext.Provider>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <App />,
  rootElement
);
export default App;
export default function Collapse({ call }) {
  // ...

    const show=()=>{
        const myObject = this.context.object?.current; // Get the object
        // Perform the task here

        call()
        setIcon(!icon)
    }

  // ...
}
Robert May
  • 799
  • 6
  • 13
0

You can move the three.js related code in different file and import where required

T3.js

// Creating renderer
const renderer = new THREE.WebGLRenderer();
// Creating scene
const scene = new THREE.Scene();
// Creating camera
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

const animate = function () {
  // requestAnimationFrame(this.animate);

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

export default {
  renderer,
  scene,
  camera,
  texture: new THREE.TextureLoader(),
  gltfLoader: new GLTFLoader(),
  gltfHandler: function (gltf) {
    console.log(this);
    const grass = this.texture.load('/textures/Grass.PNG');
    let mesh = gltf.scene;
    mesh.traverse((child) => {
      // if (child.material && child.material.name === "M_UnitBox_349") {
      //     child.material.map=grass

      //     // console.log(child.material)

      //   }
      if (child.material) {
        child.material.map = grass;
        // child.material.metalness=0.8
        // child.material.roughness=0.2
        // child.material = new THREE.MeshStandardMaterial({
        //   map: grass,
        //   metalness:1,
        //   roughness:10,
        //   metalnessMap:asphalt,
        //   envMap:cubeTextureLoader
        //   // transparent:true,
        //   // opacity: 0.2,
        // });
        // console.log(child.material)
      }
    });

    mesh.position.y = -50;
    scene.add(mesh);
  },
  orbit: new OrbitControls(camera, renderer.domElement),
  sizes: {
    width: window.innerWidth,
    height: window.innerHeight,
  },
  init: function () {
    this.renderer.setSize(window.innerWidth, window.innerHeight);

    this.camera.position.x = 100;
    this.camera.position.y = 20;
    this.camera.position.z = 2;
    this.camera.lookAt(0, -10, 0);

    window.addEventListener('resize', () => {
      // Update sizes
      this.sizes.width = window.innerWidth;
      this.sizes.height = window.innerHeight;

      // Update camera
      this.camera.aspect = this.sizes.width / this.sizes.height;
      this.camera.updateProjectionMatrix();

      // Update renderer
      this.renderer.setSize(this.sizes.width, this.sizes.height);
      this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    });

    //loader
    this.gltfLoader.load('LeftBankUnitBoxes.glb', this.gltfHandler);

    //creating controls
    this.orbit.maxPolarAngle = Math.PI / 2;
    // orbit.autoRotate = true;
    this.orbit.update();

    //creating lights
    var Dirlight = new THREE.DirectionalLight('#ffffff', 1);
    var ambient = new THREE.AmbientLight('#ffffff', 1);
    Dirlight.position.set(-50, 20, 0).normalize();

    Dirlight.castShadow = true;
    this.scene.add(Dirlight);
    this.scene.add(ambient);
  },
  animate,
  call: function () {
    console.log('Calling log');
  },
};

App.js

import React from 'react';
import './style.css';

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
import { Button } from '@mui/material';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import T3 from './T3'

class App extends React.Component {
  componentDidMount() {
    T3.init();
    this.mount.appendChild(T3.renderer.domElement);
    T3.animate();
  }

  render() {
    return <div ref={(ref) => (this.mount = ref)} />;
  }
}

Collapse.js

import React, { useState } from 'react'
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
import { Button } from '@mui/material';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import T3 from './T3'

function Collapse({ call }) {
  const [icon, setIcon] = useState(false);

  const show = () => {
    //handleRender()
    T3.call();
    setIcon(!icon);
  };
  return (
    <>
      <Button
        style={{
          maxWidth: '30px',
          maxHeight: '30px',
          minWidth: '30px',
          minHeight: '30px',
        }}
        onClick={show}
      >
        {icon ? (
          <ArrowBackIosIcon style={{ color: 'white', fontSize: 24 }} />
        ) : (
          <ArrowForwardIosIcon
            style={{ color: 'white', fontSize: 24, fontWeight: 'bold' }}
          />
        )}
      </Button>
    </>
  );
}
Chandan
  • 11,465
  • 1
  • 6
  • 25