yp-rubix/src/transitionManager.js
2025-09-04 10:13:33 +05:30

147 lines
5.5 KiB
JavaScript

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