React/Babylon build ?ns: engineOptions, render loops

I’m going through the code in the BabylonJS and React tutorial here:

I’m actually using Gatsby as the site generator (or will be), as its a portfolio/demo site and mostly static, and I can use APIs for whatever external data I use. I’m just trying to understand the tutorial code itself.

I really can’t use Playground for this, obviously, but my questions are general API/react based.

First, the 3rd parameter for the Engine object from the Babylonjs API is engineOptions. In the Babylon/react tutorial, in the SceneComponent.jsx code, there is a new Engine declared with engineOptions passed to it.

const { antialias, engineOptions, adaptToDeviceRatio, sceneOptions, onRender, onSceneReady, ...rest } = props;

const engine = new Engine(reactCanvas.current, antialias, engineOptions, adaptToDeviceRatio);

The engineOptions appears to be from props passed to SceneComponent by the index.js, which is the parent: (also copied from the tutorial)

<SceneComponent antialias onSceneReady={onSceneReady} onRender={onRender} id='my-canvas' />

Only here, I don’t see engineProps passed in to the component. The Engine API doesn’t refer to it specifically as boolean, so I’m not sure how this defines any options. The page doesn’t throw any error, and (except an ESLINt warning about missing dependencies) I see the box spinning the way it should be run from my local server.

So the way I’m reading this is, the component is insuring that all the props that may be needed for the the canvas element are passed in, but just not all of them are necessarily declared with a value in the index file, and this itself does not cause an error. Is that correct? My understanding of React is still in development mode, but I feel I have a basic grasp of the principles. How is Engine treating this?

Also, I also want to make sure I understand whats creating the render loop. {this is where I expose my lack of understanding of how the render loop works) Any update to the DOM will trigger the useEffect React hook in the SceneComponent code . Is it the function updating the Y rotation in the index.js file, that keeps triggering DOM updates, that keeps the loop going? (combined with the engine.runRenderLoop in the component code?).

This is hard because there are so many APIs to deal with at once. Babylon, React, not to mention whatever Gatsby is doing(routing, etc). I dont want to just copy and paste, because that will limit my creation and at some point I’m going to get stuck with zero basis for knowing what to target as the problem. I’m going to be creating a variety of interactive Babylon.js pages, so I’m going to be declaring SceneComponent multiple times, and I’ll have to extract all that into component trees I suppose. But first things first. Thanks anyone who has React knowledge or insight on this.

Hi,

So the way I’m reading this is, the component is insuring that all the props that may be needed for the the canvas element are passed in, but just not all of them are necessarily declared with a value in the index file, and this itself does not cause an error. Is that correct? My understanding of React is still in development mode, but I feel I have a basic grasp of the principles. How is Engine treating this?

Correct. The engineOptions param is optional. So even if you don’t specify any options passed into your canvas element, it would still work fine.

Is it the function updating the Y rotation in the index.js file, that keeps triggering DOM updates, that keeps the loop going?

The useEffect hook is equivalent to the lifecycle methods in regular React components. See the Tip section in https://reactjs.org/docs/hooks-effect.html

It is invoked when SceneComponent is loaded. You can think it as the right lifecycle point when canvas has been loaded into DOM and you can now safely instantiate BJS stuff that require the canvas to render.

The DOM updates are controlled by the browser. None of the React or BJS js code controls/triggers DOM updates. The rendering loop for updating canvas is initiated by

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

You initialize this render loop inside the effect hook.

2 Likes

The props are not all required. Additionally, that page was written for simplicity of use as it has an Engine and Scene in a single component. They key way to look at that is that the ...rest will be all of the other props that are specified. So, whatever isn’t used by the engine and scene constructores then goes to the canvas - could be any DOM attribute (id, className, width, etc).

The useEffect won’t be triggered on component prop updates, because of the dependency array [reactCanvas], which is only set once (when the canvas element is created). You can see that because the dispose would be called on any prop updates otherwise - when a useEffect returns a function it is called once the dependency array changes.

Edit:
You can change it to useCallback if that seems clearer (it’s memoized to the element ref), but it doesn’t do cleanup to dispose the engine and remove window event listeners:

const reactCanvas= useCallback(canvas => {
  // same thing.
}, []);

The runRenderLoop is called automatically by the Babylon engine outside of the render loop of React.

2 Likes

I got it, mostly. I know that React uses a “virtual dom”, and its all abstracted from the browser DOM. I’ll leave it at that for now and focus on what goes where.

Which actually brings me to a resizing issue. When opening the file in my browser, basically just copying all the example code but into a new gatsby project, (basic “hello world” starter), the canvas is too small, only occupying the upper left corner.

That makes sense, because there is nothing in my code to indicate the size of the canvas (or engine). Adding styles directly to the canvas element in the scene component makes it occupy the full window, but then its not resizing responsively as you’re dragging the window width to resize, the way you see in the Playground. Either way, small size or full window, its not responsively resizing.

<canvas style={{ width: window.innerWidth, height: window.innerHeight }} ref={reactCanvas} {...rest} />

If i hit refresh, it resizes to whatever size I’ve adjusted the window. The react/gatsby server I have running has hot reloading, so there’s nothing else I hit refresh for. Is it supposed to already occupy the whole window, and be resizing responsively? If not, where do I put the code to for it to do so? I’m honestly not sure if this is more of a Babylon thing or a react thing, but there is code to resize the engine in SceneComponent useEffect():

const resize = () => {
                scene.getEngine().resize();
            }
            if (window) {
                window.addEventListener('resize', resize);
            }

I’m going to try it with create-react-app to see if there 's a difference. I know there is a react-babylonjs renderer but i was hoping to figure this out.

I would recommend using CSS instead of heights. You could assign an id to the canvas and have in css:

#yourCanvasId {
    width:100%;
    height: 100%;
}
1 Like

OK so I did try that again, only with className, and it works, mostly. Thanks. Not sure what i did wrong the first time, probably something silly like not importing the file. The only other thing I can say is, although it resizes, there is empty page space underneath the canvas. The more i narrow the window, the more white space there is.

which isn’t the case in the playground. (the playground being end all and be all). But no really, I’ll have to figure that out for it to be appropriate for mobile viewing. A css issue, though, perhaps? and one for tomorrow. Odd, with height being set at 100%.

That’s a general issue - I can see why you were using window client dimensions - You can see if code like this is helpful as well.

engine().onResizeObservable.add(() => {
      if (engine().getRenderHeight() > engine().getRenderWidth()) {
        camera.fovMode = Camera.FOVMODE_HORIZONTAL_FIXED;
      } else {
        camera.fovMode = Camera.FOVMODE_VERTICAL_FIXED;
      }
})

On the plus side if the playground is the same then perhaps the community can provide a better answer with a specific question using a playground as starting point?

Okay I’m posting my css solution here, since I brought it up anyway, perhaps someone else will come across it. The width and height for the canvas is set at 100% {{width: 100% height:100%}}, which is fine, that is left as is. The main container div, however, should be {{width: 100vw; height: 100vh}} which signifies 100% viewport width/height. That makes it occupy the whole window properly, and centers as the window adjusts. The engine resize listener makes what is rendered scale instead of scrunching up as the width/height changes.

Hashtag the modern web! I was being serious about the playground, I used it for years, and learned an incredible amount. Anything I can duplicate there, I will. Thanks for the help!

@3dwebgs What is your viewport meta tag in your page, if you have one?

Examples could be:

<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

It says

resetting the css to vh and vw did work tho.