3
votes

I'm trying to build a simple object-viewer in React with Meteor that can import .obj and .mtl Files using the following npm modules:

  • three(0.87.1)
  • react(15.6.1)
  • three-obj-loader(1.1.3)
  • three-mtl-loader(1.0.1)

So far i have managed to display an object using the OBJLoader. But when i try to render an object after applying a texture with MTLLoader, i get this error from console:

Uncaught TypeError: Cannot read property 'toString' of undefined at WebGLPrograms.getProgramCode (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:50707) at initMaterial (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:54628) at setProgram (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:54820) at WebGLRenderer.renderBufferDirect (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:53883) at renderObject (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:54613) at renderObjects (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:54586) at WebGLRenderer.render (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:54350) at WebGlDisplay.renderScene (WebGlDisplay.jsx:86) at onClick (WebGlDisplay.jsx:90) at HTMLUnknownElement.boundFunc (modules.js?hash=eae498e3ee56e21f967b663c5bed3444c66eaef2:8794)

Cause: material.onBeforeCompile in getProgramCode is undefined

My code looks like this:

import React, { Component } from 'react'
import THREE from 'three'
const MTLLoader = require('three-mtl-loader');
const OBJLoader = require('three-obj-loader')(THREE);

export default class WebGlDisplay extends Component {
  constructor(props) {
    super(props)
  }

      //init canvas
   init(){
    const width = this.mount.clientWidth;
    const height = this.mount.clientHeight;
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
    const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
    renderer.setClearColor('#000000', 0.2);
    renderer.setSize(width, height);
    camera.position.set(3,4,6);
    camera.lookAt(new THREE.Vector3());
    this.scene = scene;
    this.camera = camera;
    this.renderer = renderer;
    this.mount.appendChild(this.renderer.domElement);
  }

  //load & render object
  drawOBJ(){
    const mtlLoader = new MTLLoader();
    let onProgress = function(e){console.log("rendering:" + e)};
    let onError = function(e){console.log("error:" + e)};
    mtlLoader.load("eagle.mtl", materials => {
        materials.preload();
        // OBJ Loader
        const objLoader = new THREE.OBJLoader();
        this.materials = materials;
        objLoader.setMaterials(materials);
        objLoader.load("eagle.obj", object => {
              this.object = object;
            this.scene.add(object);
        }, onProgress, onError);
    }, onProgress,onError);
    this.renderScene();
  }

  componentDidMount() {
    this.init();
    this.drawOBJ();
  }

  renderScene() {
    this.renderer.render(this.scene, this.camera)
  }

  render() {
    return (
      <div onClick={(e) => this.renderScene()}
        style={{ width: '800px', height: '600px' }}
        ref={(mount) => { this.mount = mount }}
      />
    )
  }
}

Does anyone have an idea why i get this error? I've tried to use different .obj- and .mtl-files, but the error remains (whenever i try to call renderScene()).

By any chance, could it be a problem with the module versions, or maybe some timing problems while loading?

Any help would be appreciated.

2
Try to avoid this => "arrow function" and use more clear and explicit code... I don't know how this => precisely works, but this appear to me another false good-idea of the elastic Javascript syntax... mostly in a context where references must be consistants and reliable... I'am not a Javascript guru, but coming from C/C++, this kind of thing make to me the same effect than a match in a gunpowder magazine. - user1501157
Hmmm, can you look at the source of three and see if it has onBeforeCompile defined on a material? Are you absolutely sure you are using 87? - pailhead
is there any chance you could post this code, so we can track it down and see what is going on? This looks like a bug, but nothing stands out as obvious. - pailhead
arrow functions are perfectly normal JavaScript nowadays. @Sedenion I suggest you learn them. They aren't going away. There is absolutely nothing wrong with them. They are the recommended way to do many things in modern JavaScript. - gman
Yes the version is definitely 87 @pailhead . So far I couldn't really wrap my head around the three source. I didn't find any direct definition of onBeforeCompile except for an empty function in Object.assign in Material.js (i don't even understand if it is called somewhere or not). Sorry, it seems i am not very helpful here. Is there a method to put this node/react stuff online without hosting it yourself? (i don't know a fitting method, but i'm pretty new to this whole stuff anyway) - NickG

2 Answers

5
votes

The problem seems to be that the three-mtl-loader NPM package is referencing an outdated three.js version in it's package.json, so even though you are using the latest version of three, the plugin isn't!

Obviously this isn't a viable long-term fix, but I changed the version for three in node_modules/three-mtl-loader/package.json to 0.87.1 and deleted the directory node_modules/three-mtl-loader/node_modules for good measure and ran my example and it worked straight away, textures and all.

Clearly the plugin needs to be updated. I also saw at least one functional difference between the source in the plugin and in the three examples folder ('Tr' vs 'tr' in a case statement), and it doesn't follow the same initialization behavior as other loader plugins (specifically it isn't initialized by calling require("three-mtl-loader")(THREE)), so there's a bit of work to get it ship-shape.

Alternately, it appears that the author has updated the version number to 0.86.0 in their repo (which is high enough), just hasn't done a deploy to NPM. So, if you feel brave, you can just change your package.json to have the line

"dependencies": { ... "three-mtl-loader": "git+https://git@github.com/nascherman/three-mtl-loader.git", ... }

0
votes

As a workaround, what i ended up doing is to get a local copy of the newest MTLLoader Version and slightly modifing it, as it seems to be a problem with the version pointed out by @user1691694. In case somebody needs this here my way of importing it:

In the MTLLoader file, add the following line at the top:

import THREE from 'three';

and at the bottom:

module.exports.default =  THREE.MTLLoader

Use it like in the drawOBJ function in the question post and import it in the target file like this:

import MTLLoader from './MTLLoader.js';