diff --git a/party-stage/src/visualizers/DruggedOutFlowVisualizer.js b/party-stage/src/visualizers/DruggedOutFlowVisualizer.js new file mode 100644 index 0000000..8dce1cd --- /dev/null +++ b/party-stage/src/visualizers/DruggedOutFlowVisualizer.js @@ -0,0 +1,206 @@ +import { VisualizerInterface } from './VisualizerInterface.js'; + +const fragmentShader = ` +uniform float u_time; +uniform float u_beat; +uniform float u_opacity; +uniform vec2 u_resolution; +uniform vec3 u_colors[16]; +uniform int u_colorCount; +uniform float u_seed; +varying vec2 vUv; + +// --- Utility Functions --- +float hash(float n) { return fract(sin(n) * 43758.5453123); } +vec2 rotate(vec2 p, float a) { + float s = sin(a); + float c = cos(a); + return mat2(c, -s, s, c) * p; +} + +// --- SDFs for Shapes --- +float sdCircle(vec2 p, float r) { + return length(p) - r; +} + +float sdStar(vec2 p, float r, float spikes) { + float an = atan(p.y, p.x) / (2.0 * 3.14159265359); + an = fract(an * spikes); + float r2 = r * mix(0.6, 1.0, step(0.5, an)); + return length(p) - r2; +} + +float sdSmiley(vec2 p, float r, float t, float id) { + float d = sdCircle(p, r); + + // Eyes: Bigger and higher. Animate blinking. + float eyeBlink = smoothstep(0.9, 0.95, sin(t * 0.5 + id * 1.2)); + float eyeR = 0.18 * r * (1.0 - eyeBlink * 0.9); + vec2 eyePos = vec2(0.3, 0.35) * r; + d = max(d, -sdCircle(vec2(abs(p.x) - eyePos.x, p.y - eyePos.y), eyeR)); + + // Mouth: Changes shape (Smile/Surprise/Small mouth) + float mouthType = sin(t * 1.2 + id); + if (mouthType > 0.6) { // Surprise + d = max(d, -sdCircle(p - vec2(0.0, -0.2) * r, 0.15 * r)); + } else if (mouthType > -0.6) { // Smile + float mouth = sdCircle(p - vec2(0.0, -0.1) * r, 0.45 * r); + mouth = max(mouth, -(p.y - (0.05 + 0.1 * u_beat) * r)); // Open more on beat + d = max(d, -mouth); + } else { // Small mouth + d = max(d, -sdCircle(p - vec2(0.0, -0.25) * r, 0.08 * r)); + } + + return d; +} + +float distToSegment(vec2 p, vec2 a, vec2 b) { + vec2 pa = p - a, ba = b - a; + float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); + return length(pa - ba * h); +} + +// --- Main Shader --- +void main() { + // LED Grid Setup + float ledCountX = u_resolution.x; + float ledCountY = u_resolution.y; + vec2 gridUV = vec2(vUv.x * ledCountX, vUv.y * ledCountY); + vec2 gridDeriv = fwidth(gridUV); + float gridDensity = max(gridDeriv.x, gridDeriv.y); + float blurFactor = smoothstep(0.3, 0.8, gridDensity); + vec2 cell = fract(gridUV); + vec2 uv = mix((floor(gridUV) + 0.5) / vec2(ledCountX, ledCountY), vUv, blurFactor); + + float distMask = distance(cell, vec2(0.5)); + float edgeSoftness = clamp(gridDensity * 1.5, 0.0, 0.5); + float mask = 1.0 - smoothstep(0.35 - edgeSoftness, 0.45 + edgeSoftness, distMask); + mask = mix(mask, 1.0, blurFactor); + + // Aspect correction + vec2 p = (uv - 0.5); + p.x *= u_resolution.x / u_resolution.y; + + float t = u_time * 0.2 + u_seed; + float beat = u_beat; + + // --- Background: Swirling psychedelic colors --- + vec3 colorA = vec3(0.05, 0.0, 0.1); + vec3 colorB = vec3(0.1, 0.0, 0.2); + if (u_colorCount >= 2) { + colorA = u_colors[0] * 0.1; + colorB = u_colors[int(mod(1.0, float(u_colorCount)))] * 0.15; + } + + float bgNoise = sin(p.x * 3.0 + t) * cos(p.y * 3.0 - t * 0.7) + sin(length(p) * 4.0 - t); + vec3 finalColor = mix(colorA, colorB, bgNoise * 0.5 + 0.5); + finalColor += colorA * 0.05 * beat; // Flash background on beat + + // --- Scene Switching Logic --- + float sceneDuration = 15.0; + float sceneIndex = floor(t / sceneDuration); + float currentSceneSeed = u_seed + sceneIndex * 100.0; + + // --- "Splitting Eyes" Distortion --- + vec2 distortionCenter = vec2(sin(t * 0.3 + currentSceneSeed) * 0.5, cos(t * 0.4 + currentSceneSeed * 1.1) * 0.5); + float distortionRadius = 0.35 + beat * 0.15; + float distToDistortion = length(p - distortionCenter); + + vec2 distortedP = p; + if (distToDistortion < distortionRadius) { + float strength = smoothstep(distortionRadius, 0.0, distToDistortion); + distortedP += normalize(p - distortionCenter) * strength * (0.15 + beat * 0.2); + } + + // --- Pre-calculate Lasers for interaction --- + vec2 lp1[3], lp2[3]; + for (int j = 0; j < 3; j++) { + float laserSeed = currentSceneSeed + float(j) * 789.0; + lp1[j] = vec2(sin(t * 0.4 + laserSeed) * 0.8, cos(t * 0.3 + laserSeed * 1.1) * 0.5); + lp2[j] = vec2(sin(t * 0.5 + laserSeed * 1.5) * 0.8, cos(t * 0.4 + laserSeed * 1.7) * 0.5); + } + + // --- Render Symbols --- + int numSymbols = 8 + int(mod(currentSceneSeed, 16.0)); + for (int i = 0; i < 24; i++) { + if (i >= numSymbols) break; + float fi = float(i); + float symbolSeed = currentSceneSeed + fi * 123.456; + + // Motion: Orbital + attraction to distortion center + float speed = 0.3 + hash(symbolSeed) * 0.4; + vec2 basePos = vec2( + sin(t * speed + symbolSeed) * 0.8, + cos(t * speed * 0.8 + symbolSeed * 1.4) * 0.5 + ); + vec2 pos = mix(basePos, distortionCenter, 0.15 + beat * 0.1); + + vec2 sp = distortedP - pos; + sp = rotate(sp, t * (0.1 + fract(symbolSeed * 0.7))); + + float d; + float size = 0.06 + fract(symbolSeed * 0.3) * 0.04 + beat * 0.02; + + // Determine symbol type based on seed + if (mod(fi + sceneIndex, 3.0) == 0.0) { // Stars + d = sdStar(sp, size, 5.0 + floor(hash(symbolSeed * 1.2) * 3.0)); + } else if (mod(fi + sceneIndex, 3.0) == 1.0) { // Smilies + d = sdSmiley(sp, size, u_time, fi); + } else { // Circles + d = sdCircle(sp, size); + } + + // Interaction: Check proximity to lasers + float laserHit = 0.0; + for (int j = 0; j < 3; j++) { + float distToLaser = distToSegment(pos, lp1[j], lp2[j]); + laserHit = max(laserHit, smoothstep(0.15, 0.0, distToLaser)); + } + + float intensity = smoothstep(0.01, 0.0, d); + + // Pick a color + vec3 symbolColor = vec3(1.0); + if (u_colorCount > 0) { + int colorIdx = int(mod(fi + floor(t * 0.1), float(u_colorCount))); + symbolColor = u_colors[colorIdx]; + } + + // Interaction: Laser impact + symbolColor = mix(symbolColor, vec3(1.0), laserHit * 0.8); + float finalGlow = (0.5 + beat * 0.5 + laserHit * 2.0); + + finalColor = mix(finalColor, symbolColor, intensity * clamp(0.7 + beat * 0.3 + laserHit, 0.0, 1.0)); + + // Add a slight glow/aura + finalColor += symbolColor * (1.0 - smoothstep(0.0, size * 2.5, abs(d))) * 0.15 * finalGlow; + } + + // --- Draw Laser Beams --- + for (int i = 0; i < 3; i++) { + float distToBeam = distToSegment(distortedP, lp1[i], lp2[i]); + float beamWidth = 0.008 + beat * 0.005; + float beamIntensity = smoothstep(beamWidth, 0.0, distToBeam); + + vec3 beamColor = vec3(1.0); + if (u_colorCount > 0) { + int colorIdx = int(mod(float(i) + floor(t * 0.2), float(u_colorCount))); + beamColor = u_colors[colorIdx]; + } + + finalColor += beamColor * beamIntensity * (0.6 + beat * 0.4); + } + + gl_FragColor = vec4(finalColor, mask * u_opacity); +} +`; + +export class DruggedOutFlowVisualizer extends VisualizerInterface { + getName() { + return "Drugged Out Flow"; + } + + getFragmentShader() { + return fragmentShader; + } +} \ No newline at end of file diff --git a/party-stage/src/visualizers/index.js b/party-stage/src/visualizers/index.js index ad09a09..de672d2 100644 --- a/party-stage/src/visualizers/index.js +++ b/party-stage/src/visualizers/index.js @@ -2,12 +2,14 @@ import { ClassicVisualizer } from './ClassicVisualizer.js'; import { NebulaVisualizer } from './NebulaVisualizer.js'; import { FloatingShapesVisualizer } from './FloatingShapesVisualizer.js'; import { SynthwaveRunVisualizer } from './SynthwaveRunVisualizer.js'; +import { DruggedOutFlowVisualizer } from './DruggedOutFlowVisualizer.js'; export const visualizerLibrary = [ new ClassicVisualizer(), new NebulaVisualizer(), new FloatingShapesVisualizer(), new SynthwaveRunVisualizer(), + new DruggedOutFlowVisualizer(), ]; export default visualizerLibrary;