loader added
This commit is contained in:
parent
73e11094ce
commit
7c6a7df48b
14
index.html
14
index.html
|
@ -7,7 +7,19 @@
|
||||||
<title>Young Pandas</title>
|
<title>Young Pandas</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
334
src/main.js
334
src/main.js
|
@ -9,6 +9,95 @@ import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
|
||||||
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
|
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
|
||||||
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.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
|
// 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);
|
||||||
|
@ -39,6 +128,9 @@ let mixer = null;
|
||||||
let nextMixer = null;
|
let nextMixer = null;
|
||||||
let autoRotationAngle = 0;
|
let autoRotationAngle = 0;
|
||||||
|
|
||||||
|
// Store preloaded models
|
||||||
|
let preloadedModels = {};
|
||||||
|
|
||||||
// Bold scene roughness animation state
|
// Bold scene roughness animation state
|
||||||
let boldRoughnessAnimation = {
|
let boldRoughnessAnimation = {
|
||||||
isActive: false,
|
isActive: false,
|
||||||
|
@ -50,9 +142,20 @@ let boldRoughnessAnimation = {
|
||||||
materials: [] // Store references to bold materials
|
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
|
// Renderer setup
|
||||||
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||||
|
|
||||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
renderer.setClearColor(0x000000);
|
renderer.setClearColor(0x000000);
|
||||||
|
@ -62,7 +165,6 @@ renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||||||
renderer.toneMappingExposure = 1.2;
|
renderer.toneMappingExposure = 1.2;
|
||||||
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||||
renderer.physicallyCorrectLights = true;
|
renderer.physicallyCorrectLights = true;
|
||||||
|
|
||||||
document.body.appendChild(renderer.domElement);
|
document.body.appendChild(renderer.domElement);
|
||||||
|
|
||||||
// Post-processing: Bloom
|
// Post-processing: Bloom
|
||||||
|
@ -171,14 +273,14 @@ const boldGlassMaterial = new THREE.MeshPhysicalMaterial({
|
||||||
alphaTest: 0
|
alphaTest: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear thick glass for innovation
|
// Clear thick glass for innovation (starts with animated values)
|
||||||
const innovationGlassMaterial = new THREE.MeshPhysicalMaterial({
|
const innovationGlassMaterial = new THREE.MeshPhysicalMaterial({
|
||||||
color: 0xffffff,
|
color: 0xffffff,
|
||||||
metalness: 0.2,
|
metalness: 0.2,
|
||||||
roughness: 0.05,
|
roughness: 0.05,
|
||||||
transmission: 1,
|
transmission: 1,
|
||||||
ior: 2,
|
ior: 1.0, // Will animate from 1 to 2
|
||||||
thickness: 2,
|
thickness: 1.0, // Will animate from 1 to 2
|
||||||
clearcoat: 1.0,
|
clearcoat: 1.0,
|
||||||
clearcoatRoughness: 0.1,
|
clearcoatRoughness: 0.1,
|
||||||
attenuationColor: new THREE.Color(0xffffff),
|
attenuationColor: new THREE.Color(0xffffff),
|
||||||
|
@ -222,11 +324,6 @@ const lightOrangeMaterial = new THREE.MeshStandardMaterial({
|
||||||
emissiveIntensity: 2.25
|
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
|
// 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} ===`);
|
||||||
|
@ -236,7 +333,6 @@ function applyMaterials(model, modelType) {
|
||||||
if (object.isMesh) {
|
if (object.isMesh) {
|
||||||
meshCount++;
|
meshCount++;
|
||||||
console.log(`Found mesh: "${object.name}"`);
|
console.log(`Found mesh: "${object.name}"`);
|
||||||
|
|
||||||
const previousMaterial = object.material;
|
const previousMaterial = object.material;
|
||||||
object.castShadow = true;
|
object.castShadow = true;
|
||||||
object.receiveShadow = true;
|
object.receiveShadow = true;
|
||||||
|
@ -249,13 +345,11 @@ function applyMaterials(model, modelType) {
|
||||||
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 roughness animation
|
// Store material reference for roughness animation
|
||||||
boldRoughnessAnimation.materials.push(object.material);
|
boldRoughnessAnimation.materials.push(object.material);
|
||||||
} 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();
|
||||||
|
|
||||||
// Store material reference for roughness animation
|
// Store material reference for roughness animation
|
||||||
boldRoughnessAnimation.materials.push(object.material);
|
boldRoughnessAnimation.materials.push(object.material);
|
||||||
}
|
}
|
||||||
|
@ -263,6 +357,7 @@ 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);
|
||||||
|
@ -279,17 +374,22 @@ function applyMaterials(model, modelType) {
|
||||||
object.material.depthWrite = false;
|
object.material.depthWrite = false;
|
||||||
object.renderOrder = 2;
|
object.renderOrder = 2;
|
||||||
|
|
||||||
|
// Store material reference for animation
|
||||||
|
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();
|
||||||
innerShell.material.side = THREE.DoubleSide;
|
innerShell.material.side = THREE.DoubleSide;
|
||||||
innerShell.material.depthWrite = false;
|
innerShell.material.depthWrite = false;
|
||||||
innerShell.material.thickness = 4;
|
|
||||||
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);
|
||||||
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)) {
|
} else if (nameMatches(object.name, orangeMeshes)) {
|
||||||
object.material = lightOrangeMaterial.clone();
|
object.material = lightOrangeMaterial.clone();
|
||||||
object.renderOrder = 0;
|
object.renderOrder = 0;
|
||||||
|
@ -331,13 +431,18 @@ function centerAndFrameModel(model, targetCamera = camera) {
|
||||||
// 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) {
|
||||||
const fixedCameraDistance = 50; // Fixed distance, much further than before
|
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);
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -349,7 +454,6 @@ function setupAnimations(model, gltf, modelType) {
|
||||||
|
|
||||||
gltf.animations.forEach((clip) => {
|
gltf.animations.forEach((clip) => {
|
||||||
const action = animMixer.clipAction(clip);
|
const action = animMixer.clipAction(clip);
|
||||||
|
|
||||||
if (modelType === 'bold') {
|
if (modelType === 'bold') {
|
||||||
// Play once for bold
|
// Play once for bold
|
||||||
action.loop = THREE.LoopOnce;
|
action.loop = THREE.LoopOnce;
|
||||||
|
@ -377,33 +481,54 @@ function setupAnimations(model, gltf, modelType) {
|
||||||
|
|
||||||
return animMixer;
|
return animMixer;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load model function
|
// Create model from preloaded data - FIXED: Always create fresh geometry
|
||||||
function loadModel(filename, modelType, onLoadCallback) {
|
function createModelFromPreloaded(modelType) {
|
||||||
loader.load(`/${filename}`, (gltf) => {
|
const preloadedData = preloadedModels[modelType];
|
||||||
const model = gltf.scene;
|
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
|
// Apply materials
|
||||||
applyMaterials(model, modelType);
|
applyMaterials(model, modelType);
|
||||||
|
|
||||||
// Setup animations
|
// Setup animations
|
||||||
const animMixer = setupAnimations(model, gltf, modelType);
|
const animMixer = setupAnimations(model, preloadedData.gltf, modelType);
|
||||||
|
|
||||||
// Center and frame model
|
// Center and frame model
|
||||||
centerAndFrameModel(model);
|
centerAndFrameModel(model);
|
||||||
|
|
||||||
if (onLoadCallback) {
|
return { model, animMixer };
|
||||||
onLoadCallback(model, animMixer);
|
|
||||||
}
|
|
||||||
}, undefined, (error) => {
|
|
||||||
console.error(`Error loading ${filename}:`, error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load initial bold model (now the first scene)
|
// Initialize first scene after all models are loaded
|
||||||
loadModel('bold.glb', 'bold', (model, animMixer) => {
|
function initializeScene() {
|
||||||
|
const { model, animMixer } = createModelFromPreloaded('bold');
|
||||||
currentModel = model;
|
currentModel = model;
|
||||||
mixer = animMixer;
|
mixer = animMixer;
|
||||||
scene.add(currentModel);
|
scene.add(currentModel);
|
||||||
|
@ -411,7 +536,21 @@ loadModel('bold.glb', 'bold', (model, animMixer) => {
|
||||||
// Start the roughness animation for bold scene
|
// Start the roughness animation for bold scene
|
||||||
boldRoughnessAnimation.isActive = true;
|
boldRoughnessAnimation.isActive = true;
|
||||||
boldRoughnessAnimation.startTime = performance.now();
|
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)
|
// Twist animation function - Updated to twist around world center (0,0,0)
|
||||||
function twistMesh(mesh, progress) {
|
function twistMesh(mesh, progress) {
|
||||||
|
@ -430,8 +569,8 @@ function twistMesh(mesh, progress) {
|
||||||
|
|
||||||
// Update world matrix to get accurate world positions
|
// Update world matrix to get accurate world positions
|
||||||
mesh.updateMatrixWorld(true);
|
mesh.updateMatrixWorld(true);
|
||||||
|
|
||||||
const tempVector = new THREE.Vector3();
|
const tempVector = new THREE.Vector3();
|
||||||
|
|
||||||
for (let i = 0; i < positions.count; i++) {
|
for (let i = 0; i < positions.count; i++) {
|
||||||
tempVector.fromBufferAttribute(positions, i);
|
tempVector.fromBufferAttribute(positions, i);
|
||||||
tempVector.applyMatrix4(mesh.matrixWorld);
|
tempVector.applyMatrix4(mesh.matrixWorld);
|
||||||
|
@ -493,6 +632,19 @@ function resetMeshGeometry(mesh) {
|
||||||
mesh.geometry.computeVertexNormals();
|
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
|
// Start transition to next or previous scene
|
||||||
function startTransition(direction = 1) {
|
function startTransition(direction = 1) {
|
||||||
if (isTransitioning) return;
|
if (isTransitioning) return;
|
||||||
|
@ -508,37 +660,30 @@ function startTransition(direction = 1) {
|
||||||
transitionDirection = direction;
|
transitionDirection = direction;
|
||||||
|
|
||||||
// Determine next model based on direction and current scene
|
// Determine next model based on direction and current scene
|
||||||
let nextModelFile = '';
|
|
||||||
let nextModelType = '';
|
let nextModelType = '';
|
||||||
|
|
||||||
if (direction > 0) {
|
if (direction > 0) {
|
||||||
// Moving forward
|
// Moving forward
|
||||||
if (currentScene === 0) {
|
if (currentScene === 0) {
|
||||||
nextModelFile = 'innovation.glb';
|
|
||||||
nextModelType = 'innovation';
|
nextModelType = 'innovation';
|
||||||
} else if (currentScene === 1) {
|
} else if (currentScene === 1) {
|
||||||
nextModelFile = 'agility.glb';
|
|
||||||
nextModelType = 'agility';
|
nextModelType = 'agility';
|
||||||
} else if (currentScene === 2) {
|
} else if (currentScene === 2) {
|
||||||
nextModelFile = 'storytelling.glb';
|
|
||||||
nextModelType = 'storytelling';
|
nextModelType = 'storytelling';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Moving backward
|
// Moving backward
|
||||||
if (currentScene === 1) {
|
if (currentScene === 1) {
|
||||||
nextModelFile = 'bold.glb';
|
|
||||||
nextModelType = 'bold';
|
nextModelType = 'bold';
|
||||||
} else if (currentScene === 2) {
|
} else if (currentScene === 2) {
|
||||||
nextModelFile = 'innovation.glb';
|
|
||||||
nextModelType = 'innovation';
|
nextModelType = 'innovation';
|
||||||
} else if (currentScene === 3) {
|
} else if (currentScene === 3) {
|
||||||
nextModelFile = 'agility.glb';
|
|
||||||
nextModelType = 'agility';
|
nextModelType = 'agility';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextModelFile) {
|
if (nextModelType) {
|
||||||
loadModel(nextModelFile, nextModelType, (model, animMixer) => {
|
const { model, animMixer } = createModelFromPreloaded(nextModelType);
|
||||||
nextModel = model;
|
nextModel = model;
|
||||||
nextMixer = animMixer;
|
nextMixer = animMixer;
|
||||||
|
|
||||||
|
@ -557,9 +702,7 @@ function startTransition(direction = 1) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
scene.add(nextModel);
|
scene.add(nextModel);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,7 +720,6 @@ function updateTransition(deltaTime) {
|
||||||
if (currentModel) {
|
if (currentModel) {
|
||||||
// Move current model up and fade out
|
// Move current model up and fade out
|
||||||
// currentModel.position.y = easedProgress * 10;
|
// currentModel.position.y = easedProgress * 10;
|
||||||
|
|
||||||
currentModel.traverse((obj) => {
|
currentModel.traverse((obj) => {
|
||||||
if (obj.material) {
|
if (obj.material) {
|
||||||
const targetOpacity = 1 - easedProgress;
|
const targetOpacity = 1 - easedProgress;
|
||||||
|
@ -597,7 +739,6 @@ function updateTransition(deltaTime) {
|
||||||
if (nextModel) {
|
if (nextModel) {
|
||||||
// Keep next model in place and just fade in (no vertical movement)
|
// Keep next model in place and just fade in (no vertical movement)
|
||||||
nextModel.position.y = 0;
|
nextModel.position.y = 0;
|
||||||
|
|
||||||
nextModel.traverse((obj) => {
|
nextModel.traverse((obj) => {
|
||||||
if (obj.material) {
|
if (obj.material) {
|
||||||
const targetOpacity = easedProgress;
|
const targetOpacity = easedProgress;
|
||||||
|
@ -616,17 +757,19 @@ function updateTransition(deltaTime) {
|
||||||
|
|
||||||
// Complete transition
|
// Complete transition
|
||||||
if (transitionProgress >= 1) {
|
if (transitionProgress >= 1) {
|
||||||
// Remove current model
|
// FIXED: Reset geometry before removing the model
|
||||||
if (currentModel) {
|
if (currentModel) {
|
||||||
scene.remove(currentModel);
|
// Reset all geometry to original state before removal
|
||||||
|
currentModel.traverse((object) => {
|
||||||
// Clean up geometry user data
|
if (object.isMesh) {
|
||||||
currentModel.traverse((obj) => {
|
resetMeshGeometry(object);
|
||||||
if (obj.geometry && obj.geometry.userData.originalPositions) {
|
|
||||||
delete obj.geometry.userData.originalPositions;
|
|
||||||
delete obj.geometry.userData.bounds;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Clean up geometry user data completely
|
||||||
|
cleanupGeometryData(currentModel);
|
||||||
|
|
||||||
|
scene.remove(currentModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch to next model
|
// Switch to next model
|
||||||
|
@ -641,7 +784,7 @@ function updateTransition(deltaTime) {
|
||||||
if (Array.isArray(obj.material)) {
|
if (Array.isArray(obj.material)) {
|
||||||
obj.material.forEach(mat => {
|
obj.material.forEach(mat => {
|
||||||
mat.opacity = 1;
|
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;
|
mat.transparent = mat.transmission > 0;
|
||||||
} else {
|
} else {
|
||||||
mat.transparent = mat.transmission > 0;
|
mat.transparent = mat.transmission > 0;
|
||||||
|
@ -649,7 +792,7 @@ function updateTransition(deltaTime) {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
obj.material.opacity = 1;
|
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;
|
obj.material.transparent = obj.material.transmission > 0;
|
||||||
} else {
|
} else {
|
||||||
obj.material.transparent = obj.material.transmission > 0;
|
obj.material.transparent = obj.material.transmission > 0;
|
||||||
|
@ -668,6 +811,11 @@ function updateTransition(deltaTime) {
|
||||||
scrollDownCount = 0;
|
scrollDownCount = 0;
|
||||||
scrollUpCount = 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}`);
|
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
|
// Animation loop
|
||||||
const clock = new THREE.Clock();
|
const clock = new THREE.Clock();
|
||||||
|
|
||||||
|
@ -721,14 +866,12 @@ function animate() {
|
||||||
twistProgress += twistSpeed;
|
twistProgress += twistSpeed;
|
||||||
if (twistProgress > 1.0) {
|
if (twistProgress > 1.0) {
|
||||||
twistProgress = 1.0;
|
twistProgress = 1.0;
|
||||||
|
// FIXED: Reset geometry after twist completes
|
||||||
// Reset geometry after twist completes
|
currentModel.traverse((object) => {
|
||||||
// currentModel.traverse((object) => {
|
if (object.isMesh) {
|
||||||
// if (object.isMesh) {
|
resetMeshGeometry(object);
|
||||||
// resetMeshGeometry(object);
|
}
|
||||||
// }
|
});
|
||||||
// });
|
|
||||||
|
|
||||||
isTwisting = false;
|
isTwisting = false;
|
||||||
} else {
|
} else {
|
||||||
// Apply twist to current model
|
// Apply twist to current model
|
||||||
|
@ -754,7 +897,7 @@ function animate() {
|
||||||
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.9 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;
|
||||||
|
|
||||||
|
@ -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
|
// Turntable rotation for current model
|
||||||
// if (currentModel && !isTransitioning) {
|
// if (currentModel && !isTransitioning) {
|
||||||
// autoRotationAngle += delta * 0.5;
|
// autoRotationAngle += delta * 0.5;
|
||||||
|
@ -781,7 +955,26 @@ function animate() {
|
||||||
composer.render();
|
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
|
// Handle window resize
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
|
@ -790,3 +983,6 @@ window.addEventListener('resize', () => {
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
composer.setSize(window.innerWidth, window.innerHeight);
|
composer.setSize(window.innerWidth, window.innerHeight);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Start the application
|
||||||
|
init();
|
||||||
|
|
125
src/style.css
125
src/style.css
|
@ -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;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
min-width: 320px;
|
||||||
|
min-height: 100vh;
|
||||||
overflow: hidden;
|
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;
|
||||||
}
|
}
|
Loading…
Reference in a new issue