'use client' import React, { useEffect, useRef, useState, useCallback } from 'react' import { Engine, Scene, Vector3, HemisphericLight, ArcRotateCamera, MeshBuilder, StandardMaterial, Color3, Color4, AbstractMesh, Mesh, Nullable, SceneLoader } from '@babylonjs/core' import '@babylonjs/loaders' import { getCacheFileName, loadCachedData, parseAndCacheScene, ParsedMeshData } from '../utils/meshCache' interface ModelViewerProps { modelPath: string onModelLoaded?: (modelData: { meshes: AbstractMesh[] boundingBox: { min: { x: number; y: number; z: number } max: { x: number; y: number; z: number } } }) => void onError?: (error: string) => void } const ModelViewer: React.FC = ({ modelPath, onModelLoaded, onError }) => { const canvasRef = useRef(null) const engineRef = useRef>(null) const sceneRef = useRef>(null) const [isLoading, setIsLoading] = useState(false) const isInitializedRef = useRef(false) const isDisposedRef = useRef(false) useEffect(() => { isDisposedRef.current = false isInitializedRef.current = false return () => { isDisposedRef.current = true } }, []) useEffect(() => { if (!canvasRef.current || isInitializedRef.current) return const canvas = canvasRef.current const engine = new Engine(canvas, true) engineRef.current = engine const scene = new Scene(engine) sceneRef.current = scene scene.clearColor = new Color4(0.1, 0.1, 0.15, 1) const camera = new ArcRotateCamera('camera', 0, Math.PI / 3, 20, Vector3.Zero(), scene) camera.attachControl(canvas, true) camera.lowerRadiusLimit = 2 camera.upperRadiusLimit = 200 camera.wheelDeltaPercentage = 0.01 camera.panningSensibility = 50 camera.angularSensibilityX = 1000 camera.angularSensibilityY = 1000 const ambientLight = new HemisphericLight('ambientLight', new Vector3(0, 1, 0), scene) ambientLight.intensity = 0.4 ambientLight.diffuse = new Color3(0.7, 0.7, 0.8) ambientLight.specular = new Color3(0.2, 0.2, 0.3) ambientLight.groundColor = new Color3(0.3, 0.3, 0.4) const keyLight = new HemisphericLight('keyLight', new Vector3(1, 1, 0), scene) keyLight.intensity = 0.6 keyLight.diffuse = new Color3(1, 1, 0.9) keyLight.specular = new Color3(1, 1, 0.9) const fillLight = new HemisphericLight('fillLight', new Vector3(-1, 0.5, -1), scene) fillLight.intensity = 0.3 fillLight.diffuse = new Color3(0.8, 0.8, 1) engine.runRenderLoop(() => { if (!isDisposedRef.current) { scene.render() } }) const handleResize = () => { if (!isDisposedRef.current) { engine.resize() } } window.addEventListener('resize', handleResize) isInitializedRef.current = true return () => { isDisposedRef.current = true isInitializedRef.current = false window.removeEventListener('resize', handleResize) if (engineRef.current) { engineRef.current.dispose() engineRef.current = null } sceneRef.current = null } }, []) useEffect(() => { if (!isInitializedRef.current || !modelPath || isDisposedRef.current) { return } const loadModel = async () => { if (!sceneRef.current || isDisposedRef.current) { return } setIsLoading(true) console.log('🚀 Loading GLTF model:', modelPath) try { const cacheFileName = getCacheFileName(modelPath) const cachedData = await loadCachedData(cacheFileName) if (cachedData) { console.log('📦 Using cached mesh data for analysis') } else { console.log('🔄 No cached data found, parsing scene...') } const result = await SceneLoader.ImportMeshAsync( '', modelPath, '', sceneRef.current ) console.log('✅ GLTF Model loaded successfully!') console.log('📊 Loaded Meshes:', result.meshes.length) if (result.meshes.length > 0) { const boundingBox = result.meshes[0].getHierarchyBoundingVectors() const size = boundingBox.max.subtract(boundingBox.min) const maxDimension = Math.max(size.x, size.y, size.z) const camera = sceneRef.current!.activeCamera as ArcRotateCamera camera.radius = maxDimension * 2 camera.target = result.meshes[0].position const parsedData = await parseAndCacheScene(result.meshes, cacheFileName, modelPath) onModelLoaded?.({ meshes: result.meshes, boundingBox: { min: boundingBox.min, max: boundingBox.max } }) console.log('🎉 Model ready for viewing!') } else { console.warn('⚠️ No meshes found in model') onError?.('No geometry found in model') } } catch (error) { console.error('❌ Error loading GLTF model:', error) onError?.(`Failed to load model: ${error}`) } finally { if (!isDisposedRef.current) { setIsLoading(false) } } } loadModel() }, [modelPath]) return (
{isLoading && (
Loading Model...
)}
) } export default ModelViewer