transition logic fixed

This commit is contained in:
Anuj K 2025-09-04 10:13:33 +05:30
parent bd2bccdcb2
commit a2de17c581
2 changed files with 225 additions and 445 deletions

View file

@ -1,8 +1,10 @@
import './style.css' import './style.css';
import * as THREE from 'three'; import * as THREE from 'three';
import { SceneLoader } from './sceneLoader.js'; import { SceneLoader } from './sceneLoader.js';
import { createScene, setupLighting, setupControls } from './sceneSetup.js'; import { createScene, setupLighting, setupControls } from './sceneSetup.js';
import { createModelFromPreloaded } from './modelManager.js'; import { createModelFromPreloaded } from './modelManager.js';
import { import {
currentModel, currentModel,
nextModel, nextModel,
@ -13,87 +15,77 @@ import {
onMouseScroll, onMouseScroll,
setCurrentModel, setCurrentModel,
setMixer, setMixer,
setGLBRepulsionSystem setGLBRepulsionSystem,
calculateTransitionVectors // ⬅ added
} from './transitionManager.js'; } from './transitionManager.js';
import { import {
startBoldRoughnessAnimation, startBoldRoughnessAnimation,
updateBoldRoughnessAnimation, updateBoldRoughnessAnimation,
updateInnovationGlassAnimation updateInnovationGlassAnimation
} from './animationManager.js'; } from './animationManager.js';
// Fluid distortion imports
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { createFluidSimulation, FluidDistortionShader } from './fluidDistortion.js'; import { createFluidSimulation, FluidDistortionShader } from './fluidDistortion.js';
// Starfield import
import { createStarfield } from './starfield.js'; import { createStarfield } from './starfield.js';
// Initialize loader /* ------------------------------------------------------------------ */
/* loader */
const sceneLoader = new SceneLoader(); const sceneLoader = new SceneLoader();
sceneLoader.setLoadingMessage('Preparing Your Experience...'); sceneLoader.setLoadingMessage('Preparing Your Experience...');
// Create scene components /* ------------------------------------------------------------------ */
/* scene */
const { scene, camera, renderer, composer } = createScene(); const { scene, camera, renderer, composer } = createScene();
setupLighting(scene, camera); setupLighting(scene, camera);
const controls = setupControls(camera, renderer); const controls = setupControls(camera, renderer);
// Create starfield /* realign transition vectors whenever user moves the camera */
controls.addEventListener('change', () => calculateTransitionVectors(camera));
/* ------------------------------------------------------------------ */
/* starfield, turntable & global vars */
const starfield = createStarfield(scene); const starfield = createStarfield(scene);
// Turntable animation settings
const turntableSpeed = 0.5; const turntableSpeed = 0.5;
// Store preloaded models
let preloadedModels = {}; let preloadedModels = {};
// Enhanced fluid simulation + distortion pass /* ------------------------------------------------------------------ */
/* post-processing: fluid distortion */
const dpr = renderer.getPixelRatio ? renderer.getPixelRatio() : Math.min(window.devicePixelRatio || 1, 2); const dpr = renderer.getPixelRatio ? renderer.getPixelRatio() : Math.min(window.devicePixelRatio || 1, 2);
const fluid = createFluidSimulation(renderer, dpr); const fluid = createFluidSimulation(renderer, dpr);
const distortionPass = new ShaderPass(FluidDistortionShader); const distortionPass = new ShaderPass(FluidDistortionShader);
distortionPass.material.uniforms.tSim.value = fluid.getTexture(); distortionPass.material.uniforms.tSim.value = fluid.getTexture();
distortionPass.material.uniforms.iResolution.value.set(window.innerWidth * dpr, window.innerHeight * dpr); distortionPass.material.uniforms.iResolution.value.set(window.innerWidth * dpr, window.innerHeight * dpr);
distortionPass.material.uniforms.amount.value = 0.005; // Stronger distortion distortionPass.material.uniforms.amount.value = 0.005;
distortionPass.material.uniforms.chromaticAmount.value = 0.002; // Enhanced chromatic aberration distortionPass.material.uniforms.chromaticAmount.value = 0.002;
// Enhanced lighting parameters
distortionPass.material.uniforms.lightIntensity.value = 0; distortionPass.material.uniforms.lightIntensity.value = 0;
distortionPass.material.uniforms.lightColor.value.set(1, 1, 1); distortionPass.material.uniforms.lightColor.value.set(1, 1, 1);
distortionPass.material.uniforms.normalStrength.value = 2.0; distortionPass.material.uniforms.normalStrength.value = 2.0;
distortionPass.material.uniforms.ambientLight.value = 1; distortionPass.material.uniforms.ambientLight.value = 1;
distortionPass.material.uniforms.rippleWhiteness.value = 0.025;
// New ripple whiteness parameters distortionPass.material.uniforms.rippleBrightness.value = 1;
distortionPass.material.uniforms.rippleWhiteness.value = 0.025; // Amount of white tint
distortionPass.material.uniforms.rippleBrightness.value = 1; // Brightness boost for ripples
composer.addPass(distortionPass); composer.addPass(distortionPass);
// Enhanced pointer tracking /* ------------------------------------------------------------------ */
const pointer = { /* pointer + mouse utilities */
x: -1, const pointer = { x: -1, y: -1, strength: 0, prevX: -1, prevY: -1 };
y: -1,
strength: 0.0,
prevX: -1,
prevY: -1,
trail: [], // Store trail positions for enhanced effect
maxTrailLength: 5
};
// Mouse coordinates for starfield and GLB interaction
const mouse = new THREE.Vector2(); const mouse = new THREE.Vector2();
const raycaster = new THREE.Raycaster(); const raycaster = new THREE.Raycaster();
// GLB cursor repulsion parameters /* ------------------------------------------------------------------ */
/* GLB cursor repulsion system */
const glbRepulsion = { const glbRepulsion = {
radius: 30, // Repulsion radius radius: 30,
maxDistance: 2, // Maximum distance GLB can move from original position maxDistance: 2,
strength: 8, // Repulsion strength strength: 8,
originalPositions: new Map(), // Store original positions for each model originalPositions: new Map(),
currentTargets: new Map(), // Store current target positions currentTargets: new Map(),
interpolationSpeed: 3 // Speed of position interpolation interpolationSpeed: 3
}; };
// Connect GLB repulsion system to transition manager
setGLBRepulsionSystem(glbRepulsion); setGLBRepulsionSystem(glbRepulsion);
/* ------------------------------------------------------------------ */
/* helper: convert DOM coords → simulation coords */
function toSimPixels(e) { function toSimPixels(e) {
const rect = renderer.domElement.getBoundingClientRect(); const rect = renderer.domElement.getBoundingClientRect();
const x = (e.clientX - rect.left) * dpr; const x = (e.clientX - rect.left) * dpr;
@ -101,236 +93,141 @@ function toSimPixels(e) {
return { x, y }; return { x, y };
} }
/* ------------------------------------------------------------------ */
/* pointer events */
renderer.domElement.addEventListener('pointermove', (e) => { renderer.domElement.addEventListener('pointermove', (e) => {
const { x, y } = toSimPixels(e); const { x, y } = toSimPixels(e);
const dx = (pointer.prevX < 0) ? 0 : Math.abs(x - pointer.prevX); const dx = pointer.prevX < 0 ? 0 : Math.abs(x - pointer.prevX);
const dy = (pointer.prevY < 0) ? 0 : Math.abs(y - pointer.prevY); const dy = pointer.prevY < 0 ? 0 : Math.abs(y - pointer.prevY);
const speed = Math.min(Math.sqrt(dx * dx + dy * dy) / (6.0 * dpr), 1.0); // More sensitive const speed = Math.min(Math.sqrt(dx * dx + dy * dy) / (6 * dpr), 1);
pointer.x = x; pointer.x = x; pointer.y = y; pointer.strength = speed * 1.2;
pointer.y = y; pointer.prevX = x; pointer.prevY = y;
pointer.strength = speed * 1.2; // Enhanced strength
pointer.prevX = x;
pointer.prevY = y;
// Update light position to follow cursor
const rect = renderer.domElement.getBoundingClientRect(); const rect = renderer.domElement.getBoundingClientRect();
const normalizedX = (e.clientX - rect.left) / rect.width; const nx = (e.clientX - rect.left) / rect.width;
const normalizedY = 1.0 - (e.clientY - rect.top) / rect.height; // Flip Y const ny = 1 - (e.clientY - rect.top) / rect.height;
distortionPass.material.uniforms.lightPosition.value.set(normalizedX, normalizedY, 1.0); distortionPass.material.uniforms.lightPosition.value.set(nx, ny, 1);
// Update mouse coordinates for starfield and GLB interaction mouse.x = nx * 2 - 1;
mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1; mouse.y = -ny * 2 + 1;
mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
}, { passive: true }); }, { passive: true });
renderer.domElement.addEventListener('pointerleave', () => { renderer.domElement.addEventListener('pointerleave', () => {
pointer.x = -1; Object.assign(pointer, { x: -1, y: -1, strength: 0 });
pointer.y = -1; mouse.set(-999, -999);
pointer.strength = 0.0; distortionPass.material.uniforms.lightPosition.value.set(0.5, 0.5, 1);
mouse.x = -999;
mouse.y = -999;
// Reset light to center when mouse leaves
distortionPass.material.uniforms.lightPosition.value.set(0.5, 0.5, 1.0);
}, { passive: true }); }, { passive: true });
// GLB cursor repulsion function /* ------------------------------------------------------------------ */
function updateGLBRepulsion(camera, mouse, deltaTime) { /* GLB repulsion update */
if (!mouse || mouse.x === -999 || mouse.y === -999) { function updateGLBRepulsion(camera, mouse, dt) {
// Mouse is not active, return models to original positions if (mouse.x === -999) {
if (currentModel) { // return objects to original pos
const originalPos = glbRepulsion.originalPositions.get(currentModel); [currentModel, nextModel].forEach(m => {
if (originalPos) { if (!m) return;
const currentTarget = glbRepulsion.currentTargets.get(currentModel) || new THREE.Vector3(); const orig = glbRepulsion.originalPositions.get(m);
currentTarget.copy(originalPos); if (!orig) return;
glbRepulsion.currentTargets.set(currentModel, currentTarget); const tgt = glbRepulsion.currentTargets.get(m) || orig.clone();
// Interpolate to original position tgt.copy(orig);
const lerpFactor = Math.min(glbRepulsion.interpolationSpeed * deltaTime, 1.0); glbRepulsion.currentTargets.set(m, tgt);
currentModel.position.lerp(currentTarget, lerpFactor); m.position.lerp(tgt, Math.min(glbRepulsion.interpolationSpeed * dt, 1));
} });
}
if (nextModel) {
const originalPos = glbRepulsion.originalPositions.get(nextModel);
if (originalPos) {
const currentTarget = glbRepulsion.currentTargets.get(nextModel) || new THREE.Vector3();
currentTarget.copy(originalPos);
glbRepulsion.currentTargets.set(nextModel, currentTarget);
// Interpolate to original position
const lerpFactor = Math.min(glbRepulsion.interpolationSpeed * deltaTime, 1.0);
nextModel.position.lerp(currentTarget, lerpFactor);
}
}
return; return;
} }
// Get mouse position in 3D world space
raycaster.setFromCamera(mouse, camera); raycaster.setFromCamera(mouse, camera);
const distance = 50; // Project to a plane at fixed distance const mouseWorld = raycaster.ray.direction.clone().multiplyScalar(50).add(raycaster.ray.origin);
const mouseWorldPos = raycaster.ray.direction.clone().multiplyScalar(distance).add(raycaster.ray.origin);
// Handle current model repulsion [currentModel, nextModel].forEach(m => {
if (currentModel) { if (!m) return;
let originalPos = glbRepulsion.originalPositions.get(currentModel); if (!glbRepulsion.originalPositions.has(m))
if (!originalPos) { glbRepulsion.originalPositions.set(m, m.position.clone());
originalPos = currentModel.position.clone();
glbRepulsion.originalPositions.set(currentModel, originalPos);
}
// Calculate repulsion const orig = glbRepulsion.originalPositions.get(m);
const modelPos = currentModel.position.clone(); const dx = m.position.x - mouseWorld.x;
const dx = modelPos.x - mouseWorldPos.x; const dy = m.position.y - mouseWorld.y;
const dy = modelPos.y - mouseWorldPos.y; const dz = m.position.z - mouseWorld.z;
const dz = modelPos.z - mouseWorldPos.z;
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz); const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
let target = orig.clone();
let targetPos = originalPos.clone();
if (dist < glbRepulsion.radius && dist > 0) { if (dist < glbRepulsion.radius && dist > 0) {
const force = (1 - dist / glbRepulsion.radius) * glbRepulsion.strength; const force = (1 - dist / glbRepulsion.radius) * glbRepulsion.strength;
const nx = dx / dist; target.add(new THREE.Vector3(dx, dy, dz).normalize().multiplyScalar(force));
const ny = dy / dist; const offset = target.clone().sub(orig);
const nz = dz / dist; if (offset.length() > glbRepulsion.maxDistance)
const repulsionVector = new THREE.Vector3(nx * force, ny * force, nz * force); target = orig.clone().add(offset.normalize().multiplyScalar(glbRepulsion.maxDistance));
targetPos.add(repulsionVector);
// Limit maximum distance from original position
const offsetFromOriginal = targetPos.clone().sub(originalPos);
if (offsetFromOriginal.length() > glbRepulsion.maxDistance) {
offsetFromOriginal.normalize().multiplyScalar(glbRepulsion.maxDistance);
targetPos = originalPos.clone().add(offsetFromOriginal);
}
} }
// Store and interpolate to target position glbRepulsion.currentTargets.set(m, target);
glbRepulsion.currentTargets.set(currentModel, targetPos); m.position.lerp(target, Math.min(glbRepulsion.interpolationSpeed * dt, 1));
const lerpFactor = Math.min(glbRepulsion.interpolationSpeed * deltaTime, 1.0); });
currentModel.position.lerp(targetPos, lerpFactor);
}
// Handle next model repulsion during transitions
if (nextModel) {
let originalPos = glbRepulsion.originalPositions.get(nextModel);
if (!originalPos) {
originalPos = nextModel.position.clone();
glbRepulsion.originalPositions.set(nextModel, originalPos);
}
// Calculate repulsion
const modelPos = nextModel.position.clone();
const dx = modelPos.x - mouseWorldPos.x;
const dy = modelPos.y - mouseWorldPos.y;
const dz = modelPos.z - mouseWorldPos.z;
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
let targetPos = originalPos.clone();
if (dist < glbRepulsion.radius && dist > 0) {
const force = (1 - dist / glbRepulsion.radius) * glbRepulsion.strength;
const nx = dx / dist;
const ny = dy / dist;
const nz = dz / dist;
const repulsionVector = new THREE.Vector3(nx * force, ny * force, nz * force);
targetPos.add(repulsionVector);
// Limit maximum distance from original position
const offsetFromOriginal = targetPos.clone().sub(originalPos);
if (offsetFromOriginal.length() > glbRepulsion.maxDistance) {
offsetFromOriginal.normalize().multiplyScalar(glbRepulsion.maxDistance);
targetPos = originalPos.clone().add(offsetFromOriginal);
}
}
// Store and interpolate to target position
glbRepulsion.currentTargets.set(nextModel, targetPos);
const lerpFactor = Math.min(glbRepulsion.interpolationSpeed * deltaTime, 1.0);
nextModel.position.lerp(targetPos, lerpFactor);
}
} }
// Initialize first scene /* ------------------------------------------------------------------ */
/* first scene setup */
function initializeScene() { function initializeScene() {
console.log('Initializing first scene (bold)');
const { model, animMixer } = createModelFromPreloaded('bold', preloadedModels, camera, controls); const { model, animMixer } = createModelFromPreloaded('bold', preloadedModels, camera, controls);
setCurrentModel(model); setCurrentModel(model);
setMixer(animMixer); setMixer(animMixer);
scene.add(currentModel); scene.add(currentModel);
// Store original position for repulsion
glbRepulsion.originalPositions.set(currentModel, currentModel.position.clone()); glbRepulsion.originalPositions.set(currentModel, currentModel.position.clone());
startBoldRoughnessAnimation(true); startBoldRoughnessAnimation(true);
console.log('Bold scene initialized');
} }
// Animation loop /* ------------------------------------------------------------------ */
/* animation loop */
const clock = new THREE.Clock(); const clock = new THREE.Clock();
function animate() { function animate() {
requestAnimationFrame(animate); requestAnimationFrame(animate);
const delta = clock.getDelta(); const dt = clock.getDelta();
// Update mixers if (mixer) mixer.update(dt);
if (mixer) mixer.update(delta); if (nextMixer) nextMixer.update(dt);
if (nextMixer) nextMixer.update(delta);
// Update transition if (isTransitioning) updateTransition(dt, scene);
if (isTransitioning) { else updateGLBRepulsion(camera, mouse, dt);
updateTransition(delta, scene);
}
// GLB cursor repulsion (only when not transitioning to avoid conflicts) // turntable
if (!isTransitioning) { if (currentModel) currentModel.rotation.y += turntableSpeed * dt;
updateGLBRepulsion(camera, mouse, delta); if (nextModel) nextModel.rotation.y += turntableSpeed * dt;
}
// Turntable rotation // material anims
if (currentModel) {
currentModel.rotation.y += turntableSpeed * delta;
}
if (nextModel) {
nextModel.rotation.y += turntableSpeed * delta;
}
// Update material animations
updateBoldRoughnessAnimation(); updateBoldRoughnessAnimation();
updateInnovationGlassAnimation(); updateInnovationGlassAnimation();
// Animate stars with cursor interaction // stars + fluid
starfield.animateStars(camera, mouse, delta); starfield.animateStars(camera, mouse, dt);
fluid.update(pointer.x, pointer.y, pointer.strength, performance.now() / 1000);
// Update enhanced fluid sim
const nowSec = performance.now() / 1000;
fluid.update(pointer.x, pointer.y, pointer.strength, nowSec);
distortionPass.material.uniforms.tSim.value = fluid.getTexture(); distortionPass.material.uniforms.tSim.value = fluid.getTexture();
controls.update(); controls.update();
composer.render(); composer.render();
} }
// Initialize the scene /* ------------------------------------------------------------------ */
/* init workflow */
async function init() { async function init() {
try { try {
console.log('Starting application initialization');
preloadedModels = await sceneLoader.loadAllModels(); preloadedModels = await sceneLoader.loadAllModels();
console.log('All models loaded successfully');
initializeScene(); initializeScene();
animate(); animate();
console.log('Animation loop started');
window.addEventListener('wheel', (event) => { window.addEventListener('wheel', (e) => onMouseScroll(e, preloadedModels, scene, camera, controls), { passive: true });
onMouseScroll(event, preloadedModels, scene, camera, controls);
}, { passive: true }); window.addEventListener('resize', () => {
console.log('Scroll event listener attached'); const w = window.innerWidth, h = window.innerHeight;
} catch (error) { camera.aspect = w / h; camera.updateProjectionMatrix();
console.error('Failed to initialize scene:', error); 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.'); sceneLoader.setLoadingMessage('Error loading experience. Please refresh.');
} }
} }
// Handle window resize /* ------------------------------------------------------------------ */
window.addEventListener('resize', () => {
console.log('Window resized');
const w = window.innerWidth;
const h = window.innerHeight;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
composer.setSize(w, h);
const pixelRatio = renderer.getPixelRatio ? renderer.getPixelRatio() : Math.min(window.devicePixelRatio || 1, 2);
distortionPass.material.uniforms.iResolution.value.set(w * pixelRatio, h * pixelRatio);
fluid.resize(w, h, pixelRatio);
});
// Start the application
init(); init();

View file

@ -2,262 +2,145 @@ import * as THREE from 'three';
import { createModelFromPreloaded, resetMeshGeometry, cleanupGeometryData } from './modelManager.js'; import { createModelFromPreloaded, resetMeshGeometry, cleanupGeometryData } from './modelManager.js';
import { startBoldRoughnessAnimation, startInnovationGlassAnimation } from './animationManager.js'; import { startBoldRoughnessAnimation, startInnovationGlassAnimation } from './animationManager.js';
// Transition state management /* ------------------------------------------------------------------ */
export let currentScene = 0; // 0: bold, 1: innovation, 2: agility, 3: storytelling /* state */
export let isTransitioning = false; export let currentScene = 0; // 0-bold | 1-innovation | 2-agility | 3-storytelling
export const fadeSpeed = 1; // Easily adjustable fade speed let pendingScene = 0; // target index during a transition
export const transitionDuration = 1; // Easily adjustable transition duration (seconds) export let isTransitioning = false;
export let scrollDownCount = 0;
export let scrollUpCount = 0;
export const scrollThreshold = 10; // Changed to 10 as requested
export let transitionStartTime = 0;
export let transitionDirection = 1; // 1 for forward, -1 for backward
// Camera-relative transition vectors export const transitionDuration = 1; // seconds
export let transitionUpVector = new THREE.Vector3(); export const scrollThreshold = 10;
export let transitionDownVector = new THREE.Vector3(); export const transitionDistance = 50;
export const transitionDistance = 50; // Increased distance for more dramatic transitions
// Scene objects let scrollDownCount = 0;
let scrollUpCount = 0;
let transitionStartTime = 0;
let transitionDirection = 1; // 1 forward | -1 back
/* ------------------------------------------------------------------ */
/* scene objects */
export let currentModel = null; export let currentModel = null;
export let nextModel = null; export let nextModel = null;
export let mixer = null; export let mixer = null;
export let nextMixer = null; export let nextMixer = null;
export let autoRotationAngle = 0;
// GLB repulsion system reference (will be set by main.js)
export let glbRepulsionSystem = null; export let glbRepulsionSystem = null;
// Setter functions to modify exported variables safely /* camera-aligned vectors */
export function setCurrentModel(model) { let transitionUpVector = new THREE.Vector3();
currentModel = model; 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);
} }
export function setMixer(animMixer) { /* ------------------------------------------------------------------ */
mixer = animMixer; /* transition start */
} export function startTransition(dir, preload, scene, camera, controls){
if(isTransitioning) return;
export function setNextModel(model) { const nextIdx = currentScene + dir;
nextModel = model; if(nextIdx < 0 || nextIdx > 3) return; // out-of-range
}
export function setNextMixer(animMixer) { isTransitioning = true;
nextMixer = animMixer; pendingScene = nextIdx;
} transitionDirection = dir;
transitionStartTime = performance.now();
export function setGLBRepulsionSystem(system) {
glbRepulsionSystem = system;
}
// Calculate camera-relative transition vectors for diagonal movement
export function calculateTransitionVectors(camera) {
// Get camera's world direction
const cameraDirection = new THREE.Vector3();
camera.getWorldDirection(cameraDirection);
// Get world up vector
const worldUp = new THREE.Vector3(0, 1, 0);
// Calculate camera's left vector - BACK TO ORIGINAL (this gave correct left direction)
const cameraLeft = new THREE.Vector3();
cameraLeft.crossVectors(worldUp, cameraDirection).normalize();
// Calculate camera's local up vector
const cameraUp = new THREE.Vector3();
cameraUp.crossVectors(cameraLeft, cameraDirection).normalize();
// Blend camera up with world up - BUT NEGATE to flip up/down direction
const blendedUp = new THREE.Vector3();
blendedUp.addVectors(
cameraUp.clone().multiplyScalar(0.5),
worldUp.clone().multiplyScalar(0.5)
).normalize().negate(); // ADD .negate() here to flip up to down
// Create diagonal vector (up-left)
const diagonalUpLeft = new THREE.Vector3();
diagonalUpLeft.addVectors(
blendedUp.clone().multiplyScalar(0.5),
cameraLeft.clone().multiplyScalar(0.5)
).normalize();
// Set transition vectors
transitionUpVector = diagonalUpLeft.clone().multiplyScalar(transitionDistance);
transitionDownVector = diagonalUpLeft.clone().multiplyScalar(-transitionDistance);
console.log('Diagonal transition vectors calculated with distance:', transitionDistance);
}
// Start transition to next or previous scene
export function startTransition(direction = 1, preloadedModels, scene, camera, controls) {
if (isTransitioning) return;
// Check bounds - now 4 scenes (0-3)
if (direction > 0 && currentScene >= 3) return; // Can't go forward from storytelling
if (direction < 0 && currentScene <= 0) return; // Can't go backward from bold
console.log(`Starting diagonal transition: direction=${direction}, currentScene=${currentScene}`);
// Calculate camera-relative diagonal transition vectors
calculateTransitionVectors(camera); calculateTransitionVectors(camera);
isTransitioning = true; const { model, animMixer } =
transitionStartTime = performance.now(); createModelFromPreloaded(sceneKey(nextIdx), preload, camera, controls);
transitionDirection = direction;
// Determine next model based on direction and current scene nextModel = model;
let nextModelType = ''; nextMixer = animMixer;
if (direction > 0) { nextModel.position.copy(dir > 0 ? transitionDownVector : transitionUpVector);
// Moving forward
if (currentScene === 0) {
nextModelType = 'innovation';
} else if (currentScene === 1) {
nextModelType = 'agility';
} else if (currentScene === 2) {
nextModelType = 'storytelling';
}
} else {
// Moving backward
if (currentScene === 1) {
nextModelType = 'bold';
} else if (currentScene === 2) {
nextModelType = 'innovation';
} else if (currentScene === 3) {
nextModelType = 'agility';
}
}
console.log(`Next model type: ${nextModelType}`); if(glbRepulsionSystem)
if (nextModelType) { glbRepulsionSystem.originalPositions.set(nextModel, nextModel.position.clone());
const { model, animMixer } = createModelFromPreloaded(nextModelType, preloadedModels, camera, controls);
nextModel = model;
nextMixer = animMixer;
// Position next model based on transition direction scene.add(nextModel);
if (transitionDirection === 1) {
// Forward: next model starts from diagonal down position (bottom-right)
nextModel.position.copy(transitionDownVector);
console.log(`Next model positioned at diagonal down vector (bottom-right): x=${nextModel.position.x}, y=${nextModel.position.y}, z=${nextModel.position.z}`);
} else {
// Backward: next model starts from diagonal up position (top-left)
nextModel.position.copy(transitionUpVector);
console.log(`Next model positioned at diagonal up vector (top-left): x=${nextModel.position.x}, y=${nextModel.position.y}, z=${nextModel.position.z}`);
}
// Register next model with GLB repulsion system
if (glbRepulsionSystem) {
glbRepulsionSystem.originalPositions.set(nextModel, nextModel.position.clone());
}
// Add next model to scene without opacity changes - it will appear instantly when it enters the camera view
scene.add(nextModel);
}
} }
// Update transition animation /* ------------------------------------------------------------------ */
export function updateTransition(deltaTime, scene) { /* transition update */
if (!isTransitioning) return; export function updateTransition(_dt, scene){
if(!isTransitioning) return;
const elapsed = (performance.now() - transitionStartTime) / 1000; const t = Math.min((performance.now() - transitionStartTime)/1000 / transitionDuration, 1);
const transitionProgress = Math.min(elapsed / transitionDuration, 1); const e = t*t*(3-2*t); // smoothstep easing
// Smooth easing function (ease-in-out) if(currentModel)
const easeInOut = (t) => t * t * (3 - 2 * t); currentModel.position.copy(
const easedProgress = easeInOut(transitionProgress); (transitionDirection>0 ? transitionUpVector : transitionDownVector).clone().multiplyScalar(e)
);
if (currentModel) { if(nextModel)
// Move current model along diagonal vector based on transition direction nextModel.position.copy(
let moveVector; (transitionDirection>0 ? transitionDownVector : transitionUpVector).clone().multiplyScalar(1-e)
if (transitionDirection === 1) { );
// Forward: current model moves top-left
moveVector = transitionUpVector.clone().multiplyScalar(easedProgress); if(t < 1) return; // still animating
console.log('Current model moving top-left (forward transition)');
} else { /* ----- complete transition -------------------------------------- */
// Backward: current model moves bottom-right if(currentModel){
moveVector = transitionDownVector.clone().multiplyScalar(easedProgress); currentModel.traverse(o => o.isMesh && resetMeshGeometry(o));
console.log('Current model moving bottom-right (backward transition)'); cleanupGeometryData(currentModel);
if(glbRepulsionSystem){
glbRepulsionSystem.originalPositions.delete(currentModel);
glbRepulsionSystem.currentTargets.delete(currentModel);
} }
currentModel.position.copy(moveVector); scene.remove(currentModel);
} }
if (nextModel) { currentModel = nextModel;
// Move next model from diagonal vector to center based on transition direction mixer = nextMixer;
let moveVector; currentModel.position.set(0,0,0);
if (transitionDirection === 1) {
// Forward: next model moves from bottom-right to center
moveVector = transitionDownVector.clone().multiplyScalar(1 - easedProgress);
console.log('Next model moving from bottom-right to center (forward transition)');
} else {
// Backward: next model moves from top-left to center
moveVector = transitionUpVector.clone().multiplyScalar(1 - easedProgress);
console.log('Next model moving from top-left to center (backward transition)');
}
nextModel.position.copy(moveVector);
}
// Complete transition if(glbRepulsionSystem)
if (transitionProgress >= 1) { glbRepulsionSystem.originalPositions.set(currentModel, currentModel.position.clone());
console.log('Diagonal transition animation complete');
// FIXED: Reset geometry before removing the model
if (currentModel) {
// Reset all geometry to original state before removal
currentModel.traverse((object) => {
if (object.isMesh) {
resetMeshGeometry(object);
}
});
// Clean up geometry user data completely
cleanupGeometryData(currentModel);
// Remove from GLB repulsion system
if (glbRepulsionSystem) {
glbRepulsionSystem.originalPositions.delete(currentModel);
glbRepulsionSystem.currentTargets.delete(currentModel);
}
scene.remove(currentModel);
console.log('Previous model removed from scene');
}
// Switch to next model nextModel = nextMixer = null;
if (nextModel) {
currentModel = nextModel;
mixer = nextMixer;
// Reset position to center
currentModel.position.set(0, 0, 0);
// Update GLB repulsion system with new center position
if (glbRepulsionSystem) {
glbRepulsionSystem.originalPositions.set(currentModel, currentModel.position.clone());
}
}
nextModel = null; currentScene = pendingScene; // now official
nextMixer = null; isTransitioning = false;
isTransitioning = false; scrollDownCount = scrollUpCount = 0;
currentScene += transitionDirection; // Update scene based on direction
scrollDownCount = 0;
scrollUpCount = 0;
// Start animations based on current scene if(currentScene === 0) startBoldRoughnessAnimation(false);
if (currentScene === 0) { if(currentScene === 1) startInnovationGlassAnimation();
// Restart bold roughness animation when returning to bold section WITHOUT delay
startBoldRoughnessAnimation(false);
} else if (currentScene === 1) {
startInnovationGlassAnimation();
}
console.log(`Diagonal transition complete. Current scene: ${currentScene}`);
}
} }
// Scroll event handler /* ------------------------------------------------------------------ */
export function onMouseScroll(event, preloadedModels, scene, camera, controls) { /* scroll handler */
if (isTransitioning) return; export function onMouseScroll(ev, preload, scene, camera, controls){
if(isTransitioning) return;
if (event.deltaY > 0) { if(ev.deltaY > 0){
// Scrolling down - move forward scrollDownCount++; scrollUpCount = 0;
scrollDownCount++; if(scrollDownCount >= scrollThreshold)
scrollUpCount = 0; // Reset up count startTransition(+1, preload, scene, camera, controls);
console.log(`Scroll down count: ${scrollDownCount}`); }else if(ev.deltaY < 0){
if (scrollDownCount >= scrollThreshold) { scrollUpCount++; scrollDownCount = 0;
startTransition(1, preloadedModels, scene, camera, controls); // Forward direction if(scrollUpCount >= scrollThreshold)
} startTransition(-1, preload, scene, camera, controls);
} else if (event.deltaY < 0) {
// Scrolling up - move backward
scrollUpCount++;
scrollDownCount = 0; // Reset down count
console.log(`Scroll up count: ${scrollUpCount}`);
if (scrollUpCount >= scrollThreshold) {
startTransition(-1, preloadedModels, scene, camera, controls); // Backward direction
}
} }
} }