expo / expo-three

Utilities for using THREE.js on Expo
MIT License
712 stars 87 forks source link

uri/localUri have no scheme on production apks #225

Open jedahan opened 2 years ago

jedahan commented 2 years ago

Only on production build of apks. Expo Go shows https:// and file:// schemes for uri/localUri.

await new TextureLoader().load(
  require('../../assets/textures/globe-light-landmass-invert.png'),
  (texture) => void console.log({texture}),
  undefined,
  (error) => void console.error(error)
)
{ texture: 
   { uuid: '03552703-2025-4D1B-837E-AF99C2FB078F',
     name: '',
     image: 
      { data: 
         { hash: '73d4f645e59fdb0a189f747dc91001c6',
           localUri: 'assets_textures_globelightlandmassinvert',
           width: 2000,
           height: 1000,
           downloading: false,
           downloaded: true,
           _downloadCallbacks: [],
           name: 'globe-light-landmass-invert',
           type: 'png',
           uri: 'assets_textures_globelightlandmassinvert' },
    }
   }
}
jedahan commented 2 years ago

https://github.com/expo/expo-three/blob/master/src/loadAsync.ts#L55-L86

Do you know where I could find out how asset uris get built, and the differences in a prod build and expo go bundle? Ran into this issue with expo-three, which I think uses an old format of requires?

loadAsync fails on guessing what format uri: 'assets_textures_globelightlandmassinvert' should be, since there is no extension anymore. We are loading with require('../assets/textures/globe-light-land-mass-invert'). The interesting thing is that there is a type: 'png' in the object. Maybe expo-three's loadAsync should look for the type key and work off of that?

jedahan commented 2 years ago

And the error you will get if you use loadAsync:

ReactNativeJS: { [Error: ExpoTHREE.loaderClassForExtension(): Unrecognized file type assets_textures_globelightlandmassinvert] line: 2969, column: 541, sourceURL: 'index.android.bundle' }
kyaroru commented 2 years ago

Perhaps you can try to change extension of .png to .xpng as stated in https://github.com/expo/expo-three/issues/185#issuecomment-732161813 then the texture shall load on production APK.

Also you can refer to my findings for loading 3d models with texture here: https://github.com/expo/expo-three/issues/151#issuecomment-904532776

jedahan commented 2 years ago

Thank you for the comment and suggestion, I will try changing my app today to see if the fixes work.

jedahan commented 2 years ago

Tentatively, it works! I am going to do more testing, but if it does, thank you so much @kyaroru !

This is roughly what we had to do:

// metro.config.js
const { getDefaultConfig } = require('@expo/metro-config')
const defaultConfig = getDefaultConfig(__dirname)
defaultConfig.resolver.assetExts.push('xpng')
module.exports = defaultConfig
// getEarth.tsx
import { loadTextureAsync } from 'expo-three'
import { Mesh, SphereGeometry, MeshPhongMaterial } from 'three'

export const getEarth = async (radius=100, resolution=128): Promise<Mesh> => {
  const map = await loadTextureAsync({ asset: require('./assets/globe.xpng') })

  return new Mesh(
    new SphereGeometry(radius, resolution, resolution), 
    new MeshPhongMaterial({ map }),
  )
}
// globe.tsx
import React from 'react'
import { GLView, ExpoWebGLRenderingContext } from 'expo-gl'
import { Renderer } from 'expo-three'
import { PerspectiveCamera, Scene, FogExp2, DirectionalLight } from 'three'
import { getEarth } from './getEarth.tsx'

export const Globe = (): React.ReactElement => {
  // stop rendering when navigating away
  let timeout; useEffect(() => () => clearTimeout(timeout), [])

  const onContextCreate = async (gl: ExpoWebGLRenderingContext): Promise<void> => {
    const { drawingBufferWidth: width, drawingBufferHeight: height } = gl
    const renderer = new Renderer({ gl })
    renderer.setSize(width, height)

    // camera
    const aspect = width / height
    const { fov, near, far } = { fov: 90, near: 0.1, far: 900 }
    const camera = new PerspectiveCamera(fov, aspect, near, far)

    const scene = new Scene()
    scene.fog = new FogExp2(0xffffff, 0.001)

    const earth = await getEarth()
    scene.add(earth)

    const spotlight = new DirectionalLight(0xffffff, 1.2)
    spotlight.position.set(10, -10, 1)
    camera.add(spotlight)

    scene.add(camera)

    // Setup an animation loop
    const render = (time: number) => {
      timeout = requestAnimationFrame(render)
      renderer.render(scene, camera)
      gl.endFrameEXP()
    }

    render(0)
  }

  return  <GLView style={{ flex: 1 }} onContextCreate={onContextCreate} />
}
jedahan commented 2 years ago

Maybe loadAsync should be deprecated, or replaced with this hack...

kyaroru commented 2 years ago

glad to know that it is working! 😜

jedahan commented 2 years ago

Yeah I owe ya a bottle of 🥃 or kombucha or something

jedahan commented 2 years ago

Short update; still need this workaround in SDK 44

obasille commented 1 year ago

@keith-kurak is this something can be addressed for the next release? This issue breaks loadAsync/loadTextureAsync for loading textures when using production APKs. Thanks!