Compare commits

...

2 commits

Author SHA1 Message Date
Anuj K 8390ac15b1 finalised, bold and innovation fixed 2025-08-30 16:07:42 +05:30
Anuj K ae39d5bbf4 bold change, transition change 2025-08-30 12:49:24 +05:30
3 changed files with 630 additions and 758 deletions

Binary file not shown.

Binary file not shown.

View file

@ -1,5 +1,4 @@
import './style.css' import './style.css'
import * as THREE from 'three'; import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'; import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
@ -16,14 +15,12 @@ class SceneLoader {
this.loadingText = document.getElementById('loading-text'); this.loadingText = document.getElementById('loading-text');
this.loadingProgressBar = document.getElementById('loading-progress-bar'); this.loadingProgressBar = document.getElementById('loading-progress-bar');
this.loadingPercentage = document.getElementById('loading-percentage'); this.loadingPercentage = document.getElementById('loading-percentage');
this.modelsToLoad = [ this.modelsToLoad = [
{ file: 'bold.glb', type: 'bold' }, { file: 'bold.glb', type: 'bold' },
{ file: 'innovation.glb', type: 'innovation' }, { file: 'innovation.glb', type: 'innovation' },
{ file: 'agility.glb', type: 'agility' }, { file: 'agility.glb', type: 'agility' },
{ file: 'storytelling.glb', type: 'storytelling' } { file: 'storytelling.glb', type: 'storytelling' }
]; ];
this.loadedModels = {}; this.loadedModels = {};
this.loadedCount = 0; this.loadedCount = 0;
this.totalModels = this.modelsToLoad.length; this.totalModels = this.modelsToLoad.length;
@ -52,11 +49,8 @@ class SceneLoader {
const dracoLoader = new DRACOLoader(); const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('node_modules/three/examples/jsm/libs/draco/'); dracoLoader.setDecoderPath('node_modules/three/examples/jsm/libs/draco/');
loader.setDRACOLoader(dracoLoader); loader.setDRACOLoader(dracoLoader);
this.modelsToLoad.forEach((modelInfo, index) => { this.modelsToLoad.forEach((modelInfo, index) => {
// this.setLoadingMessage(`Loading ${modelInfo.type}...`);
this.setLoadingMessage(`Loading experience...`); this.setLoadingMessage(`Loading experience...`);
loader.load(`/${modelInfo.file}`, loader.load(`/${modelInfo.file}`,
(gltf) => { (gltf) => {
this.loadedModels[modelInfo.type] = { this.loadedModels[modelInfo.type] = {
@ -64,11 +58,9 @@ class SceneLoader {
animations: gltf.animations, animations: gltf.animations,
gltf: gltf gltf: gltf
}; };
this.loadedCount++; this.loadedCount++;
const progress = this.loadedCount / this.totalModels; const progress = this.loadedCount / this.totalModels;
this.updateProgress(progress); this.updateProgress(progress);
if (this.loadedCount === this.totalModels) { if (this.loadedCount === this.totalModels) {
this.setLoadingMessage('Initializing Experience...'); this.setLoadingMessage('Initializing Experience...');
setTimeout(() => { setTimeout(() => {
@ -78,7 +70,6 @@ class SceneLoader {
} }
}, },
(progress) => { (progress) => {
// Individual file progress
const fileProgress = progress.loaded / progress.total; const fileProgress = progress.loaded / progress.total;
const totalProgress = (this.loadedCount + fileProgress) / this.totalModels; const totalProgress = (this.loadedCount + fileProgress) / this.totalModels;
this.updateProgress(totalProgress); this.updateProgress(totalProgress);
@ -94,25 +85,18 @@ class SceneLoader {
// Initialize loader // Initialize loader
const sceneLoader = new SceneLoader(); const sceneLoader = new SceneLoader();
// You can customize the loading message here:
sceneLoader.setLoadingMessage('Preparing Your Experience...'); sceneLoader.setLoadingMessage('Preparing Your Experience...');
// Scene setup // Scene setup
const scene = new THREE.Scene(); const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.setFocalLength(50); camera.setFocalLength(50);
const raycaster = new THREE.Raycaster(); const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2(); const mouse = new THREE.Vector2();
// Transition state management // Transition state management
let currentScene = 0; // 0: bold, 1: innovation, 2: agility, 3: storytelling let currentScene = 0; // 0: bold, 1: innovation, 2: agility, 3: storytelling
let isTransitioning = false; let isTransitioning = false;
let isTwisting = false;
let twistProgress = 0;
const twistSpeed = 0.02; // Easily adjustable twist speed
const twistStrength = 0.3;
const fadeSpeed = 1; // Easily adjustable fade speed const fadeSpeed = 1; // Easily adjustable fade speed
const transitionDuration = 1; // Easily adjustable transition duration (seconds) const transitionDuration = 1; // Easily adjustable transition duration (seconds)
let scrollDownCount = 0; let scrollDownCount = 0;
@ -121,6 +105,11 @@ const scrollThreshold = 10; // Changed to 10 as requested
let transitionStartTime = 0; let transitionStartTime = 0;
let transitionDirection = 1; // 1 for forward, -1 for backward let transitionDirection = 1; // 1 for forward, -1 for backward
// Camera-relative transition vectors
let transitionUpVector = new THREE.Vector3();
let transitionDownVector = new THREE.Vector3();
const transitionDistance = 50; // Increased distance for more dramatic transitions
// Scene objects // Scene objects
let currentModel = null; let currentModel = null;
let nextModel = null; let nextModel = null;
@ -128,6 +117,9 @@ let mixer = null;
let nextMixer = null; let nextMixer = null;
let autoRotationAngle = 0; let autoRotationAngle = 0;
// Turntable animation settings
const turntableSpeed = 0.5; // Rotation speed (radians per second)
// Store preloaded models // Store preloaded models
let preloadedModels = {}; let preloadedModels = {};
@ -135,9 +127,9 @@ let preloadedModels = {};
let boldRoughnessAnimation = { let boldRoughnessAnimation = {
isActive: false, isActive: false,
startTime: 0, startTime: 0,
delayDuration: 1.0, // 1 second delay delayDuration: 1.0, // 1 second delay (will be dynamic)
transitionDuration: 1.0, // 1 second transition transitionDuration: 1.0, // 1 second transition
startRoughness: 0.25, startRoughness: 0.5,
endRoughness: 0.05, endRoughness: 0.05,
materials: [] // Store references to bold materials materials: [] // Store references to bold materials
}; };
@ -171,7 +163,6 @@ document.body.appendChild(renderer.domElement);
const composer = new EffectComposer(renderer); const composer = new EffectComposer(renderer);
const renderPass = new RenderPass(scene, camera); const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass); composer.addPass(renderPass);
const bloomPass = new UnrealBloomPass( const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight), new THREE.Vector2(window.innerWidth, window.innerHeight),
1.0, // strength 1.0, // strength
@ -188,7 +179,6 @@ video.loop = true;
video.playsInline = true; video.playsInline = true;
video.autoplay = true; video.autoplay = true;
video.preload = 'auto'; video.preload = 'auto';
const videoTexture = new THREE.VideoTexture(video); const videoTexture = new THREE.VideoTexture(video);
videoTexture.colorSpace = THREE.SRGBColorSpace; videoTexture.colorSpace = THREE.SRGBColorSpace;
videoTexture.generateMipmaps = false; videoTexture.generateMipmaps = false;
@ -196,7 +186,7 @@ videoTexture.minFilter = THREE.LinearFilter;
videoTexture.magFilter = THREE.LinearFilter; videoTexture.magFilter = THREE.LinearFilter;
// Ensure autoplay starts (muted autoplay is commonly allowed) // Ensure autoplay starts (muted autoplay is commonly allowed)
video.play().catch(() => {}); video.play().catch(() => { });
// Local procedural environment for better PBR response (no network) // Local procedural environment for better PBR response (no network)
const pmrem = new THREE.PMREMGenerator(renderer); const pmrem = new THREE.PMREMGenerator(renderer);
@ -209,55 +199,52 @@ scene.environment = null; // This will make the renderer's clear color visible a
// Consistent Lighting Setup // Consistent Lighting Setup
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight); scene.add(ambientLight);
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x666666, 1.5); const hemiLight = new THREE.HemisphereLight(0xffffff, 0x666666, 1.5);
hemiLight.position.set(0, 20, 0); hemiLight.position.set(0, 20, 0);
scene.add(hemiLight); scene.add(hemiLight);
const fillLight = new THREE.DirectionalLight(0xffffff, 1.2); const fillLight = new THREE.DirectionalLight(0xffffff, 1.2);
fillLight.position.set(-12, 6, -8); fillLight.position.set(-12, 6, -8);
scene.add(fillLight); scene.add(fillLight);
const topLight = new THREE.DirectionalLight(0xffffff, 1.5); const topLight = new THREE.DirectionalLight(0xffffff, 1.5);
topLight.position.set(5, 15, 5); topLight.position.set(5, 15, 5);
scene.add(topLight); scene.add(topLight);
const bottomLight = new THREE.DirectionalLight(0xffffff, 0.8); const bottomLight = new THREE.DirectionalLight(0xffffff, 0.8);
bottomLight.position.set(-3, -8, 3); bottomLight.position.set(-3, -8, 3);
scene.add(bottomLight); scene.add(bottomLight);
const leftLight = new THREE.DirectionalLight(0xffffff, 1.0); const leftLight = new THREE.DirectionalLight(0xffffff, 1.0);
leftLight.position.set(-12, 2, 5); leftLight.position.set(-12, 2, 5);
scene.add(leftLight); scene.add(leftLight);
const rightLight = new THREE.DirectionalLight(0xffffff, 1.0); const rightLight = new THREE.DirectionalLight(0xffffff, 1.0);
rightLight.position.set(12, 2, -5); rightLight.position.set(12, 2, -5);
scene.add(rightLight); scene.add(rightLight);
const frontLight = new THREE.DirectionalLight(0xffffff, 0.8); const frontLight = new THREE.DirectionalLight(0xffffff, 0.8);
frontLight.position.set(8, 4, 12); frontLight.position.set(8, 4, 12);
scene.add(frontLight); scene.add(frontLight);
const backLight = new THREE.DirectionalLight(0xffffff, 0.8); const backLight = new THREE.DirectionalLight(0xffffff, 0.8);
backLight.position.set(-8, 4, -12); backLight.position.set(-8, 4, -12);
scene.add(backLight); scene.add(backLight);
const cameraLight = new THREE.PointLight(0xffffff, 0.8, 0, 2); const cameraLight = new THREE.PointLight(0xffffff, 0.8, 0, 2);
camera.add(cameraLight); camera.add(cameraLight);
scene.add(camera); scene.add(camera);
// Controls with zoom disabled // Controls with zoom disabled and camera constraints
const controls = new OrbitControls(camera, renderer.domElement); const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; controls.enableDamping = true;
controls.dampingFactor = 0.25; controls.dampingFactor = 0.25;
controls.enableZoom = false; // Disable zoom controls.enableZoom = false; // Disable zoom
// Add camera constraints to prevent extreme angles
controls.maxPolarAngle = Math.PI * 0.8; // Prevent looking too far up
controls.minPolarAngle = Math.PI * 0.2; // Prevent looking too far down
console.log('Orbit controls initialized with camera constraints');
// Material definitions // Material definitions
// Bold glass material (starts rough, will transition to clear) // Bold glass material (starts rough, will transition to clear)
const boldGlassMaterial = new THREE.MeshPhysicalMaterial({ const boldGlassMaterial = new THREE.MeshPhysicalMaterial({
color: 0xFFA500, color: 0xffffff,
metalness: 0.2, metalness: 0.2,
roughness: 0.25, // Start with rough glass roughness: 0.5, // Start with rough glass
transmission: 1, transmission: 1,
ior: 2, ior: 2,
thickness: 2, thickness: 2,
@ -273,6 +260,13 @@ const boldGlassMaterial = new THREE.MeshPhysicalMaterial({
alphaTest: 0 alphaTest: 0
}); });
// Orange wireframe material for bold Cubewire mesh
const boldWireframeMaterial = new THREE.MeshStandardMaterial({
color: 0xff8600,
metalness: 0.05,
roughness: 0.5
});
// Clear thick glass for innovation (starts with animated values) // Clear thick glass for innovation (starts with animated values)
const innovationGlassMaterial = new THREE.MeshPhysicalMaterial({ const innovationGlassMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff, color: 0xffffff,
@ -324,11 +318,41 @@ const lightOrangeMaterial = new THREE.MeshStandardMaterial({
emissiveIntensity: 2.25 emissiveIntensity: 2.25
}); });
// Calculate camera-relative transition vectors for diagonal movement
function calculateTransitionVectors() {
// 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);
}
// Apply materials based on model type // Apply materials based on model type
function applyMaterials(model, modelType) { function applyMaterials(model, modelType) {
console.log(`=== Material Assignment Debug for ${modelType} ===`); console.log(`=== Material Assignment Debug for ${modelType} ===`);
let meshCount = 0; let meshCount = 0;
model.traverse((object) => { model.traverse((object) => {
if (object.isMesh) { if (object.isMesh) {
meshCount++; meshCount++;
@ -336,9 +360,8 @@ function applyMaterials(model, modelType) {
const previousMaterial = object.material; const previousMaterial = object.material;
object.castShadow = true; object.castShadow = true;
object.receiveShadow = true; object.receiveShadow = true;
if (modelType === 'bold') { if (modelType === 'bold') {
// Bold-specific material logic - apply bold glass material to Cube mesh // Bold-specific material logic
if (object.name === 'Cube') { if (object.name === 'Cube') {
console.log(` → Applying bold glass material to "${object.name}"`); console.log(` → Applying bold glass material to "${object.name}"`);
object.material = boldGlassMaterial.clone(); object.material = boldGlassMaterial.clone();
@ -347,6 +370,10 @@ function applyMaterials(model, modelType) {
object.renderOrder = 2; object.renderOrder = 2;
// Store material reference for roughness animation // Store material reference for roughness animation
boldRoughnessAnimation.materials.push(object.material); boldRoughnessAnimation.materials.push(object.material);
} else if (object.name === 'Cubewire') {
console.log(` → Applying wireframe material to "${object.name}"`);
object.material = boldWireframeMaterial.clone();
object.renderOrder = 1;
} else { } else {
console.log(` → Applying bold glass material (fallback) to "${object.name}"`); console.log(` → Applying bold glass material (fallback) to "${object.name}"`);
object.material = boldGlassMaterial.clone(); object.material = boldGlassMaterial.clone();
@ -357,7 +384,6 @@ function applyMaterials(model, modelType) {
// Innovation-specific material logic // Innovation-specific material logic
const orangeMeshes = ['dblsc', 'ec', 'gemini', 'infinity', 'star', 'dpd']; const orangeMeshes = ['dblsc', 'ec', 'gemini', 'infinity', 'star', 'dpd'];
const targetGlassNames = ['Cube.alt90.df']; const targetGlassNames = ['Cube.alt90.df'];
const sanitize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, ''); const sanitize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, '');
const nameMatches = (name, targets) => { const nameMatches = (name, targets) => {
const clean = sanitize(name); const clean = sanitize(name);
@ -366,17 +392,14 @@ function applyMaterials(model, modelType) {
return clean === ct || clean.includes(ct) || ct.includes(clean); return clean === ct || clean.includes(ct) || ct.includes(clean);
}); });
}; };
if (nameMatches(object.name, targetGlassNames)) { if (nameMatches(object.name, targetGlassNames)) {
// Create outer glass shell with innovation-specific material // Create outer glass shell with innovation-specific material
object.material = innovationGlassMaterial.clone(); object.material = innovationGlassMaterial.clone();
object.material.side = THREE.DoubleSide; object.material.side = THREE.DoubleSide;
object.material.depthWrite = false; object.material.depthWrite = false;
object.renderOrder = 2; object.renderOrder = 2;
// Store material reference for animation // Store material reference for animation
innovationGlassAnimation.materials.push(object.material); innovationGlassAnimation.materials.push(object.material);
// Create inner glass shell // Create inner glass shell
const innerShell = object.clone(); const innerShell = object.clone();
innerShell.material = innovationGlassMaterial.clone(); innerShell.material = innovationGlassMaterial.clone();
@ -385,10 +408,8 @@ function applyMaterials(model, modelType) {
innerShell.material.transmission = 0.8; innerShell.material.transmission = 0.8;
innerShell.renderOrder = 1; innerShell.renderOrder = 1;
innerShell.scale.multiplyScalar(0.95); innerShell.scale.multiplyScalar(0.95);
// Store inner shell material reference for animation too // Store inner shell material reference for animation too
innovationGlassAnimation.materials.push(innerShell.material); innovationGlassAnimation.materials.push(innerShell.material);
object.parent.add(innerShell); object.parent.add(innerShell);
} else if (nameMatches(object.name, orangeMeshes)) { } else if (nameMatches(object.name, orangeMeshes)) {
object.material = lightOrangeMaterial.clone(); object.material = lightOrangeMaterial.clone();
@ -404,9 +425,7 @@ function applyMaterials(model, modelType) {
object.material = frostedGlassMaterial.clone(); object.material = frostedGlassMaterial.clone();
} }
} }
object.material.needsUpdate = true; object.material.needsUpdate = true;
// Cleanup previous materials // Cleanup previous materials
if (Array.isArray(previousMaterial)) { if (Array.isArray(previousMaterial)) {
previousMaterial.forEach((mat) => mat && mat.dispose && mat.dispose()); previousMaterial.forEach((mat) => mat && mat.dispose && mat.dispose());
@ -415,7 +434,6 @@ function applyMaterials(model, modelType) {
} }
} }
}); });
console.log(`Total meshes processed: ${meshCount}`); console.log(`Total meshes processed: ${meshCount}`);
console.log(`=== End Material Assignment Debug for ${modelType} ===`); console.log(`=== End Material Assignment Debug for ${modelType} ===`);
} }
@ -426,7 +444,6 @@ function centerAndFrameModel(model, targetCamera = camera) {
const center = box.getCenter(new THREE.Vector3()); const center = box.getCenter(new THREE.Vector3());
model.position.sub(center); model.position.sub(center);
model.updateMatrixWorld(true); model.updateMatrixWorld(true);
// Only set camera position if it's not already positioned (avoid reset during transitions) // Only set camera position if it's not already positioned (avoid reset during transitions)
// Use fixed camera distance that's further away from the origin // Use fixed camera distance that's further away from the origin
if (!isTransitioning) { if (!isTransitioning) {
@ -439,11 +456,11 @@ function centerAndFrameModel(model, targetCamera = camera) {
const z = fixedCameraDistance * cosAngle; const z = fixedCameraDistance * cosAngle;
targetCamera.position.set(x, y, z); targetCamera.position.set(x, y, z);
controls.target.set(0, 0, 0); controls.target.set(0, 0, 0);
// Set distance limits to lock the camera at this distance // Set distance limits to lock the camera at this distance
controls.minDistance = fixedCameraDistance; controls.minDistance = fixedCameraDistance;
controls.maxDistance = fixedCameraDistance; controls.maxDistance = fixedCameraDistance;
controls.update(); controls.update();
console.log(`Camera positioned at: x=${x}, y=${y}, z=${z}, distance=${fixedCameraDistance}`);
} }
} }
@ -451,7 +468,6 @@ function centerAndFrameModel(model, targetCamera = camera) {
function setupAnimations(model, gltf, modelType) { function setupAnimations(model, gltf, modelType) {
if (gltf.animations && gltf.animations.length > 0) { if (gltf.animations && gltf.animations.length > 0) {
const animMixer = new THREE.AnimationMixer(model); const animMixer = new THREE.AnimationMixer(model);
gltf.animations.forEach((clip) => { gltf.animations.forEach((clip) => {
const action = animMixer.clipAction(clip); const action = animMixer.clipAction(clip);
if (modelType === 'bold') { if (modelType === 'bold') {
@ -459,29 +475,31 @@ function setupAnimations(model, gltf, modelType) {
action.loop = THREE.LoopOnce; action.loop = THREE.LoopOnce;
action.clampWhenFinished = true; action.clampWhenFinished = true;
action.play(); action.play();
console.log(`Bold animation started: ${clip.name}`);
} else if (modelType === 'innovation') { } else if (modelType === 'innovation') {
// PingPong loop for innovation // PingPong loop for innovation
action.loop = THREE.LoopPingPong; action.loop = THREE.LoopPingPong;
action.play(); action.play();
console.log(`Innovation animation started: ${clip.name} (PingPong)`);
} else if (modelType === 'agility') { } else if (modelType === 'agility') {
// Regular loop for agility // Regular loop for agility
action.loop = THREE.LoopRepeat; action.loop = THREE.LoopRepeat;
action.play(); action.play();
console.log(`Agility animation started: ${clip.name} (Loop)`);
} else if (modelType === 'storytelling') { } else if (modelType === 'storytelling') {
// Play once for storytelling // Play once for storytelling
action.loop = THREE.LoopOnce; action.loop = THREE.LoopOnce;
action.clampWhenFinished = true; action.clampWhenFinished = true;
action.play(); action.play();
console.log(`Storytelling animation started: ${clip.name}`);
} }
}); });
if (modelType === 'innovation') { if (modelType === 'innovation') {
animMixer.timeScale = 3.0; // Keep existing timeScale for innovation animMixer.timeScale = 3.0; // Keep existing timeScale for innovation
console.log('Innovation animation timeScale set to 3.0');
} }
return animMixer; return animMixer;
} }
return null; return null;
} }
@ -492,142 +510,82 @@ function createModelFromPreloaded(modelType) {
console.error(`Preloaded model not found: ${modelType}`); console.error(`Preloaded model not found: ${modelType}`);
return { model: null, animMixer: null }; return { model: null, animMixer: null };
} }
console.log(`Creating model from preloaded data: ${modelType}`);
// Clear animation materials arrays when creating new models // Clear animation materials arrays when creating new models
if (modelType === 'bold') { if (modelType === 'bold') {
boldRoughnessAnimation.materials = []; boldRoughnessAnimation.materials = [];
} else if (modelType === 'innovation') { } else if (modelType === 'innovation') {
innovationGlassAnimation.materials = []; innovationGlassAnimation.materials = [];
} }
// Clone the scene deeply to ensure fresh geometry // Clone the scene deeply to ensure fresh geometry
const model = preloadedData.scene.clone(true); const model = preloadedData.scene.clone(true);
// IMPORTANT: Clone all geometries to ensure they're independent // IMPORTANT: Clone all geometries to ensure they're independent
model.traverse((object) => { model.traverse((object) => {
if (object.isMesh && object.geometry) { if (object.isMesh && object.geometry) {
object.geometry = object.geometry.clone(); object.geometry = object.geometry.clone();
// Clear any previous twist data
delete object.geometry.userData.originalPositions;
delete object.geometry.userData.originalWorldPositions;
delete object.geometry.userData.inverseWorldMatrix;
} }
}); });
// Apply materials // Apply materials
applyMaterials(model, modelType); applyMaterials(model, modelType);
// Setup animations // Setup animations
const animMixer = setupAnimations(model, preloadedData.gltf, modelType); const animMixer = setupAnimations(model, preloadedData.gltf, modelType);
// Center and frame model // Center and frame model
centerAndFrameModel(model); centerAndFrameModel(model);
console.log(`Model created successfully: ${modelType}`);
return { model, animMixer }; return { model, animMixer };
} }
// Start/restart bold roughness animation with optional delay control
function startBoldRoughnessAnimation(withDelay = true) {
console.log('Starting/restarting bold roughness animation');
// Reset all bold glass materials to starting roughness value
boldRoughnessAnimation.materials.forEach(material => {
material.roughness = boldRoughnessAnimation.startRoughness;
material.needsUpdate = true;
});
boldRoughnessAnimation.isActive = true;
boldRoughnessAnimation.startTime = performance.now();
// Set delayDuration based on withDelay parameter
boldRoughnessAnimation.delayDuration = withDelay ? 1.0 : 0.0;
console.log('Bold roughness animation started with delay:', withDelay);
}
// Initialize first scene after all models are loaded // Initialize first scene after all models are loaded
function initializeScene() { function initializeScene() {
console.log('Initializing first scene (bold)');
const { model, animMixer } = createModelFromPreloaded('bold'); const { model, animMixer } = createModelFromPreloaded('bold');
currentModel = model; currentModel = model;
mixer = animMixer; mixer = animMixer;
scene.add(currentModel); scene.add(currentModel);
// Start the roughness animation for bold scene with delay
// Start the roughness animation for bold scene startBoldRoughnessAnimation(true);
boldRoughnessAnimation.isActive = true; console.log('Bold scene initialized');
boldRoughnessAnimation.startTime = performance.now();
} }
// Start innovation glass animation // Start innovation glass animation
function startInnovationGlassAnimation() { function startInnovationGlassAnimation() {
console.log('Starting innovation glass animation');
// Reset all innovation glass materials to starting values // Reset all innovation glass materials to starting values
innovationGlassAnimation.materials.forEach(material => { innovationGlassAnimation.materials.forEach(material => {
material.ior = innovationGlassAnimation.startIor; material.ior = innovationGlassAnimation.startIor;
material.thickness = innovationGlassAnimation.startThickness; material.thickness = innovationGlassAnimation.startThickness;
material.needsUpdate = true; material.needsUpdate = true;
}); });
innovationGlassAnimation.isActive = true; innovationGlassAnimation.isActive = true;
innovationGlassAnimation.startTime = performance.now(); innovationGlassAnimation.startTime = performance.now();
console.log('Innovation glass animation started'); console.log('Innovation glass animation started');
} }
// Twist animation function - Updated to twist around world center (0,0,0)
function twistMesh(mesh, progress) {
if (!mesh || !mesh.geometry || !mesh.geometry.attributes.position) {
return;
}
const positions = mesh.geometry.attributes.position;
// Store original positions on the first run
if (!mesh.geometry.userData.originalPositions) {
mesh.geometry.userData.originalPositions = new Float32Array(positions.array);
// Store original world positions for each vertex
mesh.geometry.userData.originalWorldPositions = [];
// Update world matrix to get accurate world positions
mesh.updateMatrixWorld(true);
const tempVector = new THREE.Vector3();
for (let i = 0; i < positions.count; i++) {
tempVector.fromBufferAttribute(positions, i);
tempVector.applyMatrix4(mesh.matrixWorld);
mesh.geometry.userData.originalWorldPositions.push({
x: tempVector.x,
y: tempVector.y,
z: tempVector.z
});
}
// Store the inverse of the current world matrix for transforming back to local space
mesh.geometry.userData.inverseWorldMatrix = mesh.matrixWorld.clone().invert();
}
const originalWorldPositions = mesh.geometry.userData.originalWorldPositions;
const inverseWorldMatrix = mesh.geometry.userData.inverseWorldMatrix;
for (let i = 0; i < positions.count; i++) {
const worldPos = originalWorldPositions[i];
// Use world Y position for consistent twisting around world Y-axis
const worldY = worldPos.y;
// Calculate twist angle based on world Y position
// Normalize Y based on a reasonable range (adjust as needed)
const normalizedY = (worldY + 5) / 10; // Assuming meshes are roughly within -5 to +5 world units in Y
const twistAngle = normalizedY * progress * twistStrength * 2 * Math.PI;
// Apply twist in world coordinates around world Y-axis
const twistedWorldX = worldPos.x * Math.cos(twistAngle) - worldPos.z * Math.sin(twistAngle);
const twistedWorldY = worldPos.y; // Y remains unchanged
const twistedWorldZ = worldPos.x * Math.sin(twistAngle) + worldPos.z * Math.cos(twistAngle);
// Convert twisted world position back to local coordinates
const twistedWorldVector = new THREE.Vector3(twistedWorldX, twistedWorldY, twistedWorldZ);
twistedWorldVector.applyMatrix4(inverseWorldMatrix);
positions.setXYZ(i, twistedWorldVector.x, twistedWorldVector.y, twistedWorldVector.z);
}
positions.needsUpdate = true;
mesh.geometry.computeVertexNormals();
}
// Reset mesh geometry to original state // Reset mesh geometry to original state
function resetMeshGeometry(mesh) { function resetMeshGeometry(mesh) {
if (!mesh || !mesh.geometry || !mesh.geometry.userData.originalPositions) { if (!mesh || !mesh.geometry || !mesh.geometry.userData.originalPositions) {
return; return;
} }
const positions = mesh.geometry.attributes.position; const positions = mesh.geometry.attributes.position;
const original = mesh.geometry.userData.originalPositions; const original = mesh.geometry.userData.originalPositions;
for (let i = 0; i < positions.count; i++) { for (let i = 0; i < positions.count; i++) {
positions.setXYZ(i, original[i * 3], original[i * 3 + 1], original[i * 3 + 2]); positions.setXYZ(i, original[i * 3], original[i * 3 + 1], original[i * 3 + 2]);
} }
positions.needsUpdate = true; positions.needsUpdate = true;
mesh.geometry.computeVertexNormals(); mesh.geometry.computeVertexNormals();
} }
@ -635,7 +593,6 @@ function resetMeshGeometry(mesh) {
// FIXED: Clean up geometry data completely // FIXED: Clean up geometry data completely
function cleanupGeometryData(model) { function cleanupGeometryData(model) {
if (!model) return; if (!model) return;
model.traverse((object) => { model.traverse((object) => {
if (object.isMesh && object.geometry && object.geometry.userData) { if (object.isMesh && object.geometry && object.geometry.userData) {
delete object.geometry.userData.originalPositions; delete object.geometry.userData.originalPositions;
@ -648,20 +605,17 @@ function cleanupGeometryData(model) {
// Start transition to next or previous scene // Start transition to next or previous scene
function startTransition(direction = 1) { function startTransition(direction = 1) {
if (isTransitioning) return; if (isTransitioning) return;
// Check bounds - now 4 scenes (0-3) // Check bounds - now 4 scenes (0-3)
if (direction > 0 && currentScene >= 3) return; // Can't go forward from storytelling if (direction > 0 && currentScene >= 3) return; // Can't go forward from storytelling
if (direction < 0 && currentScene <= 0) return; // Can't go backward from bold 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();
isTransitioning = true; isTransitioning = true;
isTwisting = true;
twistProgress = 0;
transitionStartTime = performance.now(); transitionStartTime = performance.now();
transitionDirection = direction; transitionDirection = direction;
// Determine next model based on direction and current scene // Determine next model based on direction and current scene
let nextModelType = ''; let nextModelType = '';
if (direction > 0) { if (direction > 0) {
// Moving forward // Moving forward
if (currentScene === 0) { if (currentScene === 0) {
@ -681,27 +635,22 @@ function startTransition(direction = 1) {
nextModelType = 'agility'; nextModelType = 'agility';
} }
} }
console.log(`Next model type: ${nextModelType}`);
if (nextModelType) { if (nextModelType) {
const { model, animMixer } = createModelFromPreloaded(nextModelType); const { model, animMixer } = createModelFromPreloaded(nextModelType);
nextModel = model; nextModel = model;
nextMixer = animMixer; nextMixer = animMixer;
// Position next model based on transition direction
// Start next model as invisible but in normal position (no vertical offset) if (transitionDirection === 1) {
nextModel.position.y = 0; // Forward: next model starts from diagonal down position (bottom-right)
nextModel.traverse((obj) => { nextModel.position.copy(transitionDownVector);
if (obj.material) { console.log(`Next model positioned at diagonal down vector (bottom-right): x=${nextModel.position.x}, y=${nextModel.position.y}, z=${nextModel.position.z}`);
if (Array.isArray(obj.material)) {
obj.material.forEach(mat => {
mat.transparent = true;
mat.opacity = 0;
});
} else { } else {
obj.material.transparent = true; // Backward: next model starts from diagonal up position (top-left)
obj.material.opacity = 0; 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}`);
} }
} // Add next model to scene without opacity changes - it will appear instantly when it enters the camera view
});
scene.add(nextModel); scene.add(nextModel);
} }
} }
@ -709,54 +658,42 @@ function startTransition(direction = 1) {
// Update transition animation // Update transition animation
function updateTransition(deltaTime) { function updateTransition(deltaTime) {
if (!isTransitioning) return; if (!isTransitioning) return;
const elapsed = (performance.now() - transitionStartTime) / 1000; const elapsed = (performance.now() - transitionStartTime) / 1000;
const transitionProgress = Math.min(elapsed / transitionDuration, 1); const transitionProgress = Math.min(elapsed / transitionDuration, 1);
// Smooth easing function (ease-in-out) // Smooth easing function (ease-in-out)
const easeInOut = (t) => t * t * (3 - 2 * t); const easeInOut = (t) => t * t * (3 - 2 * t);
const easedProgress = easeInOut(transitionProgress); const easedProgress = easeInOut(transitionProgress);
if (currentModel) { if (currentModel) {
// Move current model up and fade out // Move current model along diagonal vector based on transition direction
// currentModel.position.y = easedProgress * 10; let moveVector;
currentModel.traverse((obj) => { if (transitionDirection === 1) {
if (obj.material) { // Forward: current model moves top-left
const targetOpacity = 1 - easedProgress; moveVector = transitionUpVector.clone().multiplyScalar(easedProgress);
if (Array.isArray(obj.material)) { console.log('Current model moving top-left (forward transition)');
obj.material.forEach(mat => {
mat.transparent = true;
mat.opacity = targetOpacity;
});
} else { } else {
obj.material.transparent = true; // Backward: current model moves bottom-right
obj.material.opacity = targetOpacity; moveVector = transitionDownVector.clone().multiplyScalar(easedProgress);
console.log('Current model moving bottom-right (backward transition)');
} }
currentModel.position.copy(moveVector);
} }
});
}
if (nextModel) { if (nextModel) {
// Keep next model in place and just fade in (no vertical movement) // Move next model from diagonal vector to center based on transition direction
nextModel.position.y = 0; let moveVector;
nextModel.traverse((obj) => { if (transitionDirection === 1) {
if (obj.material) { // Forward: next model moves from bottom-right to center
const targetOpacity = easedProgress; moveVector = transitionDownVector.clone().multiplyScalar(1 - easedProgress);
if (Array.isArray(obj.material)) { console.log('Next model moving from bottom-right to center (forward transition)');
obj.material.forEach(mat => {
mat.transparent = true;
mat.opacity = targetOpacity;
});
} else { } else {
obj.material.transparent = true; // Backward: next model moves from top-left to center
obj.material.opacity = targetOpacity; moveVector = transitionUpVector.clone().multiplyScalar(1 - easedProgress);
console.log('Next model moving from top-left to center (backward transition)');
} }
nextModel.position.copy(moveVector);
} }
});
}
// Complete transition // Complete transition
if (transitionProgress >= 1) { if (transitionProgress >= 1) {
console.log('Diagonal transition animation complete');
// FIXED: Reset geometry before removing the model // FIXED: Reset geometry before removing the model
if (currentModel) { if (currentModel) {
// Reset all geometry to original state before removal // Reset all geometry to original state before removal
@ -765,71 +702,43 @@ function updateTransition(deltaTime) {
resetMeshGeometry(object); resetMeshGeometry(object);
} }
}); });
// Clean up geometry user data completely // Clean up geometry user data completely
cleanupGeometryData(currentModel); cleanupGeometryData(currentModel);
scene.remove(currentModel); scene.remove(currentModel);
console.log('Previous model removed from scene');
} }
// Switch to next model // Switch to next model
if (nextModel) { if (nextModel) {
currentModel = nextModel; currentModel = nextModel;
mixer = nextMixer; mixer = nextMixer;
// Reset position to center
// Reset position and opacity currentModel.position.set(0, 0, 0);
currentModel.position.y = 0;
currentModel.traverse((obj) => {
if (obj.material) {
if (Array.isArray(obj.material)) {
obj.material.forEach(mat => {
mat.opacity = 1;
if (currentScene + transitionDirection === 3) { // Keep transparency for storytelling glass
mat.transparent = mat.transmission > 0;
} else {
mat.transparent = mat.transmission > 0;
} }
});
} else {
obj.material.opacity = 1;
if (currentScene + transitionDirection === 3) { // Keep transparency for storytelling glass
obj.material.transparent = obj.material.transmission > 0;
} else {
obj.material.transparent = obj.material.transmission > 0;
}
}
}
});
}
nextModel = null; nextModel = null;
nextMixer = null; nextMixer = null;
isTransitioning = false; isTransitioning = false;
isTwisting = false;
twistProgress = 0;
currentScene += transitionDirection; // Update scene based on direction currentScene += transitionDirection; // Update scene based on direction
scrollDownCount = 0; scrollDownCount = 0;
scrollUpCount = 0; scrollUpCount = 0;
// Start animations based on current scene
// Start innovation glass animation if we're now in the innovation scene if (currentScene === 0) {
if (currentScene === 1) { // Restart bold roughness animation when returning to bold section WITHOUT delay
startBoldRoughnessAnimation(false);
} else if (currentScene === 1) {
startInnovationGlassAnimation(); startInnovationGlassAnimation();
} }
console.log(`Diagonal transition complete. Current scene: ${currentScene}`);
console.log(`Transition complete. Current scene: ${currentScene}`);
} }
} }
// Scroll event handler // Scroll event handler
function onMouseScroll(event) { function onMouseScroll(event) {
if (isTransitioning) return; if (isTransitioning) return;
if (event.deltaY > 0) { if (event.deltaY > 0) {
// Scrolling down - move forward // Scrolling down - move forward
scrollDownCount++; scrollDownCount++;
scrollUpCount = 0; // Reset up count scrollUpCount = 0; // Reset up count
console.log(`Scroll down count: ${scrollDownCount}`); console.log(`Scroll down count: ${scrollDownCount}`);
if (scrollDownCount >= scrollThreshold) { if (scrollDownCount >= scrollThreshold) {
startTransition(1); // Forward direction startTransition(1); // Forward direction
} }
@ -838,7 +747,6 @@ function onMouseScroll(event) {
scrollUpCount++; scrollUpCount++;
scrollDownCount = 0; // Reset down count scrollDownCount = 0; // Reset down count
console.log(`Scroll up count: ${scrollUpCount}`); console.log(`Scroll up count: ${scrollUpCount}`);
if (scrollUpCount >= scrollThreshold) { if (scrollUpCount >= scrollThreshold) {
startTransition(-1); // Backward direction startTransition(-1); // Backward direction
} }
@ -847,110 +755,73 @@ function onMouseScroll(event) {
// 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 delta = clock.getDelta();
// Update mixers // Update mixers
if (mixer) mixer.update(delta); if (mixer) mixer.update(delta);
if (nextMixer) nextMixer.update(delta); if (nextMixer) nextMixer.update(delta);
// Update transition // Update transition
if (isTransitioning) { if (isTransitioning) {
updateTransition(delta); updateTransition(delta);
// Apply twist during transition
if (isTwisting && currentModel) {
twistProgress += twistSpeed;
if (twistProgress > 1.0) {
twistProgress = 1.0;
// FIXED: Reset geometry after twist completes
currentModel.traverse((object) => {
if (object.isMesh) {
resetMeshGeometry(object);
} }
}); // Turntable rotation animation
isTwisting = false; if (currentModel) {
} else { currentModel.rotation.y += turntableSpeed * delta;
// Apply twist to current model
currentModel.traverse((object) => {
if (object.isMesh) {
twistMesh(object, twistProgress);
} }
}); if (nextModel) {
nextModel.rotation.y += turntableSpeed * delta;
} }
}
}
// Update bold roughness animation // Update bold roughness animation
if (boldRoughnessAnimation.isActive) { if (boldRoughnessAnimation.isActive) {
const elapsed = (performance.now() - boldRoughnessAnimation.startTime) / 1000; const elapsed = (performance.now() - boldRoughnessAnimation.startTime) / 1000;
if (elapsed >= boldRoughnessAnimation.delayDuration) { if (elapsed >= boldRoughnessAnimation.delayDuration) {
// Delay period is over, start roughness transition // Delay period is over, start roughness transition
const transitionElapsed = elapsed - boldRoughnessAnimation.delayDuration; const transitionElapsed = elapsed - boldRoughnessAnimation.delayDuration;
const transitionProgress = Math.min(transitionElapsed / boldRoughnessAnimation.transitionDuration, 1); const transitionProgress = Math.min(transitionElapsed / boldRoughnessAnimation.transitionDuration, 1);
// Smooth easing function (ease-in-out) // Smooth easing function (ease-in-out)
const easeInOut = (t) => t * t * (3 - 2 * t); const easeInOut = (t) => t * t * (3 - 2 * t);
const easedProgress = easeInOut(transitionProgress); const easedProgress = easeInOut(transitionProgress);
// Interpolate roughness from 0.5 to 0.05
// Interpolate roughness from 0.25 to 0.05
const currentRoughness = boldRoughnessAnimation.startRoughness + const currentRoughness = boldRoughnessAnimation.startRoughness +
(boldRoughnessAnimation.endRoughness - boldRoughnessAnimation.startRoughness) * easedProgress; (boldRoughnessAnimation.endRoughness - boldRoughnessAnimation.startRoughness) * easedProgress;
// Apply to all bold materials // Apply to all bold materials
boldRoughnessAnimation.materials.forEach(material => { boldRoughnessAnimation.materials.forEach(material => {
material.roughness = currentRoughness; material.roughness = currentRoughness;
material.needsUpdate = true; material.needsUpdate = true;
}); });
// End animation when complete // End animation when complete
if (transitionProgress >= 1) { if (transitionProgress >= 1) {
boldRoughnessAnimation.isActive = false; boldRoughnessAnimation.isActive = false;
console.log('Bold roughness animation completed');
} }
} }
} }
// Update innovation glass animation // Update innovation glass animation
if (innovationGlassAnimation.isActive) { if (innovationGlassAnimation.isActive) {
const elapsed = (performance.now() - innovationGlassAnimation.startTime) / 1000; const elapsed = (performance.now() - innovationGlassAnimation.startTime) / 1000;
const transitionProgress = Math.min(elapsed / innovationGlassAnimation.transitionDuration, 1); const transitionProgress = Math.min(elapsed / innovationGlassAnimation.transitionDuration, 1);
// Smooth easing function (ease-in-out) // Smooth easing function (ease-in-out)
const easeInOut = (t) => t * t * (3 - 2 * t); const easeInOut = (t) => t * t * (3 - 2 * t);
const easedProgress = easeInOut(transitionProgress); const easedProgress = easeInOut(transitionProgress);
// Interpolate IOR from 1.0 to 2.0 // Interpolate IOR from 1.0 to 2.0
const currentIor = innovationGlassAnimation.startIor + const currentIor = innovationGlassAnimation.startIor +
(innovationGlassAnimation.endIor - innovationGlassAnimation.startIor) * easedProgress; (innovationGlassAnimation.endIor - innovationGlassAnimation.startIor) * easedProgress;
// Interpolate thickness from 1.0 to 2.0 // Interpolate thickness from 1.0 to 2.0
const currentThickness = innovationGlassAnimation.startThickness + const currentThickness = innovationGlassAnimation.startThickness +
(innovationGlassAnimation.endThickness - innovationGlassAnimation.startThickness) * easedProgress; (innovationGlassAnimation.endThickness - innovationGlassAnimation.startThickness) * easedProgress;
// Apply to all innovation glass materials // Apply to all innovation glass materials
innovationGlassAnimation.materials.forEach(material => { innovationGlassAnimation.materials.forEach(material => {
material.ior = currentIor; material.ior = currentIor;
material.thickness = currentThickness; material.thickness = currentThickness;
material.needsUpdate = true; material.needsUpdate = true;
}); });
// End animation when complete // End animation when complete
if (transitionProgress >= 1) { if (transitionProgress >= 1) {
innovationGlassAnimation.isActive = false; innovationGlassAnimation.isActive = false;
console.log('Innovation glass animation completed'); console.log('Innovation glass animation completed');
} }
} }
// Turntable rotation for current model
// if (currentModel && !isTransitioning) {
// autoRotationAngle += delta * 0.5;
// currentModel.rotation.y = autoRotationAngle;
// }
controls.update(); controls.update();
composer.render(); composer.render();
} }
@ -958,18 +829,18 @@ function animate() {
// Initialize the scene // Initialize the scene
async function init() { async function init() {
try { try {
console.log('Starting application initialization');
// Load all models first // Load all models first
preloadedModels = await sceneLoader.loadAllModels(); preloadedModels = await sceneLoader.loadAllModels();
console.log('All models loaded successfully');
// Initialize the first scene // Initialize the first scene
initializeScene(); initializeScene();
// Start the animation loop // Start the animation loop
animate(); animate();
console.log('Animation loop started');
// Attach scroll event listener // Attach scroll event listener
window.addEventListener('wheel', onMouseScroll, {passive: true}); window.addEventListener('wheel', onMouseScroll, { passive: true });
console.log('Scroll event listener attached');
} catch (error) { } catch (error) {
console.error('Failed to initialize scene:', error); console.error('Failed to initialize scene:', error);
sceneLoader.setLoadingMessage('Error loading experience. Please refresh.'); sceneLoader.setLoadingMessage('Error loading experience. Please refresh.');
@ -978,6 +849,7 @@ async function init() {
// Handle window resize // Handle window resize
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
console.log('Window resized');
camera.aspect = window.innerWidth / window.innerHeight; camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix(); camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight); renderer.setSize(window.innerWidth, window.innerHeight);