improved overlay, removed backups
This commit is contained in:
parent
a6ed2e731f
commit
8d52d89024
|
@ -1,17 +1,19 @@
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
// Enhanced ripple simulation with multiple trailing ripples and lighting
|
// Enhanced ripple simulation with multiple trailing ripples and lighting
|
||||||
|
|
||||||
const FluidSimShader = {
|
const FluidSimShader = {
|
||||||
uniforms: {
|
uniforms: {
|
||||||
tPrev: { value: null },
|
tPrev: { value: null },
|
||||||
iResolution: { value: new THREE.Vector2() },
|
iResolution: { value: new THREE.Vector2() },
|
||||||
iTime: { value: 0.0 },
|
iTime: { value: 0.0 },
|
||||||
mouse: { value: new THREE.Vector3(-1, -1, 0.0) },
|
mouse: { value: new THREE.Vector3(-1, -1, 0.0) },
|
||||||
dissipation: { value: 0.950 }, // Slightly more persistent for trails
|
dissipation: { value: 0.950 }, // Slightly more persistent for trails
|
||||||
tension: { value: 2.0 }, // Higher tension for stronger ripples
|
tension: { value: 2.2 }, // Higher tension for stronger ripples
|
||||||
radius: { value: 20.0 }, // Larger splat radius
|
radius: { value: 20.0 }, // Larger splat radius
|
||||||
trailLength: { value: 5 }, // Number of trailing ripples
|
trailLength: { value: 5 }, // Number of trailing ripples
|
||||||
},
|
},
|
||||||
|
|
||||||
vertexShader: `
|
vertexShader: `
|
||||||
varying vec2 vUv;
|
varying vec2 vUv;
|
||||||
void main() {
|
void main() {
|
||||||
|
@ -19,14 +21,14 @@ const FluidSimShader = {
|
||||||
gl_Position = vec4(position.xy, 0.0, 1.0);
|
gl_Position = vec4(position.xy, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|
||||||
fragmentShader: `
|
fragmentShader: `
|
||||||
precision highp float;
|
precision highp float;
|
||||||
varying vec2 vUv;
|
varying vec2 vUv;
|
||||||
|
|
||||||
uniform sampler2D tPrev;
|
uniform sampler2D tPrev;
|
||||||
uniform vec2 iResolution;
|
uniform vec2 iResolution;
|
||||||
uniform float iTime;
|
uniform float iTime;
|
||||||
uniform vec3 mouse;
|
uniform vec3 mouse;
|
||||||
uniform float dissipation;
|
uniform float dissipation;
|
||||||
uniform float tension;
|
uniform float tension;
|
||||||
uniform float radius;
|
uniform float radius;
|
||||||
|
@ -44,19 +46,19 @@ const FluidSimShader = {
|
||||||
float prev = currPrev.g;
|
float prev = currPrev.g;
|
||||||
|
|
||||||
// Enhanced 8-neighbor laplacian for stronger ripples
|
// Enhanced 8-neighbor laplacian for stronger ripples
|
||||||
float up = 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 down = readRG(vUv + vec2(0.0, -texel.y)).r;
|
||||||
float right = readRG(vUv + vec2( texel.x, 0.0)).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
|
// Diagonal neighbors for smoother ripples
|
||||||
float upLeft = 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 upRight = readRG(vUv + vec2( texel.x, texel.y)).r;
|
||||||
float downLeft = 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;
|
float downRight = readRG(vUv + vec2( texel.x, -texel.y)).r;
|
||||||
|
|
||||||
// Enhanced laplacian with diagonal weights
|
// 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;
|
(upLeft + upRight + downLeft + downRight) * 0.05 - curr;
|
||||||
|
|
||||||
// Wave equation with enhanced parameters
|
// Wave equation with enhanced parameters
|
||||||
|
@ -67,7 +69,7 @@ const FluidSimShader = {
|
||||||
vec2 uvPx = vUv * iResolution;
|
vec2 uvPx = vUv * iResolution;
|
||||||
vec2 d = uvPx - mouse.xy;
|
vec2 d = uvPx - mouse.xy;
|
||||||
float dist = length(d);
|
float dist = length(d);
|
||||||
|
|
||||||
// Create multiple concentric ripples
|
// Create multiple concentric ripples
|
||||||
for (float i = 0.0; i < 4.0; i++) {
|
for (float i = 0.0; i < 4.0; i++) {
|
||||||
if (i >= trailLength) break;
|
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 = {
|
export const FluidDistortionShader = {
|
||||||
uniforms: {
|
uniforms: {
|
||||||
tDiffuse: { value: null },
|
tDiffuse: { value: null },
|
||||||
tSim: { value: null },
|
tSim: { value: null },
|
||||||
iResolution: { value: new THREE.Vector2() },
|
iResolution: { value: new THREE.Vector2() },
|
||||||
amount: { value: 0.12 }, // Stronger base distortion
|
amount: { value: 0.12 }, // Stronger base distortion
|
||||||
chromaticAmount: { value: 0.015 }, // Enhanced chromatic aberration
|
chromaticAmount: { value: 0.015 }, // Enhanced chromatic aberration
|
||||||
lightPosition: { value: new THREE.Vector3(0.5, 0.5, 1.0) }, // Light position
|
lightPosition: { value: new THREE.Vector3(0.5, 0.5, 1.0) }, // Light position
|
||||||
lightIntensity: { value: 1.5 }, // Light brightness
|
lightIntensity: { value: 1.5 }, // Light brightness
|
||||||
lightColor: { value: new THREE.Color(0.8, 0.9, 1.0) }, // Cool light color
|
lightColor: { value: new THREE.Color(0.8, 0.9, 1.0) }, // Cool light color
|
||||||
normalStrength: { value: 2.0 }, // How pronounced the lighting effect is
|
normalStrength: { value: 2.0 }, // How pronounced the lighting effect is
|
||||||
ambientLight: { value: 0.15 }, // Base ambient lighting
|
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: `
|
vertexShader: `
|
||||||
varying vec2 vUv;
|
varying vec2 vUv;
|
||||||
void main() {
|
void main() {
|
||||||
|
@ -112,20 +117,22 @@ export const FluidDistortionShader = {
|
||||||
gl_Position = vec4(position.xy, 0.0, 1.0);
|
gl_Position = vec4(position.xy, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|
||||||
fragmentShader: `
|
fragmentShader: `
|
||||||
precision highp float;
|
precision highp float;
|
||||||
varying vec2 vUv;
|
varying vec2 vUv;
|
||||||
|
|
||||||
uniform sampler2D tDiffuse;
|
uniform sampler2D tDiffuse;
|
||||||
uniform sampler2D tSim;
|
uniform sampler2D tSim;
|
||||||
uniform vec2 iResolution;
|
uniform vec2 iResolution;
|
||||||
uniform float amount;
|
uniform float amount;
|
||||||
uniform float chromaticAmount;
|
uniform float chromaticAmount;
|
||||||
uniform vec3 lightPosition;
|
uniform vec3 lightPosition;
|
||||||
uniform float lightIntensity;
|
uniform float lightIntensity;
|
||||||
uniform vec3 lightColor;
|
uniform vec3 lightColor;
|
||||||
uniform float normalStrength;
|
uniform float normalStrength;
|
||||||
uniform float ambientLight;
|
uniform float ambientLight;
|
||||||
|
uniform float rippleWhiteness;
|
||||||
|
uniform float rippleBrightness;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec2 texel = 1.0 / iResolution;
|
vec2 texel = 1.0 / iResolution;
|
||||||
|
@ -147,10 +154,9 @@ export const FluidDistortionShader = {
|
||||||
// Add subtle trailing distortion based on height
|
// Add subtle trailing distortion based on height
|
||||||
vec2 trailOffset = grad * abs(hC) * amount * 0.3;
|
vec2 trailOffset = grad * abs(hC) * amount * 0.3;
|
||||||
vec2 totalOffset = baseOffset + trailOffset;
|
vec2 totalOffset = baseOffset + trailOffset;
|
||||||
|
|
||||||
// Chromatic aberration with enhanced separation
|
// Chromatic aberration with enhanced separation
|
||||||
vec2 chromaticOffset = grad * chromaticAmount;
|
vec2 chromaticOffset = grad * chromaticAmount;
|
||||||
|
|
||||||
vec2 uvR = vUv + totalOffset + chromaticOffset;
|
vec2 uvR = vUv + totalOffset + chromaticOffset;
|
||||||
vec2 uvG = vUv + totalOffset;
|
vec2 uvG = vUv + totalOffset;
|
||||||
vec2 uvB = vUv + totalOffset - chromaticOffset;
|
vec2 uvB = vUv + totalOffset - chromaticOffset;
|
||||||
|
@ -164,27 +170,38 @@ export const FluidDistortionShader = {
|
||||||
float r = texture2D(tDiffuse, uvR).r;
|
float r = texture2D(tDiffuse, uvR).r;
|
||||||
float g = texture2D(tDiffuse, uvG).g;
|
float g = texture2D(tDiffuse, uvG).g;
|
||||||
float b = texture2D(tDiffuse, uvB).b;
|
float b = texture2D(tDiffuse, uvB).b;
|
||||||
|
|
||||||
vec3 distortedColor = vec3(r, g, b);
|
vec3 distortedColor = vec3(r, g, b);
|
||||||
|
|
||||||
// Dynamic lighting calculation
|
// Dynamic lighting calculation
|
||||||
vec3 lightDir = normalize(vec3(lightPosition.xy - vUv, lightPosition.z));
|
vec3 lightDir = normalize(vec3(lightPosition.xy - vUv, lightPosition.z));
|
||||||
float NdotL = max(dot(normal, lightDir), 0.0);
|
float NdotL = max(dot(normal, lightDir), 0.0);
|
||||||
|
|
||||||
// Create rim lighting effect for ripples
|
// Create rim lighting effect for ripples
|
||||||
float rimLight = pow(1.0 - abs(dot(normal, vec3(0.0, 0.0, 1.0))), 2.0);
|
float rimLight = pow(1.0 - abs(dot(normal, vec3(0.0, 0.0, 1.0))), 2.0);
|
||||||
|
|
||||||
// Combine lighting effects
|
// Combine lighting effects
|
||||||
vec3 lighting = lightColor * (NdotL * lightIntensity + rimLight * 0.3) + ambientLight;
|
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;
|
float rippleIntensity = abs(hC) + length(grad) * 0.5;
|
||||||
rippleIntensity = clamp(rippleIntensity, 0.0, 1.0);
|
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
|
// Create a smooth falloff for the whiteness effect
|
||||||
vec3 finalColor = mix(distortedColor, distortedColor * lighting, rippleIntensity);
|
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) {
|
export function createFluidSimulation(renderer, dpr = 1) {
|
||||||
const simScene = new THREE.Scene();
|
const simScene = new THREE.Scene();
|
||||||
const simCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
|
const simCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
|
||||||
|
|
||||||
const quad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), new THREE.ShaderMaterial({
|
const quad = new THREE.Mesh(
|
||||||
uniforms: THREE.UniformsUtils.clone(FluidSimShader.uniforms),
|
new THREE.PlaneGeometry(2, 2),
|
||||||
vertexShader: FluidSimShader.vertexShader,
|
new THREE.ShaderMaterial({
|
||||||
fragmentShader: FluidSimShader.fragmentShader,
|
uniforms: THREE.UniformsUtils.clone(FluidSimShader.uniforms),
|
||||||
depthTest: false,
|
vertexShader: FluidSimShader.vertexShader,
|
||||||
depthWrite: false
|
fragmentShader: FluidSimShader.fragmentShader,
|
||||||
}));
|
depthTest: false,
|
||||||
|
depthWrite: false
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
simScene.add(quad);
|
simScene.add(quad);
|
||||||
|
|
||||||
// Higher precision for better ripple quality
|
// 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 width = Math.max(2, Math.floor(window.innerWidth * dpr));
|
||||||
let height = Math.max(2, Math.floor(window.innerHeight * dpr));
|
let height = Math.max(2, Math.floor(window.innerHeight * dpr));
|
||||||
|
|
||||||
let rtA = new THREE.WebGLRenderTarget(width, height, params);
|
let rtA = new THREE.WebGLRenderTarget(width, height, params);
|
||||||
let rtB = 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.setRenderTarget(rtB);
|
||||||
renderer.render(simScene, simCamera);
|
renderer.render(simScene, simCamera);
|
||||||
renderer.setRenderTarget(null);
|
renderer.setRenderTarget(null);
|
||||||
|
|
||||||
swap();
|
swap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
});
|
|
643
src/main copy.js
643
src/main copy.js
|
@ -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);
|
|
||||||
});
|
|
25
src/main.js
25
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
|
distortionPass.material.uniforms.chromaticAmount.value = 0.002; // Enhanced chromatic aberration
|
||||||
|
|
||||||
// Enhanced lighting parameters
|
// Enhanced lighting parameters
|
||||||
distortionPass.material.uniforms.lightIntensity.value = 1.5;
|
distortionPass.material.uniforms.lightIntensity.value = 0;
|
||||||
distortionPass.material.uniforms.lightColor.value.set(1, 1, 1); // Cool blue-white
|
distortionPass.material.uniforms.lightColor.value.set(1, 1, 1);
|
||||||
distortionPass.material.uniforms.normalStrength.value = 2.0;
|
distortionPass.material.uniforms.normalStrength.value = 2.0;
|
||||||
distortionPass.material.uniforms.ambientLight.value = 1;
|
distortionPass.material.uniforms.ambientLight.value = 1;
|
||||||
|
|
||||||
|
// 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);
|
composer.addPass(distortionPass);
|
||||||
|
|
||||||
// Enhanced pointer tracking
|
// Enhanced pointer tracking
|
||||||
|
@ -89,19 +93,19 @@ renderer.domElement.addEventListener('pointermove', (e) => {
|
||||||
const dx = (pointer.prevX < 0) ? 0 : Math.abs(x - pointer.prevX);
|
const dx = (pointer.prevX < 0) ? 0 : Math.abs(x - pointer.prevX);
|
||||||
const dy = (pointer.prevY < 0) ? 0 : Math.abs(y - pointer.prevY);
|
const dy = (pointer.prevY < 0) ? 0 : Math.abs(y - pointer.prevY);
|
||||||
const speed = Math.min(Math.sqrt(dx * dx + dy * dy) / (6.0 * dpr), 1.0); // More sensitive
|
const speed = Math.min(Math.sqrt(dx * dx + dy * dy) / (6.0 * dpr), 1.0); // More sensitive
|
||||||
|
|
||||||
pointer.x = x;
|
pointer.x = x;
|
||||||
pointer.y = y;
|
pointer.y = y;
|
||||||
pointer.strength = speed * 1.2; // Enhanced strength
|
pointer.strength = speed * 1.2; // Enhanced strength
|
||||||
pointer.prevX = x;
|
pointer.prevX = x;
|
||||||
pointer.prevY = y;
|
pointer.prevY = y;
|
||||||
|
|
||||||
// Update light position to follow cursor
|
// Update light position to follow cursor
|
||||||
const rect = renderer.domElement.getBoundingClientRect();
|
const rect = renderer.domElement.getBoundingClientRect();
|
||||||
const normalizedX = (e.clientX - rect.left) / rect.width;
|
const normalizedX = (e.clientX - rect.left) / rect.width;
|
||||||
const normalizedY = 1.0 - (e.clientY - rect.top) / rect.height; // Flip Y
|
const normalizedY = 1.0 - (e.clientY - rect.top) / rect.height; // Flip Y
|
||||||
distortionPass.material.uniforms.lightPosition.value.set(normalizedX, normalizedY, 1.0);
|
distortionPass.material.uniforms.lightPosition.value.set(normalizedX, normalizedY, 1.0);
|
||||||
|
|
||||||
// Update mouse coordinates for starfield
|
// Update mouse coordinates for starfield
|
||||||
mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
|
mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
|
||||||
mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
||||||
|
@ -111,10 +115,9 @@ renderer.domElement.addEventListener('pointerleave', () => {
|
||||||
pointer.x = -1;
|
pointer.x = -1;
|
||||||
pointer.y = -1;
|
pointer.y = -1;
|
||||||
pointer.strength = 0.0;
|
pointer.strength = 0.0;
|
||||||
|
|
||||||
mouse.x = -999;
|
mouse.x = -999;
|
||||||
mouse.y = -999;
|
mouse.y = -999;
|
||||||
|
|
||||||
// Reset light to center when mouse leaves
|
// Reset light to center when mouse leaves
|
||||||
distortionPass.material.uniforms.lightPosition.value.set(0.5, 0.5, 1.0);
|
distortionPass.material.uniforms.lightPosition.value.set(0.5, 0.5, 1.0);
|
||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
|
@ -123,17 +126,16 @@ renderer.domElement.addEventListener('pointerleave', () => {
|
||||||
function initializeScene() {
|
function initializeScene() {
|
||||||
console.log('Initializing first scene (bold)');
|
console.log('Initializing first scene (bold)');
|
||||||
const { model, animMixer } = createModelFromPreloaded('bold', preloadedModels, camera, controls);
|
const { model, animMixer } = createModelFromPreloaded('bold', preloadedModels, camera, controls);
|
||||||
|
|
||||||
setCurrentModel(model);
|
setCurrentModel(model);
|
||||||
setMixer(animMixer);
|
setMixer(animMixer);
|
||||||
scene.add(currentModel);
|
scene.add(currentModel);
|
||||||
|
|
||||||
startBoldRoughnessAnimation(true);
|
startBoldRoughnessAnimation(true);
|
||||||
console.log('Bold scene initialized');
|
console.log('Bold scene initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animation loop
|
// Animation loop
|
||||||
const clock = new THREE.Clock();
|
const clock = new THREE.Clock();
|
||||||
|
|
||||||
function animate() {
|
function animate() {
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
const delta = clock.getDelta();
|
const delta = clock.getDelta();
|
||||||
|
@ -175,10 +177,9 @@ function animate() {
|
||||||
async function init() {
|
async function init() {
|
||||||
try {
|
try {
|
||||||
console.log('Starting application initialization');
|
console.log('Starting application initialization');
|
||||||
|
|
||||||
preloadedModels = await sceneLoader.loadAllModels();
|
preloadedModels = await sceneLoader.loadAllModels();
|
||||||
console.log('All models loaded successfully');
|
console.log('All models loaded successfully');
|
||||||
|
|
||||||
initializeScene();
|
initializeScene();
|
||||||
animate();
|
animate();
|
||||||
console.log('Animation loop started');
|
console.log('Animation loop started');
|
||||||
|
@ -187,6 +188,7 @@ async function init() {
|
||||||
onMouseScroll(event, preloadedModels, scene, camera, controls);
|
onMouseScroll(event, preloadedModels, scene, camera, controls);
|
||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
console.log('Scroll event listener attached');
|
console.log('Scroll event listener attached');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize scene:', error);
|
console.error('Failed to initialize scene:', error);
|
||||||
sceneLoader.setLoadingMessage('Error loading experience. Please refresh.');
|
sceneLoader.setLoadingMessage('Error loading experience. Please refresh.');
|
||||||
|
@ -201,7 +203,6 @@ window.addEventListener('resize', () => {
|
||||||
|
|
||||||
camera.aspect = w / h;
|
camera.aspect = w / h;
|
||||||
camera.updateProjectionMatrix();
|
camera.updateProjectionMatrix();
|
||||||
|
|
||||||
renderer.setSize(w, h);
|
renderer.setSize(w, h);
|
||||||
composer.setSize(w, h);
|
composer.setSize(w, h);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue