Only one box spins

Hello,

I am currently working on rendering multiple canvas’s on a react app. I have for the most part found some success with a basic attempt… but I am trying to better understand why only my last box rotates in the code below.

I am assuming it has something to do with the fact that onRender is being used over and over and when its used on the last scene… it is the only left with the animation present… but I find this peculiar since I would expect the same behavior from const onSceneReady and it seems to behave as I would hope… I can move each scene independently of the other scenes…

import ExpenseItem from './components/ExpenseItem';
import { FreeCamera, Vector3, HemisphericLight, MeshBuilder } from "@babylonjs/core";
import SceneComponent from "./components/SceneComponent"; // uses above component in same directory
// import SceneComponent from 'babylonjs-hook'; // if you install 'babylonjs-hook' NPM.

function App() {


  let box;

const onSceneReady = (scene) => {
  // This creates and positions a free camera (non-mesh)
  var camera = new FreeCamera("camera1", new Vector3(0, 5, -10), scene);

  // This targets the camera to scene origin
  camera.setTarget(Vector3.Zero());

  const canvas = scene.getEngine().getRenderingCanvas();

  // This attaches the camera to the canvas
  camera.attachControl(canvas, true);

  // This creates a light, aiming 0,1,0 - to the sky (non-mesh)
  var light = new HemisphericLight("light", new Vector3(0, 1, 0), scene);

  // Default intensity is 1. Let's dim the light a small amount
  light.intensity = 0.7;

  // Our built-in 'box' shape.
  box = MeshBuilder.CreateBox("box", { size: 2 }, scene);

  // Move the box upward 1/2 its height
  box.position.y = 1;

  // Our built-in 'ground' shape.
  MeshBuilder.CreateGround("ground", { width: 6, height: 6 }, scene);
};

/**
 * Will run on every frame render.  We are spinning the box on y-axis.
 */
const onRender = (scene) => {
  if (box !== undefined) {
    var deltaTimeInMillis = scene.getEngine().getDeltaTime();

    const rpm = 10;
    box.rotation.y += (rpm / 60) * Math.PI * 2 * (deltaTimeInMillis / 1000);
  }
};


  return (
    <div>
      <h2>Let's get started!</h2>
      <ExpenseItem></ExpenseItem>
        <SceneComponent antialias onSceneReady={onSceneReady} onRender={onRender} id="my-canvas" />
        <SceneComponent antialias onSceneReady={onSceneReady} onRender={onRender} id="my-canvas" />
        <SceneComponent antialias onSceneReady={onSceneReady} onRender={onRender} id="my-canvas" />
        <SceneComponent antialias onSceneReady={onSceneReady} onRender={onRender} id="my-canvas" />
        <SceneComponent antialias onSceneReady={onSceneReady} onRender={onRender} id="my-canvas" />

      

      
    </div>
  );
}

export default App;

I will also attach my scene component if you wish to see it.

import { Engine, Scene } from "@babylonjs/core";
import React, { useEffect, useRef } from "react";

export default (props) => {
  const reactCanvas = useRef(null);
  const { antialias, engineOptions, adaptToDeviceRatio, sceneOptions, onRender, onSceneReady, ...rest } = props;

  useEffect(() => {
    if (reactCanvas.current) {
      const engine = new Engine(reactCanvas.current, antialias, engineOptions, adaptToDeviceRatio);
      const scene = new Scene(engine, sceneOptions);
      if (scene.isReady()) {
        props.onSceneReady(scene);
      } else {
        scene.onReadyObservable.addOnce((scene) => props.onSceneReady(scene));
      }

      engine.runRenderLoop(() => {
        if (typeof onRender === "function") {
          onRender(scene);
        }
        scene.render();
      });

      const resize = () => {
        scene.getEngine().resize();
      };

      if (window) {
        window.addEventListener("resize", resize);
      }

      return () => {
        scene.getEngine().dispose();

        if (window) {
          window.removeEventListener("resize", resize);
        }
      };
    }
  }, [reactCanvas]);

  return <canvas ref={reactCanvas} {...rest} />;
};

Your box variable is scoped outside of any of the individual functions, so in essence, you only have a single box variable. You are assigning that and then overwriting it multiple times. The final value will be the last one, since it’s being executed sequentially. Each of the rotation functions is updating the same box.

One solution to this could be an array of boxes. Another could be moving all the logic for a particular canvas/scene into it’s own component.

3 Likes

Thanks Darragh. I had a feeling this must be the case… I just found it odd that onSceneReady does not run into the same issues.

2 Likes

Well, every variable in onSeneReady is scoped to the function already. Box is the only one outside.

1 Like

You also don’t want to scope variables outside for HMR - ‘useRef’ hook is better here as well