// 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;
}
});