expo / expo-three

Utilities for using THREE.js on Expo
MIT License
751 stars 92 forks source link

How to load an obj file? #5

Closed crysfel closed 6 years ago

crysfel commented 7 years ago

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!

EvanBacon commented 6 years ago

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

Example Snacks!

kevin-bache commented 6 years ago

These snacks don't work

EvanBacon commented 6 years ago

@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"
  ]
}
Matt-F-Wu commented 6 years ago

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!

EvanBacon commented 6 years ago

@Matt-F-Wu Did you remember to add the obj extension to your app.json https://github.com/expo/expo-three/tree/master/examples/loader#expo-three-loader-example

Matt-F-Wu commented 6 years ago

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!

EvanBacon commented 6 years ago

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

Matt-F-Wu commented 6 years ago

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!

EvanBacon commented 6 years ago

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

Matt-F-Wu commented 6 years ago

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

Matt-F-Wu commented 6 years ago

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!

EvanBacon commented 6 years ago

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 💙

Matt-F-Wu commented 6 years ago

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...

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!

EvanBacon commented 6 years ago

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

Matt-F-Wu commented 6 years ago

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!

EvanBacon commented 6 years ago

@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

EvanBacon commented 6 years ago

@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!

Matt-F-Wu commented 6 years ago

@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.

EvanBacon commented 6 years ago

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

Matt-F-Wu commented 6 years ago

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

johnson-alan commented 6 years ago

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.

EvanBacon commented 6 years ago

@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/

Matt-F-Wu commented 6 years ago

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

TheCodeDestroyer commented 6 years ago

@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)?