Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to render .glb/.gltf files on mobile #273

Open
MrMacTaco opened this issue Sep 29, 2022 · 3 comments
Open

Unable to render .glb/.gltf files on mobile #273

MrMacTaco opened this issue Sep 29, 2022 · 3 comments

Comments

@MrMacTaco
Copy link

I recently started my journey into app development with React-Native and have been using Expo-Three for all my 3D rendering. Thus far, it's proven more than capable of doing everything I've thrown at it, with the one exception of rendering custom 3D models on mobile devices.

When I add() a new pre-loaded 3D model (.glb & .gltf formats) to my scene, in the app's web view, I can see everything loads correctly and the model info is presented as you'd expect. The issue arises when I attempt to run the app on an actual iOS device or virtual Android device and I'm left with this error and nothing more:
[Unhandled promise rejection: Error: FileReader.readAsArrayBuffer is not implemented]
at node_modules\react-native\Libraries\Blob\FileReader.js:null in readAsArrayBuffer
at node_modules\whatwg-fetch\dist\fetch.umd.js:null in readBlobAsArrayBuffer
at node_modules\react-native\node_modules\promise\setimmediate\core.js:null in tryCallOne
at node_modules\react-native\node_modules\promise\setimmediate\core.js:null in setImmediate$argument_0
at node_modules\react-native\Libraries\Core\Timers\JSTimers.js:null in _allocateCallback$argument_0
at node_modules\react-native\Libraries\Core\Timers\JSTimers.js:null in _callTimer
at node_modules\react-native\Libraries\Core\Timers\JSTimers.js:null in _callReactNativeMicrotasksPass
at node_modules\react-native\Libraries\Core\Timers\JSTimers.js:null in callReactNativeMicrotasks
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:null in __callReactNativeMicrotasks
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:null in __guard$argument_0
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:null in __guard
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:null in flushedQueue

I've tried every solution I can find, namely #151, yet I can't seem to get past this particular roadblock.

As for my code, I've tried various different methods of loading my models, using the GLTFLoader(), using ExpoTHREE.loadAsync() and even just using the three.js loadAsync(), plus more, and every method gets me as far as this error.

With this error, my mobile app components all load correctly, but there is no model inside my like there is on the web.

Any help or suggestions are greatly appreciated and I'd be happy to provide more information if you think it would be pertinent to solving the issue!

Thank you!

@MrMacTaco
Copy link
Author

I made a little progress on what is causing the issue. I was able to track down where that function, (readAsArrayBuffer), is located and it quite literally isn't implemented, (which makes sense based on the error, lol).

So now my attention turns to a new issue, is there any known implementation of this function? I'm honestly not quite sure how to decipher what's happening in the FileReader.js file to make my own, but it seems weird to build the loadAsync() function based on another function which was never implemented in the first place.

Regardless, I'm open to any and all suggestions or advice!

Thanks!

@BenNeighbour
Copy link

BenNeighbour commented Oct 8, 2022

@MrMacTaco Hey, I had this issue earlier today and I figured out a way to do it. I created my own custom function to load the gltf/glb model (which now works in my app), but my new problem is that it appears to be valid but doesn't actually render in threejs. Anyways, here was what my loader looked like:

import { loadTextureAsync } from 'expo-three';
import { resolveAsync } from 'expo-asset-utils';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { decode } from 'base64-arraybuffer';
import * as FileSystem from "expo-file-system";

async function loadFileAsync({ asset, funcName }) {
    if (!asset)
        throw new Error(`ExpoTHREE.${funcName}: Cannot parse a null asset`);
    return (await resolveAsync(asset)).localUri ?? null;
}

export async function loadGLTFAsync({ asset, onAssetRequested }) {
    const uri = await loadFileAsync({
        asset,
        funcName: 'loadGLTFAsync',
    });

    if (!uri) return;

    const base64 = await FileSystem.readAsStringAsync(uri, {
        encoding: FileSystem.EncodingType.Base64,
    });

    const arrayBuffer = decode(base64);
    const loader = new GLTFLoader();

    return new Promise((resolve, reject) => {
        loader.parse(
            arrayBuffer,
            onAssetRequested,
            result => {
                resolve(result);
            },
            err => {
                reject(err);
            },
        );
    });
}

export const loadModel = async function (item) {
    const texturesLength = item.textures?.length || 0;
    console.info(`[loadModel] -> Textures length: ${texturesLength}`);

    const textures = [];

    for (let i = 0; i < texturesLength; i++) {
        const texture = await loadTextureAsync({
            asset: item.textures[i].image,
        });
        
        if (item.type === 'glb')
            texture.flipY = false;

        textures.push({ name: item.textures[i]?.name || '-', map: texture });
    }

    console.info(`[loadModel] -> Textures done loading`);

    const result = await loadGLTFAsync({ asset: item.model });
    console.log(result);

    let obj = result;

    console.info(`[loadModel] -> Model done loading, adding textures now...`);

    if (texturesLength > 0) {
        if (texturesLength === 1) {
            obj.traverse(function (object) {
                if (object instanceof THREE.Mesh) {
                    object.material.map = textures[0]?.map;
                }
            });
        } else {
            obj.traverse(function (object) {
                if (object instanceof THREE.Mesh) {
                    const selected = textures?.find(x => x.name === object.name);
                    object.material.map = selected?.map;
                }
            });
        }
    }

    console.info(`[loadModel] -> Textures done applied...`);

    return obj;
};

Then in my threejs code I called the loadModel method and it seemed to output the details within the gltf file itself so that bit works...

I'm now at the point where no errors are thrown but nothing is still rendered for me (it could be my gltf file so I'm trying to download a different one to test).

I came across this code somewhere else, I tweaked it slightly for my use-case. For me, this got rid of the error. You were right in the comment above because that function is used by the three dependency - as it's usually rendered in a browser, but will still (mostly) work within React Native apart from now.

The reason this works is because it uses the expo-file-system package which lets you read the base64 string from the file itself...

Hopefully this helps :)

@carmenchapa
Copy link

hey there, after a while, I'm facing memory leaks issue in my app, mostly in older iPads.
I think the problem is in decode from 'base64-arraybuffer', anyone here still using this approach?
Is there any alternative to decode that does the same?
Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants