diff --git a/src/components/plots/MorphingPoints.tsx b/src/components/plots/MorphingPoints.tsx index 6d860408..5851e18d 100644 --- a/src/components/plots/MorphingPoints.tsx +++ b/src/components/plots/MorphingPoints.tsx @@ -1,229 +1,235 @@ "use client"; -import React, { useMemo, useRef, useEffect } from 'react'; -import * as THREE from 'three'; -import { Canvas, extend, useFrame, useThree } from '@react-three/fiber'; -import gsap from 'gsap'; -import vertexShader from '@/components/textures/shaders/LandingVertex.glsl' -import fragmentShader from '@/components/textures/shaders/LandingFrag.glsl' -import './Plots.css'; -import { useGlobalStore, usePlotStore } from '@/utils/GlobalStates'; -import { useShallow } from 'zustand/shallow'; - - -// Define the type for our custom shader material's uniforms -type MorphMaterialType = THREE.ShaderMaterial & { - uniforms: { - uSphereMix: { value: number }; - uCubeMix: { value: number }; - uPlaneMix: { value: number }; - uSize: { value: number }; - uTime: { value: number }; - }; +import React, { useMemo, useRef, useEffect } from "react"; +import * as THREE from "three"; +import { Canvas, useFrame, useThree } from "@react-three/fiber"; +import { OrbitControls } from "@react-three/drei"; +import gsap from "gsap"; +import vertexShader from "@/components/textures/shaders/LandingVertex.glsl"; +import fragmentShader from "@/components/textures/shaders/LandingFrag.glsl"; +import "./Plots.css"; +import { useGlobalStore, usePlotStore } from "@/utils/GlobalStates"; +import { useShallow } from "zustand/shallow"; + +const COUNT = 15625; + +// Seeded random number generator for deterministic results +const seededRandom = (seed: number) => { + const x = Math.sin(seed++) * 10000; + return x - Math.floor(x); }; -// Define the shader material using drei's helper +const MorphingPoints = () => { + const pointsRef = useRef(null); + const materialRef = useRef(null); + const { gl } = useThree(); + + const { setMaxTextureSize, setMax3DTextureSize } = usePlotStore( + useShallow((s) => ({ + setMaxTextureSize: s.setMaxTextureSize, + setMax3DTextureSize: s.setMax3DTextureSize, + })) + ); + useEffect(() => { + const ctx = gl.getContext(); -// Make the material available to extend + setMaxTextureSize(ctx.getParameter(ctx.MAX_TEXTURE_SIZE)); + if (ctx instanceof WebGL2RenderingContext) { + setMax3DTextureSize( + ctx.getParameter(ctx.MAX_3D_TEXTURE_SIZE) + ); + } + }, [gl, setMaxTextureSize, setMax3DTextureSize]); -const MorphingPoints = () => { - const pointsRef = useRef(null); - const count = 15625; // Total number of points - const {gl} = useThree(); - const { setMaxTextureSize, setMax3DTextureSize } = usePlotStore(useShallow(state => ({ - setMaxTextureSize: state.setMaxTextureSize, - setMax3DTextureSize: state.setMax3DTextureSize - }))) - - useEffect(()=>{ - const context = gl.getContext() - //@ts-expect-error This parameter does exist - setMax3DTextureSize(context.getParameter(context.MAX_3D_TEXTURE_SIZE)) - setMaxTextureSize(context.getParameter(context.MAX_TEXTURE_SIZE)) - },[]) - - - const {colormap} = useGlobalStore(useShallow(state => ({ - colormap: state.colormap - }))) - // Pre-calculate the point positions for each shape using useMemo for performance - const { spherePositions, cubePositions, planePositions } = useMemo(() => { - const spherePositions = new Float32Array(count * 3); - const cubePositions = new Float32Array(count * 3); - const planePositions = new Float32Array(count * 3); - - // --- Sphere Positions (using Fibonacci lattice for even distribution) --- - const phi = Math.PI * (3.0 - Math.sqrt(5.0)); // Golden angle - for (let i = 0; i < count; i++) { - const y = 1 - (i / (count - 1)) * 2; // y goes from 1 to -1 - const radius = Math.sqrt(1 - y * y); - const theta = phi * i; - - const x = Math.cos(theta) * radius; - const z = Math.sin(theta) * radius; - - spherePositions[i * 3] = x * 1.2; - spherePositions[i * 3 + 1] = y * 1.2; - spherePositions[i * 3 + 2] = z * 1.2; + const { colormap } = useGlobalStore( + useShallow((s) => ({ colormap: s.colormap })) + ); + + // Geometry data - use seeded random for deterministic results + const { + spherePositions, + cubePositions, + planePositions, + spawnPositions, + delays, + } = useMemo(() => { + const sphere = new Float32Array(COUNT * 3); + const cube = new Float32Array(COUNT * 3); + const plane = new Float32Array(COUNT * 3); + const spawn = new Float32Array(COUNT * 3); + const delays = new Float32Array(COUNT); + + // Sphere (Fibonacci) + const phi = Math.PI * (3 - Math.sqrt(5)); + for (let i = 0; i < COUNT; i++) { + const y = 1 - (i / (COUNT - 1)) * 2; + const r = Math.sqrt(1 - y * y); + const t = phi * i; + + sphere[i * 3 + 0] = Math.cos(t) * r * 1.2; + sphere[i * 3 + 1] = y * 1.2; + sphere[i * 3 + 2] = Math.sin(t) * r * 1.2; + + // Use seeded random for deterministic spawn positions + const a = seededRandom(i * 3) * Math.PI * 2; + const b = seededRandom(i * 3 + 1) * Math.PI; + const d = 4 + seededRandom(i * 3 + 2) * 2; + + spawn[i * 3 + 0] = Math.sin(b) * Math.cos(a) * d; + spawn[i * 3 + 1] = Math.sin(b) * Math.sin(a) * d; + spawn[i * 3 + 2] = Math.cos(b) * d; + + delays[i] = seededRandom(i * 5); } - // --- Cube Positions (16x16x16 grid) --- - const cubRes = 25 - let i = 0; - for (let x = 0; x < cubRes; x++) { - for (let y = 0; y < cubRes; y++) { - for (let z = 0; z < cubRes; z++) { - cubePositions[i * 3] = (x / cubRes - 0.5) * 2; - cubePositions[i * 3 + 1] = (y / cubRes - 0.5) * 2; - cubePositions[i * 3 + 2] = (z / cubRes - 0.5) * 2; - i++; + // Cube + const r = 25; + let idx = 0; + for (let x = 0; x < r; x++) + for (let y = 0; y < r; y++) + for (let z = 0; z < r; z++) { + cube[idx * 3 + 0] = (x / (r - 1) - 0.5) * 2; + cube[idx * 3 + 1] = (y / (r - 1) - 0.5) * 2; + cube[idx * 3 + 2] = (z / (r - 1) - 0.5) * 2; + idx++; } - } - } - // --- Plane Positions (64x64 grid) --- - const planeRes = 125 - i = 0; - for (let x = 0; x < planeRes; x++) { - for (let y = 0; y < planeRes; y++) { - planePositions[i * 3] = (x / planeRes - 0.5) * 2.5 ; - planePositions[i * 3 + 1] = (y / planeRes - 0.5) * 2.5 ; - planePositions[i * 3 + 2] = 0; - i++; + // Plane + const p = 125; + idx = 0; + for (let x = 0; x < p; x++) + for (let y = 0; y < p; y++) { + plane[idx * 3 + 0] = (x / (p - 1) - 0.5) * 2.5; + plane[idx * 3 + 1] = (y / (p - 1) - 0.5) * 2.5; + plane[idx * 3 + 2] = 0; + idx++; } - } - return { spherePositions, cubePositions, planePositions }; - }, [count]); - - const MorphMaterial = useMemo(()=>new THREE.ShaderMaterial({ - glslVersion: THREE.GLSL3, - uniforms: { - uSphereMix: {value: 1.0}, - uCubeMix: {value: 0.0}, - uPlaneMix: {value: 0.0}, - uTime: {value: 0.0}, - cmap: { value: colormap} - }, - vertexShader, - fragmentShader - }),[]) - // Animation effect + return { + spherePositions: sphere, + cubePositions: cube, + planePositions: plane, + spawnPositions: spawn, + delays, + }; + }, []); + + const initialUniforms = useMemo( + () => ({ + uSphereMix: { value: 0 }, + uCubeMix: { value: 0 }, + uPlaneMix: { value: 0 }, + uRandomMix: { value: 0 }, + uArrivalProgress: { value: 0 }, + uHold: { value: 0 }, + uTime: { value: 0 }, + uSize: { value: 15 }, + cmap: { value: null }, + }), + [] + ); + + // Update colormap when it changes useEffect(() => { - let tl: gsap.core.Timeline | null = null; - - if (MorphMaterial) { - const uniforms = MorphMaterial.uniforms; - - // Create a GSAP timeline for the morphing animation - tl = gsap.timeline({ - repeat: -1, // Loop indefinitely - yoyo: false, - }); - - const duration = 2; // Duration of each morph transition - const delay = 3; // Time to hold each shape before morphing - - // 1. Morph from Sphere to Cube - tl.to(uniforms.uCubeMix, { - value: 1, - duration, - delay, - ease: 'power2.inOut', - }); - - // 2. Morph from Cube to Plane - tl.to(uniforms.uCubeMix, { - value: 0, - duration, - delay, - ease: 'power2.inOut', - }); - tl.to(uniforms.uPlaneMix, { - value: 1, - duration, - ease: 'power2.inOut', - }, "<"); // Animate at the same time as the previous tween - - // 3. Morph from Plane back to Sphere - tl.to(uniforms.uPlaneMix, { - value: 0, - duration, - delay, - ease: 'power2.inOut', - }); + if (materialRef.current) { + materialRef.current.uniforms.cmap.value = colormap; + materialRef.current.needsUpdate = true; } - // Cleanup function - always return this + }, [colormap]); + + // Animation timeline + useEffect(() => { + if (!materialRef.current) return; + + const u = materialRef.current.uniforms; + const tl = gsap.timeline({ repeat: -1 }); + + const arrive = 4.5; + const morph = 2.5; + const hold = 2.5; + + tl.to(u.uArrivalProgress, { value: 1, duration: arrive, ease: "power1.out" }); + tl.to(u.uSphereMix, { value: 1, duration: arrive }, "<"); + + const addHold = () => { + tl.to(u.uHold, { value: 1, duration: 0.01 }); + tl.to({}, { duration: hold }); + tl.to(u.uHold, { value: 0, duration: 0.01 }); + }; + + addHold(); + + tl.to([u.uSphereMix, u.uCubeMix], { + value: (i: number) => (i === 0 ? 0 : 1), + duration: morph, + }); + + addHold(); + + tl.to([u.uCubeMix, u.uPlaneMix], { + value: (i: number) => (i === 0 ? 0 : 1), + duration: morph, + }); + + addHold(); + + tl.to([u.uPlaneMix, u.uRandomMix], { + value: (i: number) => (i === 0 ? 0 : 1), + duration: morph, + }); + + addHold(); + + tl.to(u.uArrivalProgress, { value: 0, duration: morph }); + tl.set([u.uRandomMix, u.uSphereMix], { value: 0 }); + return () => { - if (tl) { - tl.kill(); - } + tl.kill(); }; }, []); - - // Update time uniform for the dynamic wave animation in the shader - useFrame((state) => { - if(MorphMaterial){ - MorphMaterial.uniforms.uTime.value = state.clock.getElapsedTime(); - } - if (pointsRef.current) { - pointsRef.current.rotation.y += 0.001; // Slow rotation around Y-axis - pointsRef.current.rotation.x += 0.001; - } - }); - useEffect(()=>{ - if(MorphMaterial){ - MorphMaterial.uniforms.cmap.value = colormap + useFrame((state) => { + if (materialRef.current) { + materialRef.current.uniforms.uTime.value = state.clock.getElapsedTime(); + } + if (pointsRef.current) { + pointsRef.current.rotation.y += 0.0002; + pointsRef.current.rotation.x += 0.0001; } - },[colormap]) + }); return ( - + - {/* The 'position' attribute is not used by the shader for final position, - but it's good practice to have it. We'll use sphere positions as the base. */} - - - - + + + + + + + - {/* Use the custom morphMaterial */} + ); }; - -export const LandingShapes = () =>{ - return( -
- {/*
- Browzarr -
*/} - - - +export const LandingShapes = () => ( +
+ + + -
- ) -} \ No newline at end of file +
+); \ No newline at end of file diff --git a/src/components/textures/shaders/LandingFrag.glsl b/src/components/textures/shaders/LandingFrag.glsl index 89f0d0f1..69d0400e 100644 --- a/src/components/textures/shaders/LandingFrag.glsl +++ b/src/components/textures/shaders/LandingFrag.glsl @@ -1,9 +1,13 @@ -// Fragment Shader +precision highp float; in vec3 vColor; +in float vGreyMix; +in float vAlpha; + out vec4 Color; void main() { - // Simple white color for the points - Color = vec4(vColor, 0.8); -} \ No newline at end of file + vec3 silver = vec3(0.75); + vec3 finalColor = mix(vColor, silver, vGreyMix); + Color = vec4(finalColor, vAlpha); +} diff --git a/src/components/textures/shaders/LandingVertex.glsl b/src/components/textures/shaders/LandingVertex.glsl index fcc021b1..2ba7fd57 100644 --- a/src/components/textures/shaders/LandingVertex.glsl +++ b/src/components/textures/shaders/LandingVertex.glsl @@ -1,45 +1,81 @@ +precision highp float; uniform float uSphereMix; uniform float uCubeMix; uniform float uPlaneMix; -uniform float uSize; +uniform float uRandomMix; +uniform float uArrivalProgress; +uniform float uHold; uniform float uTime; +uniform float uSize; uniform sampler2D cmap; +in vec3 aSpherePosition; +in vec3 aCubePosition; +in vec3 aPlanePosition; +in vec3 aSpawnPosition; +in vec3 aRandomPosition; +in float aDelay; +out vec3 vColor; +out float vGreyMix; +out float vAlpha; -attribute vec3 aSpherePosition; -attribute vec3 aCubePosition; -attribute vec3 aPlanePosition; - -varying vec3 vColor; - -void main() { - // Linearly interpolate between the three shapes using the mix uniforms - vec3 pos = mix(aSpherePosition, aCubePosition, uCubeMix); - pos = mix(pos, aPlanePosition, uPlaneMix); - - // Add a slight sine wave animation to make it more dynamic - // pos.y += sin(pos.x * 50.0 + uTime) * 0.005; - // pos.x += cos(pos.y * 50.0 + uTime) * 0.005; - - float minBrightness = 0.2; - float maxBrightness = 0.96; - - float r = sin(pos.z + (uTime * 0.2 ) ) ; - float g = cos(pos.y + (uTime * 0.3 ) ); - float b = cos(pos.x+pos.y + (uTime * 0.5)); - - vColor = vec3(r, g, b); - float mag = min(((sin(r+g+b) + 1.) /2.), 0.996) ; - vec4 sampled = texture(cmap, vec2(mag, 0.5)); - vColor = sampled.rgb; - // Calculate luminance (perceived brightness) - - vec4 modelPosition = modelMatrix * vec4(pos, 1.0); - vec4 viewPosition = viewMatrix * modelPosition; - vec4 projectedPosition = projectionMatrix * viewPosition; - - gl_Position = projectedPosition; - - // Make points smaller as they are further away (perspective) - gl_PointSize = (15.0 / -viewPosition.z); +// Organic flow field (only during transitions) +vec3 flowField(vec3 p, float t) { + float s = 0.4; + return vec3( + sin(p.x * s + t) * cos(p.y * s), + cos(p.y * s + t) * sin(p.z * s), + sin(p.z * s) * cos(p.x * s + t) + ) * 0.3; } +void main() { + /* ---------------- Arrival ---------------- */ + float arrival = smoothstep(aDelay, aDelay + 0.3, uArrivalProgress); + + /* ---------------- Active shape ---------------- */ + vec3 shape = mix(aSpherePosition, aCubePosition, uCubeMix); + shape = mix(shape, aPlanePosition, uPlaneMix); + shape = mix(shape, aRandomPosition, uRandomMix); + + /* ---------------- Flow during morphs ---------------- */ + float transition = uCubeMix + uPlaneMix + uRandomMix; + shape += flowField(shape, uTime) * transition * 0.5; + + /* ---------------- Spawn → shape ---------------- */ + vec3 pos = mix(aSpawnPosition, shape, arrival); + + /* ---------------- Distance logic ---------------- */ + float dist = distance(pos, shape); + // Distance influence (soft) + float farMask = smoothstep(0.35, 0.9, dist); + + // Staggered hold easing - each particle transitions at different time + float holdOffset = aDelay * 0.5; // Use half the delay range for smoother stagger + float particleHold = smoothstep(holdOffset, holdOffset + 0.5, uHold); + + // Silver influence + vGreyMix = farMask * particleHold; + + /* ---------------- Subtle shimmer for near points during hold ---------------- */ + float shimmer = sin(uTime * 1.8 + pos.x * 3.0 + pos.y * 2.0) * 0.04; + float shimmerMask = (1.0 - farMask) * particleHold; + + /* ---------------- Color → colormap ---------------- */ + float signal = sin(pos.x + pos.y + pos.z + uTime * 0.3); + float mag = clamp((signal + 1.0) * 0.5 + shimmer * shimmerMask, 0.0, 0.996); + vColor = texture(cmap, vec2(mag, 0.5)).rgb; + + /* ---------------- Alpha polish ---------------- */ + // Silver points slightly more transparent + vAlpha = mix(0.85, 0.55, pow(vGreyMix, 1.2)); + + /* ---------------- Transform ---------------- */ + vec4 mv = viewMatrix * modelMatrix * vec4(pos, 1.0); + gl_Position = projectionMatrix * mv; + + /* ---------------- Point size (nonlinear shrink) ---------------- */ + float shrink = pow(vGreyMix, 1.4); + float sizeScale = mix(1.0, 0.4, shrink); + float size = (uSize * sizeScale) / -mv.z; + gl_PointSize = clamp(size, 1.5, 20.0); +} \ No newline at end of file