firtoz / react-three-renderer

Render into a three.js canvas using React.
https://toxicfork.github.com/react-three-renderer-example/
MIT License
1.49k stars 155 forks source link

Trouble Trying to Access "Scene" via refs #171

Closed Rperry2174 closed 7 years ago

Rperry2174 commented 7 years ago

I'm currently trying to import a .obj and a .mtl file to follow the structure of the source code from this example: https://threejs.org/examples/webgl_loader_obj_mtl.html

The issue that I'm having is that after I've imported the object and the mtl I try to add it to the scene using this.refs and then subsequently scene.children.push( CONVERTED _ OBJECT ). When I look at console.log statements both before and after I see that the "scene" object is gaining a child, but I can't seem to get that child to appear on screen. Any idea what the issue is? Relevant code pasted below:

import React from 'react';
import React3 from 'react-three-renderer';
import * as THREE from 'three';
import OBJLoader from 'three-obj-loader'
OBJLoader(THREE);
import MTLLoader from 'three-mtl-loader'

class Transform extends React.Component {
  static propTypes = {
    width: React.PropTypes.number.isRequired,
    height: React.PropTypes.number.isRequired,
    color: React.PropTypes.string.isRequired,
  };
  constructor(props, context) {
    super(props, context);
    this.cameraPosition = new THREE.Vector3(1000, 500, 1000);
    this.lookAt = new THREE.Vector3(0, 200, 0)
    this.lightPosition = new THREE.Vector3(1, 1, 1)
    THREE.ImageUtils.crossOrigin = ''; //moved from render()
    this.object;

    this._onAnimate = () => {
    };
  }

  componentDidMount(){
    const {
      scene
    } = this.refs;
    console.log("ref scenein comoponent did mount", scene);
    console.log("ref scene", this.refs);

    var onProgress = function ( xhr ) {
      if ( xhr.lengthComputable ) {
        var percentComplete = xhr.loaded / xhr.total * 100;
        console.log( Math.round(percentComplete, 2) + '% downloaded' );
      }
    };
    var onError = function ( xhr ) {
      console.log("error", xhr)
    };

    console.log("on progress mounted")

    var manager = new THREE.LoadingManager();
    manager.onProgress = function ( item, loaded, total ) {
       console.log( item, loaded, total );
    };
    console.log("manager mounted")

    var texture = new THREE.Texture();
    var loader = new THREE.ImageLoader( manager );
    loader.load( 'https://s3-us-west-2.amazonaws.com/ryaperry-bucket/demo.mtl', function ( image ) {

      texture.image = image;
      texture.needsUpdate = true;

    } );

    var loader = new THREE.OBJLoader( manager );
    loader.load( 'https://s3-us-west-2.amazonaws.com/ryaperry-bucket/demo.obj', function ( object ) {

      object.traverse( function ( child ) {
        if ( child instanceof THREE.Mesh ) {
          child.material.map = texture;
        }
      });

      object.position.y = - 95;
      console.log("Scene before: ", scene) //shows a scene with 4 children
      scene.children.push(object);
      console.log("Scene after: ", scene) // shows a scene with 5 children 

    }, onProgress, onError );

  }

  render() {
    const {
      width,
      height,
    } = this.props;

    return (<React3
      mainCamera="camera" // this points to the perspectiveCamera below
      width={width}
      height={height}
      onAnimate={this._onAnimate}
      ref="react3"
    >
      <scene ref="scene">
        <perspectiveCamera
          name="camera"
          fov={70}
          aspect={width / height}
          near={1}
          far={3000}
          position={this.cameraPosition}
          lookAt={this.lookAt}
        />
        <gridHelper size={1000} divisions={10} />
        <directionalLight color={0xffffff} intensity={2} position={this.lightPosition} />
         <mesh
        >
          <boxGeometry
            width={1}
            height={1}
            depth={1}
          />
          <meshBasicMaterial
            color={new THREE.Color( this.props.color )}
            map= {THREE.ImageUtils.loadTexture('https://s3-us-west-2.amazonaws.com/ryaperry-bucket/grasslight-big.jpg')}
          />
        </mesh>
      </scene>
    </React3>);
  }
}
export default Transform;

In the above example it seems as though everything is happening correctly. The output is group which makes sense, but then as I use the React devTools I don't see that new group as an element.

Additionally, in examples online there is often the "scene.add(MESH, CAMERA, etc.)" method used for an object that was created; if someone knows a better way to do that in react-three-renderer than using refs (I did see the other issue on this thread about that) - then that may also solve this issue. Any help would be greatly appreciated.

toxicFork commented 7 years ago

scene.add should work, it will not appear in react devtools because it will be happening outside of react.

57 had some workarounds for externally loaded models as well, which involved using a <group ref={...}/> as a child of the scene and then doing group.add(thing) instead of adding it directly to the scene, but I am guessing it may have a similar result in the end.

I'll take a deeper look when I have the opportunity (hopefully soon but can't promise ETA) and report back on what I find :)

Rperry2174 commented 7 years ago

Just to update as far as the answer to the question I asked; the solution you suggested worked out. I created the following function:

loadThing(){
    const PATH = "https://s3-us-west-2.amazonaws.com/BUCKET/"
    const MTL_FILE = "MTLFILE.mtl"
    const OBJ_FILE = "OBJFILE.obj"

    var onProgress = function ( xhr ) {
      if ( xhr.lengthComputable ) {
        var percentComplete = xhr.loaded / xhr.total * 100;
        console.log( Math.round(percentComplete, 2) + '% downloaded' );
      }
    };
    var onError = function ( xhr ) {
      console.log("error", xhr)
    };

    const mtlLoader = new MTLLoader();
    mtlLoader.setBaseUrl(PATH);
    mtlLoader.setPath(PATH); // One of these might not be needed
    mtlLoader.crossOrigin = '*'; // Use as needed
    mtlLoader.load(MTL_FILE, materials => {
        materials.preload();
        // OBJ Loader
        const objLoader = new THREE.OBJLoader();
        objLoader.setMaterials(materials);
        objLoader.setPath(PATH);
        objLoader.load(OBJ_FILE, object => {
            object.scale.set(300, 300, 300);

            for(let child of object.children) {
                // child.material.side = THREE.DoubleSide //still not sure about this
            }
            this.refs.group.add(object); //Puts the loaded object into the render tag with ref "group"

        }, onProgress, onError);
    });
  }

then, in the componentDidMount function I call this loadThing() function like this:

componentDidMount(){
     this.loadThing()
}

Finally, I'll paste the whole render function, but the relevant part that relates to this is the <group ref="group /> which is where it eventually puts the object.

render() {
    const {
      width,
      height,
    } = this.props;

    return (<React3
      mainCamera="camera"
      width={width}
      height={height}
      onAnimate={this._onAnimate}
      ref="react3"
    >
      <scene ref="scene">
        <perspectiveCamera
          name="camera"
          fov={70}
          aspect={width / height}
          near={1}
          far={3000}
          position={this.cameraPosition}
          lookAt={this.lookAt}
        />
            <ambientLight
                color={new THREE.Color("white")}
            />
        <gridHelper size={1000} divisions={10} />
        <directionalLight color={0xffffff} intensity={2} position={this.lightPosition} />
         <mesh
        >
          <boxGeometry
            width={1}
            height={1}
            depth={1}
          />
          <meshBasicMaterial
            color={new THREE.Color( this.props.color )}
            map= {THREE.ImageUtils.loadTexture('https://s3-us-west-2.amazonaws.com/BUCKET/grasslight-big.jpg')}
          />
        </mesh>
        <group ref="group" />

                  <mesh
                    position={this.groundPosition}
                    rotation={this.groundRotation}
                    receiveShadow
                  >
                    <planeBufferGeometry
                      width={20000}
                      height={20000}
                    />
                    <meshPhongMaterial
                      color={0xffffff}
                      specular={0x111111}
                    >
                      <texture
                        url={'https://s3-us-west-2.amazonaws.com/BUCKET/grasslight-big.jpg'}
                        wrapS={THREE.RepeatWrapping}
                        wrapT={THREE.RepeatWrapping}
                        repeat={this.groundRepeat}
                        anisotropy={16}
                      />
                    </meshPhongMaterial>
                  </mesh>

      </scene>
    </React3>);
  }

Thanks again for the help!

Note for anyone using this code: The above code works with the exception of objLoader.setMaterials(materials) throws the following error: THREE.WebGLProgram: shader error: 0 gl.VALIDATE_STATUS false gl.getProgramInfoLog invalid shaders ERROR: 0:936: 'mapTexelToLinear' : no matching overloaded function found ERROR: 0:936: '=' : dimension mismatch ERROR: 0:936: 'assign' : cannot convert from 'const float' to 'highp 4-component vector of float'