import './style.css' import * as THREE from 'three'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js'; import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; // Loading Manager class SceneLoader { constructor() { this.loadingScreen = document.getElementById('loading-screen'); this.loadingText = document.getElementById('loading-text'); this.loadingProgressBar = document.getElementById('loading-progress-bar'); this.loadingPercentage = document.getElementById('loading-percentage'); this.modelsToLoad = [ { file: 'bold.glb', type: 'bold' }, { file: 'innovation.glb', type: 'innovation' }, { file: 'agility.glb', type: 'agility' }, { file: 'storytelling.glb', type: 'storytelling' } ]; this.loadedModels = {}; this.loadedCount = 0; this.totalModels = this.modelsToLoad.length; } setLoadingMessage(message) { this.loadingText.textContent = message; } updateProgress(progress) { const percentage = Math.round(progress * 100); this.loadingProgressBar.style.width = `${percentage}%`; this.loadingPercentage.textContent = `${percentage}%`; } hideLoadingScreen() { this.loadingScreen.classList.add('hidden'); setTimeout(() => { this.loadingScreen.style.display = 'none'; }, 800); } async loadAllModels() { return new Promise((resolve) => { const loader = new GLTFLoader(); const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath('node_modules/three/examples/jsm/libs/draco/'); loader.setDRACOLoader(dracoLoader); this.modelsToLoad.forEach((modelInfo, index) => { this.setLoadingMessage(`Loading experience...`); loader.load(`/${modelInfo.file}`, (gltf) => { this.loadedModels[modelInfo.type] = { scene: gltf.scene, animations: gltf.animations, gltf: gltf }; this.loadedCount++; const progress = this.loadedCount / this.totalModels; this.updateProgress(progress); if (this.loadedCount === this.totalModels) { this.setLoadingMessage('Initializing Experience...'); setTimeout(() => { this.hideLoadingScreen(); resolve(this.loadedModels); }, 500); } }, (progress) => { const fileProgress = progress.loaded / progress.total; const totalProgress = (this.loadedCount + fileProgress) / this.totalModels; this.updateProgress(totalProgress); }, (error) => { console.error(`Error loading ${modelInfo.file}:`, error); } ); }); }); } } // Initialize loader const sceneLoader = new SceneLoader(); sceneLoader.setLoadingMessage('Preparing Your Experience...'); // Scene setup const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.setFocalLength(50); const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); // Transition state management let currentScene = 0; // 0: bold, 1: innovation, 2: agility, 3: storytelling let isTransitioning = false; const fadeSpeed = 1; // Easily adjustable fade speed const transitionDuration = 1; // Easily adjustable transition duration (seconds) let scrollDownCount = 0; let scrollUpCount = 0; const scrollThreshold = 10; // Changed to 10 as requested let transitionStartTime = 0; let transitionDirection = 1; // 1 for forward, -1 for backward // Camera-relative transition vectors let transitionUpVector = new THREE.Vector3(); let transitionDownVector = new THREE.Vector3(); const transitionDistance = 50; // Increased distance for more dramatic transitions // Scene objects let currentModel = null; let nextModel = null; let mixer = null; let nextMixer = null; let autoRotationAngle = 0; // Store preloaded models let preloadedModels = {}; // Bold scene roughness animation state let boldRoughnessAnimation = { isActive: false, startTime: 0, delayDuration: 1.0, // 1 second delay transitionDuration: 1.0, // 1 second transition startRoughness: 0.5, endRoughness: 0.05, materials: [] // Store references to bold materials }; // Innovation glass animation state let innovationGlassAnimation = { isActive: false, startTime: 0, transitionDuration: 0.2, startIor: 1.0, endIor: 2.0, startThickness: 1.0, endThickness: 2.0, materials: [] // Store references to innovation glass materials }; // Renderer setup const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(0x000000); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.2; renderer.outputColorSpace = THREE.SRGBColorSpace; renderer.physicallyCorrectLights = true; document.body.appendChild(renderer.domElement); // Post-processing: Bloom const composer = new EffectComposer(renderer); const renderPass = new RenderPass(scene, camera); composer.addPass(renderPass); const bloomPass = new UnrealBloomPass( new THREE.Vector2(window.innerWidth, window.innerHeight), 1.0, // strength 0.45, // radius 0.85 // threshold ); composer.addPass(bloomPass); // Video texture for emissive "screen"-like effect on orange material const video = document.createElement('video'); video.src = '/shader-flash.webm'; video.muted = true; video.loop = true; video.playsInline = true; video.autoplay = true; video.preload = 'auto'; const videoTexture = new THREE.VideoTexture(video); videoTexture.colorSpace = THREE.SRGBColorSpace; videoTexture.generateMipmaps = false; videoTexture.minFilter = THREE.LinearFilter; videoTexture.magFilter = THREE.LinearFilter; // Ensure autoplay starts (muted autoplay is commonly allowed) video.play().catch(() => { }); // Local procedural environment for better PBR response (no network) const pmrem = new THREE.PMREMGenerator(renderer); const roomEnv = new RoomEnvironment(); scene.environment = pmrem.fromScene(roomEnv).texture; pmrem.dispose(); roomEnv.dispose(); scene.environment = null; // This will make the renderer's clear color visible again // Consistent Lighting Setup const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); const hemiLight = new THREE.HemisphereLight(0xffffff, 0x666666, 1.5); hemiLight.position.set(0, 20, 0); scene.add(hemiLight); const fillLight = new THREE.DirectionalLight(0xffffff, 1.2); fillLight.position.set(-12, 6, -8); scene.add(fillLight); const topLight = new THREE.DirectionalLight(0xffffff, 1.5); topLight.position.set(5, 15, 5); scene.add(topLight); const bottomLight = new THREE.DirectionalLight(0xffffff, 0.8); bottomLight.position.set(-3, -8, 3); scene.add(bottomLight); const leftLight = new THREE.DirectionalLight(0xffffff, 1.0); leftLight.position.set(-12, 2, 5); scene.add(leftLight); const rightLight = new THREE.DirectionalLight(0xffffff, 1.0); rightLight.position.set(12, 2, -5); scene.add(rightLight); const frontLight = new THREE.DirectionalLight(0xffffff, 0.8); frontLight.position.set(8, 4, 12); scene.add(frontLight); const backLight = new THREE.DirectionalLight(0xffffff, 0.8); backLight.position.set(-8, 4, -12); scene.add(backLight); const cameraLight = new THREE.PointLight(0xffffff, 0.8, 0, 2); camera.add(cameraLight); scene.add(camera); // Controls with zoom disabled and camera constraints const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.25; controls.enableZoom = false; // Disable zoom // Add camera constraints to prevent extreme angles controls.maxPolarAngle = Math.PI * 0.8; // Prevent looking too far up controls.minPolarAngle = Math.PI * 0.2; // Prevent looking too far down console.log('Orbit controls initialized with camera constraints'); // Material definitions // Bold glass material (starts rough, will transition to clear) const boldGlassMaterial = new THREE.MeshPhysicalMaterial({ color: 0xffffff, metalness: 0.2, roughness: 0.5, // Start with rough glass transmission: 1, ior: 2, thickness: 2, clearcoat: 1.0, clearcoatRoughness: 0.1, attenuationColor: new THREE.Color(0xffffff), attenuationDistance: 0.8, envMapIntensity: 0, specularIntensity: 1.0, specularColor: new THREE.Color(0xffffff), transparent: true, depthWrite: false, alphaTest: 0 }); // Orange wireframe material for bold Cubewire mesh const boldWireframeMaterial = new THREE.MeshStandardMaterial({ color: 0xff8600, metalness: 0.05, roughness: 0.5 }); // Clear thick glass for innovation (starts with animated values) const innovationGlassMaterial = new THREE.MeshPhysicalMaterial({ color: 0xffffff, metalness: 0.2, roughness: 0.05, transmission: 1, ior: 1.0, // Will animate from 1 to 2 thickness: 1.0, // Will animate from 1 to 2 clearcoat: 1.0, clearcoatRoughness: 0.1, attenuationColor: new THREE.Color(0xffffff), attenuationDistance: 0.8, envMapIntensity: 0, specularIntensity: 1.0, specularColor: new THREE.Color(0x000000), transparent: true, depthWrite: false, alphaTest: 0 }); // Slightly frosted glass for agility and storytelling const frostedGlassMaterial = new THREE.MeshPhysicalMaterial({ color: 0xffffff, metalness: 0.0, roughness: 0.25, transmission: 1.0, ior: 1.5, thickness: 2.0, clearcoat: 0.75, clearcoatRoughness: 0.25, attenuationColor: new THREE.Color(0xffffff), attenuationDistance: 1.5, envMapIntensity: 1.25, specularIntensity: 1.0, specularColor: new THREE.Color(0xffffff), transparent: true, depthWrite: false, side: THREE.DoubleSide }); // Orange material with video shader for innovation const lightOrangeMaterial = new THREE.MeshStandardMaterial({ color: 0xff8600, metalness: 0.05, roughness: 0.4, envMapIntensity: 0, emissive: new THREE.Color(0xffad47), emissiveMap: videoTexture, emissiveIntensity: 2.25 }); // Calculate camera-relative transition vectors for diagonal movement function calculateTransitionVectors() { // Get camera's world direction const cameraDirection = new THREE.Vector3(); camera.getWorldDirection(cameraDirection); // Get world up vector const worldUp = new THREE.Vector3(0, 1, 0); // Calculate camera's left vector - BACK TO ORIGINAL (this gave correct left direction) const cameraLeft = new THREE.Vector3(); cameraLeft.crossVectors(worldUp, cameraDirection).normalize(); // Calculate camera's local up vector const cameraUp = new THREE.Vector3(); cameraUp.crossVectors(cameraLeft, cameraDirection).normalize(); // Blend camera up with world up - BUT NEGATE to flip up/down direction const blendedUp = new THREE.Vector3(); blendedUp.addVectors( cameraUp.clone().multiplyScalar(0.5), worldUp.clone().multiplyScalar(0.5) ).normalize().negate(); // ADD .negate() here to flip up to down // Create diagonal vector (up-left) const diagonalUpLeft = new THREE.Vector3(); diagonalUpLeft.addVectors( blendedUp.clone().multiplyScalar(0.5), cameraLeft.clone().multiplyScalar(0.5) ).normalize(); // Set transition vectors transitionUpVector = diagonalUpLeft.clone().multiplyScalar(transitionDistance); transitionDownVector = diagonalUpLeft.clone().multiplyScalar(-transitionDistance); console.log('Diagonal transition vectors calculated with distance:', transitionDistance); } // Apply materials based on model type function applyMaterials(model, modelType) { console.log(`=== Material Assignment Debug for ${modelType} ===`); let meshCount = 0; model.traverse((object) => { if (object.isMesh) { meshCount++; console.log(`Found mesh: "${object.name}"`); const previousMaterial = object.material; object.castShadow = true; object.receiveShadow = true; if (modelType === 'bold') { // Bold-specific material logic - apply bold glass material to Cube mesh if (modelType === 'bold') { // Bold-specific material logic if (object.name === 'Cube') { console.log(` → Applying bold glass material to "${object.name}"`); object.material = boldGlassMaterial.clone(); object.material.side = THREE.DoubleSide; object.material.depthWrite = false; object.renderOrder = 2; // Store material reference for roughness animation boldRoughnessAnimation.materials.push(object.material); } else if (object.name === 'Cubewire') { console.log(` → Applying wireframe material to "${object.name}"`); object.material = boldWireframeMaterial.clone(); object.renderOrder = 1; } else { console.log(` → Applying bold glass material (fallback) to "${object.name}"`); object.material = boldGlassMaterial.clone(); // Store material reference for roughness animation boldRoughnessAnimation.materials.push(object.material); } } } else if (modelType === 'innovation') { // Innovation-specific material logic const orangeMeshes = ['dblsc', 'ec', 'gemini', 'infinity', 'star', 'dpd']; const targetGlassNames = ['Cube.alt90.df']; const sanitize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, ''); const nameMatches = (name, targets) => { const clean = sanitize(name); return targets.some((t) => { const ct = sanitize(t); return clean === ct || clean.includes(ct) || ct.includes(clean); }); }; if (nameMatches(object.name, targetGlassNames)) { // Create outer glass shell with innovation-specific material object.material = innovationGlassMaterial.clone(); object.material.side = THREE.DoubleSide; object.material.depthWrite = false; object.renderOrder = 2; // Store material reference for animation innovationGlassAnimation.materials.push(object.material); // Create inner glass shell const innerShell = object.clone(); innerShell.material = innovationGlassMaterial.clone(); innerShell.material.side = THREE.DoubleSide; innerShell.material.depthWrite = false; innerShell.material.transmission = 0.8; innerShell.renderOrder = 1; innerShell.scale.multiplyScalar(0.95); // Store inner shell material reference for animation too innovationGlassAnimation.materials.push(innerShell.material); object.parent.add(innerShell); } else if (nameMatches(object.name, orangeMeshes)) { object.material = lightOrangeMaterial.clone(); object.renderOrder = 0; } } else { // Agility and Storytelling use frosted glass material for all meshes if (object.name.startsWith('base')) { console.log(` → Applying frosted glass material to "${object.name}"`); object.material = frostedGlassMaterial.clone(); } else { console.log(` → Applying frosted glass material (fallback) to "${object.name}"`); object.material = frostedGlassMaterial.clone(); } } object.material.needsUpdate = true; // Cleanup previous materials if (Array.isArray(previousMaterial)) { previousMaterial.forEach((mat) => mat && mat.dispose && mat.dispose()); } else if (previousMaterial && previousMaterial.dispose) { previousMaterial.dispose(); } } }); console.log(`Total meshes processed: ${meshCount}`); console.log(`=== End Material Assignment Debug for ${modelType} ===`); } // Center and frame model with camera function centerAndFrameModel(model, targetCamera = camera) { const box = new THREE.Box3().setFromObject(model); const center = box.getCenter(new THREE.Vector3()); model.position.sub(center); model.updateMatrixWorld(true); // Only set camera position if it's not already positioned (avoid reset during transitions) // Use fixed camera distance that's further away from the origin if (!isTransitioning) { const fixedCameraDistance = 50; // Fixed distance, much further than before // Calculate isometric-like position with 35-degree angles const angle = 35 * Math.PI / 180; // Convert 35 degrees to radians const cosAngle = Math.cos(angle); const x = fixedCameraDistance * cosAngle; const y = fixedCameraDistance * cosAngle; const z = fixedCameraDistance * cosAngle; targetCamera.position.set(x, y, z); controls.target.set(0, 0, 0); // Set distance limits to lock the camera at this distance controls.minDistance = fixedCameraDistance; controls.maxDistance = fixedCameraDistance; controls.update(); console.log(`Camera positioned at: x=${x}, y=${y}, z=${z}, distance=${fixedCameraDistance}`); } } // Setup animations based on model type function setupAnimations(model, gltf, modelType) { if (gltf.animations && gltf.animations.length > 0) { const animMixer = new THREE.AnimationMixer(model); gltf.animations.forEach((clip) => { const action = animMixer.clipAction(clip); if (modelType === 'bold') { // Play once for bold action.loop = THREE.LoopOnce; action.clampWhenFinished = true; action.play(); console.log(`Bold animation started: ${clip.name}`); } else if (modelType === 'innovation') { // PingPong loop for innovation action.loop = THREE.LoopPingPong; action.play(); console.log(`Innovation animation started: ${clip.name} (PingPong)`); } else if (modelType === 'agility') { // Regular loop for agility action.loop = THREE.LoopRepeat; action.play(); console.log(`Agility animation started: ${clip.name} (Loop)`); } else if (modelType === 'storytelling') { // Play once for storytelling action.loop = THREE.LoopOnce; action.clampWhenFinished = true; action.play(); console.log(`Storytelling animation started: ${clip.name}`); } }); if (modelType === 'innovation') { animMixer.timeScale = 3.0; // Keep existing timeScale for innovation console.log('Innovation animation timeScale set to 3.0'); } return animMixer; } return null; } // Create model from preloaded data - FIXED: Always create fresh geometry function createModelFromPreloaded(modelType) { const preloadedData = preloadedModels[modelType]; if (!preloadedData) { console.error(`Preloaded model not found: ${modelType}`); return { model: null, animMixer: null }; } console.log(`Creating model from preloaded data: ${modelType}`); // Clear animation materials arrays when creating new models if (modelType === 'bold') { boldRoughnessAnimation.materials = []; } else if (modelType === 'innovation') { innovationGlassAnimation.materials = []; } // Clone the scene deeply to ensure fresh geometry const model = preloadedData.scene.clone(true); // IMPORTANT: Clone all geometries to ensure they're independent model.traverse((object) => { if (object.isMesh && object.geometry) { object.geometry = object.geometry.clone(); } }); // Apply materials applyMaterials(model, modelType); // Setup animations const animMixer = setupAnimations(model, preloadedData.gltf, modelType); // Center and frame model centerAndFrameModel(model); console.log(`Model created successfully: ${modelType}`); return { model, animMixer }; } // Initialize first scene after all models are loaded function initializeScene() { console.log('Initializing first scene (bold)'); const { model, animMixer } = createModelFromPreloaded('bold'); currentModel = model; mixer = animMixer; scene.add(currentModel); // Start the roughness animation for bold scene boldRoughnessAnimation.isActive = true; boldRoughnessAnimation.startTime = performance.now(); console.log('Bold scene initialized and roughness animation started'); } // Start innovation glass animation function startInnovationGlassAnimation() { console.log('Starting innovation glass animation'); // Reset all innovation glass materials to starting values innovationGlassAnimation.materials.forEach(material => { material.ior = innovationGlassAnimation.startIor; material.thickness = innovationGlassAnimation.startThickness; material.needsUpdate = true; }); innovationGlassAnimation.isActive = true; innovationGlassAnimation.startTime = performance.now(); console.log('Innovation glass animation started'); } // Reset mesh geometry to original state function resetMeshGeometry(mesh) { if (!mesh || !mesh.geometry || !mesh.geometry.userData.originalPositions) { return; } const positions = mesh.geometry.attributes.position; const original = mesh.geometry.userData.originalPositions; for (let i = 0; i < positions.count; i++) { positions.setXYZ(i, original[i * 3], original[i * 3 + 1], original[i * 3 + 2]); } positions.needsUpdate = true; mesh.geometry.computeVertexNormals(); } // FIXED: Clean up geometry data completely function cleanupGeometryData(model) { if (!model) return; model.traverse((object) => { if (object.isMesh && object.geometry && object.geometry.userData) { delete object.geometry.userData.originalPositions; delete object.geometry.userData.originalWorldPositions; delete object.geometry.userData.inverseWorldMatrix; } }); } // Start transition to next or previous scene function startTransition(direction = 1) { if (isTransitioning) return; // Check bounds - now 4 scenes (0-3) if (direction > 0 && currentScene >= 3) return; // Can't go forward from storytelling if (direction < 0 && currentScene <= 0) return; // Can't go backward from bold console.log(`Starting diagonal transition: direction=${direction}, currentScene=${currentScene}`); // Calculate camera-relative diagonal transition vectors calculateTransitionVectors(); isTransitioning = true; transitionStartTime = performance.now(); transitionDirection = direction; // Determine next model based on direction and current scene let nextModelType = ''; if (direction > 0) { // Moving forward if (currentScene === 0) { nextModelType = 'innovation'; } else if (currentScene === 1) { nextModelType = 'agility'; } else if (currentScene === 2) { nextModelType = 'storytelling'; } } else { // Moving backward if (currentScene === 1) { nextModelType = 'bold'; } else if (currentScene === 2) { nextModelType = 'innovation'; } else if (currentScene === 3) { nextModelType = 'agility'; } } console.log(`Next model type: ${nextModelType}`); if (nextModelType) { const { model, animMixer } = createModelFromPreloaded(nextModelType); nextModel = model; nextMixer = animMixer; // Start next model at the diagonal down position (camera-relative) nextModel.position.copy(transitionDownVector); nextModel.traverse((obj) => { if (obj.material) { if (Array.isArray(obj.material)) { obj.material.forEach(mat => { mat.transparent = true; mat.opacity = 0; }); } else { obj.material.transparent = true; obj.material.opacity = 0; } } }); scene.add(nextModel); console.log(`Next model positioned at diagonal down vector: x=${nextModel.position.x}, y=${nextModel.position.y}, z=${nextModel.position.z}`); } } // Update transition animation function updateTransition(deltaTime) { if (!isTransitioning) return; const elapsed = (performance.now() - transitionStartTime) / 1000; const transitionProgress = Math.min(elapsed / transitionDuration, 1); // Smooth easing function (ease-in-out) const easeInOut = (t) => t * t * (3 - 2 * t); const easedProgress = easeInOut(transitionProgress); if (currentModel) { // Move current model along diagonal up-left vector const moveVector = transitionUpVector.clone().multiplyScalar(easedProgress); currentModel.position.copy(moveVector); currentModel.traverse((obj) => { if (obj.material) { const targetOpacity = 1 - easedProgress; if (Array.isArray(obj.material)) { obj.material.forEach(mat => { mat.transparent = true; mat.opacity = targetOpacity; }); } else { obj.material.transparent = true; obj.material.opacity = targetOpacity; } } }); } if (nextModel) { // Move next model from diagonal down-left vector to center (0,0,0) const moveVector = transitionDownVector.clone().multiplyScalar(1 - easedProgress); nextModel.position.copy(moveVector); nextModel.traverse((obj) => { if (obj.material) { const targetOpacity = easedProgress; if (Array.isArray(obj.material)) { obj.material.forEach(mat => { mat.transparent = true; mat.opacity = targetOpacity; }); } else { obj.material.transparent = true; obj.material.opacity = targetOpacity; } } }); } // Complete transition if (transitionProgress >= 1) { console.log('Diagonal transition animation complete'); // FIXED: Reset geometry before removing the model if (currentModel) { // Reset all geometry to original state before removal currentModel.traverse((object) => { if (object.isMesh) { resetMeshGeometry(object); } }); // Clean up geometry user data completely cleanupGeometryData(currentModel); scene.remove(currentModel); console.log('Previous model removed from scene'); } // Switch to next model if (nextModel) { currentModel = nextModel; mixer = nextMixer; // Reset position and opacity currentModel.position.set(0, 0, 0); currentModel.traverse((obj) => { if (obj.material) { if (Array.isArray(obj.material)) { obj.material.forEach(mat => { mat.opacity = 1; if (currentScene + transitionDirection === 3) { // Keep transparency for storytelling glass mat.transparent = mat.transmission > 0; } else { mat.transparent = mat.transmission > 0; } }); } else { obj.material.opacity = 1; if (currentScene + transitionDirection === 3) { // Keep transparency for storytelling glass obj.material.transparent = obj.material.transmission > 0; } else { obj.material.transparent = obj.material.transmission > 0; } } } }); } nextModel = null; nextMixer = null; isTransitioning = false; currentScene += transitionDirection; // Update scene based on direction scrollDownCount = 0; scrollUpCount = 0; // Start innovation glass animation if we're now in the innovation scene if (currentScene === 1) { startInnovationGlassAnimation(); } console.log(`Diagonal transition complete. Current scene: ${currentScene}`); } } // Scroll event handler function onMouseScroll(event) { if (isTransitioning) return; if (event.deltaY > 0) { // Scrolling down - move forward scrollDownCount++; scrollUpCount = 0; // Reset up count console.log(`Scroll down count: ${scrollDownCount}`); if (scrollDownCount >= scrollThreshold) { startTransition(1); // Forward direction } } else if (event.deltaY < 0) { // Scrolling up - move backward scrollUpCount++; scrollDownCount = 0; // Reset down count console.log(`Scroll up count: ${scrollUpCount}`); if (scrollUpCount >= scrollThreshold) { startTransition(-1); // Backward direction } } } // Animation loop const clock = new THREE.Clock(); function animate() { requestAnimationFrame(animate); const delta = clock.getDelta(); // Update mixers if (mixer) mixer.update(delta); if (nextMixer) nextMixer.update(delta); // Update transition if (isTransitioning) { updateTransition(delta); } // Update bold roughness animation if (boldRoughnessAnimation.isActive) { const elapsed = (performance.now() - boldRoughnessAnimation.startTime) / 1000; if (elapsed >= boldRoughnessAnimation.delayDuration) { // Delay period is over, start roughness transition const transitionElapsed = elapsed - boldRoughnessAnimation.delayDuration; const transitionProgress = Math.min(transitionElapsed / boldRoughnessAnimation.transitionDuration, 1); // Smooth easing function (ease-in-out) const easeInOut = (t) => t * t * (3 - 2 * t); const easedProgress = easeInOut(transitionProgress); // Interpolate roughness from 0.25 to 0.05 const currentRoughness = boldRoughnessAnimation.startRoughness + (boldRoughnessAnimation.endRoughness - boldRoughnessAnimation.startRoughness) * easedProgress; // Apply to all bold materials boldRoughnessAnimation.materials.forEach(material => { material.roughness = currentRoughness; material.needsUpdate = true; }); // End animation when complete if (transitionProgress >= 1) { boldRoughnessAnimation.isActive = false; console.log('Bold roughness animation completed'); } } } // Update innovation glass animation if (innovationGlassAnimation.isActive) { const elapsed = (performance.now() - innovationGlassAnimation.startTime) / 1000; const transitionProgress = Math.min(elapsed / innovationGlassAnimation.transitionDuration, 1); // Smooth easing function (ease-in-out) const easeInOut = (t) => t * t * (3 - 2 * t); const easedProgress = easeInOut(transitionProgress); // Interpolate IOR from 1.0 to 2.0 const currentIor = innovationGlassAnimation.startIor + (innovationGlassAnimation.endIor - innovationGlassAnimation.startIor) * easedProgress; // Interpolate thickness from 1.0 to 2.0 const currentThickness = innovationGlassAnimation.startThickness + (innovationGlassAnimation.endThickness - innovationGlassAnimation.startThickness) * easedProgress; // Apply to all innovation glass materials innovationGlassAnimation.materials.forEach(material => { material.ior = currentIor; material.thickness = currentThickness; material.needsUpdate = true; }); // End animation when complete if (transitionProgress >= 1) { innovationGlassAnimation.isActive = false; console.log('Innovation glass animation completed'); } } controls.update(); composer.render(); } // Initialize the scene async function init() { try { console.log('Starting application initialization'); // Load all models first preloadedModels = await sceneLoader.loadAllModels(); console.log('All models loaded successfully'); // Initialize the first scene initializeScene(); // Start the animation loop animate(); console.log('Animation loop started'); // Attach scroll event listener window.addEventListener('wheel', onMouseScroll, { passive: true }); console.log('Scroll event listener attached'); } catch (error) { console.error('Failed to initialize scene:', error); sceneLoader.setLoadingMessage('Error loading experience. Please refresh.'); } } // Handle window resize window.addEventListener('resize', () => { console.log('Window resized'); camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); composer.setSize(window.innerWidth, window.innerHeight); }); // Start the application init();