4
votes

Main objective : Load animated models exported from Maya into React Native app Exported files : obj, mtl & png file

I have setup https://github.com/react-community/react-native-webgl in my React Native project and it is working properly.

Now, when I am trying to load the MTL file using the MTLLoader, I am getting following error:

Can't find variable: document

Apparently, the MTLLoader is calling TextureLoader which internally calls some load function which has 'document' reference. So what could be the solution to this ?

Here are the two files that I am using:

three.js

const THREE = require("three");
global.THREE = THREE;
if (!window.addEventListener)
    window.addEventListener = () => { };
// require("three/examples/js/renderers/Projector");
require("three/examples/js/loaders/MTLLoader");
require("three/examples/js/loaders/OBJLoader");
export default THREE;

ThreeView.js

import React, { Component } from "react";
import { StyleSheet, View } from "react-native";
import { WebGLView } from "react-native-webgl";
import THREE from "./three";
import { image } from "src/res/image";

export default class ThreeView extends Component {
    requestId: *;
    componentWillUnmount() {
    cancelAnimationFrame(this.requestId);
}
onContextCreate = (gl: WebGLRenderingContext) => {
    const rngl = gl.getExtension("RN");

    const { drawingBufferWidth: width, drawingBufferHeight: height } = gl;
    const renderer = new THREE.WebGLRenderer({
        canvas: {
            width,
            height,
            style: {},
            addEventListener: () => { },
            removeEventListener: () => { },
            clientHeight: height
        },
        context: gl
    });
    renderer.setSize(width, height);
    renderer.setClearColor(0xffffff, 1);

    let camera, scene;
    let cube;

    function init() {
        camera = new THREE.PerspectiveCamera(75, width / height, 1, 1100);
        camera.position.y = 150;
        camera.position.z = 500;
        scene = new THREE.Scene();

        var mtlLoader = new THREE.MTLLoader();
        mtlLoader.load('female-croupier-2013-03-26.mtl', function (materials) {
            materials.preload();

            var objLoader = new THREE.OBJLoader();
            objLoader.setMaterials(materials);
            objLoader.load('female-croupier-2013-03-26.obj', function (object) {
                scene.add(object);
            }, onLoading, onErrorLoading);
        }, onLoading, onErrorLoading);
    }
    const onLoading = (xhr) => {
        console.log((xhr.loaded / xhr.total * 100) + '% loaded');
    };
    const onErrorLoading = (error) => {
        console.log('An error happened', error);
    };
    const animate = () => {
        this.requestId = requestAnimationFrame(animate);
        renderer.render(scene, camera);

        // cube.rotation.y += 0.05;

        gl.flush();
        rngl.endFrame();
    };

    init();
    animate();
};
render() {
    return (
        <View style={styles.container}>
            <WebGLView
                style={styles.webglView}
                onContextCreate={this.onContextCreate}
            />
        </View>
    );
}
}

const styles = StyleSheet.create({
container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center"
},
webglView: {
    width: 300,
    height: 300
}
});
4
You may be able to override the load function in ImageLoader and use var image = new Image() instead of document.createElementNS.2pha
@2pha : By 'Image' what do you mean ? React Native Image component ? Or Html Image ? I have to import it right ?Aditya
I dont know how to import the correct 'Image' so now I am getting Can't find variable: ImageAditya
Image is a contructor function to create a html image element, an alternative to calling document.createElement. It should not have to be imported. I am coming from a html/web perspective though, I don't know in if it would work in React Native though (I have never used it). It was just a suggestion to try.2pha
I don’t think it will work like this. HTML elements are not available in React Native apps.Aditya

4 Answers

4
votes

This error is as others have said caused by threejs trying to use features from a browser which react-native does not have.

I've gotten so far as to be able to load the textures (which is the stage you're getting the error from) by monkey patching the texture loader to use the loader in react-native-webgl. Add this in your init function (right near the top preferably).

//make sure you have defined renderer and rngl

/*

const renderer = new THREE.WebGLRenderer(...)

const rngl = gl.getExtension("RN");

*/

const loadTexture = async function(url, onLoad, onProgress, onError) {
      let textureObject = new THREE.Texture();
      console.log("loading",url,'with fancy texture loader');
      let properties = renderer.properties.get(textureObject);


      var texture = await rngl.loadTexture({yflip: false, image: url});

      /*
      rngl.loadTexture({ image: url })
        .then(({ texture }) => {
        */
          console.log("Texture [" + url + "] Loaded!")
          texture.needsUpdate = true;
          properties.__webglTexture = texture;
          properties.__webglInit = true;
          console.log(texture);
              
          
          if (onLoad !== undefined) {
            //console.warn('loaded tex', texture);
            onLoad(textureObject);
          }
          
  
      //});

    
      return textureObject;
  
  }
  
  THREE.TextureLoader.prototype.load = loadTexture;   

This solves the problem of loading textures and I can see them load in Charles but they still don't render on a model so I'm stuck past that point. Technically a correct answer but you'll be stuck as soon as you've implemented it. I'm hoping you can comment back and tell me you've gotten further.

1
votes

I had a similar setup and encountered same issue. My option was to switch to JSONLoader which doesn’t need document to render in react-native. So, I just loaded my model in Blender with a three-js addon, then exported it as json. Just check out this process of adding a three-js adon to Blender

https://www.youtube.com/watch?v=mqjwgTAGQRY

All the best

1
votes

this might get you closer:

The GLTF format supports embedding texture images (as base64). If your asset pipeline allows it, you could convert to GLTF and then load into three/react-native.

I had to provide some "window" polyfills for "decodeUriComponent" and "atob" because GLTFLoader uses FileLoader to parse the base64:

I've successfully loaded embedded buffers, but you'll need more polyfills to load textures. TextureLoader uses ImageLoader, which uses document.createElementNS

1
votes

You are using the MTLLoader which uses TextureLoader, and the TextureLoader uses the ImageLoader. The imageloader uses the document.createElementNS() function.

what i did to solve this was to directly call the THREEjs TextureLoader:

let texture = new THREE.Texture(
     url //URL = a base 64 JPEG string in this case     
     );

(for the use of Texture check the Texture documentation)

Then i used the Image class from React native (instead of the THREEjs Image, which requires the DOM to be constructed) to give that to the Texture as a property:

import { Image } from 'react-native';

var img = new Image(128, 128);
img.src = url;
texture.normal = img;

And then finally map the texture over the target material:

const mat = new THREE.MeshPhongMaterial();
mat.map = texture;

In the react native documentation it will explain how the react native Image element can be used, it supports base64 encoded JPEG.

Maybe there's a way for you to single out the part where it calls for the TextureLoader and replace that part with this answer. Let me know how it works out.

side note, i havent tried to display this yet in my webGLView, but in the logs it looked like normal threejs objects, it's worth the try