expo / expo-three

Utilities for using THREE.js on Expo
MIT License
738 stars 89 forks source link

Unstable #237

Closed edvard-bjarnason closed 2 years ago

edvard-bjarnason commented 2 years ago

We are not getting good results with this package using either ios simulator or physical device, restarts, crashes and things not updating. Is the package unstable or are we just unlucky? Endless problems really, getting better stability with expo 41 but still problems. What versions are you using, can someone post package.json that works?

EvanBacon commented 2 years ago

This package currently doesn't have a code owner/maintainer so YMMV. The most important class is Renderer. The TextureLoader is also pretty useful.

Given that three.js changes pretty often, most of the model loading optimizations have been removed.

jedahan commented 2 years ago

Thank you for the pointers. Since our project is react native only, I was able to vendor those two functions and one class to remove the expo-three dependency, and things work well on sdk44. This is partial, but here is some of our code that made it work that might help others:

import { Asset, useAssets } from 'expo-asset'
import { loadTextureAsync } from './expo-three-vendored'

// top level
const [assets] = useAssets([
    require('../../assets/textures/globe-light-landmass-invert.png'),
    require('../../assets/textures/globe-background-dark.png'),
    require('../../assets/textures/glow-inner.png'),
   require('../../assets/textures/particle.png'),
])

// in some async function...
const texture = await loadTextureAsync(assets[0])
// pass it into where you need it
import { Asset } from 'expo-asset'
import {
  Vector3,
  WebGLRenderer,
  Color,
  WebGLRendererParameters,
  TextureLoader,
  Texture,
  ImageLoader,
} from 'three'

type RendererProps = WebGLRendererParameters & {
  gl: WebGLRenderingContext
  canvas?: HTMLCanvasElement
  pixelRatio?: number
  clearColor?: Color | string | number
  width?: number
  height?: number
}
export class Renderer extends WebGLRenderer {
  constructor({
    gl: context,
    canvas,
    pixelRatio = 1,
    clearColor,
    width,
    height,
    ...props
  }: RendererProps) {
    const inputCanvas =
      canvas ||
      ({
        width: context.drawingBufferWidth,
        height: context.drawingBufferHeight,
        style: {},
        addEventListener: (() => {}) as any,
        removeEventListener: (() => {}) as any,
        clientHeight: context.drawingBufferHeight,
      } as HTMLCanvasElement)

    super({
      canvas: inputCanvas,
      context,
      ...props,
    })

    this.setPixelRatio(pixelRatio)

    if (width && height) this.setSize(width, height)
    if (clearColor) this.setClearColor(clearColor)
  }
}

export const loadTextureAsync = async (asset: Asset): Promise<Texture> => {
  return new Promise((resolve) => {
    new ExpoTextureLoader().load(asset, resolve)
  })
}
class ExpoTextureLoader extends TextureLoader {
  load(asset: any, onLoad?: (texture: Texture) => void): Texture {
    if (!asset) {
      throw new Error(
        'ExpoTHREE.TextureLoader.load(): Cannot parse a null asset'
      )
    }
    const texture = new Texture()

    const loader = new ImageLoader(this.manager)
    loader.setCrossOrigin(this.crossOrigin)
    ;(async () => {
      await asset.downloadAsync()

      function parseAsset(image: any) {
        texture.image = image
        texture.needsUpdate = true

        if (onLoad !== undefined) onLoad(texture)
      }

      // Forces passing to `gl.texImage2D(...)` verbatim
      ;(texture as any)['isDataTexture'] = true

      parseAsset({
        data: asset,
        width: asset.width,
        height: asset.height,
      })
    })()
    return texture
  }
}
EvanBacon commented 2 years ago

Another note is that you don't need to await texture loading, you can pass an unloaded texture to a material and it will update when it has loaded, example.