147 lines
5.5 KiB
JavaScript
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);
|
|
}
|
|
}
|