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 } from './transitionManager.js'; import { startBoldRoughnessAnimation, updateBoldRoughnessAnimation, updateInnovationGlassAnimation } from './animationManager.js'; import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; import { createInkSimulation, InkDistortionShader } from './fluidDistortion.js'; import { createStarfield } from './starfield.js'; const sceneLoader = new SceneLoader(); sceneLoader.setLoadingMessage('Preparing Your Experience...'); const { scene, camera, renderer, composer } = createScene(); setupLighting(scene, camera); const controls = setupControls(camera, renderer); controls.addEventListener('change', () => calculateTransitionVectors(camera)); const starfield = createStarfield(scene); const turntableSpeed = 0.5; let preloadedModels = {}; const dpr = renderer.getPixelRatio ? renderer.getPixelRatio() : Math.min(window.devicePixelRatio || 1, 2); const inkSim = createInkSimulation(renderer, dpr); const distortionPass = new ShaderPass(InkDistortionShader); distortionPass.material.uniforms.tSim.value = inkSim.getTexture(); distortionPass.material.uniforms.iResolution.value.set(window.innerWidth * dpr, window.innerHeight * dpr); distortionPass.material.uniforms.amount.value = 0.025; distortionPass.material.uniforms.chromaticAmount.value = 0.100; distortionPass.material.uniforms.noiseScale.value = 2.5; distortionPass.material.uniforms.flowSpeed.value = 1.2; distortionPass.material.uniforms.inkDensity.value = 0.35; distortionPass.material.uniforms.chaosAmount.value = 1.5; composer.addPass(distortionPass); const pointer = { x: -1, y: -1, strength: 0, prevX: -1, prevY: -1 }; const mouse = new THREE.Vector2(); const raycaster = new THREE.Raycaster(); const glbRepulsion = { radius: 30, maxDistance: 2, strength: 8, originalPositions: new Map(), currentTargets: new Map(), interpolationSpeed: 3 }; setGLBRepulsionSystem(glbRepulsion); 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 }; } 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) / (3 * dpr), 1); pointer.x = x; pointer.y = y; pointer.strength = speed * 4.0; 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; 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); }, { passive: true }); function updateGLBRepulsion(camera, mouse, dt) { if (mouse.x === -999) { [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)); }); } 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); } const clock = new THREE.Clock(); function animate() { requestAnimationFrame(animate); const dt = clock.getDelta(); const elapsedTime = clock.getElapsedTime(); if (mixer) mixer.update(dt); if (nextMixer) nextMixer.update(dt); if (isTransitioning) updateTransition(dt, scene); else updateGLBRepulsion(camera, mouse, dt); if (currentModel) currentModel.rotation.y += turntableSpeed * dt; if (nextModel) nextModel.rotation.y += turntableSpeed * dt; updateBoldRoughnessAnimation(); updateInnovationGlassAnimation(); starfield.animateStars(camera, mouse, dt); inkSim.update(pointer.x, pointer.y, pointer.strength, elapsedTime); distortionPass.material.uniforms.tSim.value = inkSim.getTexture(); distortionPass.material.uniforms.time.value = elapsedTime; controls.update(); composer.render(); } 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); inkSim.resize(w, h, pr); }); } catch (err) { console.error('Failed to initialise:', err); sceneLoader.setLoadingMessage('Error loading experience. Please refresh.'); } } init();