Having issues with models on babylon js + react + typescript

Greetings,

I’m currently trying to import the example model from story book, which is the avocado and the boom box. My main problem is that any attempt to import a model causes the same error despite many things I have tried. I am able to render shapes and other simple scenes, just not model imports. I have tried a simple standard model as well, and that runs into the same issue. Here is the code I am using, so i’m hoping someone can point me in the right direction, thanks.The error is quite unspecific and not very informative:

index.js:1 The above error occurred in the component:

at ry (https://localhost:3000/static/js/1.chunk.js:293575:13)
at ScaledModelWithProgress (https://localhost:3000/static/js/2.chunk.js:571:5)

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.

Here is the code that this is resulting from:

import React, { Component, createRef } from 'react';

import { Vector3, Color3 } from '@babylonjs/core';

import { Scene, Engine, useBeforeRender, useClick, useHover } from 'react-babylonjs';

import { ActionManager, SetValueAction } from '@babylonjs/core/Actions';

import { ScaledModelWithProgress } from './ScaledModelWithProgress'

import {

    Box

} from 'grommet';

import '@babylonjs/loaders';

interface INotificationBoxProps {

};

interface INotificationBoxState {

    color: Color3;

    avocadoYPos: number;

    avocadoScaling: number;

}

const getRandomColor = (function () {

    // const Colors = ['#4F86EC', '#D9503F', '#F2BD42', '#58A55C'];

    const Colors = [[0.31, 0.53, 0.93, 1], [0.85, 0.31, 0.25, 1], [0.95, 0.74, 0.26, 1], [0.35, 0.65, 0.36, 1]];

    let i = 0;

    return () => {

        i++;

        return Colors[i % Colors.length];

    }

})();

// class WithUseClick extends Component<INotificationBoxProps, INotificationBoxState>

// {

//     private ref = createRef<HTMLDivElement>();

//     constructor(props) {

//         super(props);

//         this.state = {

//             color: Color3.FromArray(getRandomColor()),

//             avocadoYPos: -1.5,

//             avocadoScaling: 3.0

//         };

//     }

//     render() {

//         return (<sphere name='sphere1' ref={this.ref}

//             diameter={2} segments={32}

//             position={new Vector3(0, 1, 0)}>

//             <standardMaterial name='mat' diffuseColor={this.state.color} />

//         </sphere>);

//     }

// }

export class Client3D extends Component<INotificationBoxProps, INotificationBoxState> {

    constructor(props) {

        super(props);

        this.state = {

            color: Color3.FromArray(getRandomColor()),

            avocadoYPos: -1.5,

            avocadoScaling: 3.0

        };

    }

    onModelLoaded = (model, sceneContext) => {

        const mesh = model.meshes[1]

        mesh.actionManager = new ActionManager(sceneContext.scene)

        mesh.actionManager.registerAction(

            new SetValueAction(

                ActionManager.OnPointerOverTrigger,

                mesh.material,

                'wireframe',

                true

            )

        )

        mesh.actionManager.registerAction(

            new SetValueAction(

                ActionManager.OnPointerOutTrigger,

                mesh.material,

                'wireframe',

                false

            )

        )

    }

    render() {

        const baseUrl = 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/';

        return (

            <Box>

                <Engine antialias adaptToDeviceRatio canvasId='babylonJS'>

                    <Scene>

                        <arcRotateCamera name='camera1' alpha={Math.PI / 2} beta={Math.PI / 2} radius={9.0} target={Vector3.Zero()} minZ={0.001} />

                        <hemisphericLight name='light1' intensity={0.7} direction={Vector3.Up()} />

                        <ScaledModelWithProgress rootUrl={`${baseUrl}BoomBox/glTF/`} sceneFilename='BoomBox.gltf' scaleTo={3}

                            progressBarColor={Color3.FromInts(255, 165, 0)} center={new Vector3(2.5, 0, 0)}

                            onModelLoaded={this.onModelLoaded}

                        />

                        {/*<React.Suspense fallback={<box />}>

              <Model rootUrl={`${baseUrl}Avocado/glTF/`} sceneFilename='Avocado.gltf' />

          </React.Suspense>

          */}

                        <ScaledModelWithProgress rootUrl={`${baseUrl}Avocado/glTF/`} sceneFilename='Avocado.gltf'

                            scaleTo={this.state.avocadoScaling}

                            progressBarColor={Color3.FromInts(255, 165, 0)}

                            center={new Vector3(-2.5, this.state.avocadoYPos, 0)}

                        />

                    </Scene>

                </Engine>

            </Box>

        );

    };

}

Here is the JSX file I copied from the storybook example on models:

import React, { Component } from 'react'

import { Model, Box, StandardMaterial, Mesh, withScene } from 'react-babylonjs'

import { Vector3, Matrix, Color3 } from '@babylonjs/core';

// try with later versions of RHL to get hooks working here:

// const [loadProgress, updateProgress] = useState(0)

export class ScaledModelWithProgress extends Component {

    constructor() {

        super()

        this.state = {

            loadProgress: 0

        }

    }

    render() {

        return (

            <React.Fragment>

                <Model

                    scaleToDimension={this.props.scaleTo}

                    onLoadProgress={(evt) => {

                        let modelLoadProgress = evt.lengthComputable

                            ? evt.loaded / evt.total

                            : evt.loaded / (this.props.estimatedFileSize * 0.085) /* provided fileSize is not for 'view' manifest, a bad guess often, but generally factor ~0.085 smaller  */

                        this.setState({ loadProgress: modelLoadProgress })

                    }}

                    onModelLoaded={(model) => {

                        this.setState({ loadProgress: 1 })

                        if (this.props.onModelLoaded) {

                            this.props.onModelLoaded(model, this.props.sceneContext)

                        }

                    }}

                    position={this.props.center}

                    rootUrl={this.props.rootUrl}

                    sceneFilename={this.props.sceneFilename}

                    pluginExtension={this.props.fileExtension}

                    rotation={this.props.modelRotation}

                />

                {(this.state.loadProgress < 1) &&

                    <Mesh rotation={this.props.progressRotation} position={this.props.center}>

                        <Box key='progress' name='boxProgress' height={this.props.scaleTo / 15} width={this.props.scaleTo} depth={this.props.scaleTo / 30} scaling={new Vector3(this.state.loadProgress, 1, 1)}

                            position={new Vector3(this.props.scaleTo / 2, 0, this.props.scaleTo / 60)}

                            setPivotMatrix={[Matrix.Translation(-this.props.scaleTo, 0, 0)]}

                            setPreTransformMatrix={[Matrix.Translation(-this.props.scaleTo / 2, 0, 0)]}>

                            <StandardMaterial diffuseColor={this.props.progressBarColor} specularColor={Color3.Black()} />

                        </Box>

                        <Box key='back' name='boxBack' height={this.props.scaleTo / 15} width={this.props.scaleTo} depth={this.props.scaleTo / 30}

                            position={new Vector3(0, 0, this.props.scaleTo / -60)}

                        />

                    </Mesh>

                }

            </React.Fragment>

        )

    }

}

Thanks for your help, its much appreciated!

Simple repo demonstrating the issue i’m running into:

serp777/BabylonJSTypescriptTestError (github.com)

Welcome aboard!

Without a repro it will be hard to help.

Adding @brianzinn in case your error would ring a bell as he is the master of Babylon React.

2 Likes

Thanks for your assistance! I will go ahead and create a repo starting from the create-react-app --template typescript that replicates this particular issue I’m having.

Hey again, here is the link to my example repo:

serp777/BabylonJSTypescriptTestError (github.com)

Hi @arusse02 - Welcome to the forum!

With react-babylonjs v3 the Model component took on a breaking change and it now throws a promise while the model is loading (that’s the weird way Suspense works!). What you would need to do now is to place your Model inside a <Suspense ../>. Additionally, if you would like your progress reported to your fallback (Suspense prop) then even the Suspense needs a context provider. There is a complete working example here that hopefully works for you, but otherwise feel free to ask more questions:
react-babylonjs/ScaledModelWithProgress.js at master · brianzinn/react-babylonjs (github.com)

:smiley:

1 Like

Thanks for your response and help. It was weird, it started working the first time, and now it doesn’t work at all. Does it matter that I have the code in a typescript file whereas this example is a js file?

Here are the new errors I am seeing after copying the code from the example you sent. I have also commited these changes to the example repo I have here:

TypeError: Cannot read property '0' of undefined

Function.Vector3.FromArray

C:/Users/russelau/sourceES6/core/Maths/math.vector.ts:1440

```
  1437 |     * @returns the new Vector3  1438 |     */  1439 |    public static FromArray(array: DeepImmutable<ArrayLike<number>>, offset: number = 0): Vector3 {> 1440 |        return new Vector3(array[offset], array[offset + 1], array[offset + 2]);       | ^  1441 |    }  1442 |   1443 |    /**
```

View compiled

Function.Mesh.Parse

C:/Users/russelau/sourceES6/core/Meshes/mesh.ts:3313

```
  3310 |            Tags.AddTagsTo(mesh, parsedMesh.tags);  3311 |        }  3312 | > 3313 |        mesh.position = Vector3.FromArray(parsedMesh.position);       | ^  3314 |   3315 |        if (parsedMesh.metadata !== undefined) {  3316 |            mesh.metadata = parsedMesh.metadata;
```

View compiled

importMesh

C:/Users/sourceES6/core/Loading/Plugins/babylonFileLoader.ts:612

```
  609 |                            }  610 |                        }  611 | > 612 |                        var mesh = Mesh.Parse(parsedMesh, scene, rootUrl);      | ^  613 |                        meshes.push(mesh);  614 |                        log += "\n\tMesh " + mesh.toString(fullDetails);  615 |                    }
```

View compiled

(anonymous function)

C:/Users/russelau/sourceES6/core/Loading/sceneLoader.ts:744

```
  741 |                var particleSystems = new Array<IParticleSystem>();  742 |                var skeletons = new Array<Skeleton>();  743 | > 744 |                if (!syncedPlugin.importMesh(meshNames, scene, data, fileInfo.rootUrl, meshes, particleSystems, skeletons, errorHandler)) {      | ^  745 |                    return;  746 |                }  747 | 
```

View compiled

dataCallback

C:/Users/russelau/sourceES6/core/Loading/sceneLoader.ts:508

```
  505 |                return;  506 |            }  507 | > 508 |            onSuccess(plugin, data, responseURL);      | ^  509 |        };  510 |   511 |        let request: Nullable<IFileRequest> = null;
```

View compiled

successCallback

C:/Users/russelau/sourceES6/core/Loading/sceneLoader.ts:533

```
  530 |            }  531 |   532 |            const successCallback = (data: string | ArrayBuffer, request?: WebRequest) => {> 533 |                dataCallback(data, request ? request.responseURL : undefined);      | ^  534 |            };  535 |   536 |            const errorCallback = (error: RequestFileError) => {
```

View compiled

XMLHttpRequest.onReadyStateChange

C:/Users/russelau/sourceES6/core/Misc/fileTools.ts:397

```
  394 |                        request.removeEventListener("readystatechange", onReadyStateChange);  395 |   396 |                        if ((request.status >= 200 && request.status < 300) || (request.status === 0 && (!DomManagement.IsWindowObjectExist() || FileTools.IsFileURL()))) {> 397 |                            onSuccess(useArrayBuffer ? request.response : request.responseText, request);      | ^  398 |                            return;  399 |                        }  400 | 
```

View compiled

I can check tonight after work. I did find a bug yesterday that needs to be fixed and may be related.

That would be awesome ill keep a lookout, thanks again.

Hey actually I figured it out. Turns out when copy pasting I accidentally forgot to include:

import ‘@babylonjs/loaders’

Once I re included that it worked like a charm

1 Like

TLDR: If you’re only importing a specific type of model ie: only .obj or only .gltf then you can benefit from a more specific import for side effects.

Great - glad you got that working! You caught another intentional “breaking” change in V3, because I used to import everything like you have there. I removed the import to allow people to be specific about what they wanted to import for reducing bundle size. You can read more about that here:
brianzinn/react-babylonjs: React for Babylon 3D engine (github.com)

and here:
react-babylonjs/breaking-changes-2.x-to-3.0.md at master · brianzinn/react-babylonjs (github.com)