diff --git a/src/fluidDistortion.js b/src/fluidDistortion.js index 76c1f71..a6d7abd 100644 --- a/src/fluidDistortion.js +++ b/src/fluidDistortion.js @@ -1,17 +1,19 @@ import * as THREE from 'three'; // Enhanced ripple simulation with multiple trailing ripples and lighting + const FluidSimShader = { uniforms: { - tPrev: { value: null }, + tPrev: { value: null }, iResolution: { value: new THREE.Vector2() }, - iTime: { value: 0.0 }, - mouse: { value: new THREE.Vector3(-1, -1, 0.0) }, + iTime: { value: 0.0 }, + mouse: { value: new THREE.Vector3(-1, -1, 0.0) }, dissipation: { value: 0.950 }, // Slightly more persistent for trails - tension: { value: 2.0 }, // Higher tension for stronger ripples - radius: { value: 20.0 }, // Larger splat radius - trailLength: { value: 5 }, // Number of trailing ripples + tension: { value: 2.2 }, // Higher tension for stronger ripples + radius: { value: 20.0 }, // Larger splat radius + trailLength: { value: 5 }, // Number of trailing ripples }, + vertexShader: ` varying vec2 vUv; void main() { @@ -19,14 +21,14 @@ const FluidSimShader = { gl_Position = vec4(position.xy, 0.0, 1.0); } `, + fragmentShader: ` precision highp float; varying vec2 vUv; - uniform sampler2D tPrev; - uniform vec2 iResolution; + uniform vec2 iResolution; uniform float iTime; - uniform vec3 mouse; + uniform vec3 mouse; uniform float dissipation; uniform float tension; uniform float radius; @@ -44,19 +46,19 @@ const FluidSimShader = { float prev = currPrev.g; // Enhanced 8-neighbor laplacian for stronger ripples - float up = readRG(vUv + vec2(0.0, texel.y)).r; - float down = readRG(vUv + vec2(0.0, -texel.y)).r; + float up = readRG(vUv + vec2(0.0, texel.y)).r; + float down = readRG(vUv + vec2(0.0, -texel.y)).r; float right = readRG(vUv + vec2( texel.x, 0.0)).r; - float left = readRG(vUv + vec2(-texel.x, 0.0)).r; - + float left = readRG(vUv + vec2(-texel.x, 0.0)).r; + // Diagonal neighbors for smoother ripples - float upLeft = readRG(vUv + vec2(-texel.x, texel.y)).r; - float upRight = readRG(vUv + vec2( texel.x, texel.y)).r; - float downLeft = readRG(vUv + vec2(-texel.x, -texel.y)).r; + float upLeft = readRG(vUv + vec2(-texel.x, texel.y)).r; + float upRight = readRG(vUv + vec2( texel.x, texel.y)).r; + float downLeft = readRG(vUv + vec2(-texel.x, -texel.y)).r; float downRight = readRG(vUv + vec2( texel.x, -texel.y)).r; // Enhanced laplacian with diagonal weights - float lap = (up + down + left + right) * 0.2 + + float lap = (up + down + left + right) * 0.2 + (upLeft + upRight + downLeft + downRight) * 0.05 - curr; // Wave equation with enhanced parameters @@ -67,7 +69,7 @@ const FluidSimShader = { vec2 uvPx = vUv * iResolution; vec2 d = uvPx - mouse.xy; float dist = length(d); - + // Create multiple concentric ripples for (float i = 0.0; i < 4.0; i++) { if (i >= trailLength) break; @@ -91,20 +93,23 @@ const FluidSimShader = { ` }; -// Enhanced distortion shader with dynamic lighting +// Enhanced distortion shader with dynamic lighting and ripple whiteness export const FluidDistortionShader = { uniforms: { - tDiffuse: { value: null }, - tSim: { value: null }, - iResolution: { value: new THREE.Vector2() }, - amount: { value: 0.12 }, // Stronger base distortion + tDiffuse: { value: null }, + tSim: { value: null }, + iResolution: { value: new THREE.Vector2() }, + amount: { value: 0.12 }, // Stronger base distortion chromaticAmount: { value: 0.015 }, // Enhanced chromatic aberration - lightPosition: { value: new THREE.Vector3(0.5, 0.5, 1.0) }, // Light position - lightIntensity: { value: 1.5 }, // Light brightness - lightColor: { value: new THREE.Color(0.8, 0.9, 1.0) }, // Cool light color - normalStrength: { value: 2.0 }, // How pronounced the lighting effect is - ambientLight: { value: 0.15 }, // Base ambient lighting + lightPosition: { value: new THREE.Vector3(0.5, 0.5, 1.0) }, // Light position + lightIntensity: { value: 1.5 }, // Light brightness + lightColor: { value: new THREE.Color(0.8, 0.9, 1.0) }, // Cool light color + normalStrength: { value: 2.0 }, // How pronounced the lighting effect is + ambientLight: { value: 0.15 }, // Base ambient lighting + rippleWhiteness: { value: 0.15 }, // Amount of white tint for ripples + rippleBrightness: { value: 1.8 }, // Brightness multiplier for ripple areas }, + vertexShader: ` varying vec2 vUv; void main() { @@ -112,20 +117,22 @@ export const FluidDistortionShader = { gl_Position = vec4(position.xy, 0.0, 1.0); } `, + fragmentShader: ` precision highp float; varying vec2 vUv; - uniform sampler2D tDiffuse; uniform sampler2D tSim; - uniform vec2 iResolution; + uniform vec2 iResolution; uniform float amount; uniform float chromaticAmount; - uniform vec3 lightPosition; + uniform vec3 lightPosition; uniform float lightIntensity; - uniform vec3 lightColor; + uniform vec3 lightColor; uniform float normalStrength; uniform float ambientLight; + uniform float rippleWhiteness; + uniform float rippleBrightness; void main() { vec2 texel = 1.0 / iResolution; @@ -147,10 +154,9 @@ export const FluidDistortionShader = { // Add subtle trailing distortion based on height vec2 trailOffset = grad * abs(hC) * amount * 0.3; vec2 totalOffset = baseOffset + trailOffset; - + // Chromatic aberration with enhanced separation vec2 chromaticOffset = grad * chromaticAmount; - vec2 uvR = vUv + totalOffset + chromaticOffset; vec2 uvG = vUv + totalOffset; vec2 uvB = vUv + totalOffset - chromaticOffset; @@ -164,27 +170,38 @@ export const FluidDistortionShader = { float r = texture2D(tDiffuse, uvR).r; float g = texture2D(tDiffuse, uvG).g; float b = texture2D(tDiffuse, uvB).b; - vec3 distortedColor = vec3(r, g, b); // Dynamic lighting calculation vec3 lightDir = normalize(vec3(lightPosition.xy - vUv, lightPosition.z)); float NdotL = max(dot(normal, lightDir), 0.0); - + // Create rim lighting effect for ripples float rimLight = pow(1.0 - abs(dot(normal, vec3(0.0, 0.0, 1.0))), 2.0); - + // Combine lighting effects vec3 lighting = lightColor * (NdotL * lightIntensity + rimLight * 0.3) + ambientLight; - - // Apply lighting selectively - stronger where there are ripples + + // Calculate ripple intensity for both lighting and whiteness float rippleIntensity = abs(hC) + length(grad) * 0.5; rippleIntensity = clamp(rippleIntensity, 0.0, 1.0); + + // Apply lighting selectively - stronger where there are ripples + vec3 litColor = mix(distortedColor, distortedColor * lighting, rippleIntensity); + + // Add white tint to ripples for visibility over black areas + vec3 whiteColor = vec3(1.0, 1.0, 1.0); - // Blend original color with lit color based on ripple presence - vec3 finalColor = mix(distortedColor, distortedColor * lighting, rippleIntensity); + // Create a smooth falloff for the whiteness effect + float whiteIntensity = smoothstep(0.0, 0.3, rippleIntensity) * rippleWhiteness; - gl_FragColor = vec4(finalColor, 1.0); + // Blend in the white tint + vec3 rippleColor = mix(litColor, whiteColor, whiteIntensity); + + // Brighten areas with ripples + rippleColor = mix(rippleColor, rippleColor * rippleBrightness, rippleIntensity * 0.5); + + gl_FragColor = vec4(rippleColor, 1.0); } ` }; @@ -193,14 +210,18 @@ export const FluidDistortionShader = { export function createFluidSimulation(renderer, dpr = 1) { const simScene = new THREE.Scene(); const simCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); - - const quad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), new THREE.ShaderMaterial({ - uniforms: THREE.UniformsUtils.clone(FluidSimShader.uniforms), - vertexShader: FluidSimShader.vertexShader, - fragmentShader: FluidSimShader.fragmentShader, - depthTest: false, - depthWrite: false - })); + + const quad = new THREE.Mesh( + new THREE.PlaneGeometry(2, 2), + new THREE.ShaderMaterial({ + uniforms: THREE.UniformsUtils.clone(FluidSimShader.uniforms), + vertexShader: FluidSimShader.vertexShader, + fragmentShader: FluidSimShader.fragmentShader, + depthTest: false, + depthWrite: false + }) + ); + simScene.add(quad); // Higher precision for better ripple quality @@ -215,6 +236,7 @@ export function createFluidSimulation(renderer, dpr = 1) { let width = Math.max(2, Math.floor(window.innerWidth * dpr)); let height = Math.max(2, Math.floor(window.innerHeight * dpr)); + let rtA = new THREE.WebGLRenderTarget(width, height, params); let rtB = new THREE.WebGLRenderTarget(width, height, params); @@ -247,7 +269,7 @@ export function createFluidSimulation(renderer, dpr = 1) { renderer.setRenderTarget(rtB); renderer.render(simScene, simCamera); renderer.setRenderTarget(null); - + swap(); } diff --git a/src/innovation.js b/src/innovation.js deleted file mode 100644 index b07b148..0000000 --- a/src/innovation.js +++ /dev/null @@ -1,341 +0,0 @@ -import './style.css' - -import * as THREE from 'three'; -import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; -import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'; -import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; -import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js'; -import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; -import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; -import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; - -// Scene setup -const scene = new THREE.Scene(); -const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); -camera.setFocalLength(50); - -const raycaster = new THREE.Raycaster(); -const mouse = new THREE.Vector2(); -let isTwisting = false; -let twistProgress = 0; -const twistSpeed = 0.05; // Adjust speed -const twistStrength = 0.3; // Adjust strength -let scrollCount = 0; -const scrollThreshold = 20; // Number of scroll events to trigger the animation - - -// 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); -renderer.shadowMap.enabled = true; -renderer.shadowMap.type = THREE.PCFSoftShadowMap; -renderer.toneMapping = THREE.ACESFilmicToneMapping; -renderer.toneMappingExposure = 1.2; -renderer.outputColorSpace = THREE.SRGBColorSpace; -renderer.physicallyCorrectLights = true; - -document.body.appendChild(renderer.domElement); - -// Post-processing: Bloom -const composer = new EffectComposer(renderer); -const renderPass = new RenderPass(scene, camera); -composer.addPass(renderPass); - -const bloomPass = new UnrealBloomPass( - new THREE.Vector2(window.innerWidth, window.innerHeight), - 1.0, // strength - 0.45, // radius - 0.85 // threshold -); -composer.addPass(bloomPass); - -// Video texture for emissive "screen"-like effect on orange material -const video = document.createElement('video'); -video.src = '/shader-flash.webm'; -video.muted = true; -video.loop = true; -video.playsInline = true; -video.autoplay = true; -video.preload = 'auto'; - -const videoTexture = new THREE.VideoTexture(video); -videoTexture.colorSpace = THREE.SRGBColorSpace; -videoTexture.generateMipmaps = false; -videoTexture.minFilter = THREE.LinearFilter; -videoTexture.magFilter = THREE.LinearFilter; - -// Ensure autoplay starts (muted autoplay is commonly allowed) -video.play().catch(() => {}); - -// Local procedural environment for better PBR response (no network) -const pmrem = new THREE.PMREMGenerator(renderer); -const roomEnv = new RoomEnvironment(); -scene.environment = pmrem.fromScene(roomEnv).texture; -pmrem.dispose(); -roomEnv.dispose(); -scene.environment = null; // This will make the renderer's clear color visible again - -// Lighting is authored below. - -// Lighting -const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); -scene.add(ambientLight); - -const hemiLight = new THREE.HemisphereLight(0xffffff, 0x666666, 1.5); -hemiLight.position.set(0, 20, 0); -scene.add(hemiLight); - -// // Key light (main directional) - angled to avoid direct reflection -// const keyLight = new THREE.DirectionalLight(0xffffff, 2.0); -// keyLight.position.set(12, 8, 8); -// keyLight.castShadow = true; -// keyLight.shadow.mapSize.width = 2048; -// keyLight.shadow.mapSize.height = 2048; -// scene.add(keyLight); - -// Fill light (opposite side) - angled -const fillLight = new THREE.DirectionalLight(0xffffff, 1.2); -fillLight.position.set(-12, 6, -8); -scene.add(fillLight); - -// Top light - angled to avoid direct downward reflection -const topLight = new THREE.DirectionalLight(0xffffff, 1.5); -topLight.position.set(5, 15, 5); -scene.add(topLight); - -// Bottom light - angled upward -const bottomLight = new THREE.DirectionalLight(0xffffff, 0.8); -bottomLight.position.set(-3, -8, 3); -scene.add(bottomLight); - -// Side lights for even illumination - angled -const leftLight = new THREE.DirectionalLight(0xffffff, 1.0); -leftLight.position.set(-12, 2, 5); -scene.add(leftLight); - -const rightLight = new THREE.DirectionalLight(0xffffff, 1.0); -rightLight.position.set(12, 2, -5); -scene.add(rightLight); - -// Front and back lights - angled to avoid direct camera reflection -const frontLight = new THREE.DirectionalLight(0xffffff, 0.8); -frontLight.position.set(8, 4, 12); -scene.add(frontLight); - -const backLight = new THREE.DirectionalLight(0xffffff, 0.8); -backLight.position.set(-8, 4, -12); -scene.add(backLight); - -// Reduced camera light -const cameraLight = new THREE.PointLight(0xffffff, 0.8, 0, 2); -camera.add(cameraLight); -scene.add(camera); - -// Controls -const controls = new OrbitControls(camera, renderer.domElement); -controls.enableDamping = true; -controls.dampingFactor = 0.25; - -const loader = new GLTFLoader(); -const dracoLoader = new DRACOLoader(); -dracoLoader.setDecoderPath('node_modules/three/examples/jsm/libs/draco/'); -loader.setDRACOLoader(dracoLoader); -let mixer = null; - -loader.load('/innovation.glb', (gltf) => { - const model = gltf.scene; - scene.add(model); - - // --- Define and Apply Materials --- - const glassMaterial = new THREE.MeshPhysicalMaterial({ - color: 0xffffff, - metalness: 0.2, - roughness: 0.05, - transmission: 1, - ior: 2, - thickness: 2, - clearcoat: 1.0, - clearcoatRoughness: 0.1, - attenuationColor: new THREE.Color(0xffffff), - attenuationDistance: 0.8, - envMapIntensity: 0, - specularIntensity: 1.0, - specularColor: new THREE.Color(0x000000), - transparent: true, - depthWrite: false, - alphaTest: 0 - }); - - const lightOrangeMaterial = new THREE.MeshStandardMaterial({ - color: 0xff8600, metalness: 0.05, roughness: 0.4, - envMapIntensity: 0, emissive: new THREE.Color(0xffad47), - emissiveMap: videoTexture, emissiveIntensity: 2.25 - }); - - 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); - return targets.some((t) => { - const ct = sanitize(t); - return clean === ct || clean.includes(ct) || ct.includes(clean); - }); - }; - - model.traverse((object) => { - if (object.isMesh) { - object.castShadow = true; - object.receiveShadow = true; - if (nameMatches(object.name, targetGlassNames)) { - // Create outer glass shell - object.material = glassMaterial.clone(); - object.material.side = THREE.DoubleSide; - object.material.depthWrite = false; - object.renderOrder = 2; // Render outer glass last - - // Create inner glass shell for better depth perception - const innerShell = object.clone(); - innerShell.material = glassMaterial.clone(); - innerShell.material.side = THREE.DoubleSide; - innerShell.material.depthWrite = false; - innerShell.material.thickness = 4; // Thinner inner layer - innerShell.material.transmission = 0.8; // More transparent inner layer - innerShell.renderOrder = 1; // Render inner glass before outer - - // Scale inner shell slightly smaller - innerShell.scale.multiplyScalar(0.95); - object.parent.add(innerShell); - - } else if (nameMatches(object.name, orangeMeshes)) { - object.material = lightOrangeMaterial.clone(); - object.renderOrder = 0; // Render orange objects first - } - } - }); - - // Compute bounds for camera framing - const box = new THREE.Box3().setFromObject(model); - const size = box.getSize(new THREE.Vector3()); - const center = box.getCenter(new THREE.Vector3()); - - // Set up animations - if (gltf.animations && gltf.animations.length > 0) { - mixer = new THREE.AnimationMixer(model); - gltf.animations.forEach((clip) => { - mixer.clipAction(clip).play(); - }); - mixer.timeScale = 3.0; - } - - // Position camera - const maxDim = Math.max(size.x, size.y, size.z); - camera.position.set(center.x, center.y, center.z + maxDim * 2); - controls.target.copy(center); - controls.update(); -}, undefined, (error) => { - console.error('Error loading model:', error); -}); -const clock = new THREE.Clock(); - -function onMouseScroll(event) { - // Only count scrolls if the animation is not already running - if (!isTwisting) { - // You can check event.deltaY to determine scroll direction - if (event.deltaY !== 0) { - scrollCount++; - console.log(`Scroll count: ${scrollCount}`); // For debugging - } - - if (scrollCount >= scrollThreshold) { - isTwisting = true; - twistProgress = 0; - scrollCount = 0; // Reset the counter - } - } -} - -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); - - // Also store bounding box data - const box = new THREE.Box3().setFromObject(mesh); - mesh.geometry.userData.bounds = { - size: box.getSize(new THREE.Vector3()), - center: box.getCenter(new THREE.Vector3()) - }; - } - - const original = mesh.geometry.userData.originalPositions; - const { size, center } = mesh.geometry.userData.bounds; - const totalHeight = size.y; // Use Y-size for the twist axis - - for (let i = 0; i < positions.count; i++) { - const x = original[i * 3]; - const y = original[i * 3 + 1]; - const z = original[i * 3 + 2]; - - // Normalize the y-position from 0 to 1 based on the mesh's height - const normalizedY = (y - center.y + totalHeight / 2) / totalHeight; - - // Calculate the twist angle based on normalized y and progress - const twistAngle = normalizedY * progress * twistStrength * 2 * Math.PI; - - // Apply rotation to the X and Z coordinates - positions.setX(i, x * Math.cos(twistAngle) - z * Math.sin(twistAngle)); - positions.setY(i, y); // Y remains unchanged as it's the axis of rotation - positions.setZ(i, x * Math.sin(twistAngle) + z * Math.cos(twistAngle)); - } - - positions.needsUpdate = true; - mesh.geometry.computeVertexNormals(); -} - -// Attach the click event listener -window.addEventListener('wheel', onMouseScroll, {passive: true}); - -function animate() { - requestAnimationFrame(animate); - - const delta = clock.getDelta(); - if (mixer) mixer.update(delta); - - controls.update(); - - // The main loop for the twisting animation - if (isTwisting) { - twistProgress += twistSpeed; - if (twistProgress > 1.0) { - twistProgress = 1.0; - isTwisting = false; - } - - // Traverse the entire scene to find all meshes to twist - scene.traverse((object) => { - if (object.isMesh) { - twistMesh(object, twistProgress); - } - }); - } - - composer.render(); -} -animate(); - -window.addEventListener('resize', () => { - camera.aspect = window.innerWidth / window.innerHeight; - camera.updateProjectionMatrix(); - renderer.setSize(window.innerWidth, window.innerHeight); - composer.setSize(window.innerWidth, window.innerHeight); -}); diff --git a/src/main copy.js b/src/main copy.js deleted file mode 100644 index 3eb854e..0000000 --- a/src/main copy.js +++ /dev/null @@ -1,643 +0,0 @@ -import './style.css' - -import * as THREE from 'three'; -import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; -import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'; -import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; -import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js'; -import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; -import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; -import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; - -// Scene setup -const scene = new THREE.Scene(); -const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); -camera.setFocalLength(50); - -const raycaster = new THREE.Raycaster(); -const mouse = new THREE.Vector2(); - -// Transition state management -let currentScene = 0; // 0: innovation, 1: agility, 2: storytelling -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 transitionDuration = 1; // Easily adjustable transition duration (seconds) -let scrollCount = 0; -const scrollThreshold = 10; // Changed to 10 as requested -let transitionStartTime = 0; - -// Scene objects -let currentModel = null; -let nextModel = null; -let mixer = null; -let nextMixer = null; -let autoRotationAngle = 0; - -// 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); -renderer.shadowMap.enabled = true; -renderer.shadowMap.type = THREE.PCFSoftShadowMap; -renderer.toneMapping = THREE.ACESFilmicToneMapping; -renderer.toneMappingExposure = 1.2; -renderer.outputColorSpace = THREE.SRGBColorSpace; -renderer.physicallyCorrectLights = true; - -document.body.appendChild(renderer.domElement); - -// Post-processing: Bloom -const composer = new EffectComposer(renderer); -const renderPass = new RenderPass(scene, camera); -composer.addPass(renderPass); - -const bloomPass = new UnrealBloomPass( - new THREE.Vector2(window.innerWidth, window.innerHeight), - 1.0, // strength - 0.45, // radius - 0.85 // threshold -); -composer.addPass(bloomPass); - -// Video texture for emissive "screen"-like effect on orange material -const video = document.createElement('video'); -video.src = '/shader-flash.webm'; -video.muted = true; -video.loop = true; -video.playsInline = true; -video.autoplay = true; -video.preload = 'auto'; - -const videoTexture = new THREE.VideoTexture(video); -videoTexture.colorSpace = THREE.SRGBColorSpace; -videoTexture.generateMipmaps = false; -videoTexture.minFilter = THREE.LinearFilter; -videoTexture.magFilter = THREE.LinearFilter; - -// Ensure autoplay starts (muted autoplay is commonly allowed) -video.play().catch(() => {}); - -// Local procedural environment for better PBR response (no network) -const pmrem = new THREE.PMREMGenerator(renderer); -const roomEnv = new RoomEnvironment(); -scene.environment = pmrem.fromScene(roomEnv).texture; -pmrem.dispose(); -roomEnv.dispose(); -scene.environment = null; // This will make the renderer's clear color visible again - -// Consistent Lighting Setup -const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); -scene.add(ambientLight); - -const hemiLight = new THREE.HemisphereLight(0xffffff, 0x666666, 1.5); -hemiLight.position.set(0, 20, 0); -scene.add(hemiLight); - -const fillLight = new THREE.DirectionalLight(0xffffff, 1.2); -fillLight.position.set(-12, 6, -8); -scene.add(fillLight); - -const topLight = new THREE.DirectionalLight(0xffffff, 1.5); -topLight.position.set(5, 15, 5); -scene.add(topLight); - -const bottomLight = new THREE.DirectionalLight(0xffffff, 0.8); -bottomLight.position.set(-3, -8, 3); -scene.add(bottomLight); - -const leftLight = new THREE.DirectionalLight(0xffffff, 1.0); -leftLight.position.set(-12, 2, 5); -scene.add(leftLight); - -const rightLight = new THREE.DirectionalLight(0xffffff, 1.0); -rightLight.position.set(12, 2, -5); -scene.add(rightLight); - -const frontLight = new THREE.DirectionalLight(0xffffff, 0.8); -frontLight.position.set(8, 4, 12); -scene.add(frontLight); - -const backLight = new THREE.DirectionalLight(0xffffff, 0.8); -backLight.position.set(-8, 4, -12); -scene.add(backLight); - -const cameraLight = new THREE.PointLight(0xffffff, 0.8, 0, 2); -camera.add(cameraLight); -scene.add(camera); - -// Controls -const controls = new OrbitControls(camera, renderer.domElement); -controls.enableDamping = true; -controls.dampingFactor = 0.25; - -// Material definitions -// Clear thick glass for innovation -const innovationGlassMaterial = new THREE.MeshPhysicalMaterial({ - color: 0xffffff, - metalness: 0.2, - roughness: 0.05, - transmission: 1, - ior: 2, - thickness: 2, - clearcoat: 1.0, - clearcoatRoughness: 0.1, - attenuationColor: new THREE.Color(0xffffff), - attenuationDistance: 0.8, - envMapIntensity: 0, - specularIntensity: 1.0, - specularColor: new THREE.Color(0x000000), - transparent: true, - depthWrite: false, - alphaTest: 0 -}); - -// Slightly frosted glass for agility and storytelling -const frostedGlassMaterial = new THREE.MeshPhysicalMaterial({ - color: 0xffffff, - metalness: 0.0, - roughness: 0.25, - transmission: 1.0, - ior: 1.5, - thickness: 2.0, - clearcoat: 0.75, - clearcoatRoughness: 0.25, - attenuationColor: new THREE.Color(0xffffff), - attenuationDistance: 1.5, - envMapIntensity: 1.25, - specularIntensity: 1.0, - specularColor: new THREE.Color(0xffffff), - transparent: true, - depthWrite: false, - side: THREE.DoubleSide -}); - -// Orange material with video shader for innovation -const lightOrangeMaterial = new THREE.MeshStandardMaterial({ - color: 0xff8600, - metalness: 0.05, - roughness: 0.4, - envMapIntensity: 0, - emissive: new THREE.Color(0xffad47), - emissiveMap: videoTexture, - 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} ===`); - let meshCount = 0; - - model.traverse((object) => { - if (object.isMesh) { - meshCount++; - console.log(`Found mesh: "${object.name}"`); - - const previousMaterial = object.material; - object.castShadow = true; - object.receiveShadow = true; - - if (modelType === 'innovation') { - // 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); - return targets.some((t) => { - const ct = sanitize(t); - return clean === ct || clean.includes(ct) || ct.includes(clean); - }); - }; - - if (nameMatches(object.name, targetGlassNames)) { - // Create outer glass shell with innovation-specific material - object.material = innovationGlassMaterial.clone(); - object.material.side = THREE.DoubleSide; - object.material.depthWrite = false; - object.renderOrder = 2; - - // 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); - - } else if (nameMatches(object.name, orangeMeshes)) { - object.material = lightOrangeMaterial.clone(); - object.renderOrder = 0; - } - } else { - // Agility and Storytelling use frosted glass material for all meshes - if (object.name.startsWith('base')) { - console.log(` → Applying frosted glass material to "${object.name}"`); - object.material = frostedGlassMaterial.clone(); - } else { - console.log(` → Applying frosted glass material (fallback) to "${object.name}"`); - object.material = frostedGlassMaterial.clone(); - } - } - - object.material.needsUpdate = true; - - // Cleanup previous materials - if (Array.isArray(previousMaterial)) { - previousMaterial.forEach((mat) => mat && mat.dispose && mat.dispose()); - } else if (previousMaterial && previousMaterial.dispose) { - previousMaterial.dispose(); - } - } - }); - - console.log(`Total meshes processed: ${meshCount}`); - console.log(`=== End Material Assignment Debug for ${modelType} ===`); -} - -// Center and frame model with camera -function centerAndFrameModel(model, targetCamera = camera) { - const box = new THREE.Box3().setFromObject(model); - const center = box.getCenter(new THREE.Vector3()); - model.position.sub(center); - model.updateMatrixWorld(true); - - const size = box.getSize(new THREE.Vector3()); - const maxDim = Math.max(size.x, size.y, size.z); - - // Only set camera position if it's not already positioned (avoid reset during transitions) - if (!isTransitioning) { - targetCamera.position.set(0, 0, maxDim * 2); - controls.target.set(0, 0, 0); - controls.update(); - } -} - -// Setup animations based on model type -function setupAnimations(model, gltf, modelType) { - if (gltf.animations && gltf.animations.length > 0) { - const animMixer = new THREE.AnimationMixer(model); - - gltf.animations.forEach((clip) => { - const action = animMixer.clipAction(clip); - - if (modelType === 'innovation') { - // PingPong loop for innovation - action.loop = THREE.LoopPingPong; - action.play(); - } else if (modelType === 'agility') { - // Regular loop for agility - action.loop = THREE.LoopRepeat; - action.play(); - } else if (modelType === 'storytelling') { - // Play once for storytelling - action.loop = THREE.LoopOnce; - action.clampWhenFinished = true; - action.play(); - } - }); - - if (modelType === 'innovation') { - animMixer.timeScale = 3.0; // Keep existing timeScale for innovation - } - - return animMixer; - } - return null; -} - -// Load model function -function loadModel(filename, modelType, onLoadCallback) { - loader.load(`/${filename}`, (gltf) => { - const model = gltf.scene; - - // Apply materials - applyMaterials(model, modelType); - - // Setup animations - const animMixer = setupAnimations(model, gltf, modelType); - - // Center and frame model - centerAndFrameModel(model); - - if (onLoadCallback) { - onLoadCallback(model, animMixer); - } - }, undefined, (error) => { - console.error(`Error loading ${filename}:`, error); - }); -} - -// Load initial innovation model -loadModel('innovation.glb', 'innovation', (model, animMixer) => { - currentModel = model; - mixer = animMixer; - scene.add(currentModel); -}); - -// Twist animation function -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); - - // Also store bounding box data - const box = new THREE.Box3().setFromObject(mesh); - mesh.geometry.userData.bounds = { - size: box.getSize(new THREE.Vector3()), - center: box.getCenter(new THREE.Vector3()) - }; - } - - const original = mesh.geometry.userData.originalPositions; - const { size, center } = mesh.geometry.userData.bounds; - const totalHeight = size.y; // Use Y-size for the twist axis - - for (let i = 0; i < positions.count; i++) { - const x = original[i * 3]; - const y = original[i * 3 + 1]; - const z = original[i * 3 + 2]; - - // Normalize the y-position from 0 to 1 based on the mesh's height - const normalizedY = (y - center.y + totalHeight / 2) / totalHeight; - - // Calculate the twist angle based on normalized y and progress - const twistAngle = normalizedY * progress * twistStrength * 2 * Math.PI; - - // Apply rotation to the X and Z coordinates - positions.setX(i, x * Math.cos(twistAngle) - z * Math.sin(twistAngle)); - positions.setY(i, y); // Y remains unchanged as it's the axis of rotation - positions.setZ(i, x * Math.sin(twistAngle) + z * Math.cos(twistAngle)); - } - - positions.needsUpdate = true; - mesh.geometry.computeVertexNormals(); -} - -// Reset mesh geometry to original state -function resetMeshGeometry(mesh) { - if (!mesh || !mesh.geometry || !mesh.geometry.userData.originalPositions) { - return; - } - - const positions = mesh.geometry.attributes.position; - const original = mesh.geometry.userData.originalPositions; - - for (let i = 0; i < positions.count; i++) { - positions.setXYZ(i, original[i * 3], original[i * 3 + 1], original[i * 3 + 2]); - } - - positions.needsUpdate = true; - mesh.geometry.computeVertexNormals(); -} - -// Start transition to next scene -function startTransition() { - if (isTransitioning || currentScene >= 2) return; - - isTransitioning = true; - isTwisting = true; - twistProgress = 0; - transitionStartTime = performance.now(); - - // Load next model - let nextModelFile = ''; - let nextModelType = ''; - - if (currentScene === 0) { - nextModelFile = 'agility.glb'; - nextModelType = 'agility'; - } else if (currentScene === 1) { - nextModelFile = 'storytelling.glb'; - nextModelType = 'storytelling'; - } - - if (nextModelFile) { - loadModel(nextModelFile, nextModelType, (model, animMixer) => { - nextModel = model; - nextMixer = animMixer; - - // Start next model as invisible and positioned below - nextModel.position.y = -10; - nextModel.traverse((obj) => { - if (obj.material) { - if (Array.isArray(obj.material)) { - obj.material.forEach(mat => { - mat.transparent = true; - mat.opacity = 0; - }); - } else { - obj.material.transparent = true; - obj.material.opacity = 0; - } - } - }); - - scene.add(nextModel); - }); - } -} - -// Update transition animation -function updateTransition(deltaTime) { - if (!isTransitioning) return; - - const elapsed = (performance.now() - transitionStartTime) / 1000; - const transitionProgress = Math.min(elapsed / transitionDuration, 1); - - // Smooth easing function (ease-in-out) - const easeInOut = (t) => t * t * (3 - 2 * t); - const easedProgress = easeInOut(transitionProgress); - - if (currentModel) { - // Move current model up and fade out - currentModel.position.y = easedProgress * 10; - - currentModel.traverse((obj) => { - if (obj.material) { - const targetOpacity = 1 - easedProgress; - if (Array.isArray(obj.material)) { - obj.material.forEach(mat => { - mat.transparent = true; - mat.opacity = targetOpacity; - }); - } else { - obj.material.transparent = true; - obj.material.opacity = targetOpacity; - } - } - }); - } - - if (nextModel) { - // Move next model to center and fade in - nextModel.position.y = -10 + (easedProgress * 10); - - nextModel.traverse((obj) => { - if (obj.material) { - const targetOpacity = easedProgress; - if (Array.isArray(obj.material)) { - obj.material.forEach(mat => { - mat.transparent = true; - mat.opacity = targetOpacity; - }); - } else { - obj.material.transparent = true; - obj.material.opacity = targetOpacity; - } - } - }); - } - - // Complete transition - if (transitionProgress >= 1) { - // Remove current 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; - } - }); - } - - // Switch to next model - if (nextModel) { - currentModel = nextModel; - mixer = nextMixer; - - // Reset position and opacity - currentModel.position.y = 0; - currentModel.traverse((obj) => { - if (obj.material) { - if (Array.isArray(obj.material)) { - obj.material.forEach(mat => { - mat.opacity = 1; - if (currentScene === 2) { // Keep transparency for storytelling glass - mat.transparent = mat.transmission > 0; - } else { - mat.transparent = mat.transmission > 0; - } - }); - } else { - obj.material.opacity = 1; - if (currentScene === 2) { // Keep transparency for storytelling glass - obj.material.transparent = obj.material.transmission > 0; - } else { - obj.material.transparent = obj.material.transmission > 0; - } - } - } - }); - } - - nextModel = null; - nextMixer = null; - isTransitioning = false; - isTwisting = false; - twistProgress = 0; - currentScene++; - scrollCount = 0; - - console.log(`Transition complete. Current scene: ${currentScene}`); - } -} - -// Scroll event handler -function onMouseScroll(event) { - // Only count downward scrolls and if not currently transitioning - if (!isTransitioning && event.deltaY > 0) { - scrollCount++; - console.log(`Scroll count: ${scrollCount}`); - - if (scrollCount >= scrollThreshold) { - startTransition(); - } - } -} - -// Attach scroll event listener -window.addEventListener('wheel', onMouseScroll, {passive: true}); - -// Animation loop -const clock = new THREE.Clock(); - -function animate() { - requestAnimationFrame(animate); - - const delta = clock.getDelta(); - - // Update mixers - if (mixer) mixer.update(delta); - if (nextMixer) nextMixer.update(delta); - - // Update transition - if (isTransitioning) { - updateTransition(delta); - - // Apply twist during transition - if (isTwisting && currentModel) { - twistProgress += twistSpeed; - if (twistProgress > 1.0) { - twistProgress = 1.0; - - // Reset geometry after twist completes - // currentModel.traverse((object) => { - // if (object.isMesh) { - // resetMeshGeometry(object); - // } - // }); - - isTwisting = false; - } else { - // Apply twist to current model - currentModel.traverse((object) => { - if (object.isMesh) { - twistMesh(object, twistProgress); - } - }); - } - } - } - - // Turntable rotation for current model - if (currentModel && !isTransitioning) { - autoRotationAngle += delta * 0.5; - currentModel.rotation.y = autoRotationAngle; - } - - controls.update(); - composer.render(); -} - -animate(); - -// Handle window resize -window.addEventListener('resize', () => { - camera.aspect = window.innerWidth / window.innerHeight; - camera.updateProjectionMatrix(); - renderer.setSize(window.innerWidth, window.innerHeight); - composer.setSize(window.innerWidth, window.innerHeight); -}); \ No newline at end of file diff --git a/src/main.js b/src/main.js index 0d81208..e639413 100644 --- a/src/main.js +++ b/src/main.js @@ -56,11 +56,15 @@ distortionPass.material.uniforms.amount.value = 0.005; // Stronger distortion distortionPass.material.uniforms.chromaticAmount.value = 0.002; // Enhanced chromatic aberration // Enhanced lighting parameters -distortionPass.material.uniforms.lightIntensity.value = 1.5; -distortionPass.material.uniforms.lightColor.value.set(1, 1, 1); // Cool blue-white +distortionPass.material.uniforms.lightIntensity.value = 0; +distortionPass.material.uniforms.lightColor.value.set(1, 1, 1); distortionPass.material.uniforms.normalStrength.value = 2.0; distortionPass.material.uniforms.ambientLight.value = 1; +// New ripple whiteness parameters +distortionPass.material.uniforms.rippleWhiteness.value = 0.025; // Amount of white tint +distortionPass.material.uniforms.rippleBrightness.value = 1; // Brightness boost for ripples + composer.addPass(distortionPass); // Enhanced pointer tracking @@ -89,19 +93,19 @@ renderer.domElement.addEventListener('pointermove', (e) => { const dx = (pointer.prevX < 0) ? 0 : Math.abs(x - pointer.prevX); const dy = (pointer.prevY < 0) ? 0 : Math.abs(y - pointer.prevY); const speed = Math.min(Math.sqrt(dx * dx + dy * dy) / (6.0 * dpr), 1.0); // More sensitive - + pointer.x = x; pointer.y = y; pointer.strength = speed * 1.2; // Enhanced strength pointer.prevX = x; pointer.prevY = y; - + // Update light position to follow cursor const rect = renderer.domElement.getBoundingClientRect(); const normalizedX = (e.clientX - rect.left) / rect.width; const normalizedY = 1.0 - (e.clientY - rect.top) / rect.height; // Flip Y distortionPass.material.uniforms.lightPosition.value.set(normalizedX, normalizedY, 1.0); - + // Update mouse coordinates for starfield mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1; mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1; @@ -111,10 +115,9 @@ renderer.domElement.addEventListener('pointerleave', () => { pointer.x = -1; pointer.y = -1; pointer.strength = 0.0; - mouse.x = -999; mouse.y = -999; - + // Reset light to center when mouse leaves distortionPass.material.uniforms.lightPosition.value.set(0.5, 0.5, 1.0); }, { passive: true }); @@ -123,17 +126,16 @@ renderer.domElement.addEventListener('pointerleave', () => { function initializeScene() { console.log('Initializing first scene (bold)'); const { model, animMixer } = createModelFromPreloaded('bold', preloadedModels, camera, controls); - setCurrentModel(model); setMixer(animMixer); scene.add(currentModel); - startBoldRoughnessAnimation(true); console.log('Bold scene initialized'); } // Animation loop const clock = new THREE.Clock(); + function animate() { requestAnimationFrame(animate); const delta = clock.getDelta(); @@ -175,10 +177,9 @@ function animate() { async function init() { try { console.log('Starting application initialization'); - preloadedModels = await sceneLoader.loadAllModels(); console.log('All models loaded successfully'); - + initializeScene(); animate(); console.log('Animation loop started'); @@ -187,6 +188,7 @@ async function init() { onMouseScroll(event, preloadedModels, scene, camera, controls); }, { passive: true }); console.log('Scroll event listener attached'); + } catch (error) { console.error('Failed to initialize scene:', error); sceneLoader.setLoadingMessage('Error loading experience. Please refresh.'); @@ -201,7 +203,6 @@ window.addEventListener('resize', () => { camera.aspect = w / h; camera.updateProjectionMatrix(); - renderer.setSize(w, h); composer.setSize(w, h);