import React, { useEffect, useRef } from 'react'; import * as THREE from 'three'; interface CyberCanvasProps { vmState: 'off' | 'booting' | 'running' | 'stopping' | 'suspended'; gpuLoad: number; } export const Cyber3DCanvas: React.FC = ({ vmState, gpuLoad }) => { const mountRef = useRef(null); useEffect(() => { const container = mountRef.current; if (!container) return; const width = container.clientWidth || 300; const height = container.clientHeight || 300; const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000); camera.position.set(0, 3, 6); camera.lookAt(0, 0, 0); const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setSize(width, height); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); container.appendChild(renderer.domElement); // Cyber grid const gridHelper = new THREE.GridHelper(10, 20, 0x00ffcc, 0x112233); gridHelper.position.y = -1; scene.add(gridHelper); // Cyber GPU Core Cube const geometry = new THREE.BoxGeometry(1.6, 1.6, 1.6); const wireframeGeo = new THREE.WireframeGeometry(geometry); // Custom material let activeColor = 0x00ffcc; // default cyan if (vmState === 'running') activeColor = 0x00ff66; // green else if (vmState === 'booting') activeColor = 0xffaa00; // amber else if (vmState === 'off') activeColor = 0x445566; // dim dark const lineMaterial = new THREE.LineBasicMaterial({ color: activeColor, linewidth: 2, }); const cubeWireframe = new THREE.LineSegments(wireframeGeo, lineMaterial); scene.add(cubeWireframe); // Inner glowing core const coreGeo = new THREE.IcosahedronGeometry(0.7, 2); const coreMat = new THREE.MeshBasicMaterial({ color: activeColor, wireframe: true, transparent: true, opacity: vmState === 'off' ? 0.2 : 0.8, }); const coreMesh = new THREE.Mesh(coreGeo, coreMat); scene.add(coreMesh); // Particle ring const particleCount = 60; const particlesGeo = new THREE.BufferGeometry(); const positions = new Float32Array(particleCount * 3); for (let i = 0; i < particleCount; i++) { const angle = (i / particleCount) * Math.PI * 2; const radius = 2.2; positions[i * 3] = Math.cos(angle) * radius; positions[i * 3 + 1] = (Math.random() - 0.5) * 0.4; positions[i * 3 + 2] = Math.sin(angle) * radius; } particlesGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3)); const particleMat = new THREE.PointsMaterial({ color: activeColor, size: 0.08, transparent: true, opacity: 0.8, }); const particleSystem = new THREE.Points(particlesGeo, particleMat); scene.add(particleSystem); let animationFrameId: number; let clock = new THREE.Clock(); const animate = () => { animationFrameId = requestAnimationFrame(animate); const delta = clock.getDelta(); const speed = vmState === 'running' ? 0.8 + (gpuLoad / 100) * 2 : 0.2; cubeWireframe.rotation.x += delta * speed * 0.5; cubeWireframe.rotation.y += delta * speed; coreMesh.rotation.y -= delta * speed * 0.8; particleSystem.rotation.y += delta * speed * 0.3; renderer.render(scene, camera); }; animate(); const handleResize = () => { if (!container) return; const w = container.clientWidth; const h = container.clientHeight; camera.aspect = w / h; camera.updateProjectionMatrix(); renderer.setSize(w, h); }; window.addEventListener('resize', handleResize); return () => { cancelAnimationFrame(animationFrameId); window.removeEventListener('resize', handleResize); if (container.contains(renderer.domElement)) { container.removeChild(renderer.domElement); } geometry.dispose(); wireframeGeo.dispose(); lineMaterial.dispose(); coreGeo.dispose(); coreMat.dispose(); particlesGeo.dispose(); particleMat.dispose(); renderer.dispose(); }; }, [vmState, gpuLoad]); return (
{vmState}
GPU LOAD: {gpuLoad}%
); };