expo/expo-three

How to load an obj file?

Closed this issue ยท 24 comments

Hi folks! Thanks for maintaining this project!

I'm wondering if you have an example of loading an OBJ file and applying a texture? All I want to achieve is to being able to load a model, apply a texture and rotate it.

This is what I have so far:

import React, { Component } from 'react';
import { ScrollView, Text, StyleSheet } from 'react-native';
import Expo, { Asset, GLView } from 'expo';
import * as THREE from 'three';
import ExpoTHREE from 'expo-three';
global.THREE = THREE;
require('./OBJLoader');


console.disableYellowBox = true;


export default class ModelScreen extends Component {
  static navigationOptions = {
    title: '3D Model',
  };

  state = {
    loaded: false,
  }

  componentWillMount() {
    this.preloadAssetsAsync();
  }

  async preloadAssetsAsync() {
    await Promise.all([
      require('../assets/suzuki.obj'),
      require('../assets/MotociklySuzuki_LOW001.jpg'),
      require('../assets/male02.obj'),
      require('../assets/UV_Grid_Sm.jpg'),
    ].map((module) => Asset.fromModule(module).downloadAsync()));
    this.setState({ loaded: true });
  }

  onContextCreate = async (gl) => {
    const width = gl.drawingBufferWidth;
    const height = gl.drawingBufferHeight;
    console.log(width, height);
    gl.createRenderbuffer = () => {};
    gl.bindRenderbuffer = () => {};
    gl.renderbufferStorage  = () => {};
    gl.framebufferRenderbuffer  = () => {};

    const camera = new THREE.PerspectiveCamera( 45, width / height, 1, 2000 );
		camera.position.z = 250;

    const scene = new THREE.Scene();
		const ambient = new THREE.AmbientLight( 0x101030 );
		const directionalLight = new THREE.DirectionalLight( 0xffeedd );
		directionalLight.position.set( 0, 0, 1 );
    scene.add(ambient);
		scene.add(directionalLight);

    // Texture
    const textureAsset = Asset.fromModule(require('../assets/MotociklySuzuki_LOW001.jpg'));
    const texture = new THREE.Texture();
    texture.image = {
      data: textureAsset,
      width: textureAsset.width,
      height: textureAsset.height,
    };;
		texture.needsUpdate = true;
    texture.isDataTexture = true;
    const material =  new THREE.MeshPhongMaterial({ map: texture });

    // Object
    const modelAsset = Asset.fromModule(require('../assets/suzuki.obj'));
    const loader = new THREE.OBJLoader();
    const model = loader.parse(
      await Expo.FileSystem.readAsStringAsync(modelAsset.localUri));

    model.traverse((child) => {
      if (child instanceof THREE.Mesh) {
        child.material = material;
      }
    });

    model.position.y = - 95;
		scene.add(model);


    const renderer = ExpoTHREE.createRenderer({ gl });
    renderer.setPixelRatio(2);
		renderer.setSize(width, height);


    const animate = () => {
      // camera.position.x += ( 7.25 - camera.position.x ) * .05;
			// camera.position.y += ( 62.75 - camera.position.y ) * .05;

			camera.lookAt( scene.position );

			renderer.render( scene, camera );
      gl.endFrameEXP();
      requestAnimationFrame(animate);
    };
    animate();
  };

  render() {
    return (
      <ScrollView style={styles.container}>
        <Text>This is your avatar:</Text>
        { this.state.loaded &&
          <GLView
            ref={(ref) => this.glView = ref}
            style={styles.glview}
            onContextCreate={this.onContextCreate}
          />
        }
        <Text>Something here!</Text>
      </ScrollView>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 15,
    backgroundColor: '#fff',
  },
  glview: {
    width: 350,
    height: 500
  },
});

I don't get any errors, the app builds and runs ok, but I don't see anything on the GLView, any pointers?

Thanks in advance!

As of 2.0.6 you can now call ExpoTHREE.loadAsync and this will load your models!

Example Snacks!

These snacks don't work

@kevin-bache I think there is a bug right now that is preventing the assets from being bundled. If you "Export to XDE" then add "obj", "mtl" to the assetExts then they will run! ๐Ÿ˜…๐Ÿ˜

"packagerOpts": {
  "assetExts": [
    "ttf",
    "mp4",
    "otf",
    "xml",
    "obj",
    "mtl"
  ]
}

Hi Evan,

When I try to load and .obj file, I got the following error:

Unable to resolve ../assets/objects/low-poly-chest.obj" from "./C:\\Users\\haoha\\quest\\screens\\CameraNav.js: could not resolve C:\\Users\\haoha\\quest\\assets\\objects\\low-poly-chest.obj' as a file nor as a folder","name":"UnableToResolveError","type":"UnableToResolveError","errors":[{}]},"type":"bundling_error"}"
It's as if expo doesn't recognize .obj file, and my expo-three is version 2.2.0.
Do you know what might be happening here? I am certain that my path is correct, thank you!

Wow, thank you for prompt response. I did add the extensions to app.json however.

I believe the error is because I passed loadAsync() an asset instead of just a url:

Asset.fromModule(require('../assets/objects/low-poly-chest.obj'));

Using url makes the error disappear :-) Thank you!

Yay! It's been a long week but I feel like 2.2.1 may support Assets as well

Thanks Evan, I am not in a hurry so feel free to come back to this question next week:

const chest = {
      'obj': require('../assets/objects/low-poly-chest.obj'),
      'mtl': require('../assets/objects/low-poly-chest.mtl'),
      'png': require('../assets/objects/low-poly-chest.png'),
    };

    /// Load chest!
    const assetProvider = (name) => {
      return chest[name];
    };
    const chestObj = await ExpoTHREE.loadAsync(
      [chest['obj'], chest['mtl']],
      null,
      assetProvider,
    );

The assetProvider is causing some error, taking it out will solve it but the png material will be gone.

ExpoTHREE.loadAsync: Cannot parse undefined assets. Please pass valid resources for: undefined."

__expoConsoleLog
    C:\Users\haoha\quest\node_modules\expo\src\logs\RemoteConsole.js:98:8
error
    C:\Users\haoha\quest\node_modules\react-native\Libraries\ReactNative\YellowBox.js:71:16
loadAsync$
    C:\Users\haoha\quest\node_modules\expo-three\lib\loadAsync.js:10:4
tryCatch
    C:\Users\haoha\quest\node_modules\regenerator-runtime\runtime.js:63:44
invoke
    C:\Users\haoha\quest\node_modules\regenerator-runtime\runtime.js:337:30
tryCatch
    C:\Users\haoha\quest\node_modules\regenerator-runtime\runtime.js:63:44

Thanks again for your help, have a nice weekend!

Could you share a snack possibly? Also is the image name in the mtl "png"? Maybe it's asking for an image with a different name and the asset provider is returning an item from the object that doesn't exist

Omg, nope. The name is low-poly-chest.png, I see what happened now, stupid mistake, sorry.

The error went away but somehow the object is all black, I made a snack here: https://snack.expo.io/SJxikuKHG

I used the same 3D files as the example here: https://snack.expo.io/@bacon/load-simple-obj-model

I also tried setting the material of the mesh to color red, but it still showed up black:
chestObj.material = new THREE.MeshBasicMaterial( { color: 0xff0000 } );

Not sure what I am doing wrong here,
Thanks a bunch!

man, you scared me. We are having some framebuffer issues in this version and I thought your material bug was related. Luckily it's not! So for some reason I can't even begin to fathom, .mtl files fail to download in Expo. So I wrote a thing into expo-three that downloads from FileSystem instead. You'll need to upgrade to a new version tho: 2.2.2-alpha.0 ๐Ÿ’™

Hi Evan, I tried the new version but there is something strange here:

`ExpoTHREE.loadAsync: OMG, this asset couldn't be downloaded! Open an issue on GitHub...

  • node_modules\react-native\Libraries\ReactNative\YellowBox.js:71:16 in error
  • node_modules\expo-three\lib\loadAsync.js:19:4 in loadAsync$
  • node_modules\regenerator-runtime\runtime.js:63:44 in tryCatch
  • node_modules\regenerator-runtime\runtime.js:337:30 in invoke
  • node_modules\regenerator-runtime\runtime.js:63:44 in tryCatch`

And this happens only with obj/mtl files that I added this morning, the files that I added last night with the older version 2.2.0 doesn't report this issue (Although they still show up as black).

I am also trying to look at how assetProvider is used (since it is the only function that will return my png file), and seems like it will only be used like: loader.setPath(assetProvider); given the way I called loadAsync with first argument being [obj_file, mtl_file], is this intended?

I feel kinda bad that I am pestering you with these issues, but thank you very much!

ExpoTHREE.loadAsync: OMG, this asset couldn't be downloaded! Open an issue on GitHub...
I added this error last night to catch assets that couldn't be downloaded.

yeah, loader.setPath is a hack I'm using to work with all the three.js loaders.

given the way I called loadAsync with first argument being [obj_file, mtl_file], is this intended?

Yeah you could call it in any order.

Questions

  • Does this only happen in snack
  • What device are you using
  • Could you provide source code
  • Does your material have ambient values? (Ka in the mtl file)

Hi Evan,

  1. This happens on my windows 10 machine that I use to write my code
  2. The app is downloaded to my iPhone 6s
  3. https://github.com/Matt-F-Wu/quest is my github repo, the file that has all the code in is: https://github.com/Matt-F-Wu/quest/blob/master/screens/CameraNav.js
  4. It has ambient values, there is Ka in the mtl file

Thanks a bunch!

@Matt-F-Wu There is a lot of private stuff here, I got the code and I'll look at it now, I would recommend adding a .gitignore to hide your .p12 and add a secret.js with the GMaps api key in it

@Matt-F-Wu the problem is your chest model is corrupted, I've tried exporting and loading as dae, json, object, none worked to load the texture. When I load into mesh lab I see the following error with the model:

Warning: invalid framebuffer operation

I think you should try rebuilding the mesh / cleaning it up. Then everything should work!

@EvanBacon Thanks a bunch Evan!
What tool did you use to generate the obj/mtl/png files? I just downloaded from Clara.io, and it looked fine on my PC with "print 3D", but I guess that's probably not right.

I use MeshLab to view things, and blender to export and stuff

Awesome, thank you! I will use blender from now on :-)

Hi Evan, I'm also having trouble getting a .obj + .mtl to display properly using ExpoTHREE.loadAsync(). It's currently displaying as a black object.

I've added "mtl" and "obj" to the appExts of app.json per the above. Here's the code I'm using below:

import Expo, { Asset } from 'expo'
import React, { Component } from 'react'
import { PanResponder} from 'react-native'
import { connect } from 'react-redux'

const THREE = require('three') // Supported builtin module
import ExpoTHREE from 'expo-three'
console.disableYellowBox = true

const scaleLongestSideToSize = (mesh, size) => {
  const { x: width, y: height, z: depth } =
    new THREE.Box3().setFromObject(mesh).size()
  const longest = Math.max(width, Math.max(height, depth))
  const scale = size / longest
  mesh.scale.set(scale, scale, scale)
}

class AR extends Component {

  constructor () {
    super()

    this._panResponder = PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onStartShouldSetPanResponderCapture: () => true,
      onMoveShouldSetPanResponder: () => true,
      onMoveShouldSetPanResponderCapture: () => true,
      onPanResponderRelease: this._handlePanResponderRelease.bind(this)
    })

    this.state = {
      loaded: false,
    }
  }

  // React Native Touch event responders
  _handlePanResponderRelease = () => {
    this.props.navigation.goBack()
  }

  // ARKit/Three.js functions
  _onGLContextCreate = async (gl) => {
    // Create AR Session
    const arSession = await this._glView.startARSessionAsync()

    // Overwrite Three.js camera with ARKit camera
    const camera = ExpoTHREE.createARCamera(
      arSession,
      gl.drawingBufferWidth,
      gl.drawingBufferHeight,
      0.01,
      1000
    )

    // Configure the renderer
    const renderer = ExpoTHREE.createRenderer({ gl })
    const { drawingBufferWidth: width, drawingBufferHeight: height} = gl
    renderer.setSize(width, height)

    // Create the scene and set its background to device camera's live video feed
    const scene = new THREE.Scene()
    scene.background = ExpoTHREE.createARBackgroundTexture(arSession, renderer)

    // Render model
    const loadModelsAsync = async () => {
      /// Get all the files in the mesh
      const model = {
        'orange.obj': require('../assets/orange/orange.obj'),
        'orange.mtl': require('../assets/orange/orange.mtl')
      }

      /// Load model!
      const mesh = await ExpoTHREE.loadAsync(
        [
          model['orange.obj'],
          model['orange.mtl']
        ],
        null,
        name => model[name],
      )

      /// Update size and position
      ExpoTHREE.utils.scaleLongestSideToSize(mesh, 0.9)
      ExpoTHREE.utils.alignMesh(mesh, { y: 1 })
      /// Smooth mesh
      // ExpoTHREE.utils.computeMeshNormals(mesh)

      /// Add the mesh to the scene
      const { x: xFromScreen, y: yFromScreen, z: zFromScreen } = camera.getWorldPosition()
      mesh.position.set(xFromScreen, yFromScreen, zFromScreen - 2)
      scene.add(mesh)
      this.mesh = mesh
    }

    await loadModelsAsync()

    // Define frame animation behavior and begin animation loop
    const animate = () => {
      requestAnimationFrame(animate)

      this.mesh.rotation.y += 0.1

      renderer.render(scene, camera)
      gl.endFrameEXP()
    }
    animate()

  }

  render () {
    return (
      <Expo.GLView
        {...this._panResponder.panHandlers}
        ref={ref => this._glView = ref}
        style={styles.container}
        onContextCreate={this._onGLContextCreate}
      />
    )
  }
}

const styles = {
  container: {
    flex: 1,
    justifyContent: 'center'
  },
  textTitle: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#6c5ce7',
    textAlign: 'center'
  }
}

export default connect()(AR)

I get the following warning (error?) in the XDE when loading the .mtl:

Oh No! Evan screwed something up!! You tried to download an Expo.Asset and for some reason it didn't cache... Known reasons are: it's an .mtl file 3:55:11 PM Load local file file:///var/mobile/Containers/Data/Application/C808A17B-B4D5-407C-954F-F88DF4E4141A/Library/Caches/ExponentExperienceData/%2540allenhj%252Fnot-seek/ExponentAsset-3661710ad895f81034d9ef6c1eb2adea.mtl 3:55:11 PM loadAsset: 3.717ms 3:55:11 PM Load local file file:///var/mobile/Containers/Data/Application/C808A17B-B4D5-407C-954F-F88DF4E4141A/Library/Caches/ExponentExperienceData/%2540allenhj%252Fnot-seek/ExponentAsset-32af9473699e63cff63822fbff24b831.obj

I've tried opening this and other .objs in MeshLab (which I just downloaded) and get the following error for basically any .obj I try opening: "Warning: There are gl errors: invalid framebuffer operation."

Any help you might provide would be very much appreciated.

@AllenHJ Could you please share the actual snack, with the models included. You can DM me on slack if that is more secure for you: @Bacon https://slack.expo.io/

@AllenHJ @EvanBacon Did you guys manage to solve this issue? I am hitting this issue too somehow.

@EvanBacon I apologize for necro posting, just seemed like the proper issue to ask this. Calling loadAsync should work for FBX files as well(as long as they are added to assetExts)?