| | |
| | 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; |
| |
|
| | this.init(); |
| | this.animate(); |
| | this.setupEventListeners(); |
| | this.setupFPSCounter(); |
| | } |
| |
|
| | init() { |
| | |
| | this.scene = new THREE.Scene(); |
| | this.scene.background = new THREE.Color(0x111827); |
| |
|
| | |
| | this.camera = new THREE.PerspectiveCamera(75, this.getAspectRatio(), 0.1, 1000); |
| | this.camera.position.z = 5; |
| |
|
| | |
| | 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); |
| |
|
| | |
| | this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement); |
| | this.controls.enableDamping = true; |
| | this.controls.dampingFactor = 0.05; |
| |
|
| | |
| | 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); |
| |
|
| | |
| | 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); |
| |
|
| | |
| | const gridHelper = new THREE.GridHelper(10, 10, 0x444444, 0x222222); |
| | this.scene.add(gridHelper); |
| |
|
| | |
| | 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()); |
| |
|
| | |
| | if (this.autoRotate) { |
| | this.cube.rotation.x += this.rotationSpeed; |
| | this.cube.rotation.y += this.rotationSpeed * 1.2; |
| | } |
| |
|
| | |
| | this.controls.update(); |
| |
|
| | |
| | this.updateRotationDisplay(); |
| |
|
| | |
| | this.calculateFPS(); |
| |
|
| | |
| | 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() { |
| | |
| | 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; |
| | }); |
| |
|
| | |
| | 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); |
| | }); |
| |
|
| | |
| | document.querySelectorAll('.color-preset').forEach(button => { |
| | button.addEventListener('click', (e) => { |
| | const color = e.currentTarget.dataset.color; |
| | this.changeCubeColor(color); |
| | |
| | |
| | document.querySelectorAll('.color-preset').forEach(btn => { |
| | btn.classList.remove('active'); |
| | }); |
| | e.currentTarget.classList.add('active'); |
| | }); |
| | }); |
| |
|
| | |
| | document.getElementById('reset-btn').addEventListener('click', () => { |
| | this.resetCube(); |
| | }); |
| |
|
| | |
| | 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 = '<i data-feather="pause" class="w-5 h-5 mr-2"></i>Pause Rotation'; |
| | } else { |
| | autoRotateBtn.classList.remove('pulse'); |
| | icon.setAttribute('data-feather', 'play'); |
| | autoRotateBtn.innerHTML = '<i data-feather="play" class="w-5 h-5 mr-2"></i>Auto Rotate'; |
| | } |
| | feather.replace(); |
| | }); |
| | } |
| |
|
| | setupFPSCounter() { |
| | |
| | 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() { |
| | |
| | this.cube.rotation.set(0, 0, 0); |
| | |
| | |
| | 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'; |
| | |
| | |
| | document.getElementById('x-rotation').value = 0; |
| | document.getElementById('y-rotation').value = 0; |
| | document.getElementById('z-rotation').value = 0; |
| | |
| | |
| | this.changeCubeColor('red'); |
| | document.querySelectorAll('.color-preset').forEach(btn => { |
| | btn.classList.remove('active'); |
| | }); |
| | document.querySelector('.color-preset[data-color="red"]').classList.add('active'); |
| | |
| | |
| | this.camera.position.set(0, 0, 5); |
| | this.controls.update(); |
| | |
| | |
| | if (this.autoRotate) { |
| | this.toggleAutoRotate(); |
| | const autoRotateBtn = document.getElementById('auto-rotate-btn'); |
| | autoRotateBtn.classList.remove('pulse'); |
| | autoRotateBtn.innerHTML = '<i data-feather="play" class="w-5 h-5 mr-2"></i>Auto Rotate'; |
| | feather.replace(); |
| | } |
| | } |
| |
|
| | toggleAutoRotate() { |
| | this.autoRotate = !this.autoRotate; |
| | } |
| | } |
| |
|
| | |
| | document.addEventListener('DOMContentLoaded', () => { |
| | new CubeScene(); |
| | |
| | |
| | feather.replace(); |
| | |
| | |
| | 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)'; |
| | } |
| | }); |
| | }); |
| | |
| | |
| | document.querySelector('.color-preset[data-color="red"]').classList.add('active'); |
| | }); |
| |
|
| | |
| | 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; |
| | } |
| | }); |