brianzinn / react-babylonjs

React for Babylon 3D engine
https://brianzinn.github.io/react-babylonjs/
829 stars 105 forks source link

Migrating from individual models to instances #297

Closed DenysAshikhin closed 5 months ago

DenysAshikhin commented 1 year ago

Hi,

I am currently creating copies/clones of the same model using:

  <Model
        rootUrl={`./`}
        sceneFilename={`cavity_blocks.glb?id=${id}`}
        scaleToDimension={toScale ? toScale : 10}
        position={display ? currentCoord : new Vector3(9999999, 9999999, 99999999)}
        rotation={rotation}
        onModelLoaded={onModelLoaded}
        onLoadProgress={updateProgress}
      ></Model>

Inside of OnModelLoaded I'm also grabbing the mesh reference:

const onModelLoaded = (model) =>{
 coreRef.current = model.rootMesh;
 }

Then I am moving individual models with a useBeforeRender:

   useBeforeRender(
    (scene) => {
      if (display && isVisible)
        if (coreRef.current && tag.threeX && factoryDimensions.width !== 0) {
          var deltaTimeInMillis = scene.getEngine().getDeltaTime();

          let xDiff = coreRef.current.position.x - targetCoord.x;
          let zDiff = coreRef.current.position.y - targetCoord.y;
          let yDiff = coreRef.current.position.z - targetCoord.z;

          let distance = Math.pow(xDiff, 2) + Math.pow(yDiff, 2);

          if (distance > 0.25) {

            let xMove = (targetCoord.x - previousCoord.x) / 1000;
            let yMove = (targetCoord.z - previousCoord.z) / 1000;
            let zMove = (targetCoord.y - previousCoord.y) / 1000;

            coreRef.current.lookAt(targetCoord);
            coreRef.current.rotation = new Vector3(
              coreRef.current.rotation.x + Math.PI / 2,
              coreRef.current.rotation.y,
              coreRef.current.rotation.z - Math.PI / 2
            );

            coreRef.current.position.x += deltaTimeInMillis * xMove;
            coreRef.current.position.z += deltaTimeInMillis * yMove;
            coreRef.current.position.y += deltaTimeInMillis * zMove;
          }
        }
    },
    undefined,
    undefined,
    undefined,
    [tag, factoryDimensions, coreRef, coreRef.current, boxRef, targetCoord, previousCoord, isVisible, trajectoryCoord]
  );

Is it possible to achieve this using instances? The meshes are static, and only their individual location/rotation is updated as in the beforeRenderLoop. If it is, can someone guide the start? I have tried going over the doc for assetManager and mesh from instance but I'm not sure how to combine these things

brianzinn commented 1 year ago

The Model component doesn’t do much. Just a wrapper for a scene loader hook: https://github.com/brianzinn/react-babylonjs/blob/master/packages/react-babylonjs/src/customComponents/Model.tsx#L55

If you have captured that mesh reference then you can declaratively use “InstancedMesh” or create instance yourself and use “fromInstance” prop on a “Mesh”. Don’t let the name of the property mislead you as it is available on ALL declared types.

DenysAshikhin commented 1 year ago

So I used the

<Model 
onLoad={(root_mesh)=>{
       root_mesh_ref.current = root_mesh}
}

/Model>

Then later:

{root_mesh_ref.current && (
<Mesh
fromInstance={root_mesh_ref.current.rootMesh}
/>
)

Except I get:

ReactBabylonJSHostConfig.ts:481 fromInstance wrong type. AbstractMesh {_isDirty: true, _nodeDataStorage: _InternalNodeDataInfo, state: '', metadata: null, reservedDataStore: null, …} class Mesh extends _abstractMesh_js__WEBPACK_IMPORTED_MODULE_13__.AbstractMesh {
  /**
   * @constructor
   * @param name The value used by scene.getMeshByName() to do a lookup.
   * @param scene The scene… 
    at Mesh
    at Suspense
    at CoreModel (http://localhost:3000/static/js/src_assets_3d_icon_svg-src_assets_Exit_Fullscreen_svg-src_assets_Fullscreen_svg-src_assets_Ma-1115ee.chunk.js:7805:5)
Am I doing it wrong? 
DenysAshikhin commented 1 year ago

Swapping to abstractMesh leads to no errors, but no model or anything shows up

  <abstractMesh
                      fromInstance={cavity_block_ref.current.rootMesh}
                      position={new Vector3(10, 10, 10)}
                      name={`core_block.glb?id=${3132132133115}-model`}
                    />

However, if I try to use: instancedMesh I get the following:

  <instancedMesh
                      fromInstance={cavity_block_ref.current.rootMesh}
                      position={new Vector3(10, 10, 10)}
                      name={`core_block.glb?id=${3132132133115}-model`}
/>
instancedMesh.ts:46 Uncaught TypeError: Cannot read properties of undefined (reading 'getScene')
    at new InstancedMesh (instancedMesh.ts:46:1)
    at createInstance (ReactBabylonJSHostConfig.ts:550:1)
    at completeWork (react-reconciler.development.js:10851:1)
    at completeUnitOfWork (react-reconciler.development.js:18725:1)
    at performUnitOfWork (react-reconciler.development.js:18697:1)
    at workLoopSync (react-reconciler.development.js:18597:1)
    at renderRootSync (react-reconciler.development.js:18565:1)
    at recoverFromConcurrentError (react-reconciler.development.js:17948:1)
    at performSyncWorkOnRoot (react-reconciler.development.js:18194:1)
    at flushSyncCallbacks (react-reconciler.development.js:2936:1)
brianzinn commented 1 year ago

hi, sorry i didn't notice a reply.

For the abstract mesh. I would expect it to move the position of the loaded model, if that was the last positional update. You may want to be specific with the prop disposeOnUnmount. You can verify that in the Inspector.

For the instancedMesh one. If you are using the code in your original message then you have destructured the parameter incorrectly. I think that may be the cause of the other one as well. Do a console.log or look at the properties of "rootMesh" parameter. The object being returned is actually a loaded model with animations, animation groups, etc: https://github.com/brianzinn/react-babylonjs/blob/master/packages/react-babylonjs/src/hooks/loaders/loadedModel.ts#L15

The code you likely want is not

<Model 
  onLoad={(root_mesh)=>{
    root_mesh_ref.current = root_mesh
  }}
</Model>

but instead

<Model 
  onModelLoaded={({rootMesh})=>{
    root_mesh_ref.current = rootMesh
  }}
</Model>

or

<Model 
  onModelLoaded={(loadedModel)=>{
    root_mesh_ref.current = loadedModel.rootMesh
  }}
</Model>

Your editor should be telling you all of this. If you are using TypeScript and you strongly type your useRef<T> then you will know as well. image

DenysAshikhin commented 1 year ago

Long time no chat!

So I got it to work with doing the following:

<>

    <Suspense fallback={<box name="fallback" position={new Vector3(0, 0, 0)} />}>
                    <Model
                      ref={cavity_ref_temp}
                      rootUrl={ `./`}
                      sceneFilename={`cavity_blocks_compressed.glb?id=sample_instance_root3332`}
                      position={new Vector3(20, 20, 20)}
                      onModelLoaded={(rootMesh) => {
                        cavity_block_ref.current = rootMesh;
                        console.log(`setting cavity_block_ref:`);
                        setTimeout(() => {
                          setCavityBlockLoaded(true);
                        }, 2000);
                      }}
                    />
       </Suspense>

        {cavityBlockLoaded && (
                  <>

                    { <abstractMesh
                      fromInstance={cavity_block_ref.current.rootMesh}
                      position={new Vector3(1, 10, 1)}
                      name={`cavity_blocks_compressed2.glb?id=${31115}-model`}
                    />
                    <abstractMesh
                      fromInstance={cavity_block_ref.current.rootMesh}
                      position={new Vector3(10, 1, 1)}
                      name={`cavity_blocks_compressed3.glb?id=${33212315}-model`}
                    /> 
                  </>
                )}
</>

However, only the last abstract mesh is visible, the original and the first is not showing up, it seems like they are just controlling the same mesh position.

I tried using

     <instancedMesh
                      source={cavity_block_ref.current.rootMesh}
                      position={new Vector3(10, 1, 1)}
                      name={`cavity_blocks_compressed3.glb?id=${33212315}-model`}
                    /> 

But that thew a bunch of console errors - any ideas what I need to tweak to create seperate instances of the initial model?

brianzinn commented 1 year ago

Yes. When you use fromInstance the reconciler will not create a new mesh. So, it will just overwrite position and the last one received from the reconciler will be what you see. You probably want to use an instanced mesh. I'm going to publish a version now where the model has a forward ref to the root mesh. Then I think your example will work, if you switch abstract mesh to instance - using "source" prop.

brianzinn commented 1 year ago

@DenysAshikhin try on 3.1.26 that was just published. It will let you use <Model ref={...} /> as you have done.

DenysAshikhin commented 1 year ago

Sounds good, let me give it a shot!

DenysAshikhin commented 1 year ago

Updating fails to compile now: image

My package.json: image image

brianzinn commented 1 year ago

It’s been renamed recently. Are you able to go to latest?

DenysAshikhin commented 1 year ago

Sorry, which library to go latest?

brianzinn commented 1 year ago

@babylonjs.

DenysAshikhin commented 1 year ago

Okay we are back up and running but I get the following stack: image Am I supposed to use the red or green ref for instancedMesh?

image

brianzinn commented 1 year ago

I would say you would use cavity_ref_temp.current for the instancedMesh. it will just be a transformnode though - depends on your model itself. i haven't tried instancing a hierarchy before.

DenysAshikhin commented 1 year ago

image image

😢

brianzinn commented 1 year ago

you have something like:

const cavity_ref_temp = useRef();

Can you change your timeout to:

setTimeout(() => {
  console.log('cavite_ref_temp', cavity_ref_temp.current)
  setCavityBlockLoaded(true);
}...);

You will hopefully see that the "rootMesh/root" is the ".current". You need to get the mesh you are after from the children of that Mesh. Babylon.js creates that root node and parents the model mesh(es) to it. Something seems to be off. Maybe we can make a code sandbox.

brianzinn commented 1 year ago

if you upload your glb here: https://sandbox.babylonjs.com/

then open the inspector - when you expand the "nodes" then you will see your meshes.

brianzinn commented 5 months ago

There is an official community request that will pave a road to make this easier to address: https://github.com/BabylonJS/Babylon.js/issues/14567

Closing out from inactivity. Thank-you for your contribution.