import * as THREE from 'three'; import { createModelFromPreloaded, resetMeshGeometry, cleanupGeometryData } from './modelManager.js'; import { startBoldRoughnessAnimation, startInnovationGlassAnimation } from './animationManager.js'; /* ------------------------------------------------------------------ */ /* state */ export let currentScene = 0; // 0-bold | 1-innovation | 2-agility | 3-storytelling let pendingScene = 0; // target index during a transition export let isTransitioning = false; export const transitionDuration = 1; // seconds export const scrollThreshold = 10; export const transitionDistance = 50; let scrollDownCount = 0; let scrollUpCount = 0; let transitionStartTime = 0; let transitionDirection = 1; // 1 forward | -1 back /* ------------------------------------------------------------------ */ /* scene objects */ export let currentModel = null; export let nextModel = null; export let mixer = null; export let nextMixer = null; export let glbRepulsionSystem = null; /* camera-aligned vectors */ let transitionUpVector = new THREE.Vector3(); let transitionDownVector = new THREE.Vector3(); /* setters ----------------------------------------------------------- */ export const setCurrentModel = m => currentModel = m; export const setMixer = m => mixer = m; export const setGLBRepulsionSystem = s => glbRepulsionSystem = s; /* utilities --------------------------------------------------------- */ const sceneKey = idx => ['bold','innovation','agility','storytelling'][idx]; /* compute camera-relative diagonal vectors */ export function calculateTransitionVectors(camera){ const fwd = new THREE.Vector3(); camera.getWorldDirection(fwd).normalize(); const up = new THREE.Vector3(0,1,0); const right = new THREE.Vector3().crossVectors(fwd, up).normalize(); const camUp = new THREE.Vector3().crossVectors(right, fwd).normalize(); const diag = new THREE.Vector3() .addScaledVector(camUp, 0.5) .addScaledVector(right, -0.5) .normalize(); transitionUpVector .copy(diag).multiplyScalar( transitionDistance); transitionDownVector.copy(diag).multiplyScalar(-transitionDistance); } /* ------------------------------------------------------------------ */ /* transition start */ export function startTransition(dir, preload, scene, camera, controls){ if(isTransitioning) return; const nextIdx = currentScene + dir; if(nextIdx < 0 || nextIdx > 3) return; // out-of-range isTransitioning = true; pendingScene = nextIdx; transitionDirection = dir; transitionStartTime = performance.now(); calculateTransitionVectors(camera); const { model, animMixer } = createModelFromPreloaded(sceneKey(nextIdx), preload, camera, controls); nextModel = model; nextMixer = animMixer; nextModel.position.copy(dir > 0 ? transitionDownVector : transitionUpVector); if(glbRepulsionSystem) glbRepulsionSystem.originalPositions.set(nextModel, nextModel.position.clone()); scene.add(nextModel); } /* ------------------------------------------------------------------ */ /* transition update */ export function updateTransition(_dt, scene){ if(!isTransitioning) return; const t = Math.min((performance.now() - transitionStartTime)/1000 / transitionDuration, 1); const e = t*t*(3-2*t); // smoothstep easing if(currentModel) currentModel.position.copy( (transitionDirection>0 ? transitionUpVector : transitionDownVector).clone().multiplyScalar(e) ); if(nextModel) nextModel.position.copy( (transitionDirection>0 ? transitionDownVector : transitionUpVector).clone().multiplyScalar(1-e) ); if(t < 1) return; // still animating /* ----- complete transition -------------------------------------- */ if(currentModel){ currentModel.traverse(o => o.isMesh && resetMeshGeometry(o)); cleanupGeometryData(currentModel); if(glbRepulsionSystem){ glbRepulsionSystem.originalPositions.delete(currentModel); glbRepulsionSystem.currentTargets.delete(currentModel); } scene.remove(currentModel); } currentModel = nextModel; mixer = nextMixer; currentModel.position.set(0,0,0); if(glbRepulsionSystem) glbRepulsionSystem.originalPositions.set(currentModel, currentModel.position.clone()); nextModel = nextMixer = null; currentScene = pendingScene; // now official isTransitioning = false; scrollDownCount = scrollUpCount = 0; if(currentScene === 0) startBoldRoughnessAnimation(false); if(currentScene === 1) startInnovationGlassAnimation(); } /* ------------------------------------------------------------------ */ /* scroll handler */ export function onMouseScroll(ev, preload, scene, camera, controls){ if(isTransitioning) return; if(ev.deltaY > 0){ scrollDownCount++; scrollUpCount = 0; if(scrollDownCount >= scrollThreshold) startTransition(+1, preload, scene, camera, controls); }else if(ev.deltaY < 0){ scrollUpCount++; scrollDownCount = 0; if(scrollUpCount >= scrollThreshold) startTransition(-1, preload, scene, camera, controls); } }