loader added

This commit is contained in:
Anuj K 2025-08-29 17:26:28 +05:30
parent 73e11094ce
commit 7c6a7df48b
3 changed files with 890 additions and 563 deletions

View file

@ -7,7 +7,19 @@
<title>Young Pandas</title>
</head>
<body>
<!-- Loading Screen -->
<div id="loading-screen">
<div class="loading-container">
<div class="loading-spinner"></div>
<div class="loading-text" id="loading-text">Loading Experience...</div>
<div class="loading-progress">
<div class="loading-progress-bar" id="loading-progress-bar"></div>
</div>
<div class="loading-percentage" id="loading-percentage">0%</div>
</div>
</div>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<script type="module" src="src/main.js"></script>
</body>
</html>

View file

@ -9,6 +9,95 @@ import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
// Loading Manager
class SceneLoader {
constructor() {
this.loadingScreen = document.getElementById('loading-screen');
this.loadingText = document.getElementById('loading-text');
this.loadingProgressBar = document.getElementById('loading-progress-bar');
this.loadingPercentage = document.getElementById('loading-percentage');
this.modelsToLoad = [
{ file: 'bold.glb', type: 'bold' },
{ file: 'innovation.glb', type: 'innovation' },
{ file: 'agility.glb', type: 'agility' },
{ file: 'storytelling.glb', type: 'storytelling' }
];
this.loadedModels = {};
this.loadedCount = 0;
this.totalModels = this.modelsToLoad.length;
}
setLoadingMessage(message) {
this.loadingText.textContent = message;
}
updateProgress(progress) {
const percentage = Math.round(progress * 100);
this.loadingProgressBar.style.width = `${percentage}%`;
this.loadingPercentage.textContent = `${percentage}%`;
}
hideLoadingScreen() {
this.loadingScreen.classList.add('hidden');
setTimeout(() => {
this.loadingScreen.style.display = 'none';
}, 800);
}
async loadAllModels() {
return new Promise((resolve) => {
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('node_modules/three/examples/jsm/libs/draco/');
loader.setDRACOLoader(dracoLoader);
this.modelsToLoad.forEach((modelInfo, index) => {
// this.setLoadingMessage(`Loading ${modelInfo.type}...`);
this.setLoadingMessage(`Loading experience...`);
loader.load(`/${modelInfo.file}`,
(gltf) => {
this.loadedModels[modelInfo.type] = {
scene: gltf.scene,
animations: gltf.animations,
gltf: gltf
};
this.loadedCount++;
const progress = this.loadedCount / this.totalModels;
this.updateProgress(progress);
if (this.loadedCount === this.totalModels) {
this.setLoadingMessage('Initializing Experience...');
setTimeout(() => {
this.hideLoadingScreen();
resolve(this.loadedModels);
}, 500);
}
},
(progress) => {
// Individual file progress
const fileProgress = progress.loaded / progress.total;
const totalProgress = (this.loadedCount + fileProgress) / this.totalModels;
this.updateProgress(totalProgress);
},
(error) => {
console.error(`Error loading ${modelInfo.file}:`, error);
}
);
});
});
}
}
// Initialize loader
const sceneLoader = new SceneLoader();
// You can customize the loading message here:
sceneLoader.setLoadingMessage('Preparing Your Experience...');
// Scene setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
@ -39,6 +128,9 @@ let mixer = null;
let nextMixer = null;
let autoRotationAngle = 0;
// Store preloaded models
let preloadedModels = {};
// Bold scene roughness animation state
let boldRoughnessAnimation = {
isActive: false,
@ -50,9 +142,20 @@ let boldRoughnessAnimation = {
materials: [] // Store references to bold materials
};
// Innovation glass animation state
let innovationGlassAnimation = {
isActive: false,
startTime: 0,
transitionDuration: 0.2,
startIor: 1.0,
endIor: 2.0,
startThickness: 1.0,
endThickness: 2.0,
materials: [] // Store references to innovation glass materials
};
// Renderer setup
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000);
@ -62,7 +165,6 @@ renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.physicallyCorrectLights = true;
document.body.appendChild(renderer.domElement);
// Post-processing: Bloom
@ -171,14 +273,14 @@ const boldGlassMaterial = new THREE.MeshPhysicalMaterial({
alphaTest: 0
});
// Clear thick glass for innovation
// Clear thick glass for innovation (starts with animated values)
const innovationGlassMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
metalness: 0.2,
roughness: 0.05,
transmission: 1,
ior: 2,
thickness: 2,
ior: 1.0, // Will animate from 1 to 2
thickness: 1.0, // Will animate from 1 to 2
clearcoat: 1.0,
clearcoatRoughness: 0.1,
attenuationColor: new THREE.Color(0xffffff),
@ -222,11 +324,6 @@ const lightOrangeMaterial = new THREE.MeshStandardMaterial({
emissiveIntensity: 2.25
});
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('node_modules/three/examples/jsm/libs/draco/');
loader.setDRACOLoader(dracoLoader);
// Apply materials based on model type
function applyMaterials(model, modelType) {
console.log(`=== Material Assignment Debug for ${modelType} ===`);
@ -236,7 +333,6 @@ function applyMaterials(model, modelType) {
if (object.isMesh) {
meshCount++;
console.log(`Found mesh: "${object.name}"`);
const previousMaterial = object.material;
object.castShadow = true;
object.receiveShadow = true;
@ -249,13 +345,11 @@ function applyMaterials(model, modelType) {
object.material.side = THREE.DoubleSide;
object.material.depthWrite = false;
object.renderOrder = 2;
// Store material reference for roughness animation
boldRoughnessAnimation.materials.push(object.material);
} else {
console.log(` → Applying bold glass material (fallback) to "${object.name}"`);
object.material = boldGlassMaterial.clone();
// Store material reference for roughness animation
boldRoughnessAnimation.materials.push(object.material);
}
@ -263,6 +357,7 @@ function applyMaterials(model, modelType) {
// Innovation-specific material logic
const orangeMeshes = ['dblsc', 'ec', 'gemini', 'infinity', 'star', 'dpd'];
const targetGlassNames = ['Cube.alt90.df'];
const sanitize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, '');
const nameMatches = (name, targets) => {
const clean = sanitize(name);
@ -279,17 +374,22 @@ function applyMaterials(model, modelType) {
object.material.depthWrite = false;
object.renderOrder = 2;
// Store material reference for animation
innovationGlassAnimation.materials.push(object.material);
// Create inner glass shell
const innerShell = object.clone();
innerShell.material = innovationGlassMaterial.clone();
innerShell.material.side = THREE.DoubleSide;
innerShell.material.depthWrite = false;
innerShell.material.thickness = 4;
innerShell.material.transmission = 0.8;
innerShell.renderOrder = 1;
innerShell.scale.multiplyScalar(0.95);
object.parent.add(innerShell);
// Store inner shell material reference for animation too
innovationGlassAnimation.materials.push(innerShell.material);
object.parent.add(innerShell);
} else if (nameMatches(object.name, orangeMeshes)) {
object.material = lightOrangeMaterial.clone();
object.renderOrder = 0;
@ -331,13 +431,18 @@ function centerAndFrameModel(model, targetCamera = camera) {
// Use fixed camera distance that's further away from the origin
if (!isTransitioning) {
const fixedCameraDistance = 50; // Fixed distance, much further than before
targetCamera.position.set(0, 0, fixedCameraDistance);
// Calculate isometric-like position with 35-degree angles
const angle = 35 * Math.PI / 180; // Convert 35 degrees to radians
const cosAngle = Math.cos(angle);
const x = fixedCameraDistance * cosAngle;
const y = fixedCameraDistance * cosAngle;
const z = fixedCameraDistance * cosAngle;
targetCamera.position.set(x, y, z);
controls.target.set(0, 0, 0);
// Set distance limits to lock the camera at this distance
controls.minDistance = fixedCameraDistance;
controls.maxDistance = fixedCameraDistance;
controls.update();
}
}
@ -349,7 +454,6 @@ function setupAnimations(model, gltf, modelType) {
gltf.animations.forEach((clip) => {
const action = animMixer.clipAction(clip);
if (modelType === 'bold') {
// Play once for bold
action.loop = THREE.LoopOnce;
@ -377,33 +481,54 @@ function setupAnimations(model, gltf, modelType) {
return animMixer;
}
return null;
}
// Load model function
function loadModel(filename, modelType, onLoadCallback) {
loader.load(`/${filename}`, (gltf) => {
const model = gltf.scene;
// Create model from preloaded data - FIXED: Always create fresh geometry
function createModelFromPreloaded(modelType) {
const preloadedData = preloadedModels[modelType];
if (!preloadedData) {
console.error(`Preloaded model not found: ${modelType}`);
return { model: null, animMixer: null };
}
// Clear animation materials arrays when creating new models
if (modelType === 'bold') {
boldRoughnessAnimation.materials = [];
} else if (modelType === 'innovation') {
innovationGlassAnimation.materials = [];
}
// Clone the scene deeply to ensure fresh geometry
const model = preloadedData.scene.clone(true);
// IMPORTANT: Clone all geometries to ensure they're independent
model.traverse((object) => {
if (object.isMesh && object.geometry) {
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
applyMaterials(model, modelType);
// Setup animations
const animMixer = setupAnimations(model, gltf, modelType);
const animMixer = setupAnimations(model, preloadedData.gltf, modelType);
// Center and frame model
centerAndFrameModel(model);
if (onLoadCallback) {
onLoadCallback(model, animMixer);
}
}, undefined, (error) => {
console.error(`Error loading ${filename}:`, error);
});
return { model, animMixer };
}
// Load initial bold model (now the first scene)
loadModel('bold.glb', 'bold', (model, animMixer) => {
// Initialize first scene after all models are loaded
function initializeScene() {
const { model, animMixer } = createModelFromPreloaded('bold');
currentModel = model;
mixer = animMixer;
scene.add(currentModel);
@ -411,7 +536,21 @@ loadModel('bold.glb', 'bold', (model, animMixer) => {
// Start the roughness animation for bold scene
boldRoughnessAnimation.isActive = true;
boldRoughnessAnimation.startTime = performance.now();
});
}
// Start innovation glass animation
function startInnovationGlassAnimation() {
// Reset all innovation glass materials to starting values
innovationGlassAnimation.materials.forEach(material => {
material.ior = innovationGlassAnimation.startIor;
material.thickness = innovationGlassAnimation.startThickness;
material.needsUpdate = true;
});
innovationGlassAnimation.isActive = true;
innovationGlassAnimation.startTime = performance.now();
console.log('Innovation glass animation started');
}
// Twist animation function - Updated to twist around world center (0,0,0)
function twistMesh(mesh, progress) {
@ -430,8 +569,8 @@ function twistMesh(mesh, progress) {
// 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);
@ -493,6 +632,19 @@ function resetMeshGeometry(mesh) {
mesh.geometry.computeVertexNormals();
}
// FIXED: Clean up geometry data completely
function cleanupGeometryData(model) {
if (!model) return;
model.traverse((object) => {
if (object.isMesh && object.geometry && object.geometry.userData) {
delete object.geometry.userData.originalPositions;
delete object.geometry.userData.originalWorldPositions;
delete object.geometry.userData.inverseWorldMatrix;
}
});
}
// Start transition to next or previous scene
function startTransition(direction = 1) {
if (isTransitioning) return;
@ -508,37 +660,30 @@ function startTransition(direction = 1) {
transitionDirection = direction;
// Determine next model based on direction and current scene
let nextModelFile = '';
let nextModelType = '';
if (direction > 0) {
// Moving forward
if (currentScene === 0) {
nextModelFile = 'innovation.glb';
nextModelType = 'innovation';
} else if (currentScene === 1) {
nextModelFile = 'agility.glb';
nextModelType = 'agility';
} else if (currentScene === 2) {
nextModelFile = 'storytelling.glb';
nextModelType = 'storytelling';
}
} else {
// Moving backward
if (currentScene === 1) {
nextModelFile = 'bold.glb';
nextModelType = 'bold';
} else if (currentScene === 2) {
nextModelFile = 'innovation.glb';
nextModelType = 'innovation';
} else if (currentScene === 3) {
nextModelFile = 'agility.glb';
nextModelType = 'agility';
}
}
if (nextModelFile) {
loadModel(nextModelFile, nextModelType, (model, animMixer) => {
if (nextModelType) {
const { model, animMixer } = createModelFromPreloaded(nextModelType);
nextModel = model;
nextMixer = animMixer;
@ -557,9 +702,7 @@ function startTransition(direction = 1) {
}
}
});
scene.add(nextModel);
});
}
}
@ -577,7 +720,6 @@ function updateTransition(deltaTime) {
if (currentModel) {
// Move current model up and fade out
// currentModel.position.y = easedProgress * 10;
currentModel.traverse((obj) => {
if (obj.material) {
const targetOpacity = 1 - easedProgress;
@ -597,7 +739,6 @@ function updateTransition(deltaTime) {
if (nextModel) {
// Keep next model in place and just fade in (no vertical movement)
nextModel.position.y = 0;
nextModel.traverse((obj) => {
if (obj.material) {
const targetOpacity = easedProgress;
@ -616,17 +757,19 @@ function updateTransition(deltaTime) {
// Complete transition
if (transitionProgress >= 1) {
// Remove current model
// FIXED: Reset geometry before removing the model
if (currentModel) {
scene.remove(currentModel);
// Clean up geometry user data
currentModel.traverse((obj) => {
if (obj.geometry && obj.geometry.userData.originalPositions) {
delete obj.geometry.userData.originalPositions;
delete obj.geometry.userData.bounds;
// Reset all geometry to original state before removal
currentModel.traverse((object) => {
if (object.isMesh) {
resetMeshGeometry(object);
}
});
// Clean up geometry user data completely
cleanupGeometryData(currentModel);
scene.remove(currentModel);
}
// Switch to next model
@ -641,7 +784,7 @@ function updateTransition(deltaTime) {
if (Array.isArray(obj.material)) {
obj.material.forEach(mat => {
mat.opacity = 1;
if (currentScene === 3) { // Keep transparency for storytelling glass
if (currentScene + transitionDirection === 3) { // Keep transparency for storytelling glass
mat.transparent = mat.transmission > 0;
} else {
mat.transparent = mat.transmission > 0;
@ -649,7 +792,7 @@ function updateTransition(deltaTime) {
});
} else {
obj.material.opacity = 1;
if (currentScene === 3) { // Keep transparency for storytelling glass
if (currentScene + transitionDirection === 3) { // Keep transparency for storytelling glass
obj.material.transparent = obj.material.transmission > 0;
} else {
obj.material.transparent = obj.material.transmission > 0;
@ -668,6 +811,11 @@ function updateTransition(deltaTime) {
scrollDownCount = 0;
scrollUpCount = 0;
// Start innovation glass animation if we're now in the innovation scene
if (currentScene === 1) {
startInnovationGlassAnimation();
}
console.log(`Transition complete. Current scene: ${currentScene}`);
}
}
@ -697,9 +845,6 @@ function onMouseScroll(event) {
}
}
// Attach scroll event listener
window.addEventListener('wheel', onMouseScroll, {passive: true});
// Animation loop
const clock = new THREE.Clock();
@ -721,14 +866,12 @@ function animate() {
twistProgress += twistSpeed;
if (twistProgress > 1.0) {
twistProgress = 1.0;
// Reset geometry after twist completes
// currentModel.traverse((object) => {
// if (object.isMesh) {
// resetMeshGeometry(object);
// }
// });
// FIXED: Reset geometry after twist completes
currentModel.traverse((object) => {
if (object.isMesh) {
resetMeshGeometry(object);
}
});
isTwisting = false;
} else {
// Apply twist to current model
@ -754,7 +897,7 @@ function animate() {
const easeInOut = (t) => t * t * (3 - 2 * t);
const easedProgress = easeInOut(transitionProgress);
// Interpolate roughness from 0.9 to 0.05
// Interpolate roughness from 0.25 to 0.05
const currentRoughness = boldRoughnessAnimation.startRoughness +
(boldRoughnessAnimation.endRoughness - boldRoughnessAnimation.startRoughness) * easedProgress;
@ -771,6 +914,37 @@ function animate() {
}
}
// Update innovation glass animation
if (innovationGlassAnimation.isActive) {
const elapsed = (performance.now() - innovationGlassAnimation.startTime) / 1000;
const transitionProgress = Math.min(elapsed / innovationGlassAnimation.transitionDuration, 1);
// Smooth easing function (ease-in-out)
const easeInOut = (t) => t * t * (3 - 2 * t);
const easedProgress = easeInOut(transitionProgress);
// Interpolate IOR from 1.0 to 2.0
const currentIor = innovationGlassAnimation.startIor +
(innovationGlassAnimation.endIor - innovationGlassAnimation.startIor) * easedProgress;
// Interpolate thickness from 1.0 to 2.0
const currentThickness = innovationGlassAnimation.startThickness +
(innovationGlassAnimation.endThickness - innovationGlassAnimation.startThickness) * easedProgress;
// Apply to all innovation glass materials
innovationGlassAnimation.materials.forEach(material => {
material.ior = currentIor;
material.thickness = currentThickness;
material.needsUpdate = true;
});
// End animation when complete
if (transitionProgress >= 1) {
innovationGlassAnimation.isActive = false;
console.log('Innovation glass animation completed');
}
}
// Turntable rotation for current model
// if (currentModel && !isTransitioning) {
// autoRotationAngle += delta * 0.5;
@ -781,7 +955,26 @@ function animate() {
composer.render();
}
animate();
// Initialize the scene
async function init() {
try {
// Load all models first
preloadedModels = await sceneLoader.loadAllModels();
// Initialize the first scene
initializeScene();
// Start the animation loop
animate();
// Attach scroll event listener
window.addEventListener('wheel', onMouseScroll, {passive: true});
} catch (error) {
console.error('Failed to initialize scene:', error);
sceneLoader.setLoadingMessage('Error loading experience. Please refresh.');
}
}
// Handle window resize
window.addEventListener('resize', () => {
@ -790,3 +983,6 @@ window.addEventListener('resize', () => {
renderer.setSize(window.innerWidth, window.innerHeight);
composer.setSize(window.innerWidth, window.innerHeight);
});
// Start the application
init();

View file

@ -1,8 +1,127 @@
body {
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
overflow: hidden;
}
canvas {
display: block;
/* Loading Screen Styles */
#loading-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #000000 0%, #1a1a1a 100%);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
transition: opacity 0.8s ease-out, visibility 0.8s ease-out;
}
#loading-screen.hidden {
opacity: 0;
visibility: hidden;
}
.loading-container {
text-align: center;
max-width: 400px;
padding: 2rem;
}
.loading-spinner {
width: 60px;
height: 60px;
margin: 0 auto 2rem;
border: 3px solid rgba(255, 165, 0, 0.1);
border-top: 3px solid #FFA500;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
font-size: 1.5rem;
font-weight: 300;
color: #ffffff;
margin-bottom: 2rem;
letter-spacing: 0.5px;
text-transform: uppercase;
}
.loading-progress {
width: 100%;
height: 2px;
background-color: rgba(255, 165, 0, 0.1);
border-radius: 1px;
overflow: hidden;
margin-bottom: 1rem;
}
.loading-progress-bar {
height: 100%;
background: linear-gradient(90deg, #FFA500 0%, #ff8600 100%);
width: 0%;
transition: width 0.3s ease;
border-radius: 1px;
}
.loading-percentage {
font-size: 0.9rem;
color: #FFA500;
font-weight: 500;
letter-spacing: 1px;
}
/* Responsive Design */
@media (max-width: 768px) {
.loading-container {
padding: 1rem;
}
.loading-text {
font-size: 1.2rem;
}
.loading-spinner {
width: 50px;
height: 50px;
}
}
#app {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
}