import { useEffect, useRef, useState } from 'react'; import { Play, Pause, RotateCcw, Maximize2, Grid3x3, Eye, EyeOff, Activity } from 'lucide-react'; import '../styles/Viewport.css'; interface ViewportProps { locale?: string; } export function Viewport({ locale = 'en' }: ViewportProps) { const canvasRef = useRef(null); const containerRef = useRef(null); const [isPlaying, setIsPlaying] = useState(false); const [showGrid, setShowGrid] = useState(true); const [showGizmos, setShowGizmos] = useState(true); const [showStats, setShowStats] = useState(false); const animationFrameRef = useRef(); const glRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const gl = canvas.getContext('webgl2') || canvas.getContext('webgl'); if (!gl) { console.error('WebGL not supported'); return; } glRef.current = gl; const resizeCanvas = () => { if (!canvas || !containerRef.current) return; const container = containerRef.current; const rect = container.getBoundingClientRect(); const dpr = window.devicePixelRatio || 1; canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; canvas.style.width = `${rect.width}px`; canvas.style.height = `${rect.height}px`; gl.viewport(0, 0, canvas.width, canvas.height); }; resizeCanvas(); window.addEventListener('resize', resizeCanvas); initWebGL(gl); return () => { window.removeEventListener('resize', resizeCanvas); if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); } }; }, []); useEffect(() => { if (isPlaying) { startRenderLoop(); } else { if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); } } }, [isPlaying]); const initWebGL = (gl: WebGLRenderingContext) => { gl.clearColor(0.1, 0.1, 0.12, 1.0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.enable(gl.DEPTH_TEST); renderFrame(gl, 0); }; const startRenderLoop = () => { let startTime = performance.now(); const render = (currentTime: number) => { const elapsed = (currentTime - startTime) / 1000; if (glRef.current) { renderFrame(glRef.current, elapsed); } animationFrameRef.current = requestAnimationFrame(render); }; animationFrameRef.current = requestAnimationFrame(render); }; const renderFrame = (gl: WebGLRenderingContext, time: number) => { gl.clearColor(0.1, 0.1, 0.12, 1.0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); if (showGrid) { drawGrid(gl); } }; const drawGrid = (gl: WebGLRenderingContext) => { const vertexShaderSource = ` attribute vec3 position; uniform mat4 projection; uniform mat4 view; void main() { gl_Position = projection * view * vec4(position, 1.0); } `; const fragmentShaderSource = ` precision mediump float; uniform vec4 color; void main() { gl_FragColor = color; } `; const vertexShader = gl.createShader(gl.VERTEX_SHADER)!; gl.shaderSource(vertexShader, vertexShaderSource); gl.compileShader(vertexShader); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)!; gl.shaderSource(fragmentShader, fragmentShaderSource); gl.compileShader(fragmentShader); const program = gl.createProgram()!; gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program); const gridSize = 10; const gridStep = 1; const vertices: number[] = []; for (let i = -gridSize; i <= gridSize; i += gridStep) { vertices.push(i, 0, -gridSize, i, 0, gridSize); vertices.push(-gridSize, 0, i, gridSize, 0, i); } const buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); const positionLocation = gl.getAttribLocation(program, 'position'); gl.enableVertexAttribArray(positionLocation); gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0); const colorLocation = gl.getUniformLocation(program, 'color'); gl.uniform4f(colorLocation, 0.3, 0.3, 0.35, 1.0); gl.drawArrays(gl.LINES, 0, vertices.length / 3); }; const handlePlayPause = () => { setIsPlaying(!isPlaying); }; const handleReset = () => { setIsPlaying(false); if (glRef.current) { renderFrame(glRef.current, 0); } }; const handleFullscreen = () => { if (containerRef.current) { if (document.fullscreenElement) { document.exitFullscreen(); } else { containerRef.current.requestFullscreen(); } } }; return (
{showStats && (
FPS: 60
Draw Calls: 0
)}
); }