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;