Many Scenes with a single engine

I had a 3D store created with react and babylon. I can see that a separate engine is generated for each individual product card. Doesn’t this put a strain on the system? What would be an optimal way to handle multiple scenes?


import React, { useEffect, useRef } from 'react';
import * as BABYLON from '@babylonjs/core';
import { useToast } from '@/components/ui/use-toast';

interface ProductViewerProps {
  modelSrc?: string;
  height?: string;
  width?: string;
  autoRotate?: boolean;
}

const ProductViewer: React.FC<ProductViewerProps> = ({ 
  modelSrc = '/models/default.glb', 
  height = '400px', 
  width = '100%',
  autoRotate = true
}) => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const engineRef = useRef<BABYLON.Engine | null>(null);
  const sceneRef = useRef<BABYLON.Scene | null>(null);
  const { toast } = useToast();

  useEffect(() => {
    if (!canvasRef.current) return;

    // Initialize Babylon.js
    const canvas = canvasRef.current;
    const engine = new BABYLON.Engine(canvas, true);
    engineRef.current = engine;
    
    // Create scene
    const scene = new BABYLON.Scene(engine);
    sceneRef.current = scene;
    
    // Set scene background color
    scene.clearColor = new BABYLON.Color4(0, 0, 0, 0);

    // Add camera
    const camera = new BABYLON.ArcRotateCamera(
      'camera',
      -Math.PI / 2,
      Math.PI / 2.5,
      5,
      BABYLON.Vector3.Zero(),
      scene
    );
    camera.attachControl(canvas, true);
    camera.lowerRadiusLimit = 2;
    camera.upperRadiusLimit = 10;
    
    // Add lights
    const light1 = new BABYLON.HemisphericLight('light1', new BABYLON.Vector3(0, 1, 0), scene);
    light1.intensity = 0.7;
    
    const light2 = new BABYLON.DirectionalLight('light2', new BABYLON.Vector3(0, -1, 1), scene);
    light2.position = new BABYLON.Vector3(0, 5, -5);
    light2.intensity = 0.5;

    // For demonstration, create a simple shape since we don't have actual models available
    // In a real application, you would load models with BABYLON.SceneLoader
    const sphere = BABYLON.MeshBuilder.CreateSphere('sphere', { diameter: 2 }, scene);
    const material = new BABYLON.StandardMaterial('material', scene);
    material.diffuseColor = new BABYLON.Color3(0.55, 0.35, 0.84);
    material.specularColor = new BABYLON.Color3(0.2, 0.2, 0.2);
    material.emissiveColor = new BABYLON.Color3(0.1, 0.1, 0.1);
    sphere.material = material;

    // Auto-rotation
    if (autoRotate) {
      scene.registerBeforeRender(() => {
        sphere.rotation.y += 0.005;
      });
    }

    // Resize engine when window is resized
    const handleResize = () => {
      if (engineRef.current) {
        engineRef.current.resize();
      }
    };
    window.addEventListener('resize', handleResize);

    // Start render loop
    engine.runRenderLoop(() => {
      if (sceneRef.current) {
        sceneRef.current.render();
      }
    });

    // In a real application, you would load models like this:
    /*
    BABYLON.SceneLoader.ImportMesh(
      "",
      "",
      modelSrc,
      scene,
      (meshes) => {
        // Success callback
        const root = meshes[0];
        root.scaling = new BABYLON.Vector3(1, 1, 1);
        
        // Auto-rotation setup
        if (autoRotate) {
          scene.registerBeforeRender(() => {
            root.rotation.y += 0.005;
          });
        }
        
        // Calculate bounding info to center and position the model properly
        const boundingInfo = calculateBoundingInfo(meshes);
        root.position = new BABYLON.Vector3(0, -boundingInfo.min.y, 0);
        
        // Set camera position based on model size
        camera.radius = boundingInfo.diagonalLength * 2;
      },
      null,
      (scene, message) => {
        // Error callback
        console.error("Error loading model:", message);
        toast({
          title: "Error loading 3D model",
          description: "Could not load the 3D model. Please try again later.",
          variant: "destructive"
        });
      }
    );
    
    function calculateBoundingInfo(meshes) {
      let min = new BABYLON.Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
      let max = new BABYLON.Vector3(Number.MIN_VALUE, Number.MIN_VALUE, Number.MIN_VALUE);
      
      for (const mesh of meshes) {
        if (mesh.getBoundingInfo) {
          const meshBoundingInfo = mesh.getBoundingInfo();
          const meshMin = meshBoundingInfo.minimum;
          const meshMax = meshBoundingInfo.maximum;
          
          min = BABYLON.Vector3.Minimize(min, meshMin);
          max = BABYLON.Vector3.Maximize(max, meshMax);
        }
      }
      
      const diagonalVector = max.subtract(min);
      const diagonalLength = diagonalVector.length();
      
      return {
        min: min,
        max: max,
        diagonalLength: diagonalLength
      };
    }
    */

    return () => {
      window.removeEventListener('resize', handleResize);
      
      // Clean up Babylon.js resources
      if (sceneRef.current) {
        sceneRef.current.dispose();
        sceneRef.current = null;
      }
      
      if (engineRef.current) {
        engineRef.current.dispose();
        engineRef.current = null;
      }
    };
  }, [modelSrc, autoRotate, toast]);

  return (
    <div style={{ width, height, position: 'relative' }}>
      <canvas 
        ref={canvasRef} 
        style={{ width: '100%', height: '100%', outline: 'none', touchAction: 'none' }}
      />
    </div>
  );
};

export default ProductViewer;

An engine is a rendering context. So you need an engine per canvas. You can’t switch contexts (easily) with a single engine.

So while there is some redundancy, if you render to multiple canvases (and not a few scenes on a single canvas), you will need multiple engines.

EDIT - if you can reuse canvases and you just switch objects/elements/scenes, reuse the engine and save the construction costs. This is a wonderful optimization

Canvas swap is a brilliant idea to reduce the cpu/gpu load to a minimum. For many visible objects I will replace the 3D preview with a thumbnail until it is clicked.

Thank you! :slight_smile:

We also support multiple views from a single engine : Babylon.js docs

1 Like

There are 4 independent canvases on this page all rendered using the same scene but with different cameras.

Babylon.js - Multi views demo

This means that all products should be placed far apart in a scene and I need one screen and one camera per product?

That would work too! :slight_smile:

You would also have more display options and could display several products in one canvas.

how about you just draw 1 offscreen multi-view canvas,

and all the other canvases just use the html5 canvas 2d api to draw parts of that master canvas.

@Heaust-ops can you provide me with a small example, please?

1 Like

here : )

top half and bottom half are 2 different canvases displaying 2 different scenes

on 2nd thought, multiple views is a way better option lol

1 Like

Thank you @Heaust-ops it is always better to have multiple options to solve a problem. :slight_smile:

1 Like