Compare commits
5 commits
8d52d89024
...
587bd28fd0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
587bd28fd0 | ||
![]() |
63c6e78b18 | ||
![]() |
a2de17c581 | ||
![]() |
bd2bccdcb2 | ||
![]() |
6bd4a96e2d |
BIN
public/noise.jpg
Normal file
BIN
public/noise.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 260 KiB |
BIN
public/noisex.jpg
Normal file
BIN
public/noisex.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 275 KiB |
BIN
public/noisexx.jpg
Normal file
BIN
public/noisexx.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
|
@ -1,19 +1,25 @@
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
// Enhanced ripple simulation with multiple trailing ripples and lighting
|
const textureLoader = new THREE.TextureLoader();
|
||||||
|
const noiseTexture = textureLoader.load('./noise.jpg');
|
||||||
|
noiseTexture.wrapS = THREE.RepeatWrapping;
|
||||||
|
noiseTexture.wrapT = THREE.RepeatWrapping;
|
||||||
|
|
||||||
const FluidSimShader = {
|
const InkSimShader = {
|
||||||
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.97 },
|
||||||
tension: { value: 2.2 }, // Higher tension for stronger ripples
|
turbulence: { value: 0.2 },
|
||||||
radius: { value: 20.0 }, // Larger splat radius
|
scale: { value: 0.9 },
|
||||||
trailLength: { value: 5 }, // Number of trailing ripples
|
speed: { value: 0.5 },
|
||||||
|
octaves: { value: 2 },
|
||||||
|
lacunarity: { value: 2.0 },
|
||||||
|
gain: { value: 0.5 },
|
||||||
|
mouseRadius: { value: 0.15 }
|
||||||
},
|
},
|
||||||
|
|
||||||
vertexShader: `
|
vertexShader: `
|
||||||
varying vec2 vUv;
|
varying vec2 vUv;
|
||||||
void main() {
|
void main() {
|
||||||
|
@ -21,7 +27,6 @@ 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;
|
||||||
|
@ -30,86 +35,96 @@ const FluidSimShader = {
|
||||||
uniform float iTime;
|
uniform float iTime;
|
||||||
uniform vec3 mouse;
|
uniform vec3 mouse;
|
||||||
uniform float dissipation;
|
uniform float dissipation;
|
||||||
uniform float tension;
|
uniform float turbulence;
|
||||||
uniform float radius;
|
uniform float scale;
|
||||||
uniform float trailLength;
|
uniform float speed;
|
||||||
|
uniform float octaves;
|
||||||
|
uniform float lacunarity;
|
||||||
|
uniform float gain;
|
||||||
|
uniform float mouseRadius;
|
||||||
|
|
||||||
vec2 readRG(vec2 uv) {
|
vec2 hash22(vec2 p) {
|
||||||
vec4 c = texture2D(tPrev, uv);
|
p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)));
|
||||||
return c.rg;
|
return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
|
||||||
|
}
|
||||||
|
|
||||||
|
float noise(vec2 p) {
|
||||||
|
vec2 i = floor(p);
|
||||||
|
vec2 f = fract(p);
|
||||||
|
vec2 u = f * f * (3.0 - 2.0 * f);
|
||||||
|
return mix(
|
||||||
|
mix(dot(hash22(i + vec2(0.0, 0.0)), f - vec2(0.0, 0.0)),
|
||||||
|
dot(hash22(i + vec2(1.0, 0.0)), f - vec2(1.0, 0.0)), u.x),
|
||||||
|
mix(dot(hash22(i + vec2(0.0, 1.0)), f - vec2(0.0, 1.0)),
|
||||||
|
dot(hash22(i + vec2(1.0, 1.0)), f - vec2(1.0, 1.0)), u.x), u.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
float fbm(vec2 p) {
|
||||||
|
float value = 0.0;
|
||||||
|
float amplitude = 0.5;
|
||||||
|
float frequency = scale;
|
||||||
|
for(float i = 0.0; i < 8.0; i++) {
|
||||||
|
if(i >= octaves) break;
|
||||||
|
value += amplitude * noise(p * frequency);
|
||||||
|
frequency *= lacunarity;
|
||||||
|
amplitude *= gain;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec2 texel = 1.0 / iResolution;
|
vec2 uv = vUv;
|
||||||
vec2 currPrev = readRG(vUv);
|
vec4 prev = texture2D(tPrev, uv);
|
||||||
float curr = currPrev.r;
|
float time = iTime * speed;
|
||||||
float prev = currPrev.g;
|
float distortion = 0.0;
|
||||||
|
if(mouse.z > 0.001) {
|
||||||
// Enhanced 8-neighbor laplacian for stronger ripples
|
vec2 mouseUv = mouse.xy / iResolution;
|
||||||
float up = readRG(vUv + vec2(0.0, texel.y)).r;
|
vec2 diff = uv - mouseUv;
|
||||||
float down = readRG(vUv + vec2(0.0, -texel.y)).r;
|
float dist = length(diff);
|
||||||
float right = readRG(vUv + vec2( texel.x, 0.0)).r;
|
float falloff = 1.0 - smoothstep(0.0, mouseRadius * 2.0, dist);
|
||||||
float left = readRG(vUv + vec2(-texel.x, 0.0)).r;
|
float coreFalloff = 1.0 - smoothstep(0.0, mouseRadius * 0.5, dist);
|
||||||
|
if(falloff > 0.0) {
|
||||||
// Diagonal neighbors for smoother ripples
|
vec2 p = uv * 6.0 + time * 0.3;
|
||||||
float upLeft = readRG(vUv + vec2(-texel.x, texel.y)).r;
|
vec2 warpQ = vec2(fbm(p), fbm(p + vec2(5.2, 1.3)));
|
||||||
float upRight = readRG(vUv + vec2( texel.x, texel.y)).r;
|
vec2 warpR = vec2(fbm(p + 3.0 * warpQ + vec2(1.7, 9.2) + time * 0.15),
|
||||||
float downLeft = readRG(vUv + vec2(-texel.x, -texel.y)).r;
|
fbm(p + 3.0 * warpQ + vec2(8.3, 2.8) + time * 0.12));
|
||||||
float downRight = readRG(vUv + vec2( texel.x, -texel.y)).r;
|
float inkDistortion = fbm(p + 4.0 * warpR + diff * 8.0) * turbulence;
|
||||||
|
float angle = atan(diff.y, diff.x);
|
||||||
// Enhanced laplacian with diagonal weights
|
float spiral = sin(angle * 4.0 + time * 10.0 + dist * 15.0) * 0.6;
|
||||||
float lap = (up + down + left + right) * 0.2 +
|
inkDistortion += spiral * coreFalloff;
|
||||||
(upLeft + upRight + downLeft + downRight) * 0.05 - curr;
|
float mouseNoise = fbm(uv * 10.0 + time * 4.0 + mouseUv * 6.0);
|
||||||
|
inkDistortion += mouseNoise * mouse.z * 1.2;
|
||||||
// Wave equation with enhanced parameters
|
distortion += inkDistortion * falloff * mouse.z * 0.8;
|
||||||
float next = curr + (curr - prev) * dissipation + lap * tension;
|
}
|
||||||
|
float trailFalloff = 1.0 - smoothstep(0.0, mouseRadius * 4.0, dist);
|
||||||
// Multiple trailing ripples from mouse movement
|
if(trailFalloff > 0.0) {
|
||||||
if (mouse.z > 0.0001) {
|
float trail = fbm(uv * 8.0 + time * 2.0 + mouseUv * 4.0) * mouse.z * 0.3;
|
||||||
vec2 uvPx = vUv * iResolution;
|
distortion += trail * trailFalloff;
|
||||||
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;
|
|
||||||
|
|
||||||
float offset = i * radius * 0.4; // Spacing between ripples
|
|
||||||
float r = radius + offset;
|
|
||||||
float timeOffset = i * 0.3; // Temporal offset for trailing effect
|
|
||||||
|
|
||||||
// Gaussian with sine wave for ripple pattern
|
|
||||||
float g = exp(-pow(dist - offset, 2.0) / (r * r * 0.5));
|
|
||||||
float ripple = sin(dist * 0.2 - iTime * 8.0 + timeOffset) * g;
|
|
||||||
|
|
||||||
// Diminishing strength for trailing ripples
|
|
||||||
float strength = mouse.z * (1.0 - i * 0.2) * 0.8;
|
|
||||||
next += ripple * strength;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
float newValue = prev.r * dissipation + distortion * 0.12;
|
||||||
gl_FragColor = vec4(next, curr, 0.0, 1.0);
|
newValue = clamp(newValue, -2.0, 2.0);
|
||||||
|
gl_FragColor = vec4(newValue, prev.r, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
};
|
};
|
||||||
|
|
||||||
// Enhanced distortion shader with dynamic lighting and ripple whiteness
|
export const InkDistortionShader = {
|
||||||
export const FluidDistortionShader = {
|
|
||||||
uniforms: {
|
uniforms: {
|
||||||
tDiffuse: { value: null },
|
tDiffuse: { value: null },
|
||||||
tSim: { value: null },
|
tSim: { value: null },
|
||||||
|
tNoise: { value: noiseTexture },
|
||||||
iResolution: { value: new THREE.Vector2() },
|
iResolution: { value: new THREE.Vector2() },
|
||||||
amount: { value: 0.12 }, // Stronger base distortion
|
amount: { value: 0.035 },
|
||||||
chromaticAmount: { value: 0.015 }, // Enhanced chromatic aberration
|
chromaticAmount: { value: 0.015 },
|
||||||
lightPosition: { value: new THREE.Vector3(0.5, 0.5, 1.0) }, // Light position
|
time: { value: 0.0 },
|
||||||
lightIntensity: { value: 1.5 }, // Light brightness
|
noiseScale: { value: 2.0 },
|
||||||
lightColor: { value: new THREE.Color(0.8, 0.9, 1.0) }, // Cool light color
|
flowSpeed: { value: 1.0 },
|
||||||
normalStrength: { value: 2.0 }, // How pronounced the lighting effect is
|
inkDensity: { value: 0.4 },
|
||||||
ambientLight: { value: 0.15 }, // Base ambient lighting
|
chaosAmount: { value: 1.3 },
|
||||||
rippleWhiteness: { value: 0.15 }, // Amount of white tint for ripples
|
grainStrength: { value: 3.12 },
|
||||||
rippleBrightness: { value: 1.8 }, // Brightness multiplier for ripple areas
|
grainScale: { value: 8.0 }
|
||||||
},
|
},
|
||||||
|
|
||||||
vertexShader: `
|
vertexShader: `
|
||||||
varying vec2 vUv;
|
varying vec2 vUv;
|
||||||
void main() {
|
void main() {
|
||||||
|
@ -117,173 +132,155 @@ 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 sampler2D tNoise;
|
||||||
uniform vec2 iResolution;
|
uniform vec2 iResolution;
|
||||||
uniform float amount;
|
uniform float amount;
|
||||||
uniform float chromaticAmount;
|
uniform float chromaticAmount;
|
||||||
uniform vec3 lightPosition;
|
uniform float time;
|
||||||
uniform float lightIntensity;
|
uniform float noiseScale;
|
||||||
uniform vec3 lightColor;
|
uniform float flowSpeed;
|
||||||
uniform float normalStrength;
|
uniform float inkDensity;
|
||||||
uniform float ambientLight;
|
uniform float chaosAmount;
|
||||||
uniform float rippleWhiteness;
|
uniform float grainStrength;
|
||||||
uniform float rippleBrightness;
|
uniform float grainScale;
|
||||||
|
|
||||||
|
vec2 hash22(vec2 p) {
|
||||||
|
p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)));
|
||||||
|
return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
|
||||||
|
}
|
||||||
|
|
||||||
|
float noise(vec2 p) {
|
||||||
|
vec2 i = floor(p);
|
||||||
|
vec2 f = fract(p);
|
||||||
|
vec2 u = f * f * (3.0 - 2.0 * f);
|
||||||
|
return mix(
|
||||||
|
mix(dot(hash22(i + vec2(0.0, 0.0)), f - vec2(0.0, 0.0)),
|
||||||
|
dot(hash22(i + vec2(1.0, 0.0)), f - vec2(1.0, 0.0)), u.x),
|
||||||
|
mix(dot(hash22(i + vec2(0.0, 1.0)), f - vec2(0.0, 1.0)),
|
||||||
|
dot(hash22(i + vec2(1.0, 1.0)), f - vec2(1.0, 1.0)), u.x), u.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
float fbm(vec2 p) {
|
||||||
|
float value = 0.0;
|
||||||
|
float amplitude = 0.5;
|
||||||
|
float frequency = 1.0;
|
||||||
|
for(int i = 0; i < 4; i++) {
|
||||||
|
value += amplitude * noise(p * frequency);
|
||||||
|
frequency *= 2.0;
|
||||||
|
amplitude *= 0.5;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec2 texel = 1.0 / iResolution;
|
float distortionField = texture2D(tSim, vUv).r;
|
||||||
|
vec2 totalDistortion = vec2(distortionField) * amount;
|
||||||
// Sample height field for normal calculation
|
float distortionIntensity = abs(distortionField);
|
||||||
float hC = texture2D(tSim, vUv).r;
|
if(distortionIntensity > 0.01) {
|
||||||
float hL = texture2D(tSim, vUv - vec2(texel.x, 0.0)).r;
|
vec2 flowTime = vec2(time * flowSpeed * 0.3, time * flowSpeed * 0.2);
|
||||||
float hR = texture2D(tSim, vUv + vec2(texel.x, 0.0)).r;
|
vec2 flowNoise = vec2(
|
||||||
float hD = texture2D(tSim, vUv - vec2(0.0, texel.y)).r;
|
fbm(vUv * noiseScale + flowTime),
|
||||||
float hU = texture2D(tSim, vUv + vec2(0.0, texel.y)).r;
|
fbm(vUv * noiseScale + flowTime + vec2(100.0, 50.0))
|
||||||
|
);
|
||||||
// Calculate gradient and normal
|
totalDistortion += flowNoise * chaosAmount * 0.02 * distortionIntensity;
|
||||||
vec2 grad = vec2(hR - hL, hU - hD) * normalStrength;
|
float swirl = sin(time * 2.0 + vUv.x * 15.0) * cos(time * 1.7 + vUv.y * 12.0);
|
||||||
vec3 normal = normalize(vec3(-grad.x, -grad.y, 1.0));
|
vec2 swirlOffset = vec2(-swirl, swirl) * 0.01 * chaosAmount * distortionIntensity;
|
||||||
|
totalDistortion += swirlOffset;
|
||||||
// Enhanced distortion with trailing effect
|
}
|
||||||
vec2 baseOffset = grad * amount;
|
vec2 chromaticOffset = totalDistortion * chromaticAmount;
|
||||||
|
vec2 chaosChromatic = totalDistortion * 0.3;
|
||||||
// Add subtle trailing distortion based on height
|
vec2 uvR = vUv + totalDistortion + chromaticOffset + chaosChromatic;
|
||||||
vec2 trailOffset = grad * abs(hC) * amount * 0.3;
|
vec2 uvG = vUv + totalDistortion;
|
||||||
vec2 totalOffset = baseOffset + trailOffset;
|
vec2 uvB = vUv + totalDistortion - chromaticOffset - chaosChromatic * 0.5;
|
||||||
|
|
||||||
// Chromatic aberration with enhanced separation
|
|
||||||
vec2 chromaticOffset = grad * chromaticAmount;
|
|
||||||
vec2 uvR = vUv + totalOffset + chromaticOffset;
|
|
||||||
vec2 uvG = vUv + totalOffset;
|
|
||||||
vec2 uvB = vUv + totalOffset - chromaticOffset;
|
|
||||||
|
|
||||||
// Clamp UVs
|
|
||||||
uvR = clamp(uvR, vec2(0.0), vec2(1.0));
|
uvR = clamp(uvR, vec2(0.0), vec2(1.0));
|
||||||
uvG = clamp(uvG, vec2(0.0), vec2(1.0));
|
uvG = clamp(uvG, vec2(0.0), vec2(1.0));
|
||||||
uvB = clamp(uvB, vec2(0.0), vec2(1.0));
|
uvB = clamp(uvB, vec2(0.0), vec2(1.0));
|
||||||
|
|
||||||
// Sample distorted colors
|
|
||||||
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 color = vec3(r, g, b);
|
||||||
|
if(distortionIntensity > 0.01) {
|
||||||
// Dynamic lighting calculation
|
float density = distortionIntensity * inkDensity;
|
||||||
vec3 lightDir = normalize(vec3(lightPosition.xy - vUv, lightPosition.z));
|
float inkEffect = 1.0 + density * 0.5;
|
||||||
float NdotL = max(dot(normal, lightDir), 0.0);
|
color *= inkEffect;
|
||||||
|
float bleeding = smoothstep(0.02, 0.6, distortionIntensity);
|
||||||
// Create rim lighting effect for ripples
|
color = mix(color, color * 0.92, bleeding * 0.3);
|
||||||
float rimLight = pow(1.0 - abs(dot(normal, vec3(0.0, 0.0, 1.0))), 2.0);
|
vec2 grainUv = vUv * grainScale + vec2(time * 0.05, time * 0.03);
|
||||||
|
vec3 grainColor = texture2D(tNoise, grainUv).rgb;
|
||||||
// Combine lighting effects
|
grainColor = (grainColor - 0.5) * grainStrength * distortionIntensity;
|
||||||
vec3 lighting = lightColor * (NdotL * lightIntensity + rimLight * 0.3) + ambientLight;
|
color += grainColor;
|
||||||
|
}
|
||||||
// Calculate ripple intensity for both lighting and whiteness
|
gl_FragColor = vec4(color, 1.0);
|
||||||
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);
|
|
||||||
|
|
||||||
// Create a smooth falloff for the whiteness effect
|
|
||||||
float whiteIntensity = smoothstep(0.0, 0.3, rippleIntensity) * rippleWhiteness;
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
};
|
};
|
||||||
|
|
||||||
// Enhanced fluid simulation factory
|
export function createInkSimulation(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(
|
const quad = new THREE.Mesh(
|
||||||
new THREE.PlaneGeometry(2, 2),
|
new THREE.PlaneGeometry(2, 2),
|
||||||
new THREE.ShaderMaterial({
|
new THREE.ShaderMaterial({
|
||||||
uniforms: THREE.UniformsUtils.clone(FluidSimShader.uniforms),
|
uniforms: THREE.UniformsUtils.clone(InkSimShader.uniforms),
|
||||||
vertexShader: FluidSimShader.vertexShader,
|
vertexShader: InkSimShader.vertexShader,
|
||||||
fragmentShader: FluidSimShader.fragmentShader,
|
fragmentShader: InkSimShader.fragmentShader,
|
||||||
depthTest: false,
|
depthTest: false,
|
||||||
depthWrite: false
|
depthWrite: false
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
simScene.add(quad);
|
simScene.add(quad);
|
||||||
|
|
||||||
// Higher precision for better ripple quality
|
|
||||||
const params = {
|
const params = {
|
||||||
minFilter: THREE.LinearFilter,
|
minFilter: THREE.LinearFilter,
|
||||||
magFilter: THREE.LinearFilter,
|
magFilter: THREE.LinearFilter,
|
||||||
format: THREE.RGBAFormat,
|
format: THREE.RGBAFormat,
|
||||||
type: THREE.FloatType, // Use float for better precision
|
type: THREE.FloatType,
|
||||||
depthBuffer: false,
|
depthBuffer: false,
|
||||||
stencilBuffer: false
|
stencilBuffer: false
|
||||||
};
|
};
|
||||||
|
let width = Math.max(2, Math.floor(window.innerWidth * dpr * 0.5));
|
||||||
let width = Math.max(2, Math.floor(window.innerWidth * dpr));
|
let height = Math.max(2, Math.floor(window.innerHeight * dpr * 0.5));
|
||||||
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);
|
||||||
|
|
||||||
// Initialize
|
|
||||||
renderer.setRenderTarget(rtA);
|
renderer.setRenderTarget(rtA);
|
||||||
renderer.clear();
|
renderer.clear();
|
||||||
renderer.setRenderTarget(rtB);
|
renderer.setRenderTarget(rtB);
|
||||||
renderer.clear();
|
renderer.clear();
|
||||||
renderer.setRenderTarget(null);
|
renderer.setRenderTarget(null);
|
||||||
|
|
||||||
quad.material.uniforms.iResolution.value.set(width, height);
|
quad.material.uniforms.iResolution.value.set(width, height);
|
||||||
quad.material.uniforms.tPrev.value = rtA.texture;
|
quad.material.uniforms.tPrev.value = rtA.texture;
|
||||||
|
|
||||||
function swap() {
|
function swap() {
|
||||||
const tmp = rtA; rtA = rtB; rtB = tmp;
|
const tmp = rtA;
|
||||||
|
rtA = rtB;
|
||||||
|
rtB = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
function update(mouseX, mouseY, strength, timeSec) {
|
function update(mouseX, mouseY, strength, timeSec) {
|
||||||
quad.material.uniforms.iTime.value = timeSec;
|
quad.material.uniforms.iTime.value = timeSec;
|
||||||
|
|
||||||
if (mouseX < 0.0 || mouseY < 0.0) {
|
if (mouseX < 0.0 || mouseY < 0.0) {
|
||||||
quad.material.uniforms.mouse.value.set(-1, -1, 0.0);
|
quad.material.uniforms.mouse.value.set(-1, -1, 0.0);
|
||||||
} else {
|
} else {
|
||||||
// Enhanced strength for better trailing effect
|
const enhancedStrength = Math.max(0.0, Math.min(1.0, strength * 2.5));
|
||||||
const enhancedStrength = Math.max(0.0, Math.min(1.0, strength * 1.5));
|
quad.material.uniforms.mouse.value.set(mouseX * 0.5, mouseY * 0.5, enhancedStrength);
|
||||||
quad.material.uniforms.mouse.value.set(mouseX, mouseY, enhancedStrength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quad.material.uniforms.tPrev.value = rtA.texture;
|
quad.material.uniforms.tPrev.value = rtA.texture;
|
||||||
renderer.setRenderTarget(rtB);
|
renderer.setRenderTarget(rtB);
|
||||||
renderer.render(simScene, simCamera);
|
renderer.render(simScene, simCamera);
|
||||||
renderer.setRenderTarget(null);
|
renderer.setRenderTarget(null);
|
||||||
|
|
||||||
swap();
|
swap();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTexture() {
|
function getTexture() {
|
||||||
return rtA.texture;
|
return rtA.texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resize(w, h, newDpr = dpr) {
|
function resize(w, h, newDpr = dpr) {
|
||||||
width = Math.max(2, Math.floor(w * newDpr));
|
width = Math.max(2, Math.floor(w * newDpr * 0.5));
|
||||||
height = Math.max(2, Math.floor(h * newDpr));
|
height = Math.max(2, Math.floor(h * newDpr * 0.5));
|
||||||
rtA.setSize(width, height);
|
rtA.setSize(width, height);
|
||||||
rtB.setSize(width, height);
|
rtB.setSize(width, height);
|
||||||
quad.material.uniforms.iResolution.value.set(width, height);
|
quad.material.uniforms.iResolution.value.set(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { update, getTexture, resize };
|
return { update, getTexture, resize };
|
||||||
}
|
}
|
||||||
|
|
241
src/main.js
241
src/main.js
|
@ -1,4 +1,4 @@
|
||||||
import './style.css'
|
import './style.css';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { SceneLoader } from './sceneLoader.js';
|
import { SceneLoader } from './sceneLoader.js';
|
||||||
import { createScene, setupLighting, setupControls } from './sceneSetup.js';
|
import { createScene, setupLighting, setupControls } from './sceneSetup.js';
|
||||||
|
@ -12,204 +12,163 @@ import {
|
||||||
updateTransition,
|
updateTransition,
|
||||||
onMouseScroll,
|
onMouseScroll,
|
||||||
setCurrentModel,
|
setCurrentModel,
|
||||||
setMixer
|
setMixer,
|
||||||
|
setGLBRepulsionSystem,
|
||||||
|
calculateTransitionVectors
|
||||||
} from './transitionManager.js';
|
} from './transitionManager.js';
|
||||||
import {
|
import {
|
||||||
startBoldRoughnessAnimation,
|
startBoldRoughnessAnimation,
|
||||||
updateBoldRoughnessAnimation,
|
updateBoldRoughnessAnimation,
|
||||||
updateInnovationGlassAnimation
|
updateInnovationGlassAnimation
|
||||||
} from './animationManager.js';
|
} from './animationManager.js';
|
||||||
|
|
||||||
// Fluid distortion imports
|
|
||||||
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
|
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
|
||||||
import { createFluidSimulation, FluidDistortionShader } from './fluidDistortion.js';
|
import { createInkSimulation, InkDistortionShader } from './fluidDistortion.js';
|
||||||
|
|
||||||
// Starfield import
|
|
||||||
import { createStarfield } from './starfield.js';
|
import { createStarfield } from './starfield.js';
|
||||||
|
|
||||||
// Initialize loader
|
|
||||||
const sceneLoader = new SceneLoader();
|
const sceneLoader = new SceneLoader();
|
||||||
sceneLoader.setLoadingMessage('Preparing Your Experience...');
|
sceneLoader.setLoadingMessage('Preparing Your Experience...');
|
||||||
|
|
||||||
// Create scene components
|
|
||||||
const { scene, camera, renderer, composer } = createScene();
|
const { scene, camera, renderer, composer } = createScene();
|
||||||
setupLighting(scene, camera);
|
setupLighting(scene, camera);
|
||||||
const controls = setupControls(camera, renderer);
|
const controls = setupControls(camera, renderer);
|
||||||
|
controls.addEventListener('change', () => calculateTransitionVectors(camera));
|
||||||
// Create starfield
|
|
||||||
const starfield = createStarfield(scene);
|
const starfield = createStarfield(scene);
|
||||||
|
|
||||||
// Turntable animation settings
|
|
||||||
const turntableSpeed = 0.5;
|
const turntableSpeed = 0.5;
|
||||||
|
|
||||||
// Store preloaded models
|
|
||||||
let preloadedModels = {};
|
let preloadedModels = {};
|
||||||
|
|
||||||
// Enhanced fluid simulation + distortion pass
|
|
||||||
const dpr = renderer.getPixelRatio ? renderer.getPixelRatio() : Math.min(window.devicePixelRatio || 1, 2);
|
const dpr = renderer.getPixelRatio ? renderer.getPixelRatio() : Math.min(window.devicePixelRatio || 1, 2);
|
||||||
const fluid = createFluidSimulation(renderer, dpr);
|
const inkSim = createInkSimulation(renderer, dpr);
|
||||||
|
const distortionPass = new ShaderPass(InkDistortionShader);
|
||||||
const distortionPass = new ShaderPass(FluidDistortionShader);
|
distortionPass.material.uniforms.tSim.value = inkSim.getTexture();
|
||||||
distortionPass.material.uniforms.tSim.value = fluid.getTexture();
|
|
||||||
distortionPass.material.uniforms.iResolution.value.set(window.innerWidth * dpr, window.innerHeight * dpr);
|
distortionPass.material.uniforms.iResolution.value.set(window.innerWidth * dpr, window.innerHeight * dpr);
|
||||||
distortionPass.material.uniforms.amount.value = 0.005; // Stronger distortion
|
distortionPass.material.uniforms.amount.value = 0.03;
|
||||||
distortionPass.material.uniforms.chromaticAmount.value = 0.002; // Enhanced chromatic aberration
|
distortionPass.material.uniforms.chromaticAmount.value = 0.100;
|
||||||
|
distortionPass.material.uniforms.noiseScale.value = 0.05;
|
||||||
// Enhanced lighting parameters
|
distortionPass.material.uniforms.flowSpeed.value = 2.2;
|
||||||
distortionPass.material.uniforms.lightIntensity.value = 0;
|
distortionPass.material.uniforms.inkDensity.value = 0.35;
|
||||||
distortionPass.material.uniforms.lightColor.value.set(1, 1, 1);
|
distortionPass.material.uniforms.chaosAmount.value = 0.05;
|
||||||
distortionPass.material.uniforms.normalStrength.value = 2.0;
|
distortionPass.material.uniforms.grainStrength.value = 0.8;
|
||||||
distortionPass.material.uniforms.ambientLight.value = 1;
|
distortionPass.material.uniforms.grainScale.value = 5.0;
|
||||||
|
|
||||||
// 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);
|
||||||
|
const pointer = { x: -1, y: -1, strength: 0, prevX: -1, prevY: -1 };
|
||||||
// Enhanced pointer tracking
|
|
||||||
const pointer = {
|
|
||||||
x: -1,
|
|
||||||
y: -1,
|
|
||||||
strength: 0.0,
|
|
||||||
prevX: -1,
|
|
||||||
prevY: -1,
|
|
||||||
trail: [], // Store trail positions for enhanced effect
|
|
||||||
maxTrailLength: 5
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mouse coordinates for starfield
|
|
||||||
const mouse = new THREE.Vector2();
|
const mouse = new THREE.Vector2();
|
||||||
|
const raycaster = new THREE.Raycaster();
|
||||||
|
const glbRepulsion = {
|
||||||
|
radius: 30,
|
||||||
|
maxDistance: 2,
|
||||||
|
strength: 8,
|
||||||
|
originalPositions: new Map(),
|
||||||
|
currentTargets: new Map(),
|
||||||
|
interpolationSpeed: 3
|
||||||
|
};
|
||||||
|
setGLBRepulsionSystem(glbRepulsion);
|
||||||
function toSimPixels(e) {
|
function toSimPixels(e) {
|
||||||
const rect = renderer.domElement.getBoundingClientRect();
|
const rect = renderer.domElement.getBoundingClientRect();
|
||||||
const x = (e.clientX - rect.left) * dpr;
|
const x = (e.clientX - rect.left) * dpr;
|
||||||
const y = (rect.height - (e.clientY - rect.top)) * dpr;
|
const y = (rect.height - (e.clientY - rect.top)) * dpr;
|
||||||
return { x, y };
|
return { x, y };
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.domElement.addEventListener('pointermove', (e) => {
|
renderer.domElement.addEventListener('pointermove', (e) => {
|
||||||
const { x, y } = toSimPixels(e);
|
const { x, y } = toSimPixels(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) / (3 * dpr), 1);
|
||||||
|
|
||||||
pointer.x = x;
|
pointer.x = x;
|
||||||
pointer.y = y;
|
pointer.y = y;
|
||||||
pointer.strength = speed * 1.2; // Enhanced strength
|
pointer.strength = speed * 4.0;
|
||||||
pointer.prevX = x;
|
pointer.prevX = x;
|
||||||
pointer.prevY = y;
|
pointer.prevY = y;
|
||||||
|
|
||||||
// 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 nx = (e.clientX - rect.left) / rect.width;
|
||||||
const normalizedY = 1.0 - (e.clientY - rect.top) / rect.height; // Flip Y
|
const ny = 1 - (e.clientY - rect.top) / rect.height;
|
||||||
distortionPass.material.uniforms.lightPosition.value.set(normalizedX, normalizedY, 1.0);
|
mouse.x = nx * 2 - 1;
|
||||||
|
mouse.y = -ny * 2 + 1;
|
||||||
// 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;
|
|
||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
|
|
||||||
renderer.domElement.addEventListener('pointerleave', () => {
|
renderer.domElement.addEventListener('pointerleave', () => {
|
||||||
pointer.x = -1;
|
Object.assign(pointer, { x: -1, y: -1, strength: 0 });
|
||||||
pointer.y = -1;
|
mouse.set(-999, -999);
|
||||||
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 });
|
}, { passive: true });
|
||||||
|
function updateGLBRepulsion(camera, mouse, dt) {
|
||||||
// Initialize first scene
|
if (mouse.x === -999) {
|
||||||
|
[currentModel, nextModel].forEach(m => {
|
||||||
|
if (!m) return;
|
||||||
|
const orig = glbRepulsion.originalPositions.get(m);
|
||||||
|
if (!orig) return;
|
||||||
|
const tgt = glbRepulsion.currentTargets.get(m) || orig.clone();
|
||||||
|
tgt.copy(orig);
|
||||||
|
glbRepulsion.currentTargets.set(m, tgt);
|
||||||
|
m.position.lerp(tgt, Math.min(glbRepulsion.interpolationSpeed * dt, 1));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
raycaster.setFromCamera(mouse, camera);
|
||||||
|
const mouseWorld = raycaster.ray.direction.clone().multiplyScalar(50).add(raycaster.ray.origin);
|
||||||
|
[currentModel, nextModel].forEach(m => {
|
||||||
|
if (!m) return;
|
||||||
|
if (!glbRepulsion.originalPositions.has(m))
|
||||||
|
glbRepulsion.originalPositions.set(m, m.position.clone());
|
||||||
|
const orig = glbRepulsion.originalPositions.get(m);
|
||||||
|
const dx = m.position.x - mouseWorld.x;
|
||||||
|
const dy = m.position.y - mouseWorld.y;
|
||||||
|
const dz = m.position.z - mouseWorld.z;
|
||||||
|
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
|
let target = orig.clone();
|
||||||
|
if (dist < glbRepulsion.radius && dist > 0) {
|
||||||
|
const force = (1 - dist / glbRepulsion.radius) * glbRepulsion.strength;
|
||||||
|
target.add(new THREE.Vector3(dx, dy, dz).normalize().multiplyScalar(force));
|
||||||
|
const offset = target.clone().sub(orig);
|
||||||
|
if (offset.length() > glbRepulsion.maxDistance)
|
||||||
|
target = orig.clone().add(offset.normalize().multiplyScalar(glbRepulsion.maxDistance));
|
||||||
|
}
|
||||||
|
glbRepulsion.currentTargets.set(m, target);
|
||||||
|
m.position.lerp(target, Math.min(glbRepulsion.interpolationSpeed * dt, 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
function initializeScene() {
|
function initializeScene() {
|
||||||
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);
|
||||||
|
glbRepulsion.originalPositions.set(currentModel, currentModel.position.clone());
|
||||||
startBoldRoughnessAnimation(true);
|
startBoldRoughnessAnimation(true);
|
||||||
console.log('Bold scene initialized');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 dt = clock.getDelta();
|
||||||
|
const elapsedTime = clock.getElapsedTime();
|
||||||
// Update mixers
|
if (mixer) mixer.update(dt);
|
||||||
if (mixer) mixer.update(delta);
|
if (nextMixer) nextMixer.update(dt);
|
||||||
if (nextMixer) nextMixer.update(delta);
|
if (isTransitioning) updateTransition(dt, scene);
|
||||||
|
else updateGLBRepulsion(camera, mouse, dt);
|
||||||
// Update transition
|
if (currentModel) currentModel.rotation.y += turntableSpeed * dt;
|
||||||
if (isTransitioning) {
|
if (nextModel) nextModel.rotation.y += turntableSpeed * dt;
|
||||||
updateTransition(delta, scene);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turntable rotation
|
|
||||||
if (currentModel) {
|
|
||||||
currentModel.rotation.y += turntableSpeed * delta;
|
|
||||||
}
|
|
||||||
if (nextModel) {
|
|
||||||
nextModel.rotation.y += turntableSpeed * delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update material animations
|
|
||||||
updateBoldRoughnessAnimation();
|
updateBoldRoughnessAnimation();
|
||||||
updateInnovationGlassAnimation();
|
updateInnovationGlassAnimation();
|
||||||
|
starfield.animateStars(camera, mouse, dt);
|
||||||
// Animate stars with cursor interaction
|
inkSim.update(pointer.x, pointer.y, pointer.strength, elapsedTime);
|
||||||
starfield.animateStars(camera, mouse, delta);
|
distortionPass.material.uniforms.tSim.value = inkSim.getTexture();
|
||||||
|
distortionPass.material.uniforms.time.value = elapsedTime;
|
||||||
// Update enhanced fluid sim
|
|
||||||
const nowSec = performance.now() / 1000;
|
|
||||||
fluid.update(pointer.x, pointer.y, pointer.strength, nowSec);
|
|
||||||
distortionPass.material.uniforms.tSim.value = fluid.getTexture();
|
|
||||||
|
|
||||||
controls.update();
|
controls.update();
|
||||||
composer.render();
|
composer.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the scene
|
|
||||||
async function init() {
|
async function init() {
|
||||||
try {
|
try {
|
||||||
console.log('Starting application initialization');
|
|
||||||
preloadedModels = await sceneLoader.loadAllModels();
|
preloadedModels = await sceneLoader.loadAllModels();
|
||||||
console.log('All models loaded successfully');
|
|
||||||
|
|
||||||
initializeScene();
|
initializeScene();
|
||||||
animate();
|
animate();
|
||||||
console.log('Animation loop started');
|
window.addEventListener('wheel', (e) => onMouseScroll(e, preloadedModels, scene, camera, controls), { passive: true });
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
window.addEventListener('wheel', (event) => {
|
const w = window.innerWidth, h = window.innerHeight;
|
||||||
onMouseScroll(event, preloadedModels, scene, camera, controls);
|
camera.aspect = w / h;
|
||||||
}, { passive: true });
|
camera.updateProjectionMatrix();
|
||||||
console.log('Scroll event listener attached');
|
renderer.setSize(w, h);
|
||||||
|
composer.setSize(w, h);
|
||||||
} catch (error) {
|
const pr = renderer.getPixelRatio ? renderer.getPixelRatio() : Math.min(window.devicePixelRatio || 1, 2);
|
||||||
console.error('Failed to initialize scene:', error);
|
distortionPass.material.uniforms.iResolution.value.set(w * pr, h * pr);
|
||||||
|
inkSim.resize(w, h, pr);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to initialise:', err);
|
||||||
sceneLoader.setLoadingMessage('Error loading experience. Please refresh.');
|
sceneLoader.setLoadingMessage('Error loading experience. Please refresh.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle window resize
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
console.log('Window resized');
|
|
||||||
const w = window.innerWidth;
|
|
||||||
const h = window.innerHeight;
|
|
||||||
|
|
||||||
camera.aspect = w / h;
|
|
||||||
camera.updateProjectionMatrix();
|
|
||||||
renderer.setSize(w, h);
|
|
||||||
composer.setSize(w, h);
|
|
||||||
|
|
||||||
const pixelRatio = renderer.getPixelRatio ? renderer.getPixelRatio() : Math.min(window.devicePixelRatio || 1, 2);
|
|
||||||
distortionPass.material.uniforms.iResolution.value.set(w * pixelRatio, h * pixelRatio);
|
|
||||||
fluid.resize(w, h, pixelRatio);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start the application
|
|
||||||
init();
|
init();
|
||||||
|
|
|
@ -40,7 +40,7 @@ export const boldGlassMaterial = new THREE.MeshPhysicalMaterial({
|
||||||
|
|
||||||
// Orange wireframe material for bold Cubewire mesh
|
// Orange wireframe material for bold Cubewire mesh
|
||||||
export const boldWireframeMaterial = new THREE.MeshStandardMaterial({
|
export const boldWireframeMaterial = new THREE.MeshStandardMaterial({
|
||||||
color: 0xff8600,
|
color: 0xffa000,
|
||||||
metalness: 0.05,
|
metalness: 0.05,
|
||||||
roughness: 0.5
|
roughness: 0.5
|
||||||
});
|
});
|
||||||
|
@ -69,7 +69,7 @@ export const innovationGlassMaterial = new THREE.MeshPhysicalMaterial({
|
||||||
export const frostedGlassMaterial = new THREE.MeshPhysicalMaterial({
|
export const frostedGlassMaterial = new THREE.MeshPhysicalMaterial({
|
||||||
color: 0xffffff,
|
color: 0xffffff,
|
||||||
metalness: 0.0,
|
metalness: 0.0,
|
||||||
roughness: 0.25,
|
roughness: 0.35,
|
||||||
transmission: 1.0,
|
transmission: 1.0,
|
||||||
ior: 1.5,
|
ior: 1.5,
|
||||||
thickness: 2.0,
|
thickness: 2.0,
|
||||||
|
@ -87,11 +87,11 @@ export const frostedGlassMaterial = new THREE.MeshPhysicalMaterial({
|
||||||
|
|
||||||
// Orange material with video shader for innovation
|
// Orange material with video shader for innovation
|
||||||
export const lightOrangeMaterial = new THREE.MeshStandardMaterial({
|
export const lightOrangeMaterial = new THREE.MeshStandardMaterial({
|
||||||
color: 0xff8600,
|
color: 0xffa000,
|
||||||
metalness: 0.05,
|
metalness: 0.05,
|
||||||
roughness: 0.4,
|
roughness: 0.4,
|
||||||
envMapIntensity: 0,
|
envMapIntensity: 0,
|
||||||
emissive: new THREE.Color(0xffad47),
|
emissive: new THREE.Color(0xddbbbb),
|
||||||
emissiveMap: videoTexture,
|
emissiveMap: videoTexture,
|
||||||
emissiveIntensity: 2.25
|
emissiveIntensity: 2.25
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,11 +30,10 @@ export function createScene() {
|
||||||
const composer = new EffectComposer(renderer);
|
const composer = new EffectComposer(renderer);
|
||||||
const renderPass = new RenderPass(scene, camera);
|
const renderPass = new RenderPass(scene, camera);
|
||||||
composer.addPass(renderPass);
|
composer.addPass(renderPass);
|
||||||
|
|
||||||
const bloomPass = new UnrealBloomPass(
|
const bloomPass = new UnrealBloomPass(
|
||||||
new THREE.Vector2(window.innerWidth, window.innerHeight),
|
new THREE.Vector2(window.innerWidth, window.innerHeight),
|
||||||
1.0, // strength
|
0.8, // strength
|
||||||
0.45, // radius
|
0.4, // radius
|
||||||
0.85 // threshold
|
0.85 // threshold
|
||||||
);
|
);
|
||||||
composer.addPass(bloomPass);
|
composer.addPass(bloomPass);
|
||||||
|
@ -52,57 +51,47 @@ export function createScene() {
|
||||||
|
|
||||||
export function setupLighting(scene, camera) {
|
export function setupLighting(scene, camera) {
|
||||||
// Consistent Lighting Setup
|
// Consistent Lighting Setup
|
||||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
|
||||||
scene.add(ambientLight);
|
scene.add(ambientLight);
|
||||||
|
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x666666, 2);
|
||||||
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x666666, 1.5);
|
|
||||||
hemiLight.position.set(0, 20, 0);
|
hemiLight.position.set(0, 20, 0);
|
||||||
scene.add(hemiLight);
|
scene.add(hemiLight);
|
||||||
|
const fillLight = new THREE.DirectionalLight(0xffffff, 1.8);
|
||||||
const fillLight = new THREE.DirectionalLight(0xffffff, 1.2);
|
|
||||||
fillLight.position.set(-12, 6, -8);
|
fillLight.position.set(-12, 6, -8);
|
||||||
scene.add(fillLight);
|
scene.add(fillLight);
|
||||||
|
const topLight = new THREE.DirectionalLight(0xffffff, 2);
|
||||||
const topLight = new THREE.DirectionalLight(0xffffff, 1.5);
|
|
||||||
topLight.position.set(5, 15, 5);
|
topLight.position.set(5, 15, 5);
|
||||||
scene.add(topLight);
|
scene.add(topLight);
|
||||||
|
const bottomLight = new THREE.DirectionalLight(0xffffff, 2.2);
|
||||||
const bottomLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
|
||||||
bottomLight.position.set(-3, -8, 3);
|
bottomLight.position.set(-3, -8, 3);
|
||||||
scene.add(bottomLight);
|
scene.add(bottomLight);
|
||||||
|
const leftLight = new THREE.DirectionalLight(0xffffff, 1.5);
|
||||||
const leftLight = new THREE.DirectionalLight(0xffffff, 1.0);
|
|
||||||
leftLight.position.set(-12, 2, 5);
|
leftLight.position.set(-12, 2, 5);
|
||||||
scene.add(leftLight);
|
scene.add(leftLight);
|
||||||
|
const rightLight = new THREE.DirectionalLight(0xffffff, 1.5);
|
||||||
const rightLight = new THREE.DirectionalLight(0xffffff, 1.0);
|
|
||||||
rightLight.position.set(12, 2, -5);
|
rightLight.position.set(12, 2, -5);
|
||||||
scene.add(rightLight);
|
scene.add(rightLight);
|
||||||
|
const frontLight = new THREE.DirectionalLight(0xffffff, 1.2);
|
||||||
const frontLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
|
||||||
frontLight.position.set(8, 4, 12);
|
frontLight.position.set(8, 4, 12);
|
||||||
scene.add(frontLight);
|
scene.add(frontLight);
|
||||||
|
const backLight = new THREE.DirectionalLight(0xffffff, 1.2);
|
||||||
const backLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
|
||||||
backLight.position.set(-8, 4, -12);
|
backLight.position.set(-8, 4, -12);
|
||||||
scene.add(backLight);
|
scene.add(backLight);
|
||||||
|
const cameraLight = new THREE.PointLight(0xffffff, 0.8, 0, 3);
|
||||||
const cameraLight = new THREE.PointLight(0xffffff, 0.8, 0, 2);
|
|
||||||
camera.add(cameraLight);
|
camera.add(cameraLight);
|
||||||
scene.add(camera);
|
scene.add(camera);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupControls(camera, renderer) {
|
export function setupControls(camera, renderer) {
|
||||||
// Controls with zoom disabled and camera constraints
|
// Controls with zoom and pan disabled and camera constraints
|
||||||
const controls = new OrbitControls(camera, renderer.domElement);
|
const controls = new OrbitControls(camera, renderer.domElement);
|
||||||
controls.enableDamping = true;
|
controls.enableDamping = true;
|
||||||
controls.dampingFactor = 0.25;
|
controls.dampingFactor = 0.25;
|
||||||
controls.enableZoom = false; // Disable zoom
|
controls.enableZoom = false; // Disable zoom
|
||||||
|
controls.enablePan = false; // Disable panning
|
||||||
// Add camera constraints to prevent extreme angles
|
// Add camera constraints to prevent extreme angles
|
||||||
controls.maxPolarAngle = Math.PI * 0.8; // Prevent looking too far up
|
controls.maxPolarAngle = Math.PI * 0.8; // Prevent looking too far up
|
||||||
controls.minPolarAngle = Math.PI * 0.2; // Prevent looking too far down
|
controls.minPolarAngle = Math.PI * 0.2; // Prevent looking too far down
|
||||||
|
console.log('Orbit controls initialized with camera constraints and pan disabled');
|
||||||
console.log('Orbit controls initialized with camera constraints');
|
|
||||||
return controls;
|
return controls;
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
export function createStarfield(scene) {
|
export function createStarfield(scene) {
|
||||||
const starCount = 12000;
|
const starCount = 16000;
|
||||||
const starDistance = 300;
|
const starDistance = 300;
|
||||||
|
|
||||||
// Create geometry for stars
|
// Create geometry for stars
|
||||||
|
|
|
@ -2,238 +2,145 @@ import * as THREE from 'three';
|
||||||
import { createModelFromPreloaded, resetMeshGeometry, cleanupGeometryData } from './modelManager.js';
|
import { createModelFromPreloaded, resetMeshGeometry, cleanupGeometryData } from './modelManager.js';
|
||||||
import { startBoldRoughnessAnimation, startInnovationGlassAnimation } from './animationManager.js';
|
import { startBoldRoughnessAnimation, startInnovationGlassAnimation } from './animationManager.js';
|
||||||
|
|
||||||
// Transition state management
|
/* ------------------------------------------------------------------ */
|
||||||
export let currentScene = 0; // 0: bold, 1: innovation, 2: agility, 3: storytelling
|
/* state */
|
||||||
export let isTransitioning = false;
|
export let currentScene = 0; // 0-bold | 1-innovation | 2-agility | 3-storytelling
|
||||||
export const fadeSpeed = 1; // Easily adjustable fade speed
|
let pendingScene = 0; // target index during a transition
|
||||||
export const transitionDuration = 1; // Easily adjustable transition duration (seconds)
|
export let isTransitioning = false;
|
||||||
export let scrollDownCount = 0;
|
|
||||||
export let scrollUpCount = 0;
|
|
||||||
export const scrollThreshold = 10; // Changed to 10 as requested
|
|
||||||
export let transitionStartTime = 0;
|
|
||||||
export let transitionDirection = 1; // 1 for forward, -1 for backward
|
|
||||||
|
|
||||||
// Camera-relative transition vectors
|
export const transitionDuration = 1; // seconds
|
||||||
export let transitionUpVector = new THREE.Vector3();
|
export const scrollThreshold = 10;
|
||||||
export let transitionDownVector = new THREE.Vector3();
|
export const transitionDistance = 50;
|
||||||
export const transitionDistance = 50; // Increased distance for more dramatic transitions
|
|
||||||
|
|
||||||
// Scene objects
|
let scrollDownCount = 0;
|
||||||
|
let scrollUpCount = 0;
|
||||||
|
let transitionStartTime = 0;
|
||||||
|
let transitionDirection = 1; // 1 forward | -1 back
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* scene objects */
|
||||||
export let currentModel = null;
|
export let currentModel = null;
|
||||||
export let nextModel = null;
|
export let nextModel = null;
|
||||||
export let mixer = null;
|
export let mixer = null;
|
||||||
export let nextMixer = null;
|
export let nextMixer = null;
|
||||||
export let autoRotationAngle = 0;
|
export let glbRepulsionSystem = null;
|
||||||
|
|
||||||
// Setter functions to modify exported variables safely
|
/* camera-aligned vectors */
|
||||||
export function setCurrentModel(model) {
|
let transitionUpVector = new THREE.Vector3();
|
||||||
currentModel = model;
|
let transitionDownVector = new THREE.Vector3();
|
||||||
|
|
||||||
|
/* setters ----------------------------------------------------------- */
|
||||||
|
export const setCurrentModel = m => currentModel = m;
|
||||||
|
export const setMixer = m => mixer = m;
|
||||||
|
export const setGLBRepulsionSystem = s => glbRepulsionSystem = s;
|
||||||
|
|
||||||
|
/* utilities --------------------------------------------------------- */
|
||||||
|
const sceneKey = idx => ['bold','innovation','agility','storytelling'][idx];
|
||||||
|
|
||||||
|
/* compute camera-relative diagonal vectors */
|
||||||
|
export function calculateTransitionVectors(camera){
|
||||||
|
const fwd = new THREE.Vector3(); camera.getWorldDirection(fwd).normalize();
|
||||||
|
const up = new THREE.Vector3(0,1,0);
|
||||||
|
const right = new THREE.Vector3().crossVectors(fwd, up).normalize();
|
||||||
|
const camUp = new THREE.Vector3().crossVectors(right, fwd).normalize();
|
||||||
|
|
||||||
|
const diag = new THREE.Vector3()
|
||||||
|
.addScaledVector(camUp, 0.5)
|
||||||
|
.addScaledVector(right, -0.5)
|
||||||
|
.normalize();
|
||||||
|
|
||||||
|
transitionUpVector .copy(diag).multiplyScalar( transitionDistance);
|
||||||
|
transitionDownVector.copy(diag).multiplyScalar(-transitionDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setMixer(animMixer) {
|
/* ------------------------------------------------------------------ */
|
||||||
mixer = animMixer;
|
/* transition start */
|
||||||
}
|
export function startTransition(dir, preload, scene, camera, controls){
|
||||||
|
if(isTransitioning) return;
|
||||||
|
|
||||||
export function setNextModel(model) {
|
const nextIdx = currentScene + dir;
|
||||||
nextModel = model;
|
if(nextIdx < 0 || nextIdx > 3) return; // out-of-range
|
||||||
}
|
|
||||||
|
|
||||||
export function setNextMixer(animMixer) {
|
isTransitioning = true;
|
||||||
nextMixer = animMixer;
|
pendingScene = nextIdx;
|
||||||
}
|
transitionDirection = dir;
|
||||||
|
transitionStartTime = performance.now();
|
||||||
|
|
||||||
// Calculate camera-relative transition vectors for diagonal movement
|
|
||||||
export function calculateTransitionVectors(camera) {
|
|
||||||
// Get camera's world direction
|
|
||||||
const cameraDirection = new THREE.Vector3();
|
|
||||||
camera.getWorldDirection(cameraDirection);
|
|
||||||
// Get world up vector
|
|
||||||
const worldUp = new THREE.Vector3(0, 1, 0);
|
|
||||||
// Calculate camera's left vector - BACK TO ORIGINAL (this gave correct left direction)
|
|
||||||
const cameraLeft = new THREE.Vector3();
|
|
||||||
cameraLeft.crossVectors(worldUp, cameraDirection).normalize();
|
|
||||||
// Calculate camera's local up vector
|
|
||||||
const cameraUp = new THREE.Vector3();
|
|
||||||
cameraUp.crossVectors(cameraLeft, cameraDirection).normalize();
|
|
||||||
// Blend camera up with world up - BUT NEGATE to flip up/down direction
|
|
||||||
const blendedUp = new THREE.Vector3();
|
|
||||||
blendedUp.addVectors(
|
|
||||||
cameraUp.clone().multiplyScalar(0.5),
|
|
||||||
worldUp.clone().multiplyScalar(0.5)
|
|
||||||
).normalize().negate(); // ADD .negate() here to flip up to down
|
|
||||||
// Create diagonal vector (up-left)
|
|
||||||
const diagonalUpLeft = new THREE.Vector3();
|
|
||||||
diagonalUpLeft.addVectors(
|
|
||||||
blendedUp.clone().multiplyScalar(0.5),
|
|
||||||
cameraLeft.clone().multiplyScalar(0.5)
|
|
||||||
).normalize();
|
|
||||||
// Set transition vectors
|
|
||||||
transitionUpVector = diagonalUpLeft.clone().multiplyScalar(transitionDistance);
|
|
||||||
transitionDownVector = diagonalUpLeft.clone().multiplyScalar(-transitionDistance);
|
|
||||||
console.log('Diagonal transition vectors calculated with distance:', transitionDistance);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start transition to next or previous scene
|
|
||||||
export function startTransition(direction = 1, preloadedModels, scene, camera, controls) {
|
|
||||||
if (isTransitioning) return;
|
|
||||||
// Check bounds - now 4 scenes (0-3)
|
|
||||||
if (direction > 0 && currentScene >= 3) return; // Can't go forward from storytelling
|
|
||||||
if (direction < 0 && currentScene <= 0) return; // Can't go backward from bold
|
|
||||||
|
|
||||||
console.log(`Starting diagonal transition: direction=${direction}, currentScene=${currentScene}`);
|
|
||||||
// Calculate camera-relative diagonal transition vectors
|
|
||||||
calculateTransitionVectors(camera);
|
calculateTransitionVectors(camera);
|
||||||
|
|
||||||
isTransitioning = true;
|
const { model, animMixer } =
|
||||||
transitionStartTime = performance.now();
|
createModelFromPreloaded(sceneKey(nextIdx), preload, camera, controls);
|
||||||
transitionDirection = direction;
|
|
||||||
|
|
||||||
// Determine next model based on direction and current scene
|
nextModel = model;
|
||||||
let nextModelType = '';
|
nextMixer = animMixer;
|
||||||
if (direction > 0) {
|
nextModel.position.copy(dir > 0 ? transitionDownVector : transitionUpVector);
|
||||||
// Moving forward
|
|
||||||
if (currentScene === 0) {
|
|
||||||
nextModelType = 'innovation';
|
|
||||||
} else if (currentScene === 1) {
|
|
||||||
nextModelType = 'agility';
|
|
||||||
} else if (currentScene === 2) {
|
|
||||||
nextModelType = 'storytelling';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Moving backward
|
|
||||||
if (currentScene === 1) {
|
|
||||||
nextModelType = 'bold';
|
|
||||||
} else if (currentScene === 2) {
|
|
||||||
nextModelType = 'innovation';
|
|
||||||
} else if (currentScene === 3) {
|
|
||||||
nextModelType = 'agility';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Next model type: ${nextModelType}`);
|
if(glbRepulsionSystem)
|
||||||
if (nextModelType) {
|
glbRepulsionSystem.originalPositions.set(nextModel, nextModel.position.clone());
|
||||||
const { model, animMixer } = createModelFromPreloaded(nextModelType, preloadedModels, camera, controls);
|
|
||||||
nextModel = model;
|
|
||||||
nextMixer = animMixer;
|
|
||||||
|
|
||||||
// Position next model based on transition direction
|
scene.add(nextModel);
|
||||||
if (transitionDirection === 1) {
|
|
||||||
// Forward: next model starts from diagonal down position (bottom-right)
|
|
||||||
nextModel.position.copy(transitionDownVector);
|
|
||||||
console.log(`Next model positioned at diagonal down vector (bottom-right): x=${nextModel.position.x}, y=${nextModel.position.y}, z=${nextModel.position.z}`);
|
|
||||||
} else {
|
|
||||||
// Backward: next model starts from diagonal up position (top-left)
|
|
||||||
nextModel.position.copy(transitionUpVector);
|
|
||||||
console.log(`Next model positioned at diagonal up vector (top-left): x=${nextModel.position.x}, y=${nextModel.position.y}, z=${nextModel.position.z}`);
|
|
||||||
}
|
|
||||||
// Add next model to scene without opacity changes - it will appear instantly when it enters the camera view
|
|
||||||
scene.add(nextModel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update transition animation
|
/* ------------------------------------------------------------------ */
|
||||||
export function updateTransition(deltaTime, scene) {
|
/* transition update */
|
||||||
if (!isTransitioning) return;
|
export function updateTransition(_dt, scene){
|
||||||
|
if(!isTransitioning) return;
|
||||||
|
|
||||||
const elapsed = (performance.now() - transitionStartTime) / 1000;
|
const t = Math.min((performance.now() - transitionStartTime)/1000 / transitionDuration, 1);
|
||||||
const transitionProgress = Math.min(elapsed / transitionDuration, 1);
|
const e = t*t*(3-2*t); // smoothstep easing
|
||||||
// Smooth easing function (ease-in-out)
|
|
||||||
const easeInOut = (t) => t * t * (3 - 2 * t);
|
|
||||||
const easedProgress = easeInOut(transitionProgress);
|
|
||||||
|
|
||||||
if (currentModel) {
|
if(currentModel)
|
||||||
// Move current model along diagonal vector based on transition direction
|
currentModel.position.copy(
|
||||||
let moveVector;
|
(transitionDirection>0 ? transitionUpVector : transitionDownVector).clone().multiplyScalar(e)
|
||||||
if (transitionDirection === 1) {
|
);
|
||||||
// Forward: current model moves top-left
|
|
||||||
moveVector = transitionUpVector.clone().multiplyScalar(easedProgress);
|
if(nextModel)
|
||||||
console.log('Current model moving top-left (forward transition)');
|
nextModel.position.copy(
|
||||||
} else {
|
(transitionDirection>0 ? transitionDownVector : transitionUpVector).clone().multiplyScalar(1-e)
|
||||||
// Backward: current model moves bottom-right
|
);
|
||||||
moveVector = transitionDownVector.clone().multiplyScalar(easedProgress);
|
|
||||||
console.log('Current model moving bottom-right (backward transition)');
|
if(t < 1) return; // still animating
|
||||||
|
|
||||||
|
/* ----- complete transition -------------------------------------- */
|
||||||
|
if(currentModel){
|
||||||
|
currentModel.traverse(o => o.isMesh && resetMeshGeometry(o));
|
||||||
|
cleanupGeometryData(currentModel);
|
||||||
|
if(glbRepulsionSystem){
|
||||||
|
glbRepulsionSystem.originalPositions.delete(currentModel);
|
||||||
|
glbRepulsionSystem.currentTargets.delete(currentModel);
|
||||||
}
|
}
|
||||||
currentModel.position.copy(moveVector);
|
scene.remove(currentModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextModel) {
|
currentModel = nextModel;
|
||||||
// Move next model from diagonal vector to center based on transition direction
|
mixer = nextMixer;
|
||||||
let moveVector;
|
currentModel.position.set(0,0,0);
|
||||||
if (transitionDirection === 1) {
|
|
||||||
// Forward: next model moves from bottom-right to center
|
|
||||||
moveVector = transitionDownVector.clone().multiplyScalar(1 - easedProgress);
|
|
||||||
console.log('Next model moving from bottom-right to center (forward transition)');
|
|
||||||
} else {
|
|
||||||
// Backward: next model moves from top-left to center
|
|
||||||
moveVector = transitionUpVector.clone().multiplyScalar(1 - easedProgress);
|
|
||||||
console.log('Next model moving from top-left to center (backward transition)');
|
|
||||||
}
|
|
||||||
nextModel.position.copy(moveVector);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complete transition
|
if(glbRepulsionSystem)
|
||||||
if (transitionProgress >= 1) {
|
glbRepulsionSystem.originalPositions.set(currentModel, currentModel.position.clone());
|
||||||
console.log('Diagonal transition animation complete');
|
|
||||||
// FIXED: Reset geometry before removing the model
|
|
||||||
if (currentModel) {
|
|
||||||
// Reset all geometry to original state before removal
|
|
||||||
currentModel.traverse((object) => {
|
|
||||||
if (object.isMesh) {
|
|
||||||
resetMeshGeometry(object);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Clean up geometry user data completely
|
|
||||||
cleanupGeometryData(currentModel);
|
|
||||||
scene.remove(currentModel);
|
|
||||||
console.log('Previous model removed from scene');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch to next model
|
nextModel = nextMixer = null;
|
||||||
if (nextModel) {
|
|
||||||
currentModel = nextModel;
|
|
||||||
mixer = nextMixer;
|
|
||||||
// Reset position to center
|
|
||||||
currentModel.position.set(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
nextModel = null;
|
currentScene = pendingScene; // now official
|
||||||
nextMixer = null;
|
isTransitioning = false;
|
||||||
isTransitioning = false;
|
scrollDownCount = scrollUpCount = 0;
|
||||||
currentScene += transitionDirection; // Update scene based on direction
|
|
||||||
scrollDownCount = 0;
|
|
||||||
scrollUpCount = 0;
|
|
||||||
|
|
||||||
// Start animations based on current scene
|
if(currentScene === 0) startBoldRoughnessAnimation(false);
|
||||||
if (currentScene === 0) {
|
if(currentScene === 1) startInnovationGlassAnimation();
|
||||||
// Restart bold roughness animation when returning to bold section WITHOUT delay
|
|
||||||
startBoldRoughnessAnimation(false);
|
|
||||||
} else if (currentScene === 1) {
|
|
||||||
startInnovationGlassAnimation();
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Diagonal transition complete. Current scene: ${currentScene}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll event handler
|
/* ------------------------------------------------------------------ */
|
||||||
export function onMouseScroll(event, preloadedModels, scene, camera, controls) {
|
/* scroll handler */
|
||||||
if (isTransitioning) return;
|
export function onMouseScroll(ev, preload, scene, camera, controls){
|
||||||
|
if(isTransitioning) return;
|
||||||
|
|
||||||
if (event.deltaY > 0) {
|
if(ev.deltaY > 0){
|
||||||
// Scrolling down - move forward
|
scrollDownCount++; scrollUpCount = 0;
|
||||||
scrollDownCount++;
|
if(scrollDownCount >= scrollThreshold)
|
||||||
scrollUpCount = 0; // Reset up count
|
startTransition(+1, preload, scene, camera, controls);
|
||||||
console.log(`Scroll down count: ${scrollDownCount}`);
|
}else if(ev.deltaY < 0){
|
||||||
if (scrollDownCount >= scrollThreshold) {
|
scrollUpCount++; scrollDownCount = 0;
|
||||||
startTransition(1, preloadedModels, scene, camera, controls); // Forward direction
|
if(scrollUpCount >= scrollThreshold)
|
||||||
}
|
startTransition(-1, preload, scene, camera, controls);
|
||||||
} else if (event.deltaY < 0) {
|
|
||||||
// Scrolling up - move backward
|
|
||||||
scrollUpCount++;
|
|
||||||
scrollDownCount = 0; // Reset down count
|
|
||||||
console.log(`Scroll up count: ${scrollUpCount}`);
|
|
||||||
if (scrollUpCount >= scrollThreshold) {
|
|
||||||
startTransition(-1, preloadedModels, scene, camera, controls); // Backward direction
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue