import './style.css'; import * as THREE from 'three'; import { SceneLoader } from './sceneLoader.js'; import { createScene, setupLighting, setupControls } from './sceneSetup.js'; import { createModelFromPreloaded } from './modelManager.js'; import { currentModel, nextModel, mixer, nextMixer, isTransitioning, updateTransition, onMouseScroll, setCurrentModel, setMixer, setGLBRepulsionSystem, calculateTransitionVectors // ⬅ added } from './transitionManager.js'; import { startBoldRoughnessAnimation, updateBoldRoughnessAnimation, updateInnovationGlassAnimation } from './animationManager.js'; import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; import { createFluidSimulation, FluidDistortionShader } from './fluidDistortion.js'; import { createStarfield } from './starfield.js'; /* ------------------------------------------------------------------ */ /* loader */ const sceneLoader = new SceneLoader(); sceneLoader.setLoadingMessage('Preparing Your Experience...'); /* ------------------------------------------------------------------ */ /* scene */ const { scene, camera, renderer, composer } = createScene(); setupLighting(scene, camera); const controls = setupControls(camera, renderer); /* realign transition vectors whenever user moves the camera */ controls.addEventListener('change', () => calculateTransitionVectors(camera)); /* ------------------------------------------------------------------ */ /* starfield, turntable & global vars */ const starfield = createStarfield(scene); const turntableSpeed = 0.5; let preloadedModels = {}; /* ------------------------------------------------------------------ */ /* post-processing: fluid distortion */ const dpr = renderer.getPixelRatio ? renderer.getPixelRatio() : Math.min(window.devicePixelRatio || 1, 2); const fluid = createFluidSimulation(renderer, dpr); const distortionPass = new ShaderPass(FluidDistortionShader); distortionPass.material.uniforms.tSim.value = fluid.getTexture(); distortionPass.material.uniforms.iResolution.value.set(window.innerWidth * dpr, window.innerHeight * dpr); distortionPass.material.uniforms.amount.value = 0.005; distortionPass.material.uniforms.chromaticAmount.value = 0.002; distortionPass.material.uniforms.lightIntensity.value = 0; distortionPass.material.uniforms.lightColor.value.set(1, 1, 1); distortionPass.material.uniforms.normalStrength.value = 2.0; distortionPass.material.uniforms.ambientLight.value = 1; distortionPass.material.uniforms.rippleWhiteness.value = 0.025; distortionPass.material.uniforms.rippleBrightness.value = 1; composer.addPass(distortionPass); /* ------------------------------------------------------------------ */ /* pointer + mouse utilities */ const pointer = { x: -1, y: -1, strength: 0, prevX: -1, prevY: -1 }; const mouse = new THREE.Vector2(); const raycaster = new THREE.Raycaster(); /* ------------------------------------------------------------------ */ /* GLB cursor repulsion system */ const glbRepulsion = { radius: 30, maxDistance: 2, strength: 8, originalPositions: new Map(), currentTargets: new Map(), interpolationSpeed: 3 }; setGLBRepulsionSystem(glbRepulsion); /* ------------------------------------------------------------------ */ /* helper: convert DOM coords → simulation coords */ function toSimPixels(e) { const rect = renderer.domElement.getBoundingClientRect(); const x = (e.clientX - rect.left) * dpr; const y = (rect.height - (e.clientY - rect.top)) * dpr; return { x, y }; } /* ------------------------------------------------------------------ */ /* pointer events */ renderer.domElement.addEventListener('pointermove', (e) => { const { x, y } = toSimPixels(e); const dx = pointer.prevX < 0 ? 0 : Math.abs(x - pointer.prevX); const dy = pointer.prevY < 0 ? 0 : Math.abs(y - pointer.prevY); const speed = Math.min(Math.sqrt(dx * dx + dy * dy) / (6 * dpr), 1); pointer.x = x; pointer.y = y; pointer.strength = speed * 1.2; pointer.prevX = x; pointer.prevY = y; const rect = renderer.domElement.getBoundingClientRect(); const nx = (e.clientX - rect.left) / rect.width; const ny = 1 - (e.clientY - rect.top) / rect.height; distortionPass.material.uniforms.lightPosition.value.set(nx, ny, 1); mouse.x = nx * 2 - 1; mouse.y = -ny * 2 + 1; }, { passive: true }); renderer.domElement.addEventListener('pointerleave', () => { Object.assign(pointer, { x: -1, y: -1, strength: 0 }); mouse.set(-999, -999); distortionPass.material.uniforms.lightPosition.value.set(0.5, 0.5, 1); }, { passive: true }); /* ------------------------------------------------------------------ */ /* GLB repulsion update */ function updateGLBRepulsion(camera, mouse, dt) { if (mouse.x === -999) { // return objects to original pos [currentModel, nextModel].forEach(m => { if (!m) return; const orig = glbRepulsion.originalPositions.get(m); if (!orig) return; const tgt = glbRepulsion.currentTargets.get(m) || orig.clone(); tgt.copy(orig); glbRepulsion.currentTargets.set(m, tgt); m.position.lerp(tgt, Math.min(glbRepulsion.interpolationSpeed * dt, 1)); }); return; } raycaster.setFromCamera(mouse, camera); const mouseWorld = raycaster.ray.direction.clone().multiplyScalar(50).add(raycaster.ray.origin); [currentModel, nextModel].forEach(m => { if (!m) return; if (!glbRepulsion.originalPositions.has(m)) glbRepulsion.originalPositions.set(m, m.position.clone()); const orig = glbRepulsion.originalPositions.get(m); const dx = m.position.x - mouseWorld.x; const dy = m.position.y - mouseWorld.y; const dz = m.position.z - mouseWorld.z; const dist = Math.sqrt(dx * dx + dy * dy + dz * dz); let target = orig.clone(); if (dist < glbRepulsion.radius && dist > 0) { const force = (1 - dist / glbRepulsion.radius) * glbRepulsion.strength; target.add(new THREE.Vector3(dx, dy, dz).normalize().multiplyScalar(force)); const offset = target.clone().sub(orig); if (offset.length() > glbRepulsion.maxDistance) target = orig.clone().add(offset.normalize().multiplyScalar(glbRepulsion.maxDistance)); } glbRepulsion.currentTargets.set(m, target); m.position.lerp(target, Math.min(glbRepulsion.interpolationSpeed * dt, 1)); }); } /* ------------------------------------------------------------------ */ /* first scene setup */ function initializeScene() { const { model, animMixer } = createModelFromPreloaded('bold', preloadedModels, camera, controls); setCurrentModel(model); setMixer(animMixer); scene.add(currentModel); glbRepulsion.originalPositions.set(currentModel, currentModel.position.clone()); startBoldRoughnessAnimation(true); } /* ------------------------------------------------------------------ */ /* animation loop */ const clock = new THREE.Clock(); function animate() { requestAnimationFrame(animate); const dt = clock.getDelta(); if (mixer) mixer.update(dt); if (nextMixer) nextMixer.update(dt); if (isTransitioning) updateTransition(dt, scene); else updateGLBRepulsion(camera, mouse, dt); // turntable if (currentModel) currentModel.rotation.y += turntableSpeed * dt; if (nextModel) nextModel.rotation.y += turntableSpeed * dt; // material anims updateBoldRoughnessAnimation(); updateInnovationGlassAnimation(); // stars + fluid starfield.animateStars(camera, mouse, dt); fluid.update(pointer.x, pointer.y, pointer.strength, performance.now() / 1000); distortionPass.material.uniforms.tSim.value = fluid.getTexture(); controls.update(); composer.render(); } /* ------------------------------------------------------------------ */ /* init workflow */ async function init() { try { preloadedModels = await sceneLoader.loadAllModels(); initializeScene(); animate(); window.addEventListener('wheel', (e) => onMouseScroll(e, preloadedModels, scene, camera, controls), { passive: true }); window.addEventListener('resize', () => { const w = window.innerWidth, h = window.innerHeight; camera.aspect = w / h; camera.updateProjectionMatrix(); renderer.setSize(w, h); composer.setSize(w, h); const pr = renderer.getPixelRatio ? renderer.getPixelRatio() : Math.min(window.devicePixelRatio || 1, 2); distortionPass.material.uniforms.iResolution.value.set(w * pr, h * pr); fluid.resize(w, h, pr); }); } catch (err) { console.error('Failed to initialise:', err); sceneLoader.setLoadingMessage('Error loading experience. Please refresh.'); } } /* ------------------------------------------------------------------ */ init();