@scribehow/shaders
Version:
WebGL shaders used across various Scribe surfaces on web.
409 lines (325 loc) • 24.3 kB
JavaScript
var Z=Object.defineProperty;var N=Object.getOwnPropertySymbols;var ee=Object.prototype.hasOwnProperty,te=Object.prototype.propertyIsEnumerable;var B=(e,t,i)=>t in e?Z(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,M=(e,t)=>{for(var i in t||(t={}))ee.call(t,i)&&B(e,i,t[i]);if(N)for(var i of N(t))te.call(t,i)&&B(e,i,t[i]);return e};var E=(e,t,i)=>new Promise((a,r)=>{var s=n=>{try{g(i.next(n))}catch(c){r(c)}},o=n=>{try{g(i.throw(n))}catch(c){r(c)}},g=n=>n.done?a(n.value):Promise.resolve(n.value).then(s,o);g((i=i.apply(e,t)).next())});var D=`precision mediump float;
attribute vec2 uv;
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0, 1);
}
`;var _=`precision highp float;
const float PI = 3.14159265359;
const float WHITE_WAVE_TAIL_LENGTH = 2.0;
const float COLORED_WAVE_NOISE_MULTIPLIER = 1.5;
const float FALLOFF_CONVERSION_FACTOR = 8.0;
const float ANIMATION_TIME_SCALE = 0.6;
const float ANIMATION_SPATIAL_SCALE = 12.0;
const float ANIMATION_SPATIAL_TIME_SCALE = 1.2;
const float ANIMATION_WEIGHT_1 = 0.57;
const float ANIMATION_WEIGHT_2 = 0.43;
const float GRAIN_SCALE = 0.05;
const float WAVE_PRESENCE_COLOR_FACTOR = 0.5;
uniform vec4 uBackground;
uniform vec2 uResolution;
uniform float uTime;
uniform float uWaveAmplitude;
uniform float uWaveSpeed;
uniform float uWaveFrequency;
uniform int uWaveSide;
uniform vec3 uWaveColor1;
uniform vec3 uWaveColor2;
uniform vec3 uWaveColor3;
uniform vec3 uWaveColor4;
uniform vec3 uWaveColor5;
uniform float uMinFalloff;
uniform float uMaxFalloff;
uniform bool uEchoEnabled;
uniform float uEchoOffset;
uniform float uEchoRadius;
uniform float uEchoFrequency;
uniform float uEchoRingSize;
uniform float uEchoStrokeOpacity;
uniform float uEchoFillOpacity;
varying vec2 vUv;
// =====================================
// UTILITY FUNCTIONS
// =====================================
float tanh(float x) {
float e2x = exp(2.0 * x);
return (e2x - 1.0) / (e2x + 1.0);
}
// 3D Perlin noise
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
float snoise(vec3 v) {
const vec2 C = vec2(1.0/6.0, 1.0/3.0);
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
// First corner
vec3 i = floor(v + dot(v, C.yyy));
vec3 x0 = v - i + dot(i, C.xxx);
// Other corners
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min(g.xyz, l.zxy);
vec3 i2 = max(g.xyz, l.zxy);
vec3 x1 = x0 - i1 + C.xxx;
vec3 x2 = x0 - i2 + C.yyy;
vec3 x3 = x0 - D.yyy;
// Permutations
i = mod289(i);
vec4 p = permute(permute(permute(
i.z + vec4(0.0, i1.z, i2.z, 1.0))
+ i.y + vec4(0.0, i1.y, i2.y, 1.0))
+ i.x + vec4(0.0, i1.x, i2.x, 1.0));
// Gradients: 7x7 points over a square, mapped onto an octahedron.
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
float n_ = 0.142857142857; // 1.0/7.0
vec3 ns = n_ * D.wyz - D.xzx;
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_);
vec4 x = x_ *ns.x + ns.yyyy;
vec4 y = y_ *ns.x + ns.yyyy;
vec4 h = 1.0 - abs(x) - abs(y);
vec4 b0 = vec4(x.xy, y.xy);
vec4 b1 = vec4(x.zw, y.zw);
vec4 s0 = floor(b0)*2.0 + 1.0;
vec4 s1 = floor(b1)*2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy;
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww;
vec3 p0 = vec3(a0.xy, h.x);
vec3 p1 = vec3(a0.zw, h.y);
vec3 p2 = vec3(a1.xy, h.z);
vec3 p3 = vec3(a1.zw, h.w);
// Normalise gradients
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
// Mix final noise value
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
m = m * m;
return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
}
// =====================================
// WAVE SIDE HELPER FUNCTIONS
// =====================================
float getDistanceFromCenter(vec2 uv, int waveSide) {
if (waveSide == 0 || waveSide == 1) {
// Bottom or Top
return abs(uv.x - 0.5) * 2.0;
} else {
// Right or Left
return abs(uv.y - 0.5) * 2.0;
}
}
float getRawDistanceFromCenter(vec2 uv, int waveSide) {
if (waveSide == 0 || waveSide == 1) {
// Bottom or Top
return (uv.x - 0.5) * 2.0;
} else {
// Right or Left
return (uv.y - 0.5) * 2.0;
}
}
// =====================================
// ANIMATION FUNCTIONS
// =====================================
float calculateAnimatedTailLength(vec2 uv, float time, float minFalloff, float maxFalloff) {
float tailRange = (maxFalloff - minFalloff) * 0.5;
float tailCenter = minFalloff + tailRange;
return tailCenter + sin(time * ANIMATION_TIME_SCALE) * tailRange * ANIMATION_WEIGHT_1 + sin(uv.x * ANIMATION_SPATIAL_SCALE + time * ANIMATION_SPATIAL_TIME_SCALE) * tailRange * ANIMATION_WEIGHT_2;
}
float parabolicInterpolation(float rawDistance, float minVal, float maxVal) {
float t = (rawDistance + 1.0) * 0.5;
float t2 = t * t;
return maxVal - (maxVal - minVal) * (4.0 * t - 4.0 * t2);
}
// =====================================
// WAVE GENERATION AND CALCULATION
// =====================================
float generateWaveNoise(vec2 uv, float time, int waveIndex) {
float spatialOffset = float(waveIndex) * 0.4 - 0.2;
float temporalOffset = float(waveIndex) * 1.15;
float amplitudeMultiplier = 1.0 + float(waveIndex) * 0.15;
vec2 spatialVariation = vec2(spatialOffset, spatialOffset * 0.8);
return snoise(vec3(
(uv.x + spatialVariation.x) * uWaveFrequency + time * uWaveSpeed * 0.7,
(uv.y + spatialVariation.y) * uWaveFrequency * 0.5 + time * uWaveSpeed * 0.3,
time * 0.1 + temporalOffset
)) * uWaveAmplitude * amplitudeMultiplier;
}
float calculateWaveIntensity(vec2 uv, float noise, float tailLength) {
float basePosition;
float baseHeight = uWaveAmplitude;
float waveTop = baseHeight + noise;
if (uWaveSide == 0) { // Bottom
basePosition = uv.y;
} else if (uWaveSide == 1) { // Top
basePosition = 1.0 - uv.y;
} else if (uWaveSide == 2) { // Right
basePosition = 1.0 - uv.x;
} else { // Left
basePosition = uv.x;
}
float falloffDistance = (basePosition - waveTop) * 4.0;
float falloffRate = FALLOFF_CONVERSION_FACTOR / tailLength;
return max(0.0, 1.0 - tanh(falloffDistance * falloffRate * 0.5));
}
// =====================================
// COLOR FUNCTIONS
// =====================================
vec3 getWaveColor(vec2 uv) {
float distanceFromCenter;
vec2 noiseUv;
if (uWaveSide == 0 || uWaveSide == 1) { // Bottom or Top
distanceFromCenter = uv.x - 0.5;
// Use horizontal noise orientation for horizontal wave sides
noiseUv = vec2(uv.x, uv.y);
} else { // Right or Left
distanceFromCenter = uv.y - 0.5;
// Rotate noise orientation for vertical wave sides to maintain consistency
noiseUv = vec2(uv.y, uv.x);
}
// Generate noise to create irregular boundaries
float noiseScale = 12.0; // Controls the frequency of the irregularity
float noiseAmplitude = 0.005; // Controls the intensity of the irregularity
float noise = snoise(vec3(noiseUv * noiseScale, uTime * 0.1)) * noiseAmplitude;
// Apply noise to the distance, then take abs to maintain color symmetry
float irregularDistance = abs(distanceFromCenter + noise);
float t = irregularDistance * 2.0;
// Simple blur approximation using 5 strategic samples (unrolled)
vec3 color = vec3(0.0);
// Blended color approach with overlapping contributions
vec3 finalColor = vec3(0.0);
float totalWeight = 0.0;
// Color 5 contribution (center)
float color5Weight = max(0.0, 1.0 - t / 0.2);
color5Weight = smoothstep(0.0, 1.0, color5Weight);
finalColor += uWaveColor5 * color5Weight;
totalWeight += color5Weight;
// Color 4 contribution
float color4Weight = max(0.0, 1.0 - abs(t - 0.25) / 0.3);
color4Weight = smoothstep(0.0, 1.0, color4Weight);
finalColor += uWaveColor4 * color4Weight;
totalWeight += color4Weight;
// Color 3 contribution
float color3Weight = max(0.0, 1.0 - abs(t - 0.45) / 0.3);
color3Weight = smoothstep(0.0, 1.0, color3Weight);
finalColor += uWaveColor3 * color3Weight;
totalWeight += color3Weight;
// Color 2 contribution
float color2Weight = max(0.0, 1.0 - abs(t - 0.675) / 0.35);
color2Weight = smoothstep(0.0, 1.0, color2Weight);
finalColor += uWaveColor2 * color2Weight;
totalWeight += color2Weight;
// Color 1 contribution (outer)
float color1Weight = max(0.0, (t - 0.6) / 0.4);
color1Weight = smoothstep(0.0, 1.0, color1Weight);
finalColor += uWaveColor1 * color1Weight;
totalWeight += color1Weight;
// Normalize by total weight to avoid oversaturation
return finalColor / max(totalWeight, 0.001);
}
// =====================================
// ECHO FUNCTIONS
// =====================================
vec2 getEchoCenter(int waveSide, float echoOffset) {
if (waveSide == 0) {
// Bottom
return vec2(0.5, echoOffset);
} else if (waveSide == 1) {
// Top
return vec2(0.5, 1.0 - echoOffset);
} else if (waveSide == 2) {
// Right
return vec2(1.0 - echoOffset, 0.5);
} else {
// Left
return vec2(echoOffset, 0.5);
}
}
vec3 calculateEchoEffect(vec2 uv, vec2 center, vec2 resolution, float echoRadius, float echoFrequency, float echoRingSize, float echoStrokeOpacity, float echoFillOpacity) {
float aspect = resolution.x / resolution.y;
vec2 distVec = (uv - center) * vec2(aspect, 1.0);
float d = length(distVec);
float phase = (d - echoRadius) * echoFrequency;
float ringWidthInUV = 0.001 * echoRingSize;
float ringWidth = ringWidthInUV * echoFrequency;
float ringPhase = mod(phase, 2.0 * PI);
// Create crisp ring mask
float ringMask = smoothstep(ringWidth, ringWidth * 0.5, abs(ringPhase - PI));
float angle = atan(distVec.y, distVec.x);
float segmentCount = 1.0 + sin(d * 2.0) * 1.0; // 0-2 segments, varies by distance
float segmentAngle = angle * segmentCount;
// Add more variation to segment offset between rings
float segmentOffset = phase * 0.33;
float rotationSpeed = 0.1 + sin(d * 3.0) * 0.15; // Vary rotation speed by distance (slower)
float rotatingAngle = segmentAngle + uTime * rotationSpeed + segmentOffset;
float segmentPhase = mod(rotatingAngle, 2.0 * PI) / (2.0 * PI);
float frontEdge = smoothstep(0.98, 1.0, segmentPhase); // Sharp front edge
float trailingFade = smoothstep(0.0, 0.8, 1.0 - segmentPhase); // Long trailing fade
float segmentMask = max(frontEdge, trailingFade);
float baseIntensity = echoStrokeOpacity * exp(-d * 0.8);
float shimmerTime = uTime * 2.0 + d * 5.0 + angle * 3.0;
float shimmer = sin(shimmerTime) * 0.3 + 0.7;
// Use wave color at current position with saturation and opacity controls
vec3 baseColor = getWaveColor(uv);
// Apply saturation boost
float saturation = 1.2 + shimmer * 0.3;
vec3 finalColor = baseColor * saturation;
// Calculate ring effect (strokes)
float ringEffect = ringMask * segmentMask * baseIntensity * shimmer;
// Calculate fill effect (static fills) - smooth version
float phaseProgress = (PI - phase) / (2.0 * PI);
// Fill from inside out, max out at 3
float ringsToFill = floor(phaseProgress);
float fillEffect = min(ringsToFill, 3.0) * echoFillOpacity;
// Combine ring strokes and fills
float totalEffect = ringEffect + fillEffect;
return finalColor * totalEffect;
}
// =====================================
// MAIN FUNCTION
// =====================================
void main() {
// Input processing
vec2 uv = vUv;
vec2 resolution = uResolution;
float time = uTime;
// Wave noise generation
float whiteWaveNoise = generateWaveNoise(uv, time, 0);
float coloredWaveNoise = generateWaveNoise(uv, time, 1) * COLORED_WAVE_NOISE_MULTIPLIER;
// Wave intensity calculation
float whiteWaveIntensity = calculateWaveIntensity(uv, whiteWaveNoise, WHITE_WAVE_TAIL_LENGTH);
// Colored wave with modulated falloff
float distanceFromCenter = getDistanceFromCenter(uv, uWaveSide);
float rawDistanceFromCenter = getRawDistanceFromCenter(uv, uWaveSide);
float animatedTailLength = calculateAnimatedTailLength(uv, uTime, uMinFalloff, uMaxFalloff);
float modulatedTailLength = parabolicInterpolation(rawDistanceFromCenter, uMinFalloff, uMaxFalloff) * animatedTailLength / (uMinFalloff + (uMaxFalloff - uMinFalloff) * 0.5);
float coloredWaveIntensity = calculateWaveIntensity(uv, coloredWaveNoise, modulatedTailLength);
// Base color composition
vec4 color = uBackground;
// Grain and wave presence calculation
float grain = fract(sin(dot(uv * 1000.0, vec2(12.9898, 78.233))) * 43758.5453) * GRAIN_SCALE;
float wavePresence = max(whiteWaveIntensity, coloredWaveIntensity * WAVE_PRESENCE_COLOR_FACTOR);
// Final color composition
vec3 positionColor = getWaveColor(uv);
color.rgb = min(vec3(1.0), color.rgb + vec3(1.0) * whiteWaveIntensity + positionColor * coloredWaveIntensity + vec3(grain) * wavePresence);
// Echo effect
if (uEchoEnabled) {
vec2 center = getEchoCenter(uWaveSide, uEchoOffset);
// Render combined strokes and fills
vec3 echoEffect = calculateEchoEffect(uv, center, resolution, uEchoRadius, uEchoFrequency, uEchoRingSize, uEchoStrokeOpacity, uEchoFillOpacity);
// Add to final color
color.rgb = min(vec3(1.0), color.rgb + echoEffect);
// Ensure color values stay within valid range
color.rgb = clamp(color.rgb, 0.0, 1.0);
}
gl_FragColor = color;
}
`;import{Camera as ce,Color as p,Program as se,Vec2 as R}from"ogl";import{RenderTarget as Ce,Geometry as ne,Mesh as ae}from"ogl";function A(e){return e<.5?4*e*e*e:1-Math.pow(-2*e+2,3)/2}function k({canvas:e,renderer:t}){if(t){let i=t.gl,a=new Uint8Array(e.width*e.height*4);i.readPixels(0,0,e.width,e.height,i.RGBA,i.UNSIGNED_BYTE,a);let r=document.createElement("canvas");r.width=e.width,r.height=e.height;let s=r.getContext("2d");if(!s)return;s.fillStyle="#FFFFFF",s.fillRect(0,0,r.width,r.height);let o=new ImageData(new Uint8ClampedArray(a),e.width,e.height),g=new ImageData(new Uint8ClampedArray(o.data),o.width,o.height);for(let c=0;c<o.height;c++)for(let h=0;h<o.width;h++){let m=(c*o.width+h)*4,d=((o.height-1-c)*o.width+h)*4;g.data[d]=o.data[m],g.data[d+1]=o.data[m+1],g.data[d+2]=o.data[m+2],g.data[d+3]=o.data[m+3]}s.putImageData(g,0,0);let n=document.createElement("a");n.download="fluid-light.png",r.toBlob(c=>{c&&(n.href=URL.createObjectURL(c),n.click(),URL.revokeObjectURL(n.href))},"image/png")}else{let i=document.createElement("a");i.download="fluid-light.png",e.toBlob(a=>{a&&(i.href=URL.createObjectURL(a),i.click(),URL.revokeObjectURL(i.href))},"image/png")}}function z(e,t,i){return new ae(e,{geometry:t,program:i})}function G(e){return new ne(e,{position:{size:2,data:new Float32Array([-1,-1,3,-1,-1,3])},uv:{size:2,data:new Float32Array([0,0,2,0,0,2])}})}function T(e,t){let i=M({},e);for(let a in t)t[a]!==void 0&&(typeof t[a]=="object"&&t[a]!==null&&!Array.isArray(t[a])?i[a]=T(i[a]||{},t[a]):i[a]=t[a]);return i}import{Renderer as re}from"ogl";function le(e,t="Shader"){if(!e)throw new Error(`${t}: Canvas parameter is required and cannot be null or undefined`);if(!(e instanceof HTMLCanvasElement))throw new Error(`${t}: Canvas parameter must be an HTMLCanvasElement`);if(!e.isConnected)throw new Error(`${t}: Canvas must be attached to the DOM before creating shader`);if(!e.clientWidth||!e.clientHeight)throw new Error(`${t}: Canvas must have non-zero dimensions (width and height)`);if(typeof window!="undefined"){let i=document.createElement("canvas"),a=null;try{a=i.getContext("webgl2")||i.getContext("webgl")||i.getContext("experimental-webgl")}catch(r){throw new Error(`${t}: Failed to get WebGL context - WebGL may not be supported`)}if(!a)throw new Error(`${t}: WebGL is not supported in this browser`);i.remove()}}function U(e,t="Shader"){le(e,t);let i,a;try{i=new re({dpr:devicePixelRatio,canvas:e,width:e.clientWidth,height:e.clientHeight}),a=i.gl}catch(r){throw new Error(`${t}: Failed to create WebGL renderer - ${r instanceof Error?r.message:"Unknown error"}`)}if(!a)throw new Error(`${t}: Failed to get WebGL context from renderer`);return a.clearColor(1,1,1,0),{renderer:i,gl:a}}var ue={side:0,background:"#020617",lightWave:{amplitude:.04,speed:.4,frequency:1.2,color1:"#5648FB",color2:"#3AC3FF",color3:"#CE7FFF",color4:"#F45397",color5:"#FFB525",minFalloff:6,maxFalloff:8},echo:{enabled:!0,offset:.1,radius:.9,frequency:20,ringSize:2,strokeOpacity:.2,fillOpacity:.01,animateIn:!0,animationDuration:3e3,animationDelay:200}};function q(i){return E(this,arguments,function*(e,t={}){let{renderer:a,gl:r}=U(e,"FluidLight"),s=T(ue,t),o=JSON.parse(JSON.stringify({side:s.side,background:s.background,lightWave:s.lightWave,echo:s.echo})),g=JSON.parse(JSON.stringify(o)),n=new R,c=new ce(r,{fov:35});c.position.set(0,0,5);let h=new R,m=new R,d=performance.now();function C(){var v,W;let f=(W=(v=e.parentElement)==null?void 0:v.clientWidth)!=null?W:e.clientWidth,u=e.clientHeight;a.setSize(f,u),c.perspective({aspect:f/u}),n.set(f,u)}function x(f){let u=e.getBoundingClientRect();h.x=(f.clientX-u.left)/u.width*2-1,h.y=-((f.clientY-u.top)/u.height)*2+1}function F(f){let u=e.getBoundingClientRect(),v=f.touches[0];h.x=(v.clientX-u.left)/u.width*2-1,h.y=-((v.clientY-u.top)/u.height)*2+1}e.addEventListener("mousemove",x),e.addEventListener("touchmove",F),C();let l=fe(r,o,n),S=G(r),w=z(r,S,l),y;function P(f){y=requestAnimationFrame(P),C(),H(f),a.render({scene:w,camera:c})}function H(f){let u=new p(o.background);l.uniforms.uBackground.value=[u.r,u.g,u.b,1],l.uniforms.uTime.value=f*.001,l.uniforms.uResolution.value=n,l.uniforms.uWaveAmplitude.value=o.lightWave.amplitude,l.uniforms.uWaveSpeed.value=o.lightWave.speed,l.uniforms.uWaveFrequency.value=o.lightWave.frequency,l.uniforms.uWaveSide.value=o.side,l.uniforms.uWaveColor1.value=new p(o.lightWave.color1),l.uniforms.uWaveColor2.value=new p(o.lightWave.color2),l.uniforms.uWaveColor3.value=new p(o.lightWave.color3),l.uniforms.uWaveColor4.value=new p(o.lightWave.color4),l.uniforms.uWaveColor5.value=new p(o.lightWave.color5),l.uniforms.uMinFalloff.value=o.lightWave.minFalloff,l.uniforms.uMaxFalloff.value=o.lightWave.maxFalloff,l.uniforms.uEchoEnabled.value=o.echo.enabled,l.uniforms.uEchoOffset.value=o.echo.offset,l.uniforms.uEchoRadius.value=o.echo.radius,l.uniforms.uEchoFrequency.value=j(f),l.uniforms.uEchoRingSize.value=o.echo.ringSize,l.uniforms.uEchoStrokeOpacity.value=J(f),l.uniforms.uEchoFillOpacity.value=o.echo.fillOpacity}function j(f){if(!o.echo.animateIn)return o.echo.frequency;let u=f-d,v=o.echo.animationDelay,W=o.echo.animationDuration;if(u<v)return 0;let O=Math.min((u-v)/W,1),I=A(O);return o.echo.frequency*I}function J(f){if(!o.echo.animateIn)return o.echo.strokeOpacity;let u=f-d,v=o.echo.animationDelay,W=o.echo.animationDuration;if(u<v)return 0;let O=Math.min((u-v)/W,1),I=A(O);return o.echo.strokeOpacity*I}function $(f){Object.assign(o,f)}function Y(){Object.assign(o,JSON.parse(JSON.stringify(g))),d=performance.now()}function X(){return JSON.parse(JSON.stringify(o))}function K(){e.removeEventListener("mousemove",x),e.removeEventListener("touchmove",F),cancelAnimationFrame(y)}function Q(){return o}return P(performance.now()),{updateParams:$,reset:Y,exportConfig:X,destroy:K,getParams:Q}})}function fe(e,t,i){return new se(e,{vertex:D,fragment:_,uniforms:{uBackground:{value:[0,0,0,1]},uResolution:{value:i},uTime:{value:0},uWaveAmplitude:{value:t.lightWave.amplitude},uWaveSpeed:{value:t.lightWave.speed},uWaveFrequency:{value:t.lightWave.frequency},uWaveSide:{value:t.side},uWaveColor1:{value:new p(t.lightWave.color1)},uWaveColor2:{value:new p(t.lightWave.color2)},uWaveColor3:{value:new p(t.lightWave.color3)},uWaveColor4:{value:new p(t.lightWave.color4)},uWaveColor5:{value:new p(t.lightWave.color5)},uMinFalloff:{value:t.lightWave.minFalloff},uMaxFalloff:{value:t.lightWave.maxFalloff},uEchoEnabled:{value:t.echo.enabled},uEchoOffset:{value:t.echo.offset},uEchoRadius:{value:t.echo.radius},uEchoFrequency:{value:t.echo.frequency},uEchoRingSize:{value:t.echo.ringSize},uEchoStrokeOpacity:{value:t.echo.strokeOpacity},uEchoFillOpacity:{value:t.echo.fillOpacity}}})}function V(e){return E(this,null,function*(){let[{Pane:t},i]=yield Promise.all([import("tweakpane"),import("@tweakpane/plugin-essentials")]),a=e.getParams(),{lightWave:r,echo:s}=a,o=new t({title:"Fluid Light",expanded:!0});o.registerPlugin(i);let g=o.addBlade({view:"fpsgraph",rows:2}),n=o.addTab({pages:[{title:"Parameters"},{title:"Advanced"}]});n.pages[0].addBinding(a,"side",{label:"Side",options:{Bottom:0,Top:1,Right:2,Left:3}}),n.pages[0].addBinding(a,"background",{label:"Background"}),n.pages[0].addBinding(r,"amplitude",{label:"Amplitude",min:.01,max:.2,step:.01}),n.pages[0].addBinding(r,"speed",{label:"Speed",min:.01,max:1,step:.01}),n.pages[0].addBinding(r,"frequency",{label:"Frequency",min:.01,max:2,step:.01}),n.pages[0].addBinding(r,"color1",{label:"Color 1"}),n.pages[0].addBinding(r,"color2",{label:"Color 2"}),n.pages[0].addBinding(r,"color3",{label:"Color 3"}),n.pages[0].addBinding(r,"color4",{label:"Color 4"}),n.pages[0].addBinding(r,"color5",{label:"Color 5"}),n.pages[0].addBinding(r,"minFalloff",{label:"Min Falloff",min:1,max:10,step:.01}),n.pages[0].addBinding(r,"maxFalloff",{label:"Max Falloff",min:1,max:10,step:.01});let c=n.pages[0].addFolder({title:"Echo",expanded:!1});c.addBinding(s,"enabled",{label:"Enabled"}),c.addBinding(s,"offset",{label:"Offset",min:0,max:.5,step:.01}),c.addBinding(s,"radius",{label:"Radius",min:.01,max:1,step:.01}),c.addBinding(s,"frequency",{label:"Frequency",min:1,max:40,step:.5}),c.addBinding(s,"ringSize",{label:"Thickness",min:.1,max:4,step:.1}),c.addBinding(s,"strokeOpacity",{label:"Stroke Opacity",min:0,max:1,step:.01}),c.addBinding(s,"fillOpacity",{label:"Fill Opacity",min:0,max:.1,step:.01});let h=c.addFolder({title:"Animation",expanded:!1}),m=performance.now();return h.addBinding(s,"animateIn",{label:"Enabled"}).on("change",()=>{m=performance.now()}),s.animateIn&&(h.addBinding(s,"animationDuration",{label:"Duration",min:500,max:5e3,step:100}).on("change",()=>{m=performance.now()}),h.addBinding(s,"animationDelay",{label:"Delay",min:0,max:2e3,step:100}).on("change",()=>{m=performance.now()})),n.pages[1].addButton({title:"Reset"}).on("click",()=>{e.reset(),o.refresh(),m=performance.now()}),n.pages[1].addButton({title:"Export Config"}).on("click",()=>{let d=JSON.stringify(o.exportState(),null,2),C=new Blob([d],{type:"application/json"}),x=document.createElement("a");x.download="fluid-light-config.json",x.href=URL.createObjectURL(C),x.click(),URL.revokeObjectURL(x.href)}),n.pages[1].addButton({title:"Import Config"}).on("click",()=>{let d=document.createElement("input");d.type="file",d.accept=".json",d.onchange=C=>{var F;let x=(F=C.target.files)==null?void 0:F[0];if(x){let l=new FileReader;l.onload=S=>{var w;try{let y=JSON.parse((w=S.target)==null?void 0:w.result);o.importState(y),m=performance.now()}catch(y){console.error("Failed to import config:",y),alert("Failed to import config. Please check the file format.")}},l.readAsText(x)}},d.click()}),n.pages[1].addButton({title:"Download PNG"}).on("click",()=>{let d=document.querySelector("canvas");d&&k({canvas:d,renderer:null})}),o.on("change",()=>{e.updateParams(a)}),{pane:o,fpsGraph:g,dispose:()=>{o.dispose()}}})}var de={create:q,attachDevtools:V},ge=de;export{ge as FluidLight};
//# sourceMappingURL=index.js.map