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

hi @Sami - There are many ways you can encrypt the 3d file, but then you need to decrypt in the browser - so, from cipher to plain using javascript - I have done that in javascript in many different ways and there are libraries for that, but I’ve only done so in nodejs. The problem is not in the encryption, but that if the browser can decrypt then anybody can. If you just want it unreadable at transport level for simple users then that or an encoding may suffice. I would wager to guess that all sketchfab (their secret recipe is not public to prevent theft) has done is added a lossy compression and an obfuscated/proprietary format specific for their viewer. You will get better help by asking a new question, because this is buried in a conversation nobody else seems to be following. Click on “Questions” and then “+ New Topic” and hopefully somebody else has a more complete answer or good suggestions from experience - I have never needed to protect assets. Good luck!

2 Likes

Hi, thanks for this example. I tried this, but the problem I have is that the app loads the models again every time the button is clicked. With HTML and JS using LoadAssetsContainer doesn’t happen, but I would like to do the project in Reactjs. Is it something that I am missing?

hi @zeser,
to be able to know why your code is failing you would need to supply more code. anytime your button is clicked, if it triggers a prop change then it could be causing your component to re-render. You may need a hook like useMemo or to put the model url in your dependency array, if you are using a hook like useEffect.

Cheers,
Brian

Hi, thanks for the prompt answer. This is the code: GitHub - zeser78/react-hooks-babylonjs-loader , or here react-babylonjs-loaders - CodeSandbox

It was what I said about the dependency array. If your useEffect has no dependency array then it will run on every re-render (you will create a NEW scene and engine on EVERY render). You can fix it by adding the ref to the canvas:

useEffect(() => {
  ...
}, [canvasRef.current]);

For the asset container you will likely want to move the logic - something like move the main into the useEffect (you will get a lint warning about the dependency on that function). An easy way to trigger the scene change would be something like:

useEffect(() => {
  assetContainers[prevCount].removeAllFromScene()
  assetContainers[currentSceneIndex].addAllToScene()
}, [currentSceneIndex, assetContainers]

That should sort it out - cool project :slight_smile: Would be nice to bring in asset container support to react-babylonjs-loaders project, currently it only works with SceneLoader and AssetManager :slight_smile:

Thank you for your answer. I’ll try your advice.

Hi @brianzinn, did you make it work with this:

useEffect(() => {
  assetContainers[prevCount].removeAllFromScene()
  assetContainers[currentSceneIndex].addAllToScene()
}, [currentSceneIndex, assetContainers])

I am failing with that :confused:

hi @zeser - It’s because your dependency array doesn’t trigger a re-render. Here is a working example that you click the button and it only shows the content of that AssetContainer:

const START_INDEX = 0;
const App = () => {
  const [ringIndex, setRingIndex] = useState(START_INDEX);

  const assetContainersRef = useRef([]);

  const onButtonClick = (index) => {
    if (index !== ringIndex) {
      assetContainersRef.current[ringIndex].removeAllFromScene();
      assetContainersRef.current[index].addAllToScene();
      setRingIndex(index);
    }
  }

  const loadPromise = (root, file, scene) => {
    return new Promise((res, rej) => {
      BABYLON.SceneLoader.LoadAssetContainer(root, file, scene, function (
        container
      ) {
        let root = new BABYLON.TransformNode();

        container.meshes.forEach(m => {
          if (!m.parent) {
            m.parent = root;
          }
        });
        root.scaling = new BABYLON.Vector3(50, 50, 50);
        res(container);
      });
    });
  };

  const createScene = (scene, renderCanvas) => {
    let camera = new BABYLON.ArcRotateCamera(
      "Camera",
      -Math.PI / 2,
      Math.PI / 2,
      4,
      new BABYLON.Vector3(0, 0, 0),
      scene
    );

    camera.lowerRadiusLimit = 4;
    camera.upperRadiusLimit = 6;
    camera.setTarget(BABYLON.Vector3.Zero());
    camera.attachControl(renderCanvas, true);
    // Light
    new BABYLON.HemisphericLight(
      "light",
      new BABYLON.Vector3(100, 100, 0),
      scene
    );

    BABYLON.SceneLoader.ShowLoadingScreen = false;
    return scene;
  };

  const canvasRef = useRef(null);
  useEffect(() => {
    var main = async (scene) => {     
      for (var i = 0; i < scenes.length; i++) { 
        var assets = await loadPromise(scenes[i].root, scenes[i].file, scene);
        assetContainersRef.current.push(assets);
      }
      assetContainersRef.current[0].addAllToScene();    
    };

    const renderCanvas = canvasRef.current;
    const engine = new BABYLON.Engine(renderCanvas);
    const scene = new BABYLON.Scene(engine);

    scene.clearColor = new BABYLON.Color3(255 / 255, 240 / 255, 246 / 255);

    let sceneToRender = createScene(scene, renderCanvas);
    main(scene);

    scene.createDefaultLight();

    engine.runRenderLoop(() => {
      sceneToRender.render();
    });

    window.addEventListener("resize", function () {
      engine.resize();
    });
  }, []);

  return (
    <>
      <div className="container-menu">
        {scenes.map((item) => (
          <button onClick={() => onButtonClick(item.id)}>
            {item.file}
          </button>
        ))}
      </div>
      <div
        id="containerCanvas"
        style={{ position: `absolute`, top: 0, zIndex: 5 }}
      >
        <canvas id="renderCanvas" ref={canvasRef} />
      </div>
    </>
  );
};

export default App;

Ok, Thank you so much for your help. I’ll try this.