Hi All,
Brand new to Babylon.js and I feel like I’m missing something obvious here. I’m exploring a proof of concept for a 3D editor focused on physics and robotics and I’ve seen lots of great examples out there of traditional UI’s (assuming react) and the Babylon canvas. @dested has done some great work on QuickGa.me: Web Based 3D Multiplayer Game Engine and Hosting Platform!
When starting to build a React UI and using it to update the state of a mesh, I have no trouble accomplishing this with pure javascript and a state variable outside of my component but when using useState I get a constant re-render every time the state changes. On a certain level, this makes sense, but I’ve seen so many other examples doing this that I’m curious if I’m missing something.
In my examples below, I have a simple slider that is mapping from 0 to 360 and adjusting the rotation of the playground box. With useState my entire Babylon canvas refreshes each time I move the slider. I’ve come across this post How to change mesh properties with react state hook? which creates the same issue.
Any advice on best practices?
Vanilla JS State Handling: Working
import { useEffect, useRef, useState } from "react";
import { ArcRotateCamera, Vector3, Color3, MeshBuilder, HemisphericLight} from "@babylonjs/core";
import { GridMaterial } from "@babylonjs/materials"
import SceneComponent from './SceneComponent';
import SliderComponent from "./SliderComponent";
let box;
let boxRotation = 0;
const BasicPlaygroundComponent = (props) => {
// Will execute once when scene is ready
const onSceneReady = (scene) => {
// This creates and positions an arc rotate camera
var camera = new ArcRotateCamera("camera1", -Math.PI / 2, Math.PI / 2.5, 75, new Vector3(0, 0, 0));
// Initialize canvase
const canvas = scene.getEngine().getRenderingCanvas();
// Attaches camera 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: 10 }, scene);
// Move the box upward 1/2 its height
box.position.y = 10;
// Our built-in 'ground' shape.
var ground = MeshBuilder.CreateGround("ground", { width: 150, height: 150 }, scene);
};
//Will run on every frame render. We are spinning the box on y-axis.
const onRender = (scene) => {
box.rotation.y = boxRotation;
};
const updateBoxRotation = (val) => {
boxRotation = (val/100)*Math.PI*2;
}
return(
<div>
<SceneComponent antialias onSceneReady={onSceneReady} onRender={onRender} id='viewport'></SceneComponent>
<SliderComponent toUpdate={updateBoxRotation}></SliderComponent>
</div>
)
}
export default BasicPlaygroundComponent;
useState State Handling: Re-Renders canvas on each change
import { useEffect, useRef, useState } from "react";
import { ArcRotateCamera, Vector3, Color3, MeshBuilder, HemisphericLight} from "@babylonjs/core";
import { GridMaterial } from "@babylonjs/materials"
import SceneComponent from './SceneComponent';
import SliderComponent from "./SliderComponent";
let box;
const BasicPlaygroundComponentUseState = (props) => {
const sceneRef = useRef(null);
const [boxRotation, setBoxRotation] = useState(0)
// Will execute once when scene is ready
const onSceneReady = (scene) => {
sceneRef.current = scene;
// This creates and positions an arc rotate camera
var camera = new ArcRotateCamera("camera1", -Math.PI / 2, Math.PI / 2.5, 75, new Vector3(0, 0, 0));
// Initialize canvase
const canvas = scene.getEngine().getRenderingCanvas();
// Attaches camera 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: 10 }, scene);
// Move the box upward 1/2 its height
box.position.y = 10;
// Our built-in 'ground' shape.
var ground = MeshBuilder.CreateGround("ground", { width: 150, height: 150 }, scene);
};
//Will run on every frame render. We are spinning the box on y-axis.
const onRender = (scene) => {
//box.rotation.y = boxRotation;
};
useEffect( () => {
const scene = sceneRef.current;
if(scene){
box.rotation.y = boxRotation;
}
}, [boxRotation]);
return(
<div>
<SceneComponent antialias onSceneReady={onSceneReady} onRender={onRender} id='viewport'></SceneComponent>
<SliderComponent toUpdate={setBoxRotation}></SliderComponent>
</div>
)
}
export default BasicPlaygroundComponentUseState;