// Main 3D Scene Setup class CubeScene { constructor() { this.scene = null; this.camera = null; this.renderer = null; this.cube = null; this.controls = null; this.autoRotate = false; this.rotationSpeed = 0.01; this.frameCount = 0; this.fps = 60; this.lastTimestamp = performance.now(); this.currentColor = 0xef4444; // Red-500 hex this.init(); this.animate(); this.setupEventListeners(); this.setupFPSCounter(); } init() { // Create scene this.scene = new THREE.Scene(); this.scene.background = new THREE.Color(0x111827); // gray-900 // Create camera this.camera = new THREE.PerspectiveCamera(75, this.getAspectRatio(), 0.1, 1000); this.camera.position.z = 5; // Create renderer const canvas = document.getElementById('3d-canvas'); this.renderer = new THREE.WebGLRenderer({ canvas, antialias: true }); this.renderer.setSize(canvas.clientWidth, canvas.clientHeight); this.renderer.setPixelRatio(window.devicePixelRatio); // Add orbit controls this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement); this.controls.enableDamping = true; this.controls.dampingFactor = 0.05; // Create cube const geometry = new THREE.BoxGeometry(2, 2, 2); const material = new THREE.MeshPhongMaterial({ color: this.currentColor, shininess: 100, specular: 0x222222 }); this.cube = new THREE.Mesh(geometry, material); this.scene.add(this.cube); // Add lights const ambientLight = new THREE.AmbientLight(0x404040, 0.5); this.scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(5, 5, 5); this.scene.add(directionalLight); const redLight = new THREE.PointLight(0xef4444, 0.5, 10); redLight.position.set(-3, -3, 3); this.scene.add(redLight); // Add grid helper const gridHelper = new THREE.GridHelper(10, 10, 0x444444, 0x222222); this.scene.add(gridHelper); // Handle window resize window.addEventListener('resize', () => this.onWindowResize()); } getAspectRatio() { const container = document.getElementById('canvas-container'); return container.clientWidth / container.clientHeight; } onWindowResize() { const container = document.getElementById('canvas-container'); this.camera.aspect = container.clientWidth / container.clientHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(container.clientWidth, container.clientHeight); } animate() { requestAnimationFrame(() => this.animate()); // Auto rotation if (this.autoRotate) { this.cube.rotation.x += this.rotationSpeed; this.cube.rotation.y += this.rotationSpeed * 1.2; } // Update controls this.controls.update(); // Update rotation display this.updateRotationDisplay(); // Calculate FPS this.calculateFPS(); // Render scene this.renderer.render(this.scene, this.camera); } updateRotationDisplay() { const x = ((this.cube.rotation.x * 180) / Math.PI).toFixed(1); const y = ((this.cube.rotation.y * 180) / Math.PI).toFixed(1); const z = ((this.cube.rotation.z * 180) / Math.PI).toFixed(1); document.getElementById('rotation-values').textContent = `${x}°, ${y}°, ${z}°`; document.getElementById('x-rotation-value').textContent = `${x}°`; document.getElementById('y-rotation-value').textContent = `${y}°`; document.getElementById('z-rotation-value').textContent = `${z}°`; } calculateFPS() { this.frameCount++; const now = performance.now(); const elapsed = now - this.lastTimestamp; if (elapsed >= 1000) { this.fps = Math.round((this.frameCount * 1000) / elapsed); document.getElementById('fps-counter').textContent = this.fps; this.frameCount = 0; this.lastTimestamp = now; } } setupEventListeners() { // Rotation sliders document.getElementById('x-rotation').addEventListener('input', (e) => { this.cube.rotation.x = (e.target.value * Math.PI) / 180; }); document.getElementById('y-rotation').addEventListener('input', (e) => { this.cube.rotation.y = (e.target.value * Math.PI) / 180; }); document.getElementById('z-rotation').addEventListener('input', (e) => { this.cube.rotation.z = (e.target.value * Math.PI) / 180; }); // Scale slider document.getElementById('scale-control').addEventListener('input', (e) => { const scale = parseFloat(e.target.value); this.cube.scale.set(scale, scale, scale); document.getElementById('scale-value').textContent = `${scale.toFixed(1)}x`; document.getElementById('scale-display').textContent = scale.toFixed(1); }); // Color presets document.querySelectorAll('.color-preset').forEach(button => { button.addEventListener('click', (e) => { const color = e.currentTarget.dataset.color; this.changeCubeColor(color); // Update active state document.querySelectorAll('.color-preset').forEach(btn => { btn.classList.remove('active'); }); e.currentTarget.classList.add('active'); }); }); // Reset button document.getElementById('reset-btn').addEventListener('click', () => { this.resetCube(); }); // Auto rotate button const autoRotateBtn = document.getElementById('auto-rotate-btn'); autoRotateBtn.addEventListener('click', () => { this.toggleAutoRotate(); const icon = autoRotateBtn.querySelector('i'); if (this.autoRotate) { autoRotateBtn.classList.add('pulse'); icon.setAttribute('data-feather', 'pause'); autoRotateBtn.innerHTML = 'Pause Rotation'; } else { autoRotateBtn.classList.remove('pulse'); icon.setAttribute('data-feather', 'play'); autoRotateBtn.innerHTML = 'Auto Rotate'; } feather.replace(); }); } setupFPSCounter() { // Update FPS every second setInterval(() => { document.getElementById('fps-counter').textContent = this.fps; }, 1000); } changeCubeColor(colorName) { const colorMap = { red: 0xef4444, blue: 0x3b82f6, green: 0x10b981, purple: 0x8b5cf6, yellow: 0xf59e0b, pink: 0xec4899 }; this.currentColor = colorMap[colorName] || 0xef4444; this.cube.material.color.setHex(this.currentColor); } resetCube() { // Reset rotation this.cube.rotation.set(0, 0, 0); // Reset scale this.cube.scale.set(1, 1, 1); document.getElementById('scale-control').value = 1; document.getElementById('scale-value').textContent = '1.0x'; document.getElementById('scale-display').textContent = '1.0'; // Reset sliders document.getElementById('x-rotation').value = 0; document.getElementById('y-rotation').value = 0; document.getElementById('z-rotation').value = 0; // Reset color to red this.changeCubeColor('red'); document.querySelectorAll('.color-preset').forEach(btn => { btn.classList.remove('active'); }); document.querySelector('.color-preset[data-color="red"]').classList.add('active'); // Reset camera position this.camera.position.set(0, 0, 5); this.controls.update(); // Stop auto rotation if (this.autoRotate) { this.toggleAutoRotate(); const autoRotateBtn = document.getElementById('auto-rotate-btn'); autoRotateBtn.classList.remove('pulse'); autoRotateBtn.innerHTML = 'Auto Rotate'; feather.replace(); } } toggleAutoRotate() { this.autoRotate = !this.autoRotate; } } // Initialize the scene when DOM is loaded document.addEventListener('DOMContentLoaded', () => { new CubeScene(); // Initialize feather icons feather.replace(); // Add hover effect to color presets document.querySelectorAll('.color-preset').forEach(button => { button.addEventListener('mouseenter', () => { button.style.transform = 'translateY(-2px)'; }); button.addEventListener('mouseleave', () => { if (!button.classList.contains('active')) { button.style.transform = 'translateY(0)'; } }); }); // Set red as active color preset document.querySelector('.color-preset[data-color="red"]').classList.add('active'); }); // Add keyboard controls document.addEventListener('keydown', (e) => { const scene = window.cubeScene; if (!scene) return; switch(e.key.toLowerCase()) { case 'r': scene.resetCube(); break; case ' ': scene.toggleAutoRotate(); break; case 'arrowup': scene.cube.scale.multiplyScalar(1.1); break; case 'arrowdown': scene.cube.scale.multiplyScalar(0.9); break; } });