[ReactJs] How to load different 3d model with <select> tag?

Hello everyone,

For the purposes of our project (reactjs), we need to display different 3d models. For this, we have added a drop-down menu ( tag).
We recover the names of the files with a fetch.

We would like to display the 3d model of our choice and be able to choose another (which should replace the previous one)
Do you have an idea ?

thx again =)

ps: I am a beginner in babylonJs / I do not know typeScrypt / sorry for my English, I am French … =)

Maybe @brianzinn can help?

If you are using react-babylonjs NPM, you could just do something like this:

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

// this can be in your state and used to populate your <select/>
const models = [{
  rootUrl: `${baseUrl}BoomBox/glTF/`,
  sceneFilename: 'BoomBox.gltf'
}, {
  rootUrl: `${baseUrl}Avocado/glTF/`,
  sceneFilename: 'Avocado.gltf'
}];

const LoadModels = () => {
  const [stateModel, setModel] = useState(models[0]);
  const ref = useRef(stateModel);
  useEffect(() => {
    ref.current = stateModel; // force re-render when model changes
  }, [stateModel]);

  const onClick = _ => {
    // set to the other model.  You would use your select
    const nextModel = models.find(m => m.sceneFilename !== ref.current.sceneFilename);
    setModel(nextModel);
  };

  return (
    <>
      <ground name='ground1' width={10} height={6} subdivisions={2} position={new Vector3(0, -2, 0)}>
        <ScaledModelWithProgress
          key={stateModel.sceneFilename}
          rootUrl={stateModel.rootUrl}
          sceneFilename={stateModel.sceneFilename}
          scaleTo={3}
          progressBarColor={Color3.FromInts(135, 206, 235)}
          center={new Vector3(2, 1, 0)}/>
      </ground>

      <adtFullscreenUi name='ui'>
        <rectangle name="rect" height='50px' width='150px' horizontalAlignment={Control.VERTICAL_ALIGNMENT_TOP}>
          <rectangle>
            <babylon-button name="change-model-button" background="green" onPointerDownObservable={onClick}>
              <textBlock text='change model' fontSize={20} color="white"/>
            </babylon-button>
          </rectangle>
        </rectangle>
      </adtFullscreenUi>
    </>
  )
}

export const MainScene = () => (
  <div style={{flex: 1, display: 'flex'}}>
    <Engine antialias adaptToDeviceRatio canvasId='babylonJS'>
      <Scene>
        <arcRotateCamera name='camera1' alpha={Math.PI / 2} beta={Math.PI / 4} radius={9.0} target={Vector3.Zero()} minZ={0.001} />
        <hemisphericLight name='light1' intensity={0.7} direction={Vector3.Up()}/>
        <LoadModels/>
      </Scene>
    </Engine>
  </div>
)

If you are switching back and forth between models - you will probably want a manifest file, so the models aren’t repeatedly downloaded :slight_smile: ScaledModelWithProgress is a react component that shows a 3d model loading progress and optionally does max scaling:

If you are not using react-babylonjs NPM - just use the scene loader and then dispose loaded meshes when done otherwise you need to maintain a reference to them or make them invisible and not clickable, etc.

thank you for the help you give me.

I installed react-babylonjs, I put the imports but I have errors. Do you have any idea where this came from?

Failed to compile.

./src/App.js
Module not found: Can’t resolve ‘@babylonjs’ in ‘C:\Users\Matcoco\Documents\REACT\test-3d\src’

import React, { useEffect, useState, useRef} from ‘react’;

import ‘./App.css’;

import { Vector3, Color3, ScaledModelWithProgress, Control, LoadModels } from ‘@babylonjs’;

import { Engine, Scene } from ‘react-babylonjs’;

you maybe are using the original ‘babylonjs’ NPM? Look in your package.json - remove the babylonjs-* NPM and switch to the ES6 modules.

ie:

yarn add @babylonjs/core
yarn add @babylonjs/gui
yarn add @babylonjs/loaders

If you want to continue with the ‘babylonjs’ and not use the ES6 then you can use an old react-babylonjs@1.0.3.

Also, ScaledModelWithProgress is not part of @babylonjs. It looks like you are using CRA, so you should be using ES6. I have a working demo here:

Also, those forward slashes after @babylonjs are different NPMs:

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

thx Brianzinn =)

ah! I just understood that ScaledModelWithProgress was a component … shame on me lol

so I installed the other dependencies but … does not work! I’m not very good …

this "LoadModels " component refers to what?

here is the link code sand box version:
https://codesandbox.io/embed/frosty-dan-s3ggi?fontsize=14&hidenavigation=1&theme=dark

A few things. The babylonjs components like ground, etc. need to be inside <Scene />, because it uses a different renderer that understands BabylonJS host elements (like box, arcRotateCamera), otherwise you are using the ReactDOM renderer, which renders regular HTML things (like div, span, etc.). Also, you need to load the App as the main container with the BabylonJS Engine. This should work:

import React, { useEffect, useState, useRef} from 'react';
import ScaledModelWithProgress from './ScaledModelWithProgress';
import "@babylonjs/loaders/glTF"; // side-effects
import { Vector3, Color3 } from '@babylonjs/core';
import { Engine, Scene } from 'react-babylonjs';

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

// this can be in your state and used to populate your <select/>
const models = [{
  rootUrl: `${baseUrl}BoomBox/glTF/`,
  sceneFilename: 'BoomBox.gltf'
}, {
  rootUrl: `${baseUrl}Avocado/glTF/`,
  sceneFilename: 'Avocado.gltf'
}];

const LoadModels = ({model}) => {
  
  

  return (
    <>
      <ground name='ground1' width={10} height={6} subdivisions={2} position={new Vector3(0, -2, 0)}>
        <ScaledModelWithProgress
          key={model.sceneFilename}
          rootUrl={model.rootUrl}
          sceneFilename={model.sceneFilename}
          scaleTo={3}
          progressBarColor={Color3.FromInts(135, 206, 235)}
          center={new Vector3(2, 1, 0)}/>
      </ground>

    </>
  )
}

export const App = () => {

  const [stateModel, setModel] = useState(models[0]);
  const ref = useRef(stateModel);

  useEffect(() => {
    ref.current = stateModel; // force re-render when model changes
  }, [stateModel]);

  const onClick = _ => {
    // set to the other model.  You would use your select
    const nextModel = models.find(m => m.sceneFilename !== ref.current.sceneFilename);
    setModel(nextModel);
  };

  return (
    <div style={{flex: 1, display: 'flex'}}>
      <button onClick={onClick}>change model</button>
      <Engine antialias adaptToDeviceRatio canvasId='babylonJS'>
        <Scene>
          <arcRotateCamera name='camera1' alpha={Math.PI / 2} beta={Math.PI / 4} radius={9.0} target={Vector3.Zero()} minZ={0.001} />
          <hemisphericLight name='light1' intensity={0.7} direction={Vector3.Up()}/>
            <LoadModels model={stateModel} />
        </Scene>
      </Engine>
    </div>
  )
}

export default App;

The other things is that I would change the dependencies to this:

"@babylonjs/core": "4.1.0",
    "@babylonjs/loaders": "4.1.0",
    "@babylonjs/gui": "4.1.0",
    "react": "16.12.0",
    "react-babylonjs": "2.0.6",
    "react-dom": "16.12.0",
    "react-reconciler": "0.25.1",
    "react-scripts": "3.0.1"

For some reason the models weren’t loading on the codesandbox. I am busy with work now, but can check later if you don’t get it working. I think it will work locally for you, though.

2 Likes

thank you very much, it works! I will try to replace the button with a drop-down menu. =)

2 Likes

Hello,
Thank you for all this work. Can you tell me how to load a Gltf object from local machine.
Do I have to modify
baseUrl = $ {process.env.PUBLIC_URL} / public /;
The address is readable but nothing is displayed

Even with a simple export box to gltf, nothing is displayed

Thank you in advance

Are you using create react app? If so, that’s pointing to the ‘/public’ folder, so you can put it there. Otherwise you need to load it from a web server running CORS. Check the network tab to check you are downloading the model. Let me know how that goes!

thank you for your reply.

I cloned this repository, then:
yarn install yarn start

All work well locally

when i change the address to load a .gltf object from the local machine, it doesn’t work : (

let baseUrl = / public;
rootUrl = {$ {baseUrl}} sceneFilename = “BoomBox.gltf”
knowing that I have copied “BoomBox.gltf” to the root of the public folder

Thank you in advance.

${process.env.PUBLIC_URL} actually points to ‘/’ normally and if you build and deploy (ie: to github pages) then it would use what you have on ‘homepage’ in your package.json. If you are running locally then you want to use the root url (I would recommend to use the environment variable):

let baseUrl = '/';
...
<model rootUrl={baseUrl}  sceneFilename='BoomBox.gltf' />

So, the ‘public’ folder in your source project becomes the root directory of the webserver that spins up when you do a yarn start or npm start and you start a webserver on ‘localhost:3000’ - if you put the gltf file in the root of the public folder, you should be able to load it from localhost:3000/BoomBox.gltf.

Hope that helps. Here is a working example of loading assets from public folder: https://github.com/brianzinn/create-react-app-babylonjs/blob/master/src/withSkybox.js#L11

Thank you very much for your answer, Perfect for loading 3D objects.

Now I have another problem: s

I made a project with raw JavaScript / Babylonjs, and I want to do it again with react / Babylonjs

Here is the project I want to redo:

https://sami-benghorbal.netlify.app/

Here is the current result with React:

https://sami-benghorbal-babylon-react.netlify.app/

My problems :
1 / texture assignment
2 / turned the model
3 / Adding the hdrSkybox environment
4 / scaling and moving the model

This is the code I am using on React, can you please help me
thank you in advance

import React, {useState, Fragment} from ‘react’;
import * as BABYLON from ‘babylonjs’;
import ‘babylonjs-loaders’;
import “@babylonjs/loaders/glTF”;

import BabylonScene from ‘./Babylon.jsx’; // import the component above linking to file we just created.

export default function Modal (props) {
const onSceneMount = (e: SceneEventArgs) => {
const { canvas, scene, engine } = e;
const camera = new BABYLON.ArcRotateCamera(“Camera”, Math.PI / 2, Math.PI / 2, 2, new BABYLON.Vector3(0,0,5), scene);
camera.setTarget(BABYLON.Vector3.Zero());
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight(“light1”, new BABYLON.Vector3(0, 1, 0), scene);
light.intensity = 0.7;
engine.runRenderLoop(() => { scene.render(); });

BABYLON.SceneLoader.Append("./assets/babylonjs/models/", props.Nom , scene, function (newMeshes) { });

const Modal = (e) => {
const { closeModal } =useState(props.closeModal);
};    

}
return (

      <Fragment>  
      <div className="overlay">
        <div className="content">
          <BabylonScene onSceneMount={onSceneMount} />  
          <i class="fa fa-times" onClick={props.closeModal} aria-hidden="true"></i>         
        </div>
      </div>            
      </Fragment>
  )  

}

hi @Sami - nice demo! The first big difference is that you are not loading the HDR texture and skybox (environment.dds). The biggest difference is that you are loading the model with SceneLoader.ImportMesh in the first one and SceneLoader.Append in the new one. That means your callback signature is the scene, so probably easiest to switch back to ImportMesh, so the callback will be the meshes and copy all the code in your original callback, which is doing a lot. Your code is all imperative, so a lot of it can be reproduced in the playground as well. I think you just need to go through and diff the two projects. I don’t think your model is turned - your arcrotate camera has different alpha/beta (see constructor docs and diff the 2 projects). Lastly, I noticed in js/composants/viewer.js that you are using both the legacy and ES6 NPM imports:

import 'babylonjs-loaders';
import "@babylonjs/loaders/glTF";

I don’t think you need the second import or library. The import 'babylonjs-loaders should already import the side-effects and register the gltf loader.

2 Likes

Thank you very much, perfect it works well.

Otherwise I have a problem : ( I cannot display the model loading logo and if there is a way to add a counter (0 to 100%)

2 / is there a way to preload the model before clicking on the image and opening the modal.

Here is the link of the project (you have to click on the image, to open the modal)

sami-ben-ghorbal-chevrolet8.netlify.app

Thank you in advance.

I would start a new thread for follow up questions, since this thread was started by somebody else?

To know when the model is loaded there are some callbacks. Here is how I do progress 0-100% in my model React component:

onLoadProgress={(evt) => {
            let modelLoadProgress = evt.lengthComputable
              ? evt.loaded / evt.total
              : evt.loaded / this.props.knownFileSize

            this.setState({ loadProgress: modelLoadProgress })
          }}
          onModelLoaded={(model) => {
            this.setState({ loadProgress: 1 })
            if (this.props.onModelLoaded) {
              this.props.onModelLoaded(model, this.props.sceneContext)
            }
          }}

You can see the callbacks here - onProgress and onSuccess
https://doc.babylonjs.com/api/classes/babylon.sceneloader#importmesh

If you want to preload models before showing your application then you would want to use an AssetManager and a custom loading screen or something like that. It would depend on how you wanted your application to behave. I like to use a 3d progress bar where the model would be and that can speed up start time, but may not work for you.

1 Like

Thank you very much for your answer,

I don’t know how to make a new thread for the questions : ( or can you do it?

Here is the current result:

https://sami-ben-ghorbal-chevrolet8.netlify.app/

I have 3 questions:

1 / how can we block the downloading of 3D modes? is there a way to encrypt the 3d object? compress …? like in the Sketchfab site?

2 / on the mobile device, the shadow is not displayed nor the texture with Alpha

3 / on a mobile support, when the 1st 3D object is opened, the animation is fluid. At the opening of 2, 3 … Objects the animation jerks more and more

I don’t know if you can move it to a new thread

Thank you in advance :kissing_heart:

1 / On a public unauthenticated site you cannot block the downloading. What sketchfab does is heavily process and compress (lossy compression) the original model, which in turn uses a custom viewer to show the model. BabylonJS on the other hand uses open standards, so to protect your original model you would need to reduce the quality. There have been rippers for sketchfab in the past like NinjaRipper - once data is sent to the graphics card that information is available.

2 & 3 / Any loss of quality/functionality on mobile will require optimisations from the developer. I have also seen faulty/limited mobile devices. Reducing poly count, merging meshes, not adapting to device ratio. There are so many techniques - looking at draw calls, etc.

Those questions you have asked have many different solutions.

So how do the sites for selling 3D models?
What is the process at Sketchfab? :cry:
Is there no way to encrypt the 3D file? will only decipher on display?
How can we guarantee the minimum security of 3D files, at least for simple Internet users, and not for experts.

Thank you.