@rosalana/sandbox 0.0.5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.es.js CHANGED
@@ -1,9 +1,9 @@
1
- var S = Object.defineProperty;
2
- var L = (o, t, e) => t in o ? S(o, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : o[t] = e;
3
- var r = (o, t, e) => L(o, typeof t != "symbol" ? t + "" : t, e);
4
- class l {
5
- constructor(t, e, i, s) {
6
- this.target = t, this.type = e, this.listener = i, this.options = s, this.target.addEventListener(
1
+ var N = Object.defineProperty;
2
+ var G = (u, e, t) => e in u ? N(u, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : u[e] = t;
3
+ var o = (u, e, t) => G(u, typeof e != "symbol" ? e + "" : e, t);
4
+ class b {
5
+ constructor(e, t, n, i) {
6
+ this.target = e, this.type = t, this.listener = n, this.options = i, this.target.addEventListener(
7
7
  this.type,
8
8
  this.listener,
9
9
  this.options
@@ -16,99 +16,231 @@ class l {
16
16
  this.options
17
17
  );
18
18
  }
19
- static on(t, e, i, s) {
20
- return t.addEventListener(e, i, s), () => t.removeEventListener(e, i, s);
19
+ static on(e, t, n, i) {
20
+ return e.addEventListener(t, n, i), () => e.removeEventListener(t, n, i);
21
21
  }
22
22
  }
23
- class u extends Error {
24
- constructor(t, e) {
25
- super(t), this.code = e, this.name = "SandboxError";
23
+ class h extends Error {
24
+ constructor(t, n) {
25
+ super(t);
26
+ o(this, "name", "SandboxError");
27
+ this.code = n;
26
28
  }
27
29
  }
28
- class w extends u {
29
- constructor(t) {
30
- const e = t === "not_supported" ? "WebGL is not supported in this browser." : "Failed to create WebGL context. The GPU may be unavailable.";
30
+ class W extends h {
31
+ constructor() {
32
+ super("WebGL is not supported in this browser.", "CONTEXT_ERROR");
33
+ }
34
+ }
35
+ class ve extends h {
36
+ constructor() {
31
37
  super(
32
- e,
33
- t === "not_supported" ? "WEBGL_NOT_SUPPORTED" : "CONTEXT_CREATION_FAILED"
34
- ), this.name = "SandboxContextError";
38
+ "Failed to create WebGL context. The GPU may be unavailable.",
39
+ "CONTEXT_ERROR"
40
+ );
35
41
  }
36
42
  }
37
- class k extends u {
38
- constructor(t, e) {
43
+ class z extends h {
44
+ constructor(e, t) {
39
45
  super(
40
- `Vertex and fragment shader WebGL versions do not match (${t} vs ${e})`,
41
- "SHADER_VERSION_MISMATCH"
42
- ), this.vertexVersion = t, this.fragmentVersion = e, this.name = "SandboxShaderVersionMismatchError";
46
+ `Vertex and fragment shader WebGL versions do not match (${e} vs ${t})`,
47
+ "VALIDATION_ERROR"
48
+ ), this.vertexVersion = e, this.fragmentVersion = t;
43
49
  }
44
50
  }
45
- class c extends u {
46
- constructor(e, i, s) {
47
- const n = c.parseErrorLines(s), h = n.length > 0 ? ` at line(s): ${n.join(", ")}` : "";
51
+ class _ extends h {
52
+ constructor(t, n, i) {
53
+ const r = _.parseErrorLines(i), s = r.length > 0 ? ` at line(s): ${r.join(", ")}` : "";
48
54
  super(
49
- `${e} shader compilation failed${h}
55
+ `${t} shader compilation failed${s}
50
56
 
51
- ${s}`,
52
- "SHADER_COMPILATION_FAILED"
57
+ ${i}`,
58
+ "SHADER_ERROR"
53
59
  );
54
- /** Line numbers where errors occurred */
55
- r(this, "lines");
56
- this.shaderType = e, this.source = i, this.infoLog = s, this.name = "SandboxShaderCompilationError", this.lines = n;
60
+ o(this, "lines");
61
+ this.shaderType = t, this.source = n, this.infoLog = i, this.lines = r;
57
62
  }
58
- /** Parse error log to extract line numbers */
59
- static parseErrorLines(e) {
60
- const i = [
63
+ static parseErrorLines(t) {
64
+ const n = [
61
65
  /ERROR:\s*\d*:(\d+)/g,
62
- // Chrome/ANGLE: ERROR: 0:15
63
66
  /(\d+):(\d+)\(\d+\):/g,
64
- // Mesa: 0:15(0):
65
67
  /^(\d+):/gm
66
- // Simple: 15:
67
- ], s = /* @__PURE__ */ new Set();
68
- for (const n of i) {
69
- let h;
70
- for (; (h = n.exec(e)) !== null; ) {
71
- const f = parseInt(h[1], 10);
72
- f > 0 && s.add(f);
68
+ ], i = /* @__PURE__ */ new Set();
69
+ for (const r of n) {
70
+ let s;
71
+ for (; (s = r.exec(t)) !== null; ) {
72
+ const a = parseInt(s[1], 10);
73
+ a > 0 && i.add(a);
73
74
  }
74
75
  }
75
- return [...s].sort((n, h) => n - h);
76
+ return [...i].sort((r, s) => r - s);
76
77
  }
77
78
  }
78
- class m extends u {
79
- constructor(t) {
79
+ class U extends h {
80
+ constructor(e, t, n, i) {
81
+ super(
82
+ `The shader ${e} "${t}" has type "${i}" but expected "${n}"`,
83
+ "SHADER_ERROR"
84
+ ), this.requirement = e, this.name = t, this.expectedType = n, this.actualType = i;
85
+ }
86
+ }
87
+ class H extends h {
88
+ constructor() {
89
+ super("Shader source does not contain any function.", "SHADER_ERROR");
90
+ }
91
+ }
92
+ class q extends h {
93
+ constructor(e, t) {
94
+ super(
95
+ `Syntax error in shader import statement at line ${e}: ${t}`,
96
+ "SHADER_ERROR"
97
+ ), this.line = e, this.details = t;
98
+ }
99
+ }
100
+ class j extends h {
101
+ constructor(e, t) {
102
+ super(
103
+ `Duplicate import name "${e}" found at line ${t}. Each import must have a unique name.`,
104
+ "SHADER_ERROR"
105
+ ), this.name = e, this.line = t;
106
+ }
107
+ }
108
+ class X extends h {
109
+ constructor(e) {
110
+ super(
111
+ `Can not find module '${e}'. Check if it is defined before usage or if the name is correct.`,
112
+ "MODULE_ERROR"
113
+ ), this.moduleName = e;
114
+ }
115
+ }
116
+ class Y extends h {
117
+ constructor(e, t) {
118
+ super(
119
+ `Method '${t}' not found in shader module '${e}'. Check if the method is defined in the module source code or if the name is correct.`,
120
+ "MODULE_ERROR"
121
+ ), this.moduleName = e, this.methodName = t;
122
+ }
123
+ }
124
+ class K extends h {
125
+ constructor(e) {
126
+ super(
127
+ `Importing 'main' function from module '${e}' is forbidden.`,
128
+ "MODULE_ERROR"
129
+ ), this.moduleName = e;
130
+ }
131
+ }
132
+ class Q extends h {
133
+ constructor(e) {
134
+ super(
135
+ `Name 'default' is reserved and cannot be used as a function name in module '${e}'.`,
136
+ "MODULE_ERROR"
137
+ ), this.moduleName = e;
138
+ }
139
+ }
140
+ class J extends h {
141
+ constructor(e) {
142
+ super(
143
+ `Module name '${e}' is not allowed. Module names cannot be 'sandbox' or start with 'sandbox/'.`,
144
+ "MODULE_ERROR"
145
+ ), this.moduleName = e;
146
+ }
147
+ }
148
+ class Z extends h {
149
+ constructor(e) {
150
+ super(
151
+ `Module '${e}' is already defined. Overwriting existing modules is not allowed.`,
152
+ "MODULE_ERROR"
153
+ ), this.moduleName = e;
154
+ }
155
+ }
156
+ class ee extends h {
157
+ constructor(e, t, n) {
158
+ super(
159
+ `Uniform '${n}' mentioned for function '${t}' of module '${e}' was not found among the module's declared uniforms. Check if the uniform is declared in the module source code or if the name is correct.`,
160
+ "MODULE_ERROR"
161
+ ), this.moduleName = e, this.functionName = t, this.uniformName = n;
162
+ }
163
+ }
164
+ class te extends h {
165
+ constructor(e, t) {
166
+ super(
167
+ `Uniform '${t}' mentioned for function '${e}' was not imported from any module. Check if the function is imported from the correct module and if the uniform is declared in that module's source code with the correct name.`,
168
+ "MODULE_ERROR"
169
+ ), this.functionName = e, this.uniformName = t;
170
+ }
171
+ }
172
+ class ne extends h {
173
+ constructor(e, t) {
174
+ super(
175
+ `Mention '${e}' called in function '${t}' could not be replaced with the corresponding uniform reference. There might be an issue with the compilation process because the referenced uniform was not found among the shader requirements. Try use a different name for uniforms you want to mention in functions or check if the uniform is properly declared and mentioned in the module source code.`,
176
+ "MODULE_ERROR"
177
+ ), this.mentionName = e, this.calledInFunction = t;
178
+ }
179
+ }
180
+ class S extends h {
181
+ constructor(e) {
80
182
  super(`Shader program linking failed
81
183
 
82
- ${t}`, "PROGRAM_LINK_FAILED"), this.infoLog = t, this.name = "SandboxProgramError";
184
+ ${e}`, "PROGRAM_ERROR"), this.infoLog = e;
185
+ }
186
+ }
187
+ class ge extends h {
188
+ constructor(t) {
189
+ super(
190
+ `Failed to create WebGL texture for "${t}".`,
191
+ "TEXTURE_ERROR"
192
+ );
193
+ o(this, "name", "SandboxTextureCreationError");
194
+ this.textureName = t;
195
+ }
196
+ }
197
+ class xe extends h {
198
+ constructor(t, n) {
199
+ super(
200
+ `Cannot bind texture "${t}": all ${n} texture units are in use.`,
201
+ "TEXTURE_ERROR"
202
+ );
203
+ o(this, "name", "SandboxTextureUnitLimitError");
204
+ this.textureName = t, this.maxUnits = n;
83
205
  }
84
206
  }
85
- class R {
207
+ class ie extends h {
208
+ constructor(e) {
209
+ super(`Error in onLoad callback: ${e}`, "UNKNOWN_ERROR");
210
+ }
211
+ }
212
+ class re extends h {
213
+ constructor(e, t) {
214
+ super(`Error in onBefore/onAfter hook callback with ID ${e}: ${t}`, "UNKNOWN_ERROR");
215
+ }
216
+ }
217
+ class se {
86
218
  constructor() {
87
219
  /** Total elapsed time in seconds */
88
- r(this, "time", 0);
220
+ o(this, "time", 0);
89
221
  /** Delta time since last frame in seconds */
90
- r(this, "delta", 0);
222
+ o(this, "delta", 0);
91
223
  /** Frame counter */
92
- r(this, "frame", 0);
224
+ o(this, "frame", 0);
93
225
  /** Is clock running */
94
- r(this, "running", !1);
226
+ o(this, "running", !1);
95
227
  /** Smoothed frames per second */
96
- r(this, "fps", 0);
97
- r(this, "startTime", 0);
98
- r(this, "lastTime", 0);
99
- r(this, "rafId", null);
100
- r(this, "callback", null);
101
- r(this, "maxFps", 0);
228
+ o(this, "fps", 0);
229
+ o(this, "startTime", 0);
230
+ o(this, "lastTime", 0);
231
+ o(this, "rafId", null);
232
+ o(this, "callback", null);
233
+ o(this, "maxFps", 0);
102
234
  this.loop = this.loop.bind(this);
103
235
  }
104
236
  /**
105
237
  * Start the animation loop with a render callback.
106
238
  */
107
- start(t) {
239
+ start(e) {
108
240
  if (this.running) return this;
109
- this.callback = t, this.running = !0;
110
- const e = performance.now();
111
- return this.frame === 0 ? this.startTime = e : this.startTime = e - this.time * 1e3, this.lastTime = e, this.rafId = requestAnimationFrame(this.loop), this;
241
+ this.callback = e, this.running = !0;
242
+ const t = performance.now();
243
+ return this.frame === 0 ? this.startTime = t : this.startTime = t - this.time * 1e3, this.lastTime = t, this.rafId = requestAnimationFrame(this.loop), this;
112
244
  }
113
245
  /**
114
246
  * Stop the animation loop.
@@ -139,14 +271,14 @@ class R {
139
271
  * Advance clock by one tick (for single-shot rendering).
140
272
  * Useful when autoplay is disabled.
141
273
  */
142
- tick(t = 0) {
143
- return this.delta = t, this.time += t, this.frame++, this.callback && this.callback(this.getState()), this;
274
+ tick(e = 0) {
275
+ return this.delta = e, this.time += e, this.frame++, this.callback && this.callback(this.getState()), this;
144
276
  }
145
277
  /**
146
278
  * Set time directly (for deterministic rendering).
147
279
  */
148
- setTime(t) {
149
- return this.time = t, this;
280
+ setTime(e) {
281
+ return this.time = e, this;
150
282
  }
151
283
  /**
152
284
  * Cleanup.
@@ -157,46 +289,46 @@ class R {
157
289
  /**
158
290
  * Set maximum frames per second.
159
291
  */
160
- setMaxFps(t) {
161
- return this.maxFps = t, this;
292
+ setMaxFps(e) {
293
+ return this.maxFps = e, this;
162
294
  }
163
295
  /**
164
296
  * Internal animation frame handler.
165
297
  */
166
- loop(t) {
298
+ loop(e) {
167
299
  if (!this.running) return;
168
300
  if (this.maxFps > 0) {
169
- const i = 1e3 / this.maxFps;
170
- if (t - this.lastTime < i) {
301
+ const n = 1e3 / this.maxFps;
302
+ if (e - this.lastTime < n) {
171
303
  this.rafId = requestAnimationFrame(this.loop);
172
304
  return;
173
305
  }
174
306
  }
175
- this.delta = (t - this.lastTime) / 1e3, this.lastTime = t;
176
- const e = this.delta > 0 ? 1 / this.delta : 0;
177
- this.fps = this.fps * 0.95 + e * 0.05, this.time = (t - this.startTime) / 1e3, this.frame++, this.callback && this.callback(this.getState()), this.rafId = requestAnimationFrame(this.loop);
307
+ this.delta = (e - this.lastTime) / 1e3, this.lastTime = e;
308
+ const t = this.delta > 0 ? 1 / this.delta : 0;
309
+ this.fps = this.fps * 0.95 + t * 0.05, this.time = (e - this.startTime) / 1e3, this.frame++, this.callback && this.callback(this.getState()), this.rafId = requestAnimationFrame(this.loop);
178
310
  }
179
311
  }
180
- class p {
181
- constructor(t) {
182
- r(this, "gl");
183
- r(this, "vao", null);
184
- r(this, "vbo", null);
185
- r(this, "ibo", null);
186
- r(this, "vertexCount", 0);
187
- r(this, "indexCount", 0);
188
- r(this, "useIndices", !1);
312
+ class k {
313
+ constructor(e) {
314
+ o(this, "gl");
315
+ o(this, "vao", null);
316
+ o(this, "vbo", null);
317
+ o(this, "ibo", null);
318
+ o(this, "vertexCount", 0);
319
+ o(this, "indexCount", 0);
320
+ o(this, "useIndices", !1);
189
321
  // WebGL1 VAO extension (if available)
190
- r(this, "vaoExt", null);
191
- r(this, "isWebGL2");
192
- this.gl = t, this.isWebGL2 = t instanceof WebGL2RenderingContext, this.isWebGL2 || (this.vaoExt = t.getExtension("OES_vertex_array_object"));
322
+ o(this, "vaoExt", null);
323
+ o(this, "isWebGL2");
324
+ this.gl = e, this.isWebGL2 = e instanceof WebGL2RenderingContext, this.isWebGL2 || (this.vaoExt = e.getExtension("OES_vertex_array_object"));
193
325
  }
194
326
  /**
195
327
  * Create a fullscreen quad geometry.
196
328
  * This is the most common use case for shader effects.
197
329
  */
198
- static fullscreenQuad(t) {
199
- const e = new p(t), i = new Float32Array([
330
+ static fullscreenQuad(e) {
331
+ const t = new k(e), n = new Float32Array([
200
332
  // position texcoord
201
333
  -1,
202
334
  -1,
@@ -218,7 +350,7 @@ class p {
218
350
  1,
219
351
  1
220
352
  // top-right
221
- ]), s = new Uint16Array([
353
+ ]), i = new Uint16Array([
222
354
  0,
223
355
  1,
224
356
  2,
@@ -228,33 +360,33 @@ class p {
228
360
  3
229
361
  // second triangle
230
362
  ]);
231
- return e.setup(i, s), e;
363
+ return t.setup(n, i), t;
232
364
  }
233
365
  /**
234
366
  * Setup geometry from vertex and index data.
235
367
  */
236
- setup(t, e) {
237
- const i = this.gl;
238
- return this.createVAO(), this.bindVAO(), this.vbo = i.createBuffer(), i.bindBuffer(i.ARRAY_BUFFER, this.vbo), i.bufferData(i.ARRAY_BUFFER, t, i.STATIC_DRAW), this.vertexCount = t.length / 4, e && (this.ibo = i.createBuffer(), i.bindBuffer(i.ELEMENT_ARRAY_BUFFER, this.ibo), i.bufferData(i.ELEMENT_ARRAY_BUFFER, e, i.STATIC_DRAW), this.indexCount = e.length, this.useIndices = !0), this.unbindVAO(), this;
368
+ setup(e, t) {
369
+ const n = this.gl;
370
+ return this.createVAO(), this.bindVAO(), this.vbo = n.createBuffer(), n.bindBuffer(n.ARRAY_BUFFER, this.vbo), n.bufferData(n.ARRAY_BUFFER, e, n.STATIC_DRAW), this.vertexCount = e.length / 4, t && (this.ibo = n.createBuffer(), n.bindBuffer(n.ELEMENT_ARRAY_BUFFER, this.ibo), n.bufferData(n.ELEMENT_ARRAY_BUFFER, t, n.STATIC_DRAW), this.indexCount = t.length, this.useIndices = !0), this.unbindVAO(), this;
239
371
  }
240
372
  /**
241
373
  * Link vertex attributes to shader program.
242
374
  * Call this after compiling shaders.
243
375
  */
244
- linkAttributes(t) {
245
- const e = this.gl;
246
- this.bindVAO(), e.bindBuffer(e.ARRAY_BUFFER, this.vbo);
247
- const i = 4 * Float32Array.BYTES_PER_ELEMENT, s = this.getPositionLocation(t);
248
- s >= 0 && (e.enableVertexAttribArray(s), e.vertexAttribPointer(s, 2, e.FLOAT, !1, i, 0));
249
- const n = this.getTexcoordLocation(t);
250
- return n >= 0 && (e.enableVertexAttribArray(n), e.vertexAttribPointer(
251
- n,
376
+ linkAttributes(e) {
377
+ const t = this.gl;
378
+ this.bindVAO(), t.bindBuffer(t.ARRAY_BUFFER, this.vbo);
379
+ const n = 4 * Float32Array.BYTES_PER_ELEMENT, i = this.getPositionLocation(e);
380
+ i >= 0 && (t.enableVertexAttribArray(i), t.vertexAttribPointer(i, 2, t.FLOAT, !1, n, 0));
381
+ const r = this.getTexcoordLocation(e);
382
+ return r >= 0 && (t.enableVertexAttribArray(r), t.vertexAttribPointer(
383
+ r,
252
384
  2,
253
- e.FLOAT,
385
+ t.FLOAT,
254
386
  !1,
255
- i,
387
+ n,
256
388
  2 * Float32Array.BYTES_PER_ELEMENT
257
- )), this.useIndices && e.bindBuffer(e.ELEMENT_ARRAY_BUFFER, this.ibo), this.unbindVAO(), this;
389
+ )), this.useIndices && t.bindBuffer(t.ELEMENT_ARRAY_BUFFER, this.ibo), this.unbindVAO(), this;
258
390
  }
259
391
  /**
260
392
  * Bind geometry for rendering.
@@ -272,31 +404,31 @@ class p {
272
404
  * Draw the geometry.
273
405
  */
274
406
  draw() {
275
- const t = this.gl;
276
- return this.bindVAO(), this.useIndices ? t.drawElements(t.TRIANGLES, this.indexCount, t.UNSIGNED_SHORT, 0) : t.drawArrays(t.TRIANGLE_STRIP, 0, this.vertexCount), this;
407
+ const e = this.gl;
408
+ return this.bindVAO(), this.useIndices ? e.drawElements(e.TRIANGLES, this.indexCount, e.UNSIGNED_SHORT, 0) : e.drawArrays(e.TRIANGLE_STRIP, 0, this.vertexCount), this;
277
409
  }
278
410
  /**
279
411
  * Cleanup all GPU resources.
280
412
  */
281
413
  destroy() {
282
- const t = this.gl;
283
- this.deleteVAO(), this.vbo && (t.deleteBuffer(this.vbo), this.vbo = null), this.ibo && (t.deleteBuffer(this.ibo), this.ibo = null);
414
+ const e = this.gl;
415
+ this.deleteVAO(), this.vbo && (e.deleteBuffer(this.vbo), this.vbo = null), this.ibo && (e.deleteBuffer(this.ibo), this.ibo = null);
284
416
  }
285
417
  /**
286
418
  * Get position attribute location.
287
419
  * Tries common naming conventions.
288
420
  */
289
- getPositionLocation(t) {
290
- let e = t.getAttribLocation("a_position");
291
- return e >= 0 || (e = t.getAttribLocation("aPosition"), e >= 0) || (e = t.getAttribLocation("position"), e >= 0) ? e : -1;
421
+ getPositionLocation(e) {
422
+ let t = e.getAttribLocation("a_position");
423
+ return t >= 0 || (t = e.getAttribLocation("aPosition"), t >= 0) || (t = e.getAttribLocation("position"), t >= 0) ? t : -1;
292
424
  }
293
425
  /**
294
426
  * Get texcoord attribute location.
295
427
  * Tries common naming conventions.
296
428
  */
297
- getTexcoordLocation(t) {
298
- let e = t.getAttribLocation("a_texcoord");
299
- return e >= 0 || (e = t.getAttribLocation("aTexCoord"), e >= 0) || (e = t.getAttribLocation("texcoord"), e >= 0) || (e = t.getAttribLocation("a_uv"), e >= 0) ? e : -1;
429
+ getTexcoordLocation(e) {
430
+ let t = e.getAttribLocation("a_texcoord");
431
+ return t >= 0 || (t = e.getAttribLocation("aTexCoord"), t >= 0) || (t = e.getAttribLocation("texcoord"), t >= 0) || (t = e.getAttribLocation("a_uv"), t >= 0) ? t : -1;
300
432
  }
301
433
  // ============================================================================
302
434
  // VAO helpers (handle WebGL1 vs WebGL2 differences)
@@ -314,33 +446,21 @@ class p {
314
446
  this.vao && (this.isWebGL2 ? this.gl.deleteVertexArray(this.vao) : this.vaoExt && this.vaoExt.deleteVertexArrayOES(this.vao), this.vao = null);
315
447
  }
316
448
  }
317
- class a {
318
- constructor(t) {
319
- r(this, "gl");
320
- r(this, "program", null);
321
- r(this, "vertexShader", null);
322
- r(this, "fragmentShader", null);
323
- r(this, "version", 1);
324
- this.gl = t;
325
- }
326
- /**
327
- * Detect WebGL version from shader source.
328
- * Looks for "#version 300 es" directive.
329
- */
330
- static detectVersion(t) {
331
- return /^\s*#version\s+300\s+es/m.test(t) ? 2 : 1;
449
+ class oe {
450
+ constructor(e) {
451
+ o(this, "gl");
452
+ o(this, "program", null);
453
+ o(this, "vertexShader", null);
454
+ o(this, "fragmentShader", null);
455
+ this.gl = e;
332
456
  }
333
457
  /**
334
458
  * Compile shaders and link program.
335
459
  * @throws ShaderCompilationError if compilation fails
336
460
  * @throws ProgramLinkError if linking fails
337
461
  */
338
- compile(t, e) {
339
- this.destroy();
340
- const i = a.detectVersion(t), s = a.detectVersion(e);
341
- if (i != s)
342
- throw new k(i, s);
343
- return this.version = Math.max(i, s), this.vertexShader = this.compileShader("vertex", t), this.fragmentShader = this.compileShader("fragment", e), this.linkProgram(), this;
462
+ compile(e, t) {
463
+ return this.destroy(), this.vertexShader = this.compileShader("vertex", e), this.fragmentShader = this.compileShader("fragment", t), this.linkProgram(), this;
344
464
  }
345
465
  /**
346
466
  * Bind this program for rendering.
@@ -355,122 +475,1994 @@ class a {
355
475
  return this.program;
356
476
  }
357
477
  /**
358
- * Get detected WebGL version.
478
+ * Get attribute location.
479
+ */
480
+ getAttribLocation(e) {
481
+ return this.program ? this.gl.getAttribLocation(this.program, e) : -1;
482
+ }
483
+ /**
484
+ * Get uniform location.
485
+ */
486
+ getUniformLocation(e) {
487
+ return this.program ? this.gl.getUniformLocation(this.program, e) : null;
488
+ }
489
+ /**
490
+ * Cleanup all GPU resources.
491
+ */
492
+ destroy() {
493
+ const e = this.gl;
494
+ this.program && (this.vertexShader && e.detachShader(this.program, this.vertexShader), this.fragmentShader && e.detachShader(this.program, this.fragmentShader), e.deleteProgram(this.program), this.program = null), this.vertexShader && (e.deleteShader(this.vertexShader), this.vertexShader = null), this.fragmentShader && (e.deleteShader(this.fragmentShader), this.fragmentShader = null);
495
+ }
496
+ /**
497
+ * Compile a single shader.
498
+ * @throws ShaderCompilationError if compilation fails
499
+ */
500
+ compileShader(e, t) {
501
+ const n = this.gl, i = e === "vertex" ? n.VERTEX_SHADER : n.FRAGMENT_SHADER, r = n.createShader(i);
502
+ if (!r)
503
+ throw new _(
504
+ e,
505
+ t,
506
+ "Failed to create shader object"
507
+ );
508
+ if (n.shaderSource(r, t), n.compileShader(r), !n.getShaderParameter(r, n.COMPILE_STATUS)) {
509
+ const a = n.getShaderInfoLog(r) || "Unknown error";
510
+ throw n.deleteShader(r), new _(e, t, a);
511
+ }
512
+ return r;
513
+ }
514
+ /**
515
+ * Link vertex and fragment shaders into a program.
516
+ * @throws ProgramLinkError if linking fails
517
+ */
518
+ linkProgram() {
519
+ const e = this.gl;
520
+ if (!this.vertexShader || !this.fragmentShader)
521
+ throw new S("Shaders not compiled");
522
+ const t = e.createProgram();
523
+ if (!t)
524
+ throw new S("Failed to create program object");
525
+ if (e.attachShader(t, this.vertexShader), e.attachShader(t, this.fragmentShader), e.linkProgram(t), !e.getProgramParameter(t, e.LINK_STATUS)) {
526
+ const i = e.getProgramInfoLog(t) || "Unknown error";
527
+ throw e.deleteProgram(t), new S(i);
528
+ }
529
+ this.program = t;
530
+ }
531
+ }
532
+ class M {
533
+ constructor(e, t) {
534
+ o(this, "name");
535
+ o(this, "method");
536
+ o(this, "isArray");
537
+ o(this, "isMatrix");
538
+ o(this, "location", null);
539
+ o(this, "locationResolved", !1);
540
+ o(this, "value");
541
+ this.name = e, this.value = t;
542
+ const n = M.inferMethodInfo(t);
543
+ this.method = n.method, this.isArray = n.isArray, this.isMatrix = n.isMatrix;
544
+ }
545
+ /**
546
+ * Infer WebGL method and metadata from value type.
547
+ */
548
+ static inferMethodInfo(e) {
549
+ if (typeof e == "boolean")
550
+ return { method: "uniform1i", isArray: !1, isMatrix: !1 };
551
+ if (typeof e == "number")
552
+ return { method: "uniform1f", isArray: !1, isMatrix: !1 };
553
+ if (!Array.isArray(e))
554
+ return { method: "uniform1f", isArray: !1, isMatrix: !1 };
555
+ const t = e.length, n = e[0];
556
+ if (Array.isArray(n))
557
+ switch (n.length) {
558
+ case 2:
559
+ return { method: "uniform2fv", isArray: !0, isMatrix: !1 };
560
+ case 3:
561
+ return { method: "uniform3fv", isArray: !0, isMatrix: !1 };
562
+ case 4:
563
+ return { method: "uniform4fv", isArray: !0, isMatrix: !1 };
564
+ default:
565
+ return { method: "uniform1fv", isArray: !0, isMatrix: !1 };
566
+ }
567
+ switch (t) {
568
+ case 2:
569
+ return { method: "uniform2fv", isArray: !1, isMatrix: !1 };
570
+ case 3:
571
+ return { method: "uniform3fv", isArray: !1, isMatrix: !1 };
572
+ case 4:
573
+ return { method: "uniform4fv", isArray: !1, isMatrix: !1 };
574
+ case 9:
575
+ return { method: "uniformMatrix3fv", isArray: !1, isMatrix: !0 };
576
+ case 16:
577
+ return { method: "uniformMatrix4fv", isArray: !1, isMatrix: !0 };
578
+ default:
579
+ return { method: "uniform1fv", isArray: !0, isMatrix: !1 };
580
+ }
581
+ }
582
+ /**
583
+ * Resolve and cache uniform location from program.
584
+ * Returns null if uniform doesn't exist (optimized out by compiler, etc.)
585
+ */
586
+ resolveLocation(e, t) {
587
+ return this.locationResolved || (this.location = e.getUniformLocation(t, this.name), this.locationResolved = !0), this.location;
588
+ }
589
+ /**
590
+ * Invalidate cached location (call when program changes).
591
+ */
592
+ invalidateLocation() {
593
+ this.location = null, this.locationResolved = !1;
594
+ }
595
+ /**
596
+ * Update value (doesn't upload to GPU until upload() is called).
597
+ */
598
+ setValue(e) {
599
+ this.value = e;
600
+ }
601
+ /**
602
+ * Get current value.
603
+ */
604
+ getValue() {
605
+ return this.value;
606
+ }
607
+ /**
608
+ * Upload current value to GPU.
609
+ * @param gl - WebGL context
610
+ * @param program - Current WebGL program (for location resolution)
611
+ */
612
+ upload(e, t) {
613
+ const n = this.resolveLocation(e, t);
614
+ if (n === null)
615
+ return;
616
+ const i = this.value;
617
+ let r;
618
+ switch (typeof i == "boolean" ? r = i ? 1 : 0 : typeof i == "number" ? r = i : this.isArray && Array.isArray(i[0]) ? r = new Float32Array(
619
+ i.flat()
620
+ ) : r = new Float32Array(i), this.method) {
621
+ case "uniform1f":
622
+ e.uniform1f(n, r);
623
+ break;
624
+ case "uniform1i":
625
+ e.uniform1i(n, r);
626
+ break;
627
+ case "uniform1fv":
628
+ e.uniform1fv(n, r);
629
+ break;
630
+ case "uniform2fv":
631
+ e.uniform2fv(n, r);
632
+ break;
633
+ case "uniform3fv":
634
+ e.uniform3fv(n, r);
635
+ break;
636
+ case "uniform4fv":
637
+ e.uniform4fv(n, r);
638
+ break;
639
+ case "uniformMatrix2fv":
640
+ e.uniformMatrix2fv(n, !1, r);
641
+ break;
642
+ case "uniformMatrix3fv":
643
+ e.uniformMatrix3fv(n, !1, r);
644
+ break;
645
+ case "uniformMatrix4fv":
646
+ e.uniformMatrix4fv(n, !1, r);
647
+ break;
648
+ }
649
+ }
650
+ }
651
+ class E {
652
+ constructor(e) {
653
+ o(this, "parsed", null);
654
+ this.source = e;
655
+ }
656
+ /**
657
+ * Parse the shader source to extract imports, uniforms, functions, and version.
658
+ */
659
+ parse() {
660
+ if (this.parsed) return this.parsed;
661
+ const e = this.detectVersion(), t = this.detectImports(), n = this.detectUniforms(), i = this.detectFunctions(n);
662
+ return this.parsed = {
663
+ version: e,
664
+ imports: t,
665
+ uniforms: n,
666
+ functions: i
667
+ };
668
+ }
669
+ /**
670
+ * Check if the shader source has already been parsed
671
+ */
672
+ isParsed() {
673
+ return this.parsed !== null;
674
+ }
675
+ /**
676
+ * Change the shader source and reset the parsed result
677
+ */
678
+ setSource(e) {
679
+ this.source = e, this.parsed = null;
680
+ }
681
+ /**
682
+ * Get the detected GLSL version from the shader source without parsing the entire shader
683
+ */
684
+ version() {
685
+ return this.detectVersion();
686
+ }
687
+ detectVersion() {
688
+ return /^\s*#version\s+300\s+es/m.test(this.source) ? 2 : 1;
689
+ }
690
+ detectImports() {
691
+ const e = /^[ \t]*#import\s+(\w+)(?:\s+as\s+(\w+))?\s+from\s+["'](.+)["']/gm, t = /^[ \t]*[^\w\s]?import\b/gm, n = [], i = /* @__PURE__ */ new Set();
692
+ let r, s = 1, a = 0;
693
+ for (; (r = e.exec(this.source)) !== null; ) {
694
+ s += (this.source.substring(a, r.index).match(/\n/g) || []).length, a = r.index, i.add(s);
695
+ const f = r[1], c = r[2] || r[1], m = r[3];
696
+ if (n.some((p) => p.alias === c))
697
+ throw new j(c, s);
698
+ n.push({ name: f, alias: c, module: m, line: s });
699
+ }
700
+ let l;
701
+ for (; (l = t.exec(this.source)) !== null; ) {
702
+ const f = (this.source.substring(0, l.index).match(/\n/g) || []).length + 1;
703
+ if (i.has(f)) continue;
704
+ const c = this.source.split(`
705
+ `)[f - 1].trim();
706
+ throw new q(
707
+ f,
708
+ this.diagnoseImport(c)
709
+ );
710
+ }
711
+ return n;
712
+ }
713
+ diagnoseImport(e) {
714
+ const t = e.match(/^([^\w\s])import\b/);
715
+ if (t && t[1] !== "#")
716
+ return `Invalid prefix '${t[1]}'. Expected: #import <function> from '<module>'`;
717
+ if (/^import\b/.test(e))
718
+ return "Missing '#' prefix. Expected: #import <function> from '<module>'";
719
+ if (/^#import\s+from\b/.test(e))
720
+ return "Missing function name. Expected: #import <function> from '<module>'";
721
+ if (/^#import\s+\w+\s*$/.test(e))
722
+ return `Missing 'from' clause. Expected: #import ${e.split(/\s+/)[1]} from '<module>'`;
723
+ if (/^#import\s+\w+\s+as\s*$/.test(e) || /^#import\s+\w+\s+as\s+from\b/.test(e))
724
+ return `Missing alias name after 'as'. Expected: #import ${e.split(/\s+/)[1]} as <alias> from '<module>'`;
725
+ if (/^#import\s+\w+\s+as\s+\w+\s*$/.test(e)) {
726
+ const n = e.split(/\s+/);
727
+ return `Missing 'from' clause. Expected: #import ${n[1]} as ${n[3]} from '<module>'`;
728
+ }
729
+ if (/^#import\s+\w+(?:\s+as\s+\w+)?\s+from\s+\w+/.test(e)) {
730
+ const n = e.match(/from\s+(\S+)/);
731
+ return `Module name must be quoted. Expected: from '${n == null ? void 0 : n[1]}'`;
732
+ }
733
+ return "Invalid syntax. Expected: #import <function> from '<module>'";
734
+ }
735
+ detectUniforms() {
736
+ const e = /^[ \t]*uniform\s+(?:(?:highp|mediump|lowp)\s+)?(\w+)\s+(\w+)(?:\[(\d+)\])?\s*;/gm, t = [];
737
+ let n, i = 1, r = 0;
738
+ for (; (n = e.exec(this.source)) !== null; ) {
739
+ i += (this.source.substring(r, n.index).match(/\n/g) || []).length, r = n.index;
740
+ const s = n[1], a = n[2], l = n[3] ? parseInt(n[3], 10) : void 0;
741
+ t.push({ name: a, type: s, line: i, arrayNum: l });
742
+ }
743
+ return t;
744
+ }
745
+ detectFunctions(e) {
746
+ const t = [], n = "void|float|int|uint|bool|vec[234]|ivec[234]|uvec[234]|bvec[234]|mat[234](?:x[234])?|sampler2D|samplerCube|sampler3D|sampler2DArray", i = new RegExp(
747
+ `^[ \\t]*(${n})\\s+(\\w+)\\s*\\(([^)]*)\\)\\s*\\{`,
748
+ "gm"
749
+ );
750
+ let r;
751
+ for (; (r = i.exec(this.source)) !== null; ) {
752
+ const s = r[1], a = r[2], l = r[3].trim(), f = r.index, c = (this.source.substring(0, f).match(/\n/g) || []).length + 1, m = this.source.indexOf("{", f), p = this.findClosingBrace(this.source, m);
753
+ if (p === -1) continue;
754
+ const g = this.source.slice(m, p + 1), P = this.parseParams(l), $ = this.findFunctionCalls(g), D = this.findUniformCalls(g, e), B = this.findMentionCalls(g);
755
+ t.push({
756
+ name: a,
757
+ type: s,
758
+ params: P,
759
+ body: g,
760
+ dependencies: [...$, ...D, ...B],
761
+ line: c
762
+ });
763
+ }
764
+ return t;
765
+ }
766
+ parseParams(e) {
767
+ if (!e.trim()) return [];
768
+ const t = [], n = e.split(",");
769
+ for (const i of n) {
770
+ const r = i.trim();
771
+ if (!r) continue;
772
+ const a = r.replace(/\b(in|out|inout|const|highp|mediump|lowp)\b\s*/g, "").trim().match(/^(\w+)\s+(\w+)(?:\[\d*\])?$/);
773
+ a && t.push({
774
+ type: a[1],
775
+ name: a[2]
776
+ });
777
+ }
778
+ return t;
779
+ }
780
+ findClosingBrace(e, t) {
781
+ let n = 0, i = !1, r = !1, s = !1, a = !1;
782
+ for (let l = t; l < e.length; l++) {
783
+ const f = e[l], c = e[l + 1], m = e[l - 1];
784
+ if (!r && !a && f === "/" && c === "/") {
785
+ s = !0;
786
+ continue;
787
+ }
788
+ if (s && f === `
789
+ `) {
790
+ s = !1;
791
+ continue;
792
+ }
793
+ if (!r && !s && f === "/" && c === "*") {
794
+ a = !0, l++;
795
+ continue;
796
+ }
797
+ if (a && f === "*" && c === "/") {
798
+ a = !1, l++;
799
+ continue;
800
+ }
801
+ if (!(s || a)) {
802
+ if (f === '"' && m !== "\\") {
803
+ r = !r;
804
+ continue;
805
+ }
806
+ if (!r) {
807
+ if (f === "{")
808
+ n++, i = !0;
809
+ else if (f === "}" && (n--, i && n === 0))
810
+ return l;
811
+ }
812
+ }
813
+ }
814
+ return -1;
815
+ }
816
+ findFunctionCalls(e) {
817
+ const t = [], n = /* @__PURE__ */ new Set([
818
+ "if",
819
+ "else",
820
+ "for",
821
+ "while",
822
+ "do",
823
+ "switch",
824
+ "case",
825
+ "return",
826
+ "break",
827
+ "continue",
828
+ "discard"
829
+ ]), i = /\b([a-zA-Z_]\w*)\s*\(/g;
830
+ let r;
831
+ for (; (r = i.exec(e)) !== null; ) {
832
+ const s = r[1];
833
+ n.has(s) || t.push({
834
+ name: s,
835
+ type: "function",
836
+ index: r.index
837
+ });
838
+ }
839
+ return t;
840
+ }
841
+ findUniformCalls(e, t) {
842
+ const n = [];
843
+ for (const i of t) {
844
+ const r = new RegExp(`\\b${i.name}\\b`, "g");
845
+ let s;
846
+ for (; (s = r.exec(e)) !== null; )
847
+ n.push({
848
+ name: i.name,
849
+ type: "uniform",
850
+ index: s.index
851
+ });
852
+ }
853
+ return n;
854
+ }
855
+ findMentionCalls(e) {
856
+ const t = [], n = /@(\w+)\.([a-zA-Z_]\w*)/g;
857
+ let i;
858
+ for (; (i = n.exec(e)) !== null; ) {
859
+ const r = i[1], s = i[2];
860
+ t.push({
861
+ name: `${r}.${s}`,
862
+ type: "mention",
863
+ index: i.index
864
+ });
865
+ }
866
+ return t;
867
+ }
868
+ }
869
+ class F {
870
+ constructor(e) {
871
+ /** Flag to track if the shader has been compiled */
872
+ o(this, "isCompiled", !1);
873
+ /** Original and compiled shader parsers */
874
+ o(this, "original");
875
+ /** Compiled parser will be updated with rewritten source after processing imports */
876
+ o(this, "compiled");
877
+ /** Collected requirements from imports */
878
+ o(this, "requirements", {
879
+ uniforms: /* @__PURE__ */ new Map(),
880
+ functions: /* @__PURE__ */ new Map()
881
+ });
882
+ this.original = new E(e), this.compiled = new E(e);
883
+ }
884
+ /**
885
+ * Detect WebGL version from shader source
886
+ */
887
+ version() {
888
+ return this.original.version();
889
+ }
890
+ /**
891
+ * Get the original source code of the shader
892
+ */
893
+ source() {
894
+ return this.original.source;
895
+ }
896
+ /**
897
+ * Force recompilation of the shader, reprocessing all imports and rewrites
898
+ * It's not necessary to call this manually because the state gets lost whenever the shader is switched out and back in.
899
+ */
900
+ recompile() {
901
+ return this.isCompiled = !1, this.compile();
902
+ }
903
+ /**
904
+ * Compile the shader source, resolving all imports
905
+ */
906
+ compile() {
907
+ return this.isCompiled ? this.compiled.source : (this.original.parse().imports.length > 0 && this.processImports(), this.compiled.setSource(this.build()), this.isCompiled = !0, this.compiled.source);
908
+ }
909
+ /**
910
+ * Process all #import directives
911
+ */
912
+ processImports() {
913
+ const e = this.original.parse(), t = e.functions.flatMap((n) => n.dependencies.filter((i) => i.type === "mention").map((i) => ({
914
+ name: i.name.split(".")[0],
915
+ uniform: i.name.split(".")[1]
916
+ })));
917
+ for (const n of e.imports) {
918
+ const i = w.resolve(n.module), r = i.extract(n.name);
919
+ let s = t.filter(
920
+ (l) => l.name === r.function.name
921
+ );
922
+ if (s.length > 0) {
923
+ const l = i.getDefinition().uniforms;
924
+ if (s = s.filter((f) => {
925
+ const c = l.find(
926
+ (m) => m.name === `u_${f.uniform}` || m.name === f.uniform
927
+ );
928
+ if (c) {
929
+ r.dependencies.uniforms.some(
930
+ (p) => p.name === c.name
931
+ ) || r.dependencies.uniforms.push(c);
932
+ const m = t.indexOf(f);
933
+ return m > -1 && t.splice(m, 1), !1;
934
+ }
935
+ return !0;
936
+ }), s.length > 0)
937
+ throw new ee(
938
+ n.module,
939
+ r.function.name,
940
+ s[0].uniform
941
+ );
942
+ }
943
+ const a = i.copy();
944
+ this.processExtraction(r, n.alias, a.options), y.merge(n.module, a);
945
+ }
946
+ if (t.length > 0)
947
+ throw new te(
948
+ t[0].name,
949
+ t[0].uniform
950
+ );
951
+ }
952
+ /**
953
+ * Process an extraction: rewrite names and collect as requirements
954
+ */
955
+ processExtraction(e, t, n = {}) {
956
+ const i = e.function, r = Math.random().toString(36).substring(2, 8), s = `${t}_${r}`;
957
+ for (let l = e.dependencies.functions.length - 1; l >= 0; l--) {
958
+ const f = e.dependencies.functions[l], c = this.rewriteFunction(f, t, {
959
+ uniforms: e.dependencies.uniforms,
960
+ functions: e.dependencies.functions,
961
+ unique: s
962
+ });
963
+ this.requirements.functions.set(c.name, c);
964
+ }
965
+ const a = this.rewriteFunction(i, t, {
966
+ uniforms: e.dependencies.uniforms,
967
+ functions: e.dependencies.functions,
968
+ rename: !0,
969
+ unique: s
970
+ });
971
+ this.requirements.functions.set(a.name, a);
972
+ for (const l of e.dependencies.uniforms) {
973
+ if (R.has(l.name)) continue;
974
+ const f = {
975
+ ...l,
976
+ name: `${s}_${l.name}${l.arrayNum ? `[${l.arrayNum}]` : ""}`
977
+ };
978
+ if (n[i.name]) {
979
+ const c = Object.entries(n[i.name]).find(
980
+ ([m, p]) => p.uniform === l.name
981
+ );
982
+ c && (c[1].uniform = `${s}_${l.name}`);
983
+ }
984
+ this.requirements.uniforms.set(f.name, f);
985
+ }
986
+ n[i.name] && t !== i.name && (n[t] = n[i.name], delete n[i.name]);
987
+ }
988
+ /**
989
+ * Rewrite a function: namespace all uniform and helper function references
990
+ */
991
+ rewriteFunction(e, t, n = { rename: !1, uniforms: [], functions: [], unique: "" }) {
992
+ const i = new Set(n.uniforms.map((c) => c.name)), r = new Set(n.functions.map((c) => c.name)), s = [], a = n.unique ? n.unique : t;
993
+ for (const c of e.dependencies)
994
+ if (c.index !== void 0)
995
+ if (c.type === "uniform" && i.has(c.name)) {
996
+ if (R.has(c.name)) continue;
997
+ s.push({
998
+ index: c.index,
999
+ oldText: c.name,
1000
+ newText: `${a}_${c.name}`
1001
+ });
1002
+ } else c.type === "function" && r.has(c.name) && s.push({
1003
+ index: c.index,
1004
+ oldText: c.name,
1005
+ newText: `${a}_${c.name}`
1006
+ });
1007
+ const l = this.applyRewrites(e.body, s), f = n.rename ? t : `${a}_${e.name}`;
1008
+ return {
1009
+ ...e,
1010
+ name: f,
1011
+ body: l
1012
+ };
1013
+ }
1014
+ /**
1015
+ * Apply rewrite operations to a string, processing from end to start
1016
+ */
1017
+ applyRewrites(e, t) {
1018
+ const n = [...t].sort((r, s) => s.index - r.index);
1019
+ let i = e;
1020
+ for (const r of n)
1021
+ i = i.slice(0, r.index) + r.newText + i.slice(r.index + r.oldText.length);
1022
+ return i;
1023
+ }
1024
+ /**
1025
+ * Build the final compiled shader
1026
+ */
1027
+ build() {
1028
+ const e = this.original.parse();
1029
+ let t = this.original.source;
1030
+ t = this.removeImportLines(t, e);
1031
+ const n = this.findInsertionPointForUniforms(t), i = this.generateUniformsCode();
1032
+ i && (t = t.slice(0, n) + i + `
1033
+ ` + t.slice(n));
1034
+ const r = this.findInsertionPointForFunctions(t), s = this.generateFunctionCode();
1035
+ return s && (t = t.slice(0, r) + s + t.slice(r)), t = t.replace(/\n{3,}/g, `
1036
+
1037
+ `), t = this.replaceMentions(t, e), t;
1038
+ }
1039
+ replaceMentions(e, t) {
1040
+ let n = e;
1041
+ const i = t.functions.filter(
1042
+ (r) => r.dependencies.some((s) => s.type === "mention")
1043
+ );
1044
+ for (const r of i) {
1045
+ const s = r.dependencies.filter((a) => a.type === "mention");
1046
+ for (const a of s) {
1047
+ const l = a.name.split("."), f = new RegExp(`\\b${l[0]}_(\\w+)_${l[1]}\\b`, "g"), c = this.requirements.uniforms.keys().find((p) => {
1048
+ var g;
1049
+ return ((g = p.match(f)) == null ? void 0 : g[0]) === p;
1050
+ });
1051
+ if (!c)
1052
+ throw new ne(
1053
+ a.name,
1054
+ r.name
1055
+ );
1056
+ const m = new RegExp(`@\\b${a.name}\\b`, "g");
1057
+ n = n.replace(m, c);
1058
+ }
1059
+ }
1060
+ return n;
1061
+ }
1062
+ /**
1063
+ * Remove #import lines from shader source
1064
+ */
1065
+ removeImportLines(e, t) {
1066
+ const n = e.split(`
1067
+ `), i = new Set(t.imports.map((r) => r.line));
1068
+ return n.filter((r, s) => {
1069
+ const a = i.has(s + 1);
1070
+ if (!a && r.trim() === "" && s > 0) {
1071
+ const l = s - 1;
1072
+ if (i.has(l + 1))
1073
+ return !1;
1074
+ }
1075
+ return !a;
1076
+ }).join(`
1077
+ `);
1078
+ }
1079
+ /**
1080
+ * Find insertion point for uniforms (after existing uniforms)
1081
+ */
1082
+ findInsertionPointForUniforms(e) {
1083
+ const t = new E(e).parse(), n = t.uniforms.find(
1084
+ (a) => a.line === Math.max(...t.uniforms.map((l) => l.line ?? 0))
1085
+ ), i = e.split(`
1086
+ `);
1087
+ let r = 0;
1088
+ if (n && n.line)
1089
+ r = n.line;
1090
+ else
1091
+ for (let a = 0; a < i.length; a++) {
1092
+ const l = i[a].trim();
1093
+ if (l.startsWith("#version")) {
1094
+ r = a + 1;
1095
+ continue;
1096
+ }
1097
+ if (l.startsWith("precision ")) {
1098
+ r = a + 1;
1099
+ continue;
1100
+ }
1101
+ if (l === "" || l.startsWith("//")) {
1102
+ r === a && (r = a + 1);
1103
+ continue;
1104
+ }
1105
+ break;
1106
+ }
1107
+ let s = 0;
1108
+ for (let a = 0; a < r; a++)
1109
+ s += i[a].length + 1;
1110
+ return s;
1111
+ }
1112
+ findInsertionPointForFunctions(e) {
1113
+ const t = new E(e).parse(), n = t.functions.find(
1114
+ (a) => a.line === Math.min(...t.functions.map((l) => l.line ?? 1 / 0))
1115
+ ), i = e.split(`
1116
+ `);
1117
+ let r = 0;
1118
+ if (n && n.line)
1119
+ r = n.line - 2;
1120
+ else
1121
+ throw new H();
1122
+ let s = 0;
1123
+ for (let a = 0; a < r; a++)
1124
+ s += i[a].length + 1;
1125
+ return s;
1126
+ }
1127
+ /**
1128
+ * Generate GLSL code for uniforms
1129
+ */
1130
+ generateUniformsCode() {
1131
+ const e = [];
1132
+ if (this.requirements.uniforms.size > 0)
1133
+ for (const t of this.checkUniformsPresence())
1134
+ e.push(`uniform ${t.type} ${t.name};`);
1135
+ return e.length === 0 ? "" : e.join(`
1136
+ `) + `
1137
+ `;
1138
+ }
1139
+ /**
1140
+ * Generate GLSL code for functions
1141
+ */
1142
+ generateFunctionCode() {
1143
+ const e = [];
1144
+ if (this.requirements.functions.size > 0)
1145
+ for (const t of this.checkFunctionsPresence()) {
1146
+ const n = t.params.map((i) => `${i.type} ${i.name}`).join(", ");
1147
+ e.push(`
1148
+ ${t.type} ${t.name}(${n}) ${t.body}`);
1149
+ }
1150
+ return e.length === 0 ? "" : e.join(`
1151
+ `) + `
1152
+ `;
1153
+ }
1154
+ /**
1155
+ * Check which required uniforms are missing from the original shader
1156
+ */
1157
+ checkUniformsPresence() {
1158
+ const e = this.original.parse(), t = [], n = this.requirements.uniforms;
1159
+ for (const [i, r] of n) {
1160
+ const s = e.uniforms.find((a) => a.name === i);
1161
+ if (!s) {
1162
+ t.push(r);
1163
+ continue;
1164
+ }
1165
+ if (s.type !== r.type)
1166
+ throw new U(
1167
+ "uniform",
1168
+ i,
1169
+ r.type,
1170
+ s.type
1171
+ );
1172
+ }
1173
+ return t;
1174
+ }
1175
+ /**
1176
+ * Check which required functions are missing from the original shader
1177
+ */
1178
+ checkFunctionsPresence() {
1179
+ const e = this.original.parse(), t = [], n = this.requirements.functions;
1180
+ for (const [i, r] of n) {
1181
+ const s = e.functions.find((a) => a.name === i);
1182
+ if (!s) {
1183
+ t.push(r);
1184
+ continue;
1185
+ }
1186
+ if (s.type !== r.type)
1187
+ throw new U(
1188
+ "function",
1189
+ i,
1190
+ r.type,
1191
+ s.type
1192
+ );
1193
+ }
1194
+ return t;
1195
+ }
1196
+ }
1197
+ class v extends F {
1198
+ constructor(t, n, i = {}) {
1199
+ super(n);
1200
+ o(this, "name");
1201
+ o(this, "options", {});
1202
+ this.name = t, this.options = this.resolveOptions(i);
1203
+ }
1204
+ resolveOptions(t) {
1205
+ if (!(t != null && t.default)) return t || {};
1206
+ const n = this.original.parse(), i = t.default;
1207
+ for (const r of n.functions)
1208
+ if (!(r.name === "main" || r.name === "default"))
1209
+ if (t[r.name]) {
1210
+ const s = t[r.name];
1211
+ for (const a in i)
1212
+ a in s || (s[a] = i[a]);
1213
+ } else
1214
+ t[r.name] = { ...i };
1215
+ return delete t.default, t || {};
1216
+ }
1217
+ /**
1218
+ * Define a module with given name and source, and register it
1219
+ */
1220
+ static define(t) {
1221
+ const { name: n, source: i, options: r } = t;
1222
+ if (n === "sandbox" || n.startsWith("sandbox/"))
1223
+ throw new J(n);
1224
+ const s = new v(n, i, r);
1225
+ if (w.has(n))
1226
+ throw new Z(n);
1227
+ return w.register(n, s), s;
1228
+ }
1229
+ /**
1230
+ * Resolve a module by name from the registry, throwing an error if not found
1231
+ */
1232
+ static resolve(t) {
1233
+ return w.resolve(t);
1234
+ }
1235
+ /**
1236
+ * Create a copy of the module. To unplug references to the original object.
1237
+ * Used when copying module to the runtime registry to allow independent runtime changes to options without affecting the original module definition.
1238
+ */
1239
+ copy(t = "original") {
1240
+ return new v(
1241
+ this.name,
1242
+ this[t].source,
1243
+ JSON.parse(JSON.stringify(this.options))
1244
+ );
1245
+ }
1246
+ /**
1247
+ * Merge options from another module into this one, without affecting the original module definition.
1248
+ * Used when merging imported modules into the runtime registry.
1249
+ */
1250
+ merge(t) {
1251
+ this.options = this.options || {};
1252
+ const n = this.getDefinition().uniforms.map((i) => i.name);
1253
+ for (const [i, r] of Object.entries(t.options ?? {}))
1254
+ if (!this.options[i]) this.options[i] = r;
1255
+ else
1256
+ for (const [s, a] of Object.entries(r))
1257
+ n.includes(a.uniform) || (this.options[i][s] = a);
1258
+ }
1259
+ /**
1260
+ * Get the module definition
1261
+ */
1262
+ getDefinition() {
1263
+ return this.compile(), {
1264
+ name: this.name,
1265
+ methods: this.compiled.parse().functions.map((t) => t.name).filter((t) => t !== "main" && t !== "default"),
1266
+ uniforms: this.compiled.parse().uniforms.map((t) => ({ name: t.name, type: t.type })),
1267
+ options: this.options
1268
+ };
1269
+ }
1270
+ /**
1271
+ * Extract a method with all its dependencies
1272
+ */
1273
+ extract(t) {
1274
+ if (this.compile(), t === "main")
1275
+ throw new K(this.name);
1276
+ if (t === "default")
1277
+ throw new Q(this.name);
1278
+ const n = this.compiled.parse(), i = n.functions.find((a) => a.name === t);
1279
+ if (!i)
1280
+ throw new Y(this.name, t);
1281
+ const r = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map();
1282
+ return this.collectDependencies({
1283
+ current: i,
1284
+ scope: {
1285
+ functions: n.functions,
1286
+ uniforms: n.uniforms
1287
+ },
1288
+ collected: {
1289
+ functions: r,
1290
+ uniforms: s
1291
+ },
1292
+ visited: /* @__PURE__ */ new Set([t])
1293
+ }), {
1294
+ function: i,
1295
+ dependencies: {
1296
+ functions: Array.from(r.values()),
1297
+ uniforms: Array.from(s.values())
1298
+ }
1299
+ };
1300
+ }
1301
+ /**
1302
+ * Recursively collect all function and uniform dependencies
1303
+ */
1304
+ collectDependencies(t) {
1305
+ for (const n of t.current.dependencies)
1306
+ if (n.type === "function") {
1307
+ if (t.visited.has(n.name)) continue;
1308
+ const i = t.scope.functions.find((r) => r.name === n.name);
1309
+ i && (t.visited.add(n.name), t.collected.functions.set(n.name, i), this.collectDependencies({
1310
+ current: i,
1311
+ scope: {
1312
+ functions: t.scope.functions,
1313
+ uniforms: t.scope.uniforms
1314
+ },
1315
+ collected: {
1316
+ functions: t.collected.functions,
1317
+ uniforms: t.collected.uniforms
1318
+ },
1319
+ visited: t.visited
1320
+ }));
1321
+ } else if (n.type === "uniform") {
1322
+ const i = t.scope.uniforms.find(
1323
+ (r) => r.name === n.name
1324
+ );
1325
+ i && !t.collected.uniforms.has(n.name) && t.collected.uniforms.set(n.name, i);
1326
+ }
1327
+ }
1328
+ }
1329
+ class V {
1330
+ constructor(e = []) {
1331
+ o(this, "modules", /* @__PURE__ */ new Map());
1332
+ e.forEach((t) => {
1333
+ this.register(t.name, t);
1334
+ });
1335
+ }
1336
+ /**
1337
+ * Compile all registered modules.
1338
+ */
1339
+ compile() {
1340
+ this.modules.forEach((e) => {
1341
+ e.compile();
1342
+ });
1343
+ }
1344
+ /**
1345
+ * Get the list of available shader modules.
1346
+ */
1347
+ available() {
1348
+ return Array.from(this.modules.values()).map(
1349
+ (e) => e.getDefinition()
1350
+ );
1351
+ }
1352
+ /**
1353
+ * Get the list of uniforms required by the currently registered modules.
1354
+ * This is used to automatically set up the uniforms in the shader based on the modules in use.
1355
+ */
1356
+ defaults() {
1357
+ const e = {};
1358
+ return this.modules.forEach((t) => {
1359
+ const n = t.getDefinition();
1360
+ if (n.options)
1361
+ for (const i in n.options) {
1362
+ const r = n.options[i];
1363
+ for (const s in r) {
1364
+ const a = r[s];
1365
+ a.default !== void 0 && !n.uniforms.map((l) => l.name).includes(a.uniform) && (e[a.uniform] = a.default);
1366
+ }
1367
+ }
1368
+ }), e;
1369
+ }
1370
+ /**
1371
+ * Resolve the options from the module definitions for a given function name.
1372
+ */
1373
+ resolveOptions(e) {
1374
+ for (const t of this.modules.values())
1375
+ if (t.options && t.options[e])
1376
+ return t.options[e];
1377
+ return null;
1378
+ }
1379
+ /**
1380
+ * Register a new module in the registry.
1381
+ */
1382
+ register(e, t) {
1383
+ this.modules.set(e, t);
1384
+ }
1385
+ /**
1386
+ * Merge a module into the registry. If a module with the same name already exists, options will be merged together.
1387
+ */
1388
+ merge(e, t) {
1389
+ if (!this.modules.has(e)) return this.register(e, t);
1390
+ const n = this.modules.get(e);
1391
+ n.merge(t), this.modules.set(e, n);
1392
+ }
1393
+ /**
1394
+ * Resolve a module by name. Throws an error if the module is not found.
1395
+ */
1396
+ resolve(e) {
1397
+ const t = this.modules.get(e);
1398
+ if (!t)
1399
+ throw new X(e);
1400
+ return t;
1401
+ }
1402
+ /**
1403
+ * Check if a module exists in the registry by name.
1404
+ */
1405
+ has(e) {
1406
+ return this.modules.has(e);
1407
+ }
1408
+ /**
1409
+ * Check if the registry is empty (no modules registered).
1410
+ */
1411
+ isEmpty() {
1412
+ return this.modules.size === 0;
1413
+ }
1414
+ /**
1415
+ * Remove a module from the registry by name.
1416
+ */
1417
+ remove(e) {
1418
+ this.modules.delete(e);
1419
+ }
1420
+ /**
1421
+ * Load multiple modules into the registry at once.
1422
+ */
1423
+ load(e) {
1424
+ e.forEach((t) => {
1425
+ this.register(t.name, t);
1426
+ });
1427
+ }
1428
+ /**
1429
+ * Clear all registered modules from the registry.
1430
+ */
1431
+ clear() {
1432
+ this.modules.clear();
1433
+ }
1434
+ }
1435
+ const ae = `// ─── Constants ──────────────────────────────────────────────
1436
+
1437
+ const float PI = 3.14159265359;
1438
+ const float TAU = 6.28318530718;
1439
+
1440
+ // ─── UV Transforms ──────────────────────────────────────────
1441
+
1442
+ /**
1443
+ * Center UV coordinates — remap to range where (0,0) is canvas center.
1444
+ * Aspect-ratio corrected using resolution length.
1445
+ * @uv-modifier
1446
+ */
1447
+ vec2 center(vec2 uv) {
1448
+ return (uv - 0.5 * u_resolution) / length(u_resolution);
1449
+ }
1450
+
1451
+ /**
1452
+ * Translate UV coordinates by offset.
1453
+ * @uv-modifier
1454
+ */
1455
+ vec2 translate(vec2 uv, vec2 offset) {
1456
+ return uv - offset;
1457
+ }
1458
+
1459
+ /**
1460
+ * Scale UV coordinates by factor around origin.
1461
+ * @uv-modifier
1462
+ */
1463
+ vec2 scale(vec2 uv, float factor) {
1464
+ return uv * factor;
1465
+ }
1466
+
1467
+ /**
1468
+ * Zoom — scale UV with aspect ratio correction.
1469
+ * Expects centered UV (use center() first).
1470
+ * Pushes UV outward/inward from origin.
1471
+ * factor = zoom factor (>1 = zoom in, <1 = zoom out)
1472
+ * @uv-modifier
1473
+ */
1474
+ vec2 zoom(vec2 uv, float factor) {
1475
+ return uv / max(factor, 0.001);
1476
+ }
1477
+
1478
+ /**
1479
+ * Normalize UV to range -1 to 1 with aspect ratio correction.
1480
+ * @uv-modifier
1481
+ */
1482
+ vec2 norm(vec2 uv) {
1483
+ uv *= min(u_resolution.x, u_resolution.y) / length(u_resolution);
1484
+ return uv;
1485
+ }
1486
+
1487
+ /**
1488
+ * Rotate UV coordinates around origin by angle (radians).
1489
+ * @uv-modifier
1490
+ */
1491
+ vec2 rotate(vec2 uv, float angle) {
1492
+ float c = cos(angle);
1493
+ float s = sin(angle);
1494
+ return vec2(uv.x * c - uv.y * s, uv.x * s + uv.y * c);
1495
+ }
1496
+
1497
+ /**
1498
+ * Tile — repeat coordinates in a grid of given size.
1499
+ * @uv-modifier
1500
+ */
1501
+ vec2 tile(vec2 uv, float size) {
1502
+ return fract(uv / size) * size;
1503
+ }
1504
+
1505
+ /**
1506
+ * Cartesian to polar coordinates.
1507
+ * Returns vec2(angle, radius) where angle is -PI to PI.
1508
+ */
1509
+ vec2 polar(vec2 uv) {
1510
+ return vec2(atan(uv.y, uv.x), length(uv));
1511
+ }
1512
+
1513
+ /**
1514
+ * Aspect ratio correction — makes UV square regardless of canvas shape.
1515
+ * @uv-modifier
1516
+ */
1517
+ vec2 aspect(vec2 uv) {
1518
+ return uv * vec2(u_resolution.x / u_resolution.y, 1.0);
1519
+ }
1520
+
1521
+ // ─── Math Utilities ─────────────────────────────────────────
1522
+
1523
+ /**
1524
+ * Remap a value from one range to another.
1525
+ * map(0.5, 0.0, 1.0, -1.0, 1.0) → 0.0
1526
+ */
1527
+ float map(float value, float inMin, float inMax, float outMin, float outMax) {
1528
+ return outMin + (value - inMin) * (outMax - outMin) / (inMax - inMin);
1529
+ }
1530
+
1531
+ // ─── Noise & Random ─────────────────────────────────────────
1532
+
1533
+ /**
1534
+ * Pseudo-random hash from 2D coordinates.
1535
+ * Returns float in 0–1 range.
1536
+ */
1537
+ float hash(vec2 p) {
1538
+ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
1539
+ }
1540
+
1541
+ /**
1542
+ * 2D pseudo-random hash.
1543
+ * Returns vec2 in 0–1 range — used for point scattering (worley etc).
1544
+ */
1545
+ vec2 hash2(vec2 p) {
1546
+ p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)));
1547
+ return fract(sin(p) * 43758.5453123);
1548
+ }
1549
+
1550
+ /**
1551
+ * Smooth value noise — interpolated hash grid.
1552
+ */
1553
+ float noise(vec2 p) {
1554
+ vec2 i = floor(p);
1555
+ vec2 f = fract(p);
1556
+ vec2 u = f * f * (3.0 - 2.0 * f);
1557
+
1558
+ return mix(
1559
+ mix(hash(i), hash(i + vec2(1.0, 0.0)), u.x),
1560
+ mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x),
1561
+ u.y
1562
+ );
1563
+ }
1564
+
1565
+ /**
1566
+ * Fractal Brownian Motion — 6 octaves of layered noise.
1567
+ * Returns ~0–1 range. Great for clouds, terrain, organic textures.
1568
+ */
1569
+ float fbm(vec2 p) {
1570
+ float value = 0.0;
1571
+ float amplitude = 0.5;
1572
+
1573
+ for (int i = 0; i < 6; i++) {
1574
+ value += amplitude * noise(p);
1575
+ p *= 2.0;
1576
+ amplitude *= 0.5;
1577
+ }
1578
+
1579
+ return value;
1580
+ }
1581
+
1582
+ /**
1583
+ * Worley (cellular / voronoi) noise.
1584
+ * Returns distance to nearest random cell point.
1585
+ * Great for cells, cracks, organic patterns.
1586
+ */
1587
+ float worley(vec2 p) {
1588
+ vec2 i = floor(p);
1589
+ vec2 f = fract(p);
1590
+ float minDist = 1.0;
1591
+
1592
+ for (int y = -1; y <= 1; y++) {
1593
+ for (int x = -1; x <= 1; x++) {
1594
+ vec2 neighbor = vec2(float(x), float(y));
1595
+ vec2 point = hash2(i + neighbor);
1596
+ float dist = length(neighbor + point - f);
1597
+ minDist = min(minDist, dist);
1598
+ }
1599
+ }
1600
+
1601
+ return minDist;
1602
+ }
1603
+
1604
+ /**
1605
+ * Waves — layered sine wave interference pattern.
1606
+ * Creates clean, regular wave lines like fabric or moiré.
1607
+ * frequency = wave density (5.0 = fabric, 10.0 = fine lines)
1608
+ * Returns 0–1 range.
1609
+ */
1610
+ float waves(vec2 p, float frequency) {
1611
+ return 0.6 + 0.4 * sin(
1612
+ frequency * (p.x + p.y + cos(3.0 * p.x + 5.0 * p.y)) +
1613
+ sin(frequency * 4.0 * (p.x + p.y))
1614
+ );
1615
+ }
1616
+
1617
+ /**
1618
+ * Voronoi cell lookup — returns the position of the nearest cell point.
1619
+ * Same algorithm as worley, but returns the point instead of distance.
1620
+ * Great for UV snapping, cell-based effects.
1621
+ */
1622
+ vec2 voronoi(vec2 p) {
1623
+ vec2 i = floor(p);
1624
+ vec2 f = fract(p);
1625
+ float minDist = 10.0;
1626
+ vec2 nearest = vec2(0.0);
1627
+
1628
+ for (int y = -1; y <= 1; y++) {
1629
+ for (int x = -1; x <= 1; x++) {
1630
+ vec2 neighbor = vec2(float(x), float(y));
1631
+ vec2 point = hash2(i + neighbor);
1632
+ float dist = length(neighbor + point - f);
1633
+ if (dist < minDist) {
1634
+ minDist = dist;
1635
+ nearest = i + neighbor + point;
1636
+ }
1637
+ }
1638
+ }
1639
+
1640
+ return nearest;
1641
+ }
1642
+
1643
+ void main() {}
1644
+ `, le = `/**
1645
+ * Hex integer to RGB.
1646
+ * Usage: hex(0xFF6600) → orange
1647
+ */
1648
+ vec3 hex(int value) {
1649
+ float v = float(value);
1650
+ float r = floor(v / 65536.0);
1651
+ float g = floor((v - r * 65536.0) / 256.0);
1652
+ float b = v - r * 65536.0 - g * 256.0;
1653
+ return vec3(r, g, b) / 255.0;
1654
+ }
1655
+
1656
+ /**
1657
+ * RGB 0–255 to normalized RGB 0–1.
1658
+ * Usage: rgb255(255.0, 128.0, 0.0) → orange
1659
+ */
1660
+ vec3 rgb255(float r, float g, float b) {
1661
+ return vec3(r, g, b) / 255.0;
1662
+ }
1663
+
1664
+ /**
1665
+ * HSV to RGB.
1666
+ * Input: vec3(hue 0–1, saturation 0–1, value 0–1)
1667
+ */
1668
+ vec3 hsv(vec3 c) {
1669
+ vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
1670
+ vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
1671
+ return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
1672
+ }
1673
+
1674
+ /**
1675
+ * HSL to RGB.
1676
+ * Input: vec3(hue 0–1, saturation 0–1, lightness 0–1)
1677
+ */
1678
+ vec3 hsl(vec3 c) {
1679
+ vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
1680
+ return c.z + c.y * (rgb - 0.5) * (1.0 - abs(2.0 * c.z - 1.0));
1681
+ }
1682
+
1683
+ /**
1684
+ * Linear gradient between two colors.
1685
+ * t is clamped to 0–1.
1686
+ */
1687
+ vec3 gradient(vec3 a, vec3 b, float t) {
1688
+ return mix(a, b, clamp(t, 0.0, 1.0));
1689
+ }
1690
+
1691
+ /**
1692
+ * 3-stop gradient.
1693
+ * t=0 → a, t=0.5 → b, t=1 → c
1694
+ */
1695
+ vec3 gradient3(vec3 a, vec3 b, vec3 c, float t) {
1696
+ t = clamp(t, 0.0, 1.0);
1697
+ return t < 0.5
1698
+ ? mix(a, b, t * 2.0)
1699
+ : mix(b, c, (t - 0.5) * 2.0);
1700
+ }
1701
+
1702
+ /**
1703
+ * Cosine palette — Inigo Quilez formula.
1704
+ * color = a + b * cos(2π(c·t + d))
1705
+ * Generates infinite smooth color ramps from 4 vec3 params.
1706
+ */
1707
+ vec3 palette(vec3 a, vec3 b, vec3 c, vec3 d, float t) {
1708
+ return a + b * cos(6.28318 * (c * t + d));
1709
+ }
1710
+
1711
+ /**
1712
+ * Banded gradient — 3-zone color mapping with sharp transitions.
1713
+ * Uses tent functions instead of linear interpolation.
1714
+ * t=0 → b (center), t=1 → a (middle), t=2 → c (outer).
1715
+ * sharpness = transition width (2.0 = sharp, 1.0 = soft).
1716
+ */
1717
+ vec3 bands(vec3 a, vec3 b, vec3 c, float t, float sharpness) {
1718
+ float w1 = max(0.0, 1.0 - sharpness * abs(1.0 - t));
1719
+ float w2 = max(0.0, 1.0 - sharpness * abs(t));
1720
+ float w3 = 1.0 - min(1.0, w1 + w2);
1721
+ return a * w1 + b * w2 + c * w3;
1722
+ }
1723
+
1724
+ /**
1725
+ * Iridescent — iterative interference color pattern.
1726
+ * Generates rainbow-like colors from UV through trigonometric iteration.
1727
+ * Creates shimmering, oil-slick-like color fields.
1728
+ * time = animation time (pass u_time), speed = animation speed
1729
+ */
1730
+ vec3 iridescent(vec2 uv, float time, float speed) {
1731
+ float d = -time * 0.5 * speed;
1732
+ float a = 0.0;
1733
+ for (int i = 0; i < 8; i++) {
1734
+ a += cos(float(i) - d - a * uv.x);
1735
+ d += sin(uv.y * float(i) + a);
1736
+ }
1737
+ d += time * 0.5 * speed;
1738
+ vec3 col = vec3(cos(uv * vec2(d, a)) * 0.6 + 0.4, cos(a + d) * 0.5 + 0.5);
1739
+ return cos(col * cos(vec3(d, a, 2.5)) * 0.5 + 0.5);
1740
+ }
1741
+
1742
+ void main() {}
1743
+ `, ce = `// ─── Time Shapers ──────────────────────────────────────────
1744
+ // Convert raw time (u_time) to normalized 0–1 range.
1745
+ // Output feeds directly into easing functions.
1746
+
1747
+ /**
1748
+ * Loop — repeating sawtooth 0→1→0→1...
1749
+ * duration = cycle length in seconds
1750
+ */
1751
+ float loop(float t, float duration) {
1752
+ return fract(t / duration);
1753
+ }
1754
+
1755
+ /**
1756
+ * Pingpong — triangle wave 0→1→0→1...
1757
+ * duration = half-cycle length (0→1 takes \`duration\` seconds)
1758
+ */
1759
+ float pingpong(float t, float duration) {
1760
+ float m = mod(t, duration * 2.0);
1761
+ return m < duration ? m / duration : 2.0 - m / duration;
1762
+ }
1763
+
1764
+ /**
1765
+ * Once — single play 0→1, then stays at 1.
1766
+ * duration = animation length in seconds
1767
+ * Use with delay: once(u_time - 2.0, 3.0) starts at 2s, takes 3s.
1768
+ */
1769
+ float once(float t, float duration) {
1770
+ return clamp(t / duration, 0.0, 1.0);
1771
+ }
1772
+
1773
+ /**
1774
+ * Reverse — flip animation direction.
1775
+ * Turns 0→1 into 1→0. Works with any easing or shaper.
1776
+ * Usage: reverse(spring(loop(u_time, 2.0))) for spring zoom-in.
1777
+ */
1778
+ float reverse(float t) {
1779
+ return 1.0 - t;
1780
+ }
1781
+
1782
+ // ─── Easing Curves ─────────────────────────────────────────
1783
+ // All take t (0–1) and return shaped t (0–1).
1784
+ // Compose with time shapers: ease_in(loop(u_time, 2.0))
1785
+
1786
+ /**
1787
+ * Ease in — cubic acceleration. Slow start, fast end.
1788
+ */
1789
+ float ease_in(float t) {
1790
+ return t * t * t;
1791
+ }
1792
+
1793
+ /**
1794
+ * Ease out — cubic deceleration. Fast start, slow end.
1795
+ */
1796
+ float ease_out(float t) {
1797
+ float f = 1.0 - t;
1798
+ return 1.0 - f * f * f;
1799
+ }
1800
+
1801
+ /**
1802
+ * Ease in-out — cubic smooth both ends.
1803
+ */
1804
+ float ease_in_out(float t) {
1805
+ return t < 0.5
1806
+ ? 4.0 * t * t * t
1807
+ : 1.0 - pow(-2.0 * t + 2.0, 3.0) * 0.5;
1808
+ }
1809
+
1810
+ /**
1811
+ * Spring — damped oscillation. Overshoots then settles at 1.
1812
+ */
1813
+ float spring(float t) {
1814
+ return 1.0 - exp(-6.0 * t) * cos(12.0 * t);
1815
+ }
1816
+
1817
+ /**
1818
+ * Bounce — bouncing ball landing. Multiple bounces settling at 1.
1819
+ */
1820
+ float bounce(float t) {
1821
+ if (t < 1.0 / 2.75) {
1822
+ return 7.5625 * t * t;
1823
+ } else if (t < 2.0 / 2.75) {
1824
+ t -= 1.5 / 2.75;
1825
+ return 7.5625 * t * t + 0.75;
1826
+ } else if (t < 2.5 / 2.75) {
1827
+ t -= 2.25 / 2.75;
1828
+ return 7.5625 * t * t + 0.9375;
1829
+ } else {
1830
+ t -= 2.625 / 2.75;
1831
+ return 7.5625 * t * t + 0.984375;
1832
+ }
1833
+ }
1834
+
1835
+ /**
1836
+ * Elastic — springy overshoot with oscillation.
1837
+ */
1838
+ float elastic(float t) {
1839
+ return sin(13.0 * 3.14159 * 0.5 * t) * pow(2.0, -10.0 * t) + 1.0;
1840
+ }
1841
+
1842
+ /**
1843
+ * Overshoot — goes past 1, then pulls back. Slight rubber-band feel.
1844
+ */
1845
+ float overshoot(float t) {
1846
+ float s = 1.70158;
1847
+ float f = 1.0 - t;
1848
+ return 1.0 - f * f * (f * (s + 1.0) - s);
1849
+ }
1850
+
1851
+ /**
1852
+ * Teleport — barely creeps, then near-instant jump with spring settle.
1853
+ * Duration mostly = wait time. Jump happens at ~85% of t.
1854
+ * Tiny slow drift → snap → spring overshoot → settle at 1.
1855
+ */
1856
+ float teleport(float t) {
1857
+ float jump = smoothstep(0.8, 0.85, t);
1858
+ float creep = pow(t / 0.8, 4.0) * 0.1 * (1.0 - jump);
1859
+ float s = max(t - 0.85, 0.0) * 6.667;
1860
+ float settle = exp(-5.0 * s) * sin(s * 15.0) * 0.15;
1861
+ return creep + jump + settle;
1862
+ }
1863
+
1864
+ void main() {}
1865
+ `, ue = `#import hash from 'sandbox'
1866
+ #import noise from 'sandbox'
1867
+ #import fbm from 'sandbox'
1868
+ #import voronoi from 'sandbox'
1869
+ #import worley from 'sandbox'
1870
+
1871
+ uniform float u_intensity;
1872
+
1873
+ // ─── UV Effects ─────────────────────────────────────────────
1874
+ // Each function takes UV and returns modified UV.
1875
+ // All use u_intensity uniform for configuration.
1876
+
1877
+ /**
1878
+ * Pixelate — blocky mosaic effect.
1879
+ * Snaps UV to a grid. Higher intensity = larger pixels.
1880
+ * intensity = pixel count along shortest axis (default: 20)
1881
+ * @uv-modifier
1882
+ */
1883
+ vec2 pixelate(vec2 uv, float intensity) {
1884
+ float size = length(u_resolution) / (max(intensity, 1.0) * 10.0);
1885
+ return floor(uv / size) * size;
1886
+ }
1887
+
1888
+ /**
1889
+ * Twist — spiral distortion from center outward.
1890
+ * Expects centered UV (use center() first).
1891
+ * intensity = twist strength (0.4 = subtle, 1.0 = full spiral)
1892
+ * @uv-modifier
1893
+ */
1894
+ vec2 twist(vec2 uv, float intensity) {
1895
+ float dist = length(uv);
1896
+ float angle = atan(uv.y, uv.x) - intensity * 20.0 * dist;
1897
+ return vec2(cos(angle), sin(angle)) * dist;
1898
+ }
1899
+
1900
+ /**
1901
+ * Ripple — concentric wave distortion from center.
1902
+ * Expects centered UV (use center() first).
1903
+ * Creates water-drop-like rings.
1904
+ * intensity = wave amplitude (0.5 = gentle, 2.0 = strong)
1905
+ * @uv-modifier
1906
+ */
1907
+ vec2 ripple(vec2 uv, float intensity) {
1908
+ float dist = length(uv);
1909
+ float wave = sin(dist * 40.0 - u_time * 3.0) * intensity * 0.02;
1910
+ return uv + normalize(uv + 0.001) * wave;
1911
+ }
1912
+
1913
+ /**
1914
+ * Fisheye — barrel distortion from center.
1915
+ * Expects centered UV (use center() first).
1916
+ * Bends space outward like a fisheye lens.
1917
+ * intensity = distortion power (0.5 = subtle, 2.0 = extreme)
1918
+ * @uv-modifier
1919
+ */
1920
+ vec2 fisheye(vec2 uv, float intensity) {
1921
+ float dist = length(uv);
1922
+ float power = pow(dist, intensity) / dist;
1923
+ return uv * power;
1924
+ }
1925
+
1926
+ /**
1927
+ * Wobble — animated sine wave displacement.
1928
+ * Creates a jelly-like horizontal wobble.
1929
+ * intensity = wobble amount (1.0 = gentle, 5.0 = wild)
1930
+ * @uv-modifier
1931
+ */
1932
+ vec2 wobble(vec2 uv, float intensity) {
1933
+ uv.x += sin(uv.y * 10.0 + u_time * 2.0) * intensity * 0.01;
1934
+ uv.y += cos(uv.x * 10.0 + u_time * 2.0) * intensity * 0.01;
1935
+ return uv;
1936
+ }
1937
+
1938
+ /**
1939
+ * Organic — iterative fluid morph distortion.
1940
+ * Creates marble-like flowing patterns.
1941
+ * intensity = animation speed (3.0 = default)
1942
+ * @uv-modifier
1943
+ */
1944
+ vec2 organic(vec2 uv, float intensity) {
1945
+ float speed = u_time * intensity;
1946
+ vec2 acc = vec2(uv.x + uv.y);
1947
+
1948
+ for (int i = 0; i < 5; i++) {
1949
+ acc += sin(max(uv.x, uv.y)) + uv;
1950
+ uv += 0.5 * vec2(
1951
+ cos(5.1123314 + 0.353 * acc.y + speed * 0.131121),
1952
+ sin(acc.x - 0.113 * speed)
1953
+ );
1954
+ uv -= cos(uv.x + uv.y) - sin(uv.x * 0.711 - uv.y);
1955
+ }
1956
+
1957
+ return uv;
1958
+ }
1959
+
1960
+ /**
1961
+ * Glitch — random horizontal line displacement.
1962
+ * Creates digital glitch artifact bands.
1963
+ * intensity = glitch strength (0.5 = subtle, 3.0 = heavy)
1964
+ * @uv-modifier
1965
+ */
1966
+ vec2 glitch(vec2 uv, float intensity) {
1967
+ float line = floor(uv.y * 50.0);
1968
+ float shift = hash(vec2(line, floor(u_time * 8.0)));
1969
+ shift = step(0.9, shift) * (shift - 0.9) * 10.0;
1970
+ uv.x += shift * intensity * 0.1 * u_resolution.x;
1971
+ return uv;
1972
+ }
1973
+
1974
+ /**
1975
+ * Mirror — reflect UV across a chosen axis.
1976
+ * Creates bilateral symmetry.
1977
+ * intensity = 0 for horizontal mirror, 1 for vertical mirror
1978
+ * @uv-modifier
1979
+ */
1980
+ vec2 mirror(vec2 uv, float intensity) {
1981
+ if (intensity < 0.5) {
1982
+ uv.x = abs(uv.x);
1983
+ } else {
1984
+ uv.y = abs(uv.y);
1985
+ }
1986
+ return uv;
1987
+ }
1988
+
1989
+ /**
1990
+ * Kaleidoscope — repeating angular symmetry.
1991
+ * Expects centered UV (use center() first).
1992
+ * Creates mandala-like radial repeats.
1993
+ * intensity = number of segments (4 = quad, 6 = hex, 8 = octa)
1994
+ * @uv-modifier
1995
+ */
1996
+ vec2 kaleidoscope(vec2 uv, float intensity) {
1997
+ float segments = max(intensity, 1.0);
1998
+ float angle = atan(uv.y, uv.x);
1999
+ float segment_angle = 6.28318 / segments;
2000
+ angle = mod(angle, segment_angle);
2001
+ angle = abs(angle - segment_angle * 0.5);
2002
+ float r = length(uv);
2003
+ return vec2(cos(angle), sin(angle)) * r;
2004
+ }
2005
+
2006
+ /**
2007
+ * Warp — domain warping using fractal noise.
2008
+ * Displaces UV by layered fbm noise for smoke/cloud-like distortion.
2009
+ * Animated over time.
2010
+ * intensity = warp strength (0.5 = subtle haze, 3.0 = heavy distortion)
2011
+ * @uv-modifier
2012
+ */
2013
+ vec2 warp(vec2 uv, float intensity) {
2014
+ vec2 offset = vec2(
2015
+ fbm(uv + u_time * 0.3),
2016
+ fbm(uv + vec2(5.2, 1.3) + u_time * 0.3)
2017
+ );
2018
+ return uv + offset * intensity * 0.1;
2019
+ }
2020
+
2021
+ /**
2022
+ * Displace — noise-based UV displacement.
2023
+ * Shifts UV using smooth value noise for a wavy, heat-haze look.
2024
+ * Animated over time.
2025
+ * intensity = displacement amount (1.0 = gentle, 5.0 = strong)
2026
+ * @uv-modifier
2027
+ */
2028
+ vec2 displace(vec2 uv, float intensity) {
2029
+ float nx = noise(uv * 5.0 + u_time);
2030
+ float ny = noise(uv * 5.0 + vec2(3.7, 7.1) + u_time);
2031
+ return uv + (vec2(nx, ny) - 0.5) * intensity * 0.05;
2032
+ }
2033
+
2034
+ /**
2035
+ * Shatter — voronoi cell-based UV snapping.
2036
+ * Snaps UV to nearest cell point, creating a broken-glass look.
2037
+ * intensity = cell density (5.0 = large shards, 20.0 = fine fragments)
2038
+ * @uv-modifier
2039
+ */
2040
+ vec2 shatter(vec2 uv, float intensity) {
2041
+ return voronoi(uv * intensity) / intensity;
2042
+ }
2043
+
2044
+ /**
2045
+ * Cells — cellular distortion using Worley distance.
2046
+ * Displaces UV outward from cell edges, like looking through textured glass.
2047
+ * intensity = cell scale (3.0 = large bubbles, 15.0 = fine texture)
2048
+ * @uv-modifier
2049
+ */
2050
+ vec2 cells(vec2 uv, float intensity) {
2051
+ float dist = worley(uv * intensity);
2052
+ vec2 grad = vec2(
2053
+ worley(uv * intensity + vec2(0.01, 0.0)) - dist,
2054
+ worley(uv * intensity + vec2(0.0, 0.01)) - dist
2055
+ );
2056
+ return uv + grad * dist * 2.0;
2057
+ }
2058
+
2059
+ void main() {}
2060
+ `, fe = `#import hash from 'sandbox'
2061
+
2062
+ uniform float u_intensity;
2063
+
2064
+ // ─── Color Filters ──────────────────────────────────────────
2065
+ // Each function takes a color and returns modified color.
2066
+ // All use u_intensity uniform for configuration.
2067
+
2068
+ /**
2069
+ * Contrast — adjust contrast around mid-gray.
2070
+ * intensity: 1.0 = original, >1 = more contrast, <1 = flatter
2071
+ * @color-modifier
2072
+ */
2073
+ vec3 contrast(vec3 color, float intensity) {
2074
+ return (color - 0.5) * intensity + 0.5;
2075
+ }
2076
+
2077
+ /**
2078
+ * Brightness — multiply overall brightness.
2079
+ * intensity: 1.0 = original, >1 = brighter, <1 = darker
2080
+ * @color-modifier
2081
+ */
2082
+ vec3 brightness(vec3 color, float intensity) {
2083
+ return color * intensity;
2084
+ }
2085
+
2086
+ /**
2087
+ * Saturate — adjust color saturation.
2088
+ * intensity: 0 = grayscale, 1.0 = original, >1 = oversaturated
2089
+ * @color-modifier
2090
+ */
2091
+ vec3 saturate(vec3 color, float intensity) {
2092
+ float gray = dot(color, vec3(0.299, 0.587, 0.114));
2093
+ return mix(vec3(gray), color, intensity);
2094
+ }
2095
+
2096
+ /**
2097
+ * Posterize — reduce color to N discrete levels per channel.
2098
+ * intensity = number of levels (4 = retro, 16 = subtle, 256 = nearly smooth)
2099
+ * @color-modifier
2100
+ */
2101
+ vec3 posterize(vec3 color, float intensity) {
2102
+ float levels = max(intensity, 1.0);
2103
+ return floor(color * levels + 0.5) / levels;
2104
+ }
2105
+
2106
+ /**
2107
+ * Threshold — convert to black and white based on cutoff.
2108
+ * intensity = cutoff point (0.5 = balanced, lower = more white)
2109
+ * @color-modifier
2110
+ */
2111
+ vec3 threshold(vec3 color, float intensity) {
2112
+ float avg = dot(color, vec3(0.299, 0.587, 0.114));
2113
+ return avg > intensity ? vec3(1.0) : vec3(0.0);
2114
+ }
2115
+
2116
+ /**
2117
+ * Invert — flip all color channels.
2118
+ * intensity: 0.0 = original, 1.0 = fully inverted, 0.5 = mid-gray
2119
+ * @color-modifier
2120
+ */
2121
+ vec3 invert(vec3 color, float intensity) {
2122
+ return mix(color, 1.0 - color, intensity);
2123
+ }
2124
+
2125
+ /**
2126
+ * Glow — luminance-based bloom, bright areas get amplified.
2127
+ * intensity = glow strength (0.5 = subtle, 2.0 = intense)
2128
+ * @color-modifier
2129
+ */
2130
+ vec3 glow(vec3 color, float intensity) {
2131
+ float luminance = dot(color, vec3(0.299, 0.587, 0.114));
2132
+ return color + color * luminance * intensity;
2133
+ }
2134
+
2135
+ /**
2136
+ * Grain — animated film noise overlay.
2137
+ * intensity = noise strength (0.1 = subtle, 0.5 = heavy)
2138
+ * @color-modifier
2139
+ */
2140
+ vec3 grain(vec3 color, vec2 uv, float intensity) {
2141
+ float n = (hash(uv * 1000.0 + u_time) - 0.5) * intensity;
2142
+ return color + n;
2143
+ }
2144
+
2145
+ /**
2146
+ * Vignette — darken canvas edges.
2147
+ * Applies circular falloff from center.
2148
+ * intensity = darkness strength (1.0 = subtle, 2.0 = strong)
2149
+ * @color-modifier
2150
+ */
2151
+ vec3 vignette(vec3 color, vec2 uv, float intensity) {
2152
+ vec2 centered = uv / u_resolution - 0.5;
2153
+ float dist = length(centered);
2154
+ float falloff = 1.0 - smoothstep(0.3, 0.7, dist * intensity);
2155
+ return color * falloff;
2156
+ }
2157
+
2158
+ /**
2159
+ * Sepia — warm brownish tint like old photographs.
2160
+ * intensity: 0.0 = original, 1.0 = full sepia
2161
+ * @color-modifier
2162
+ */
2163
+ vec3 sepia(vec3 color, float intensity) {
2164
+ vec3 s;
2165
+ s.r = dot(color, vec3(0.393, 0.769, 0.189));
2166
+ s.g = dot(color, vec3(0.349, 0.686, 0.168));
2167
+ s.b = dot(color, vec3(0.272, 0.534, 0.131));
2168
+ return mix(color, s, intensity);
2169
+ }
2170
+
2171
+ /**
2172
+ * Gamma — apply gamma correction curve.
2173
+ * intensity = gamma value (1.0 = linear, 2.2 = standard sRGB, <1 = brighten darks)
2174
+ * @color-modifier
2175
+ */
2176
+ vec3 gamma(vec3 color, float intensity) {
2177
+ return pow(max(color, 0.0), vec3(1.0 / max(intensity, 0.001)));
2178
+ }
2179
+
2180
+ /**
2181
+ * Tint — blend color toward a target tint.
2182
+ * intensity: 0.0 = original, 1.0 = fully tinted
2183
+ * @color-modifier
2184
+ */
2185
+ vec3 tint(vec3 color, vec3 tintColor, float intensity) {
2186
+ return mix(color, color * tintColor, intensity);
2187
+ }
2188
+
2189
+ /**
2190
+ * Highlights — add white light to bright areas.
2191
+ * Uses max channel brightness so even saturated colors trigger highlights.
2192
+ * intensity = highlight strength (0.2 = subtle sheen, 1.0 = strong gloss)
2193
+ * @color-modifier
2194
+ */
2195
+ vec3 highlights(vec3 color, float intensity) {
2196
+ float bright = max(color.r, max(color.g, color.b + 0.3));
2197
+ return color + max(bright * 5.5 - 4.0, 0.0) * intensity;
2198
+ }
2199
+
2200
+ /**
2201
+ * Bayer 8×8 threshold matrix — computed mathematically.
2202
+ * Returns threshold value 0–1 for ordered dithering.
2203
+ */
2204
+ float bayer8(vec2 p) {
2205
+ p = mod(floor(p), 8.0);
2206
+ float value = 0.0;
2207
+ float divisor = 1.0;
2208
+ float multiplier = 16.0;
2209
+
2210
+ for (int i = 0; i < 3; i++) {
2211
+ vec2 q = mod(floor(p / divisor), 2.0);
2212
+ value += (q.x * 2.0 + q.y * 3.0 - q.x * q.y * 4.0) * multiplier;
2213
+ divisor *= 2.0;
2214
+ multiplier *= 0.25;
2215
+ }
2216
+
2217
+ return value / 64.0;
2218
+ }
2219
+
2220
+ /**
2221
+ * Dither — ordered Bayer 8×8 dithering.
2222
+ * Reduces color to discrete levels with a threshold pattern.
2223
+ * Use pixelate() on UV before coloring for blocky dither cells.
2224
+ * intensity = number of color levels (2 = 1-bit, 4 = retro, 8 = subtle)
2225
+ * @color-modifier
2226
+ */
2227
+ vec3 dither(vec3 color, vec2 uv, float intensity) {
2228
+ float levels = max(intensity, 2.0);
2229
+ float threshold = bayer8(uv) - 0.5;
2230
+ float stepSize = 1.0 / (levels - 1.0);
2231
+ color += threshold * stepSize;
2232
+ return clamp(floor(color * (levels - 1.0) + 0.5) / (levels - 1.0), 0.0, 1.0);
2233
+ }
2234
+
2235
+ /**
2236
+ * Arcade — pixelated Bayer dithering.
2237
+ * Combines pixelation and ordered dither for retro 8-bit look.
2238
+ * intensity = number of color levels (2 = 1-bit, 4 = retro, 8 = subtle)
2239
+ * @color-modifier
2240
+ */
2241
+ vec3 arcade(vec3 color, vec2 uv, float intensity) {
2242
+ vec2 grid = uv * u_resolution * 0.5;
2243
+ return dither(color, grid, intensity);
2244
+ }
2245
+
2246
+ void main() {}
2247
+ `, w = new V([
2248
+ new v("sandbox", ae),
2249
+ new v("sandbox/colors", le),
2250
+ new v("sandbox/time", ce),
2251
+ new v("sandbox/effects", ue, {
2252
+ default: {
2253
+ intensity: { uniform: "u_intensity", default: 1 }
2254
+ },
2255
+ pixelate: {
2256
+ intensity: { uniform: "u_intensity", default: 20 }
2257
+ },
2258
+ twist: {
2259
+ intensity: { uniform: "u_intensity", default: 1 }
2260
+ },
2261
+ ripple: {
2262
+ intensity: { uniform: "u_intensity", default: 1 }
2263
+ },
2264
+ fisheye: {
2265
+ intensity: { uniform: "u_intensity", default: 1 }
2266
+ },
2267
+ wobble: {
2268
+ intensity: { uniform: "u_intensity", default: 1 }
2269
+ },
2270
+ organic: {
2271
+ intensity: { uniform: "u_intensity", default: 3 }
2272
+ },
2273
+ glitch: {
2274
+ intensity: { uniform: "u_intensity", default: 1 }
2275
+ },
2276
+ mirror: {
2277
+ intensity: { uniform: "u_intensity", default: 0 }
2278
+ },
2279
+ kaleidoscope: {
2280
+ intensity: { uniform: "u_intensity", default: 6 }
2281
+ },
2282
+ zoom: {
2283
+ intensity: { uniform: "u_intensity", default: 1 }
2284
+ },
2285
+ warp: {
2286
+ intensity: { uniform: "u_intensity", default: 1 }
2287
+ },
2288
+ displace: {
2289
+ intensity: { uniform: "u_intensity", default: 1 }
2290
+ },
2291
+ shatter: {
2292
+ intensity: { uniform: "u_intensity", default: 10 }
2293
+ },
2294
+ cells: {
2295
+ intensity: { uniform: "u_intensity", default: 8 }
2296
+ },
2297
+ glass: {
2298
+ intensity: { uniform: "u_intensity", default: 1 }
2299
+ }
2300
+ }),
2301
+ new v("sandbox/filters", fe, {
2302
+ default: {
2303
+ intensity: { uniform: "u_intensity", default: 1 }
2304
+ },
2305
+ posterize: {
2306
+ intensity: { uniform: "u_intensity", default: 8 }
2307
+ },
2308
+ threshold: {
2309
+ intensity: { uniform: "u_intensity", default: 0.5 }
2310
+ },
2311
+ grain: {
2312
+ intensity: { uniform: "u_intensity", default: 0.1 }
2313
+ },
2314
+ vignette: {
2315
+ intensity: { uniform: "u_intensity", default: 1.4 }
2316
+ },
2317
+ glow: {
2318
+ intensity: { uniform: "u_intensity", default: 0.5 }
2319
+ },
2320
+ gamma: {
2321
+ intensity: { uniform: "u_intensity", default: 2.2 }
2322
+ },
2323
+ dither: {
2324
+ intensity: { uniform: "u_intensity", default: 4 }
2325
+ },
2326
+ highlights: {
2327
+ intensity: { uniform: "u_intensity", default: 0.5 }
2328
+ }
2329
+ })
2330
+ ]), y = new V(), R = /* @__PURE__ */ new Map([
2331
+ ["u_resolution", "vec2"],
2332
+ ["u_time", "float"],
2333
+ ["u_delta", "float"],
2334
+ ["u_mouse", "vec2"],
2335
+ ["u_frame", "int"]
2336
+ ]);
2337
+ class he {
2338
+ constructor(e) {
2339
+ o(this, "gl");
2340
+ o(this, "program", null);
2341
+ o(this, "uniforms", /* @__PURE__ */ new Map());
2342
+ this.gl = e;
2343
+ }
2344
+ /**
2345
+ * Attach to a WebGL program.
2346
+ * Invalidates all cached locations since they're program-specific.
2347
+ */
2348
+ attachProgram(e) {
2349
+ this.program = e;
2350
+ for (const t of this.uniforms.values())
2351
+ t.invalidateLocation();
2352
+ return this;
2353
+ }
2354
+ /**
2355
+ * Set a uniform value.
2356
+ * Creates the uniform if it doesn't exist, updates if it does.
2357
+ */
2358
+ set(e, t) {
2359
+ const n = this.uniforms.get(e);
2360
+ return n ? n.setValue(t) : this.uniforms.set(e, new M(e, t)), this;
2361
+ }
2362
+ /**
2363
+ * Set multiple uniforms at once.
2364
+ */
2365
+ setMany(e) {
2366
+ for (const [t, n] of Object.entries(e))
2367
+ this.set(t, n);
2368
+ return this;
2369
+ }
2370
+ /**
2371
+ * Get current uniform value.
2372
+ */
2373
+ get(e) {
2374
+ var t;
2375
+ return (t = this.uniforms.get(e)) == null ? void 0 : t.getValue();
2376
+ }
2377
+ /**
2378
+ * Check if uniform exists.
2379
+ */
2380
+ has(e) {
2381
+ return this.uniforms.has(e);
2382
+ }
2383
+ /**
2384
+ * Remove a uniform.
2385
+ */
2386
+ delete(e) {
2387
+ return this.uniforms.delete(e);
2388
+ }
2389
+ /**
2390
+ * Upload all uniforms to GPU.
2391
+ * Requires a program to be attached.
359
2392
  */
360
- getVersion() {
361
- return this.version;
2393
+ uploadAll() {
2394
+ if (!this.program)
2395
+ return this;
2396
+ for (const e of this.uniforms.values())
2397
+ e.upload(this.gl, this.program);
2398
+ return this;
362
2399
  }
363
2400
  /**
364
- * Get attribute location.
2401
+ * Upload only built-in uniforms (u_resolution, u_time, u_delta, u_mouse, u_frame).
2402
+ * Call this every frame with current values.
365
2403
  */
366
- getAttribLocation(t) {
367
- return this.program ? this.gl.getAttribLocation(this.program, t) : -1;
2404
+ uploadBuiltIns(e, t, n) {
2405
+ if (this.set("u_resolution", t), this.set("u_time", e.time), this.set("u_delta", e.delta), this.set("u_mouse", n), this.set("u_frame", e.frame), !this.program)
2406
+ return this;
2407
+ for (const i of R.keys()) {
2408
+ const r = this.uniforms.get(i);
2409
+ r && r.upload(this.gl, this.program);
2410
+ }
2411
+ return this;
368
2412
  }
369
2413
  /**
370
- * Get uniform location.
2414
+ * Clear all uniforms.
371
2415
  */
372
- getUniformLocation(t) {
373
- return this.program ? this.gl.getUniformLocation(this.program, t) : null;
2416
+ clear() {
2417
+ this.uniforms.clear();
374
2418
  }
375
2419
  /**
376
- * Cleanup all GPU resources.
2420
+ * Cleanup.
377
2421
  */
378
2422
  destroy() {
379
- const t = this.gl;
380
- this.program && (this.vertexShader && t.detachShader(this.program, this.vertexShader), this.fragmentShader && t.detachShader(this.program, this.fragmentShader), t.deleteProgram(this.program), this.program = null), this.vertexShader && (t.deleteShader(this.vertexShader), this.vertexShader = null), this.fragmentShader && (t.deleteShader(this.fragmentShader), this.fragmentShader = null);
2423
+ this.uniforms.clear(), this.program = null;
381
2424
  }
382
2425
  /**
383
- * Compile a single shader.
384
- * @throws ShaderCompilationError if compilation fails
2426
+ * Get all uniform names.
385
2427
  */
386
- compileShader(t, e) {
387
- const i = this.gl, s = t === "vertex" ? i.VERTEX_SHADER : i.FRAGMENT_SHADER, n = i.createShader(s);
388
- if (!n)
389
- throw new c(
390
- t,
391
- e,
392
- "Failed to create shader object"
393
- );
394
- if (i.shaderSource(n, e), i.compileShader(n), !i.getShaderParameter(n, i.COMPILE_STATUS)) {
395
- const f = i.getShaderInfoLog(n) || "Unknown error";
396
- throw i.deleteShader(n), new c(t, e, f);
397
- }
398
- return n;
2428
+ keys() {
2429
+ return this.uniforms.keys();
399
2430
  }
400
2431
  /**
401
- * Link vertex and fragment shaders into a program.
402
- * @throws ProgramLinkError if linking fails
2432
+ * Get uniform count.
403
2433
  */
404
- linkProgram() {
405
- const t = this.gl;
406
- if (!this.vertexShader || !this.fragmentShader)
407
- throw new m("Shaders not compiled");
408
- const e = t.createProgram();
409
- if (!e)
410
- throw new m("Failed to create program object");
411
- if (t.attachShader(e, this.vertexShader), t.attachShader(e, this.fragmentShader), t.linkProgram(e), !t.getProgramParameter(e, t.LINK_STATUS)) {
412
- const s = t.getProgramInfoLog(e) || "Unknown error";
413
- throw t.deleteProgram(e), new m(s);
414
- }
415
- this.program = e;
2434
+ get size() {
2435
+ return this.uniforms.size;
416
2436
  }
417
2437
  }
418
2438
  class x {
419
- constructor(t, e) {
420
- r(this, "name");
421
- r(this, "method");
422
- r(this, "isArray");
423
- r(this, "isMatrix");
424
- r(this, "location", null);
425
- r(this, "locationResolved", !1);
426
- r(this, "value");
427
- this.name = t, this.value = e;
428
- const i = x.inferMethodInfo(e);
429
- this.method = i.method, this.isArray = i.isArray, this.isMatrix = i.isMatrix;
2439
+ constructor(e, t, n, i) {
2440
+ o(this, "name");
2441
+ o(this, "gl");
2442
+ o(this, "texture", null);
2443
+ o(this, "location", null);
2444
+ o(this, "locationResolved", !1);
2445
+ o(this, "source");
2446
+ o(this, "options");
2447
+ o(this, "dynamicOverride");
2448
+ o(this, "needsUpload", !0);
2449
+ o(this, "needsReupload", !1);
2450
+ this.gl = e, this.name = t, this.source = n, this.dynamicOverride = i == null ? void 0 : i.dynamic, this.options = x.resolveOptions(n, i), this.needsReupload = this.options.dynamic;
430
2451
  }
431
2452
  /**
432
- * Infer WebGL method and metadata from value type.
2453
+ * Update the texture source.
2454
+ * Marks texture for re-upload on next bind.
2455
+ * Respects the original `dynamic` option if explicitly set, otherwise re-infers from source type.
433
2456
  */
434
- static inferMethodInfo(t) {
435
- if (typeof t == "boolean")
436
- return { method: "uniform1i", isArray: !1, isMatrix: !1 };
437
- if (typeof t == "number")
438
- return { method: "uniform1f", isArray: !1, isMatrix: !1 };
439
- if (!Array.isArray(t))
440
- return { method: "uniform1f", isArray: !1, isMatrix: !1 };
441
- const e = t.length, i = t[0];
442
- if (Array.isArray(i))
443
- switch (i.length) {
444
- case 2:
445
- return { method: "uniform2fv", isArray: !0, isMatrix: !1 };
446
- case 3:
447
- return { method: "uniform3fv", isArray: !0, isMatrix: !1 };
448
- case 4:
449
- return { method: "uniform4fv", isArray: !0, isMatrix: !1 };
450
- default:
451
- return { method: "uniform1fv", isArray: !0, isMatrix: !1 };
452
- }
453
- switch (e) {
454
- case 2:
455
- return { method: "uniform2fv", isArray: !1, isMatrix: !1 };
456
- case 3:
457
- return { method: "uniform3fv", isArray: !1, isMatrix: !1 };
458
- case 4:
459
- return { method: "uniform4fv", isArray: !1, isMatrix: !1 };
460
- case 9:
461
- return { method: "uniformMatrix3fv", isArray: !1, isMatrix: !0 };
462
- case 16:
463
- return { method: "uniformMatrix4fv", isArray: !1, isMatrix: !0 };
464
- default:
465
- return { method: "uniform1fv", isArray: !0, isMatrix: !1 };
466
- }
2457
+ setSource(e) {
2458
+ this.source = e, this.needsUpload = !0, this.needsReupload = this.dynamicOverride ?? x.isDynamicSource(e);
467
2459
  }
468
2460
  /**
469
2461
  * Resolve and cache uniform location from program.
470
- * Returns null if uniform doesn't exist (optimized out by compiler, etc.)
2462
+ * Returns null if the sampler uniform doesn't exist in the shader.
471
2463
  */
472
- resolveLocation(t, e) {
473
- return this.locationResolved || (this.location = t.getUniformLocation(e, this.name), this.locationResolved = !0), this.location;
2464
+ resolveLocation(e, t) {
2465
+ return this.locationResolved || (this.location = e.getUniformLocation(t, this.name), this.locationResolved = !0), this.location;
474
2466
  }
475
2467
  /**
476
2468
  * Invalidate cached location (call when program changes).
@@ -479,219 +2471,226 @@ class x {
479
2471
  this.location = null, this.locationResolved = !1;
480
2472
  }
481
2473
  /**
482
- * Update value (doesn't upload to GPU until upload() is called).
483
- */
484
- setValue(t) {
485
- this.value = t;
2474
+ * Bind texture to a texture unit and set the sampler uniform.
2475
+ * @param program - Current WebGL program (for location resolution)
2476
+ * @param unit - Texture unit index (0, 1, 2, ...)
2477
+ */
2478
+ upload(e, t) {
2479
+ const n = this.gl, i = this.resolveLocation(n, e);
2480
+ if (i !== null) {
2481
+ if (!this.texture) {
2482
+ if (this.texture = n.createTexture(), !this.texture) return;
2483
+ this.needsUpload = !0;
2484
+ }
2485
+ n.activeTexture(n.TEXTURE0 + t), n.bindTexture(n.TEXTURE_2D, this.texture), (this.needsUpload || this.needsReupload) && (this.uploadPixels(), this.needsUpload = !1), n.uniform1i(i, t);
2486
+ }
486
2487
  }
487
2488
  /**
488
- * Get current value.
2489
+ * Cleanup WebGL texture resource.
489
2490
  */
490
- getValue() {
491
- return this.value;
2491
+ destroy() {
2492
+ this.texture && (this.gl.deleteTexture(this.texture), this.texture = null), this.location = null, this.locationResolved = !1;
492
2493
  }
493
2494
  /**
494
- * Upload current value to GPU.
495
- * @param gl - WebGL context
496
- * @param program - Current WebGL program (for location resolution)
2495
+ * Upload pixel data from source and apply texture parameters.
497
2496
  */
498
- upload(t, e) {
499
- const i = this.resolveLocation(t, e);
500
- if (i === null)
501
- return;
502
- const s = this.value;
503
- let n;
504
- switch (typeof s == "boolean" ? n = s ? 1 : 0 : typeof s == "number" ? n = s : this.isArray && Array.isArray(s[0]) ? n = new Float32Array(
505
- s.flat()
506
- ) : n = new Float32Array(s), this.method) {
507
- case "uniform1f":
508
- t.uniform1f(i, n);
509
- break;
510
- case "uniform1i":
511
- t.uniform1i(i, n);
512
- break;
513
- case "uniform1fv":
514
- t.uniform1fv(i, n);
515
- break;
516
- case "uniform2fv":
517
- t.uniform2fv(i, n);
518
- break;
519
- case "uniform3fv":
520
- t.uniform3fv(i, n);
521
- break;
522
- case "uniform4fv":
523
- t.uniform4fv(i, n);
524
- break;
525
- case "uniformMatrix2fv":
526
- t.uniformMatrix2fv(i, !1, n);
527
- break;
528
- case "uniformMatrix3fv":
529
- t.uniformMatrix3fv(i, !1, n);
530
- break;
531
- case "uniformMatrix4fv":
532
- t.uniformMatrix4fv(i, !1, n);
533
- break;
534
- }
535
- }
536
- }
537
- const d = class d {
538
- constructor(t) {
539
- r(this, "gl");
540
- r(this, "program", null);
541
- r(this, "uniforms", /* @__PURE__ */ new Map());
542
- this.gl = t;
2497
+ uploadPixels() {
2498
+ const e = this.gl;
2499
+ e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL, this.options.flipY), e.texImage2D(
2500
+ e.TEXTURE_2D,
2501
+ 0,
2502
+ e.RGBA,
2503
+ e.RGBA,
2504
+ e.UNSIGNED_BYTE,
2505
+ this.source
2506
+ ), this.applyParameters();
543
2507
  }
544
2508
  /**
545
- * Attach to a WebGL program.
546
- * Invalidates all cached locations since they're program-specific.
2509
+ * Apply texture wrap and filter parameters.
547
2510
  */
548
- attachProgram(t) {
549
- this.program = t;
550
- for (const e of this.uniforms.values())
551
- e.invalidateLocation();
552
- return this;
2511
+ applyParameters() {
2512
+ const e = this.gl;
2513
+ e.texParameteri(
2514
+ e.TEXTURE_2D,
2515
+ e.TEXTURE_WRAP_S,
2516
+ x.resolveWrap(e, this.options.wrapS)
2517
+ ), e.texParameteri(
2518
+ e.TEXTURE_2D,
2519
+ e.TEXTURE_WRAP_T,
2520
+ x.resolveWrap(e, this.options.wrapT)
2521
+ ), e.texParameteri(
2522
+ e.TEXTURE_2D,
2523
+ e.TEXTURE_MIN_FILTER,
2524
+ this.options.minFilter === "nearest" ? e.NEAREST : e.LINEAR
2525
+ ), e.texParameteri(
2526
+ e.TEXTURE_2D,
2527
+ e.TEXTURE_MAG_FILTER,
2528
+ this.options.magFilter === "nearest" ? e.NEAREST : e.LINEAR
2529
+ );
553
2530
  }
554
2531
  /**
555
- * Set a uniform value.
556
- * Creates the uniform if it doesn't exist, updates if it does.
2532
+ * Resolve wrap mode string to WebGL constant.
557
2533
  */
558
- set(t, e) {
559
- const i = this.uniforms.get(t);
560
- return i ? i.setValue(e) : this.uniforms.set(t, new x(t, e)), this;
2534
+ static resolveWrap(e, t) {
2535
+ switch (t) {
2536
+ case "repeat":
2537
+ return e.REPEAT;
2538
+ case "mirror":
2539
+ return e.MIRRORED_REPEAT;
2540
+ case "clamp":
2541
+ default:
2542
+ return e.CLAMP_TO_EDGE;
2543
+ }
561
2544
  }
562
2545
  /**
563
- * Set multiple uniforms at once.
2546
+ * Resolve texture options with defaults.
564
2547
  */
565
- setMany(t) {
566
- for (const [e, i] of Object.entries(t))
567
- this.set(e, i);
568
- return this;
2548
+ static resolveOptions(e, t) {
2549
+ const n = (t == null ? void 0 : t.wrap) ?? "clamp";
2550
+ return {
2551
+ wrap: n,
2552
+ wrapS: (t == null ? void 0 : t.wrapS) ?? n,
2553
+ wrapT: (t == null ? void 0 : t.wrapT) ?? n,
2554
+ minFilter: (t == null ? void 0 : t.minFilter) ?? "linear",
2555
+ magFilter: (t == null ? void 0 : t.magFilter) ?? "linear",
2556
+ flipY: (t == null ? void 0 : t.flipY) ?? !0,
2557
+ dynamic: (t == null ? void 0 : t.dynamic) ?? x.isDynamicSource(e)
2558
+ };
569
2559
  }
570
2560
  /**
571
- * Get current uniform value.
2561
+ * Check if a source is dynamic (needs re-upload every frame).
572
2562
  */
573
- get(t) {
574
- var e;
575
- return (e = this.uniforms.get(t)) == null ? void 0 : e.getValue();
2563
+ static isDynamicSource(e) {
2564
+ return e instanceof HTMLVideoElement;
2565
+ }
2566
+ }
2567
+ class me {
2568
+ constructor(e) {
2569
+ o(this, "gl");
2570
+ o(this, "program", null);
2571
+ o(this, "textures", /* @__PURE__ */ new Map());
2572
+ this.gl = e;
576
2573
  }
577
2574
  /**
578
- * Check if uniform exists.
2575
+ * Attach to a WebGL program.
2576
+ * Invalidates all cached locations since they're program-specific.
579
2577
  */
580
- has(t) {
581
- return this.uniforms.has(t);
2578
+ attachProgram(e) {
2579
+ this.program = e;
2580
+ for (const t of this.textures.values())
2581
+ t.invalidateLocation();
2582
+ return this;
582
2583
  }
583
2584
  /**
584
- * Remove a uniform.
2585
+ * Set a texture.
2586
+ * Creates the texture entry if it doesn't exist, updates source if it does.
585
2587
  */
586
- delete(t) {
587
- return this.uniforms.delete(t);
2588
+ set(e, t, n) {
2589
+ const i = this.textures.get(e);
2590
+ return i ? i.setSource(t) : this.textures.set(e, new x(this.gl, e, t, n)), this;
588
2591
  }
589
2592
  /**
590
- * Upload all uniforms to GPU.
591
- * Requires a program to be attached.
2593
+ * Get a texture by name.
592
2594
  */
593
- uploadAll() {
594
- if (!this.program)
595
- return this;
596
- for (const t of this.uniforms.values())
597
- t.upload(this.gl, this.program);
598
- return this;
2595
+ get(e) {
2596
+ return this.textures.get(e);
599
2597
  }
600
2598
  /**
601
- * Upload only built-in uniforms (u_resolution, u_time, u_delta, u_mouse, u_frame).
602
- * Call this every frame with current values.
2599
+ * Check if a texture exists.
603
2600
  */
604
- uploadBuiltIns(t, e, i) {
605
- if (this.set("u_resolution", e), this.set("u_time", t.time), this.set("u_delta", t.delta), this.set("u_mouse", i), this.set("u_frame", t.frame), !this.program)
606
- return this;
607
- for (const s of d.BUILT_INS) {
608
- const n = this.uniforms.get(s);
609
- n && n.upload(this.gl, this.program);
610
- }
611
- return this;
2601
+ has(e) {
2602
+ return this.textures.has(e);
612
2603
  }
613
2604
  /**
614
- * Clear all uniforms.
2605
+ * Remove a texture and free its GPU resources.
615
2606
  */
616
- clear() {
617
- this.uniforms.clear();
2607
+ delete(e) {
2608
+ const t = this.textures.get(e);
2609
+ return t ? (t.destroy(), this.textures.delete(e)) : !1;
618
2610
  }
619
2611
  /**
620
- * Cleanup.
2612
+ * Upload all textures to their respective texture units.
2613
+ * Units are assigned sequentially (0, 1, 2, ...).
621
2614
  */
622
- destroy() {
623
- this.uniforms.clear(), this.program = null;
2615
+ uploadAll() {
2616
+ if (!this.program)
2617
+ return this;
2618
+ let e = 0;
2619
+ for (const t of this.textures.values())
2620
+ t.upload(this.program, e), e++;
2621
+ return this;
624
2622
  }
625
2623
  /**
626
- * Get all uniform names.
2624
+ * Get texture count.
627
2625
  */
628
- keys() {
629
- return this.uniforms.keys();
2626
+ get size() {
2627
+ return this.textures.size;
630
2628
  }
631
2629
  /**
632
- * Get uniform count.
2630
+ * Cleanup all textures.
633
2631
  */
634
- get size() {
635
- return this.uniforms.size;
2632
+ destroy() {
2633
+ for (const e of this.textures.values())
2634
+ e.destroy();
2635
+ this.textures.clear(), this.program = null;
636
2636
  }
637
- };
638
- /** Built-in uniform names that are handled automatically */
639
- r(d, "BUILT_INS", /* @__PURE__ */ new Set([
640
- "u_resolution",
641
- "u_time",
642
- "u_delta",
643
- "u_mouse",
644
- "u_frame"
645
- ]));
646
- let v = d;
647
- class b {
2637
+ }
2638
+ class L {
648
2639
  constructor() {
649
- r(this, "hooks", /* @__PURE__ */ new Map());
2640
+ o(this, "hooks", /* @__PURE__ */ new Map());
650
2641
  }
651
2642
  id() {
652
2643
  return Math.random().toString(36).substring(2, 10);
653
2644
  }
654
2645
  /** Add a new hook, returns a removal function */
655
- add(t) {
656
- const e = this.id();
657
- return this.hooks.set(e, t), () => this.remove(e);
2646
+ add(e) {
2647
+ const t = this.id();
2648
+ return this.hooks.set(t, e), () => this.remove(t);
658
2649
  }
659
2650
  /** Remove a hook by its ID */
660
- remove(t) {
661
- this.hooks.delete(t);
2651
+ remove(e) {
2652
+ this.hooks.delete(e);
662
2653
  }
663
2654
  /** Run all hooks with the given state */
664
- run(t) {
665
- for (const [e, i] of this.hooks)
666
- i(t) === !1 && this.remove(e);
2655
+ run(e) {
2656
+ for (const [t, n] of this.hooks)
2657
+ try {
2658
+ n(e) === !1 && this.remove(t);
2659
+ } catch (i) {
2660
+ throw new re(
2661
+ t,
2662
+ i instanceof Error ? i.message : String(i)
2663
+ );
2664
+ }
667
2665
  }
668
2666
  destroy() {
669
2667
  this.hooks.clear();
670
2668
  }
671
2669
  }
672
- class _ {
673
- constructor(t, e) {
674
- r(this, "canvas");
675
- r(this, "gl");
676
- r(this, "options");
677
- r(this, "onBeforeHooks", new b());
678
- r(this, "onAfterHooks", new b());
679
- r(this, "_program");
680
- r(this, "_geometry");
681
- r(this, "_uniforms");
682
- r(this, "_clock");
683
- r(this, "_resolution", [1, 1]);
684
- r(this, "_mouse", [0, 0]);
685
- r(this, "_version", 1);
686
- r(this, "playing", !1);
687
- this.canvas = t, this.options = e, this.gl = this.initContext(), this.enableExtensions(), this._program = new a(this.gl), this._geometry = p.fullscreenQuad(this.gl), this._uniforms = new v(this.gl), this._clock = new R(), this.options.fps && this._clock.setMaxFps(this.options.fps), this.options.onBeforeRender && this.onBeforeHooks.add(this.options.onBeforeRender), this.options.onAfterRender && this.onAfterHooks.add(this.options.onAfterRender), this.onRender = this.onRender.bind(this);
2670
+ class T {
2671
+ constructor(e, t) {
2672
+ o(this, "canvas");
2673
+ o(this, "gl");
2674
+ o(this, "options");
2675
+ o(this, "onBeforeHooks", new L());
2676
+ o(this, "onAfterHooks", new L());
2677
+ o(this, "_program");
2678
+ o(this, "_geometry");
2679
+ o(this, "_uniforms");
2680
+ o(this, "_textures");
2681
+ o(this, "_clock");
2682
+ o(this, "_resolution", [1, 1]);
2683
+ o(this, "_mouse", [0, 0]);
2684
+ o(this, "_version", 1);
2685
+ o(this, "playing", !1);
2686
+ this.canvas = e, this.options = t, this.gl = this.initContext(), this.enableExtensions(), this._program = new oe(this.gl), this._geometry = k.fullscreenQuad(this.gl), this._uniforms = new he(this.gl), this._textures = new me(this.gl), this._clock = new se(), this.options.fps && this._clock.setMaxFps(this.options.fps), this.options.onBeforeRender && this.onBeforeHooks.add(this.options.onBeforeRender), this.options.onAfterRender && this.onAfterHooks.add(this.options.onAfterRender), this.onRender = this.onRender.bind(this);
688
2687
  }
689
2688
  /**
690
2689
  * Factory method to create and setup WebGL instance.
691
2690
  */
692
- static setup(t, e) {
693
- const i = new _(t, e);
694
- return e.vertex && e.fragment && i.shader(e.vertex, e.fragment), e.uniforms && i._uniforms.setMany(e.uniforms), i;
2691
+ static setup(e, t) {
2692
+ const n = new T(e, t);
2693
+ return t.vertex && t.fragment && n.shader(t.vertex, t.fragment), t.uniforms && n._uniforms.setMany(t.uniforms), t.textures && n.texturesFromSchema(t.textures), y.isEmpty() || n._uniforms.setMany(y.defaults()), n;
695
2694
  }
696
2695
  /**
697
2696
  * Initialize WebGL context.
@@ -699,20 +2698,20 @@ class _ {
699
2698
  * Context errors are fatal but still reported via onError.
700
2699
  */
701
2700
  initContext() {
702
- const t = {
2701
+ const e = {
703
2702
  antialias: this.options.antialias,
704
2703
  preserveDrawingBuffer: this.options.preserveDrawingBuffer,
705
2704
  alpha: !0,
706
2705
  depth: !1,
707
2706
  stencil: !1
708
- }, e = this.canvas.getContext("webgl2", t);
709
- if (e)
710
- return this._version = 2, e;
711
- const i = this.canvas.getContext("webgl", t);
712
- if (i)
713
- return this._version = 1, i;
714
- const s = new w("not_supported");
715
- throw this.options.onError(s), s;
2707
+ }, t = this.canvas.getContext("webgl2", e);
2708
+ if (t)
2709
+ return this._version = 2, t;
2710
+ const n = this.canvas.getContext("webgl", e);
2711
+ if (n)
2712
+ return this._version = 1, n;
2713
+ const i = new W();
2714
+ throw this.options.onError(i), i;
716
2715
  }
717
2716
  /**
718
2717
  * Enable useful WebGL extensions.
@@ -723,50 +2722,87 @@ class _ {
723
2722
  /**
724
2723
  * Set viewport dimensions.
725
2724
  */
726
- viewport(t, e, i, s) {
727
- return this.canvas.width = i, this.canvas.height = s, this.gl.viewport(t, e, i, s), this._resolution = [i, s], this;
2725
+ viewport(e, t, n, i) {
2726
+ return this.canvas.width = n, this.canvas.height = i, this.gl.viewport(e, t, n, i), this._resolution = [n, i], this;
728
2727
  }
729
2728
  /**
730
2729
  * Set the clock time
731
2730
  */
732
- clock(t) {
733
- return this._clock.setTime(t), this;
2731
+ clock(e) {
2732
+ return this._clock.setTime(e), this;
734
2733
  }
735
2734
  /**
736
2735
  * Update mouse position.
737
2736
  */
738
- mouse(t, e) {
739
- return this._mouse = [t, e], this;
2737
+ mouse(e, t) {
2738
+ return this._mouse = [e, t], this;
740
2739
  }
741
2740
  /**
742
2741
  * Set a uniform value.
743
2742
  */
744
- uniform(t, e) {
745
- return this._uniforms.set(t, e), this;
2743
+ uniform(e, t) {
2744
+ return this._uniforms.set(e, t), this;
746
2745
  }
747
2746
  /**
748
2747
  * Set multiple uniforms.
749
2748
  */
750
- uniforms(t) {
751
- return this._uniforms.setMany(t), this;
2749
+ uniforms(e) {
2750
+ return this._uniforms.setMany(e), this;
752
2751
  }
753
2752
  /**
754
2753
  * Get current uniform value.
755
2754
  */
756
- getUniform(t) {
757
- return this._uniforms.get(t);
2755
+ getUniform(e) {
2756
+ return this._uniforms.get(e);
2757
+ }
2758
+ /**
2759
+ * Set a texture.
2760
+ */
2761
+ texture(e, t, n) {
2762
+ return this._textures.set(e, t, n), this;
2763
+ }
2764
+ /**
2765
+ * Set multiple textures from a schema.
2766
+ */
2767
+ texturesFromSchema(e) {
2768
+ for (const [t, n] of Object.entries(e))
2769
+ if (n instanceof HTMLImageElement || n instanceof HTMLCanvasElement || n instanceof HTMLVideoElement || n instanceof ImageBitmap || n instanceof ImageData || n instanceof OffscreenCanvas)
2770
+ this._textures.set(t, n);
2771
+ else {
2772
+ const { source: i, ...r } = n;
2773
+ this._textures.set(t, i, r);
2774
+ }
2775
+ return this;
2776
+ }
2777
+ /**
2778
+ * Remove a texture.
2779
+ */
2780
+ removeTexture(e) {
2781
+ return this._textures.delete(e), this;
758
2782
  }
759
2783
  /**
760
2784
  * Compile and link shaders.
761
2785
  * Errors are handled via onError callback, never thrown.
762
2786
  */
763
- shader(t, e) {
2787
+ shader(e, t) {
764
2788
  try {
765
- this._program.compile(t, e), this._version = this._program.getVersion(), this._geometry.linkAttributes(this._program);
766
- const i = this._program.getProgram();
767
- i && this._uniforms.attachProgram(i), this.options.onLoad();
768
- } catch (i) {
769
- i instanceof u && this.options.onError(i);
2789
+ if (y.clear(), e.version() !== t.version())
2790
+ throw new z(
2791
+ e.version(),
2792
+ t.version()
2793
+ );
2794
+ this._program.compile(e.source(), t.compile()), this._version = t.version(), this._geometry.linkAttributes(this._program);
2795
+ const n = this._program.getProgram();
2796
+ n && (this._uniforms.attachProgram(n), this._textures.attachProgram(n));
2797
+ try {
2798
+ this.options.onLoad();
2799
+ } catch (i) {
2800
+ throw new ie(
2801
+ i instanceof Error ? i.message : String(i)
2802
+ );
2803
+ }
2804
+ } catch (n) {
2805
+ n instanceof h && this.options.onError(n);
770
2806
  }
771
2807
  return this;
772
2808
  }
@@ -781,8 +2817,19 @@ class _ {
781
2817
  */
782
2818
  pause() {
783
2819
  if (!this.playing) return this;
784
- const t = this._clock.getState();
785
- return this.onBeforeHooks.run(t), this.playing = !1, this._clock.stop(), this.onAfterHooks.run(t), this;
2820
+ const e = this._clock.getState();
2821
+ try {
2822
+ this.onBeforeHooks.run(e);
2823
+ } catch (t) {
2824
+ t instanceof h && this.options.onError(t);
2825
+ }
2826
+ this.playing = !1, this._clock.stop();
2827
+ try {
2828
+ this.onAfterHooks.run(e);
2829
+ } catch (t) {
2830
+ t instanceof h && this.options.onError(t);
2831
+ }
2832
+ return this;
786
2833
  }
787
2834
  /**
788
2835
  * Render a single frame.
@@ -812,17 +2859,34 @@ class _ {
812
2859
  * Cleanup all resources.
813
2860
  */
814
2861
  destroy() {
815
- this.pause(), this._clock.destroy(), this._geometry.destroy(), this._program.destroy(), this._uniforms.destroy(), this.onAfterHooks.destroy(), this.onBeforeHooks.destroy();
2862
+ this.pause(), this._clock.destroy(), this._geometry.destroy(), this._program.destroy(), this._uniforms.destroy(), this._textures.destroy(), this.onAfterHooks.destroy(), this.onBeforeHooks.destroy(), y.clear();
816
2863
  }
817
2864
  /**
818
2865
  * Internal render callback.
819
2866
  */
820
- onRender(t) {
821
- const e = this.gl;
822
- this.onBeforeHooks.run(t), e.clearColor(0, 0, 0, 0), e.clear(e.COLOR_BUFFER_BIT), this._program.use(), this._uniforms.uploadBuiltIns(t, this._resolution, this._mouse), this._uniforms.uploadAll(), this._geometry.bind(), this._geometry.draw(), this.onAfterHooks.run(t);
2867
+ onRender(e) {
2868
+ const t = this.gl;
2869
+ try {
2870
+ this.onBeforeHooks.run(e);
2871
+ } catch (n) {
2872
+ n instanceof h && this.options.onError(n);
2873
+ }
2874
+ t.clearColor(0, 0, 0, 0), t.clear(t.COLOR_BUFFER_BIT), this._program.use(), this._uniforms.uploadBuiltIns(e, this._resolution, this._mouse), this._uniforms.uploadAll(), this._textures.uploadAll(), this._geometry.bind(), this._geometry.draw();
2875
+ try {
2876
+ this.onAfterHooks.run(e);
2877
+ } catch (n) {
2878
+ n instanceof h && this.options.onError(n);
2879
+ }
823
2880
  }
824
2881
  }
825
- const g = `#ifdef GL_ES
2882
+ class d extends F {
2883
+ constructor(e) {
2884
+ super(e), R.forEach((t, n) => {
2885
+ this.requirements.uniforms.set(n, { name: n, type: t, line: 0 });
2886
+ });
2887
+ }
2888
+ }
2889
+ const A = `#ifdef GL_ES
826
2890
  precision mediump float;
827
2891
  #endif
828
2892
 
@@ -835,7 +2899,7 @@ void main() {
835
2899
  v_texcoord = a_texcoord;
836
2900
  gl_Position = vec4(a_position, 0.0, 1.0);
837
2901
  }
838
- `, A = `#ifdef GL_ES
2902
+ `, O = `#ifdef GL_ES
839
2903
  precision mediump float;
840
2904
  #endif
841
2905
 
@@ -849,7 +2913,7 @@ void main() {
849
2913
  vec3 color = vec3(uv.x, uv.y, 0.5 + 0.5 * sin(u_time));
850
2914
  gl_FragColor = vec4(color, 1.0);
851
2915
  }
852
- `, E = `#version 300 es
2916
+ `, C = `#version 300 es
853
2917
 
854
2918
  in vec2 a_position;
855
2919
  in vec2 a_texcoord;
@@ -859,7 +2923,7 @@ out vec2 v_texcoord;
859
2923
  void main() {
860
2924
  v_texcoord = a_texcoord;
861
2925
  gl_Position = vec4(a_position, 0.0, 1.0);
862
- }`, V = `#version 300 es
2926
+ }`, de = `#version 300 es
863
2927
  precision highp float;
864
2928
 
865
2929
  uniform vec2 u_resolution;
@@ -874,19 +2938,22 @@ void main() {
874
2938
  vec3 color = vec3(uv.x, uv.y, 0.5 + 0.5 * sin(u_time));
875
2939
  fragColor = vec4(color, 1.0);
876
2940
  }`;
877
- class y {
878
- constructor(t, e) {
2941
+ class I {
2942
+ constructor(e, t) {
879
2943
  /** Active event listeners */
880
- r(this, "listeners", []);
2944
+ o(this, "listeners", []);
881
2945
  /** HTML canvas element */
882
- r(this, "canvasEl");
2946
+ o(this, "canvasEl");
883
2947
  /** Resolved options */
884
- r(this, "options");
2948
+ o(this, "options");
885
2949
  /** WebGL engine */
886
- r(this, "engine");
2950
+ o(this, "engine");
887
2951
  /** User sets custom vertex shader */
888
- r(this, "usingCustomVertex", !1);
889
- this.canvasEl = t, this.options = this.resolveOptions(e), this.engine = _.setup(this.canvasEl, this.options), this.setupListeners(), this.setViewport(), this.options.autoplay && this.play();
2952
+ o(this, "usingCustomVertex", !1);
2953
+ if (this.canvasEl = e, this.options = this.resolveOptions(t), this.engine = T.setup(this.canvasEl, this.options), this.setupListeners(), this.setViewport(), this.options.modules)
2954
+ for (const [n, i] of Object.entries(this.options.modules))
2955
+ this.module(n, i);
2956
+ this.options.autoplay && this.play();
890
2957
  }
891
2958
  /**
892
2959
  * Sandbox - A lightweight WebGL wrapper for shader effects.
@@ -906,23 +2973,49 @@ class y {
906
2973
  * autoplay: true,
907
2974
  * });
908
2975
  */
909
- static create(t, e) {
910
- return new y(t, e);
2976
+ static create(e, t) {
2977
+ return new I(e, t);
911
2978
  }
912
- resolveOptions(t) {
913
- const e = {
914
- vertex: g,
915
- fragment: A,
2979
+ /**
2980
+ * Define a shader module that can be imported in shader source with `#import <function> from "module_name"`.
2981
+ * @example
2982
+ * Sandbox.defineModule("my_module", source, { options });
2983
+ * // Then in shader:
2984
+ * // #import myFunc from "my_module"
2985
+ * // void main() {
2986
+ * // myFunc();
2987
+ * // }
2988
+ */
2989
+ static defineModule(e, t, n = {}) {
2990
+ v.define({ name: e, source: t, options: n });
2991
+ }
2992
+ /**
2993
+ * Get the list of available shader modules that can be used with `#import` in shader source.
2994
+ */
2995
+ static availableModules() {
2996
+ return w.available();
2997
+ }
2998
+ /**
2999
+ * Compile a shader source with Sandbox's shader preprocessor and return the final GLSL code.
3000
+ * This is useful for debugging shader code or precompiling shaders for production use.
3001
+ */
3002
+ static compile(e) {
3003
+ return new d(e).compile();
3004
+ }
3005
+ resolveOptions(e) {
3006
+ const t = {
3007
+ vertex: new d(A),
3008
+ fragment: new d(O),
916
3009
  autoplay: !0,
917
3010
  pauseWhenHidden: !0,
918
3011
  dpr: "auto",
919
3012
  fps: 0,
920
3013
  preserveDrawingBuffer: !1,
921
3014
  antialias: !0,
922
- onError: (i) => {
3015
+ onError: (s) => {
923
3016
  console.error(
924
3017
  "Oops!",
925
- i,
3018
+ s,
926
3019
  `
927
3020
  You can handle errors programmatically by providing an onError callback to suppress this log and implement custom fallback behavior.`
928
3021
  );
@@ -931,64 +3024,70 @@ You can handle errors programmatically by providing an onError callback to suppr
931
3024
  },
932
3025
  onBeforeRender: null,
933
3026
  onAfterRender: null,
934
- uniforms: {}
3027
+ uniforms: {},
3028
+ modules: {},
3029
+ textures: {}
935
3030
  };
936
- if (t != null && t.vertex && (this.usingCustomVertex = !0), t != null && t.vertex && !(t != null && t.fragment)) {
937
- const i = a.detectVersion(t.vertex);
938
- e.vertex = t.vertex, e.fragment = i === 2 ? V : A;
3031
+ if (e != null && e.vertex && (this.usingCustomVertex = !0), e != null && e.vertex && !(e != null && e.fragment)) {
3032
+ t.vertex = new d(e.vertex);
3033
+ const s = t.vertex.version();
3034
+ t.fragment = new d(s === 2 ? de : O);
939
3035
  }
940
- if (t != null && t.fragment && !(t != null && t.vertex)) {
941
- const i = a.detectVersion(t.fragment);
942
- e.fragment = t.fragment, e.vertex = i === 2 ? E : g;
3036
+ if (e != null && e.fragment && !(e != null && e.vertex)) {
3037
+ t.fragment = new d(e.fragment);
3038
+ const s = t.fragment.version();
3039
+ t.vertex = new d(s === 2 ? C : A);
943
3040
  }
944
- return { ...e, ...t };
3041
+ e != null && e.vertex && (e != null && e.fragment) && (t.vertex = new d(e.vertex), t.fragment = new d(e.fragment));
3042
+ const { vertex: n, fragment: i, ...r } = e || {};
3043
+ return { ...t, ...r };
945
3044
  }
946
3045
  setupListeners() {
947
3046
  this.listeners.push(
948
3047
  // Window resize
949
- l.on(window, "resize", () => {
3048
+ b.on(window, "resize", () => {
950
3049
  this.setViewport();
951
3050
  }),
952
3051
  // Canvas resize
953
- l.on(this.canvasEl, "resize", () => {
3052
+ b.on(this.canvasEl, "resize", () => {
954
3053
  this.setViewport();
955
3054
  }),
956
3055
  // Visibility check on scroll
957
3056
  (() => {
958
- let t = !1;
959
- return l.on(document, "scroll", (e) => {
960
- this.options.pauseWhenHidden && (this.isInViewport() ? t && !this.isPlaying() && (this.play(), t = !1) : this.isPlaying() && (this.pause(), t = !0));
3057
+ let e = !1;
3058
+ return b.on(document, "scroll", (t) => {
3059
+ this.options.pauseWhenHidden && (this.isInViewport() ? e && !this.isPlaying() && (this.play(), e = !1) : this.isPlaying() && (this.pause(), e = !0));
961
3060
  });
962
3061
  })(),
963
3062
  // Mouse tracking
964
- l.on(document, "mousemove", (t) => {
965
- this.setMouse(t.clientX || t.pageX, t.clientY || t.pageY);
3063
+ b.on(document, "mousemove", (e) => {
3064
+ this.setMouse(e.clientX || e.pageX, e.clientY || e.pageY);
966
3065
  }),
967
3066
  // Touch tracking
968
- l.on(document, "touchmove", (t) => {
969
- t.touches.length > 0 && this.setMouse(t.touches[0].clientX, t.touches[0].clientY);
3067
+ b.on(document, "touchmove", (e) => {
3068
+ e.touches.length > 0 && this.setMouse(e.touches[0].clientX, e.touches[0].clientY);
970
3069
  })
971
3070
  );
972
3071
  }
973
3072
  destroyListeners() {
974
- this.listeners.forEach((t) => t()), this.listeners = [];
3073
+ this.listeners.forEach((e) => e()), this.listeners = [];
975
3074
  }
976
3075
  setViewport() {
977
- const t = this.options.dpr === "auto" ? Math.min(2, window.devicePixelRatio || 1) : this.options.dpr, e = this.canvasEl.clientWidth || this.canvasEl.width || 1, i = this.canvasEl.clientHeight || this.canvasEl.height || 1;
3076
+ const e = this.options.dpr === "auto" ? Math.min(2, window.devicePixelRatio || 1) : this.options.dpr, t = this.canvasEl.clientWidth || this.canvasEl.width || 1, n = this.canvasEl.clientHeight || this.canvasEl.height || 1;
978
3077
  this.engine.viewport(
979
3078
  0,
980
3079
  0,
981
- Math.max(1, Math.floor(e * t)),
982
- Math.max(1, Math.floor(i * t))
3080
+ Math.max(1, Math.floor(t * e)),
3081
+ Math.max(1, Math.floor(n * e))
983
3082
  );
984
3083
  }
985
3084
  isInViewport() {
986
- const t = this.canvasEl.getBoundingClientRect();
987
- return t.bottom >= 0 && t.right >= 0 && t.top <= (window.innerHeight || document.documentElement.clientHeight) && t.left <= (window.innerWidth || document.documentElement.clientWidth);
3085
+ const e = this.canvasEl.getBoundingClientRect();
3086
+ return e.bottom >= 0 && e.right >= 0 && e.top <= (window.innerHeight || document.documentElement.clientHeight) && e.left <= (window.innerWidth || document.documentElement.clientWidth);
988
3087
  }
989
- setMouse(t, e) {
990
- const i = this.canvasEl.getBoundingClientRect();
991
- t >= i.left && t <= i.right && e >= i.top && e <= i.bottom && this.engine.mouse(t - i.left, e - i.top);
3088
+ setMouse(e, t) {
3089
+ const n = this.canvasEl.getBoundingClientRect();
3090
+ e >= n.left && e <= n.right && t >= n.top && t <= n.bottom && this.engine.mouse(e - n.left, t - n.top);
992
3091
  }
993
3092
  /**
994
3093
  * Set a single uniform value with type checking.
@@ -997,8 +3096,8 @@ You can handle errors programmatically by providing an onError callback to suppr
997
3096
  * sandbox.setUniform<number>("u_time", 1.5);
998
3097
  * sandbox.setUniform<Vec3[]>("u_colors", [[1, 0, 0], [0, 1, 0]]);
999
3098
  */
1000
- setUniform(t, e) {
1001
- return this.engine.uniform(t, e), this;
3099
+ setUniform(e, t) {
3100
+ return this.engine.uniform(e, t), this;
1002
3101
  }
1003
3102
  /**
1004
3103
  * Set multiple uniforms at once with type checking.
@@ -1014,47 +3113,112 @@ You can handle errors programmatically by providing an onError callback to suppr
1014
3113
  * u_colors: [[1, 0, 0], [0, 1, 0]],
1015
3114
  * });
1016
3115
  */
1017
- setUniforms(t) {
1018
- return this.engine.uniforms(t), this;
3116
+ setUniforms(e) {
3117
+ return this.engine.uniforms(e), this;
1019
3118
  }
1020
3119
  /**
1021
3120
  * Get current uniform value.
1022
3121
  */
1023
- getUniform(t) {
1024
- return this.engine.getUniform(t);
3122
+ getUniform(e) {
3123
+ return this.engine.getUniform(e);
3124
+ }
3125
+ /**
3126
+ * Set a texture for a sampler2D uniform.
3127
+ * @example
3128
+ * sandbox.setTexture("u_texture", imageElement);
3129
+ * sandbox.setTexture("u_texture", imageElement, { wrap: "repeat" });
3130
+ */
3131
+ setTexture(e, t, n) {
3132
+ return this.engine.texture(e, t, n), this;
3133
+ }
3134
+ /**
3135
+ * Set multiple textures at once.
3136
+ * @example
3137
+ * sandbox.setTextures({
3138
+ * u_texture: imageElement,
3139
+ * u_detail: { source: detailImg, wrap: "repeat" },
3140
+ * });
3141
+ */
3142
+ setTextures(e) {
3143
+ return this.engine.texturesFromSchema(e), this;
3144
+ }
3145
+ /**
3146
+ * Remove a texture and free its GPU resources.
3147
+ * @example
3148
+ * sandbox.removeTexture("u_texture");
3149
+ */
3150
+ removeTexture(e) {
3151
+ return this.engine.removeTexture(e), this;
1025
3152
  }
1026
3153
  /**
1027
3154
  * Update shaders.
1028
3155
  * @example
1029
3156
  * sandbox.setShader(vertexSource, fragmentSource);
1030
3157
  */
1031
- setShader(t, e) {
1032
- return this.options.vertex = t, this.options.fragment = e, this.usingCustomVertex = !0, this.engine.shader(this.options.vertex, this.options.fragment), this;
3158
+ setShader(e, t) {
3159
+ return this.options.vertex = new d(e), this.options.fragment = new d(t), this.usingCustomVertex = !0, this.engine.shader(this.options.vertex, this.options.fragment), this;
1033
3160
  }
1034
3161
  /**
1035
3162
  * Update only fragment shader (uses default vertex).
1036
3163
  * @example
1037
3164
  * sandbox.setFragment(fragmentSource);
1038
3165
  */
1039
- setFragment(t) {
1040
- const e = a.detectVersion(t), i = a.detectVersion(this.options.vertex);
1041
- return this.options.fragment = t, e !== i && (this.usingCustomVertex || (this.options.vertex = e === 2 ? E : g)), this.engine.shader(this.options.vertex, this.options.fragment), this;
3166
+ setFragment(e) {
3167
+ const t = new d(e), n = t.version(), i = this.options.vertex.version();
3168
+ return this.options.fragment = t, n !== i && (this.usingCustomVertex || (this.options.vertex = new d(
3169
+ n === 2 ? C : A
3170
+ ))), this.engine.shader(this.options.vertex, this.options.fragment), this;
3171
+ }
3172
+ /**
3173
+ * Get current fragment shader source.
3174
+ */
3175
+ getFragment() {
3176
+ return this.options.fragment.source();
3177
+ }
3178
+ /**
3179
+ * Get current vertex shader source.
3180
+ */
3181
+ getVertex() {
3182
+ return this.options.vertex.source();
1042
3183
  }
1043
3184
  /**
1044
3185
  * Set the max frame rate runtime
1045
- *
3186
+ *
1046
3187
  * @example
1047
3188
  * sandbox.setFps(30); // Limit to 30 FPS
1048
3189
  * sandbox.setFps(0); // Unlimited FPS
1049
3190
  */
1050
- setFps(t) {
1051
- return this.engine.getClock().setMaxFps(t), this;
3191
+ setFps(e) {
3192
+ return this.engine.getClock().setMaxFps(e), this;
1052
3193
  }
1053
3194
  /**
1054
3195
  * Add a runtime render hook.
1055
3196
  */
1056
- hook(t, e = "before") {
1057
- return e === "before" ? this.engine.onBeforeHooks.add(t) : this.engine.onAfterHooks.add(t);
3197
+ hook(e, t = "before") {
3198
+ return t === "before" ? this.engine.onBeforeHooks.add(e) : this.engine.onAfterHooks.add(e);
3199
+ }
3200
+ /**
3201
+ * Runtime configure the module behavior
3202
+ * @example
3203
+ * sandbox.module("my_module", { intensity: 0.5 });
3204
+ */
3205
+ module(e, t) {
3206
+ const n = y.resolveOptions(e);
3207
+ if (!n)
3208
+ return console.warn(
3209
+ `Sandbox: Counld not find options for '${e}' function. Make sure you used the correct imported name and the module is currently in use by the shader.`
3210
+ ), this;
3211
+ for (const [i, r] of Object.entries(t)) {
3212
+ const s = n[i];
3213
+ if (!s) {
3214
+ console.warn(
3215
+ `Sandbox: Option '${i}' not found for function '${e}'. Make sure to check available options with Sandbox.availableModules() and provide the correct option name.`
3216
+ );
3217
+ continue;
3218
+ }
3219
+ this.setUniform(s.uniform, r);
3220
+ }
3221
+ return this;
1058
3222
  }
1059
3223
  /**
1060
3224
  * Start animation loop.
@@ -1065,8 +3229,8 @@ You can handle errors programmatically by providing an onError callback to suppr
1065
3229
  /**
1066
3230
  * Start animation loop at specific time (in seconds).
1067
3231
  */
1068
- playAt(t) {
1069
- return this.engine.clock(t), this.engine.play(), this;
3232
+ playAt(e) {
3233
+ return this.engine.clock(e), this.engine.play(), this;
1070
3234
  }
1071
3235
  /**
1072
3236
  * Stop animation loop.
@@ -1077,9 +3241,9 @@ You can handle errors programmatically by providing an onError callback to suppr
1077
3241
  /**
1078
3242
  * Pause animation loop at specific time (in seconds).
1079
3243
  */
1080
- pauseAt(t) {
1081
- const e = this.hook((i) => {
1082
- i.time >= t && (e(), this.pause());
3244
+ pauseAt(e) {
3245
+ const t = this.hook((n) => {
3246
+ n.time >= e && (t(), this.pause());
1083
3247
  }, "after");
1084
3248
  return this;
1085
3249
  }
@@ -1092,8 +3256,8 @@ You can handle errors programmatically by providing an onError callback to suppr
1092
3256
  /**
1093
3257
  * Set current time (in seconds).
1094
3258
  */
1095
- time(t) {
1096
- return this.engine.clock(t), this;
3259
+ time(e) {
3260
+ return this.engine.clock(e), this;
1097
3261
  }
1098
3262
  /**
1099
3263
  * Render a single frame (for static rendering).
@@ -1108,8 +3272,8 @@ You can handle errors programmatically by providing an onError callback to suppr
1108
3272
  * @example
1109
3273
  * sandbox.renderAt(2.5); // Render as if 2.5 seconds elapsed
1110
3274
  */
1111
- renderAt(t) {
1112
- return this.engine.clock(t), this.engine.render(), this;
3275
+ renderAt(e) {
3276
+ return this.engine.clock(e), this.engine.render(), this;
1113
3277
  }
1114
3278
  /**
1115
3279
  * Check if currently playing.
@@ -1129,6 +3293,57 @@ You can handle errors programmatically by providing an onError callback to suppr
1129
3293
  get canvas() {
1130
3294
  return this.canvasEl;
1131
3295
  }
3296
+ /**
3297
+ * Export current frame as a data URL string.
3298
+ * Requires `preserveDrawingBuffer: true` if called while playing.
3299
+ * @example
3300
+ * const url = sandbox.renderAt(1.5).exportAsURL("image/png");
3301
+ */
3302
+ exportAsURL(e = "image/png", t) {
3303
+ return this.canvas.toDataURL(e, t);
3304
+ }
3305
+ /**
3306
+ * Export current frame as a Blob.
3307
+ * Requires `preserveDrawingBuffer: true` if called while playing.
3308
+ * @example
3309
+ * const blob = await sandbox.renderAt(1.5).exportAsBlob("image/png");
3310
+ */
3311
+ exportAsBlob(e = "image/png", t) {
3312
+ return new Promise((n, i) => {
3313
+ this.canvas.toBlob(
3314
+ (r) => {
3315
+ r ? n(r) : i(new Error("Failed to create blob from canvas."));
3316
+ },
3317
+ e,
3318
+ t
3319
+ );
3320
+ });
3321
+ }
3322
+ /**
3323
+ * Export current frame as an HTMLImageElement.
3324
+ * Requires `preserveDrawingBuffer: true` if called while playing.
3325
+ * @example
3326
+ * const img = sandbox.renderAt(1.5).exportAsImage("image/png");
3327
+ * img.onload = () => document.body.appendChild(img);
3328
+ */
3329
+ exportAsImage(e = "image/png", t) {
3330
+ const n = new Image();
3331
+ return n.src = this.exportAsURL(e, t), n;
3332
+ }
3333
+ /**
3334
+ * Capture the canvas as a MediaStream for video calls or recording.
3335
+ * @example
3336
+ * // WebRTC video call
3337
+ * const stream = sandbox.stream(30);
3338
+ * peerConnection.addTrack(stream.getVideoTracks()[0], stream);
3339
+ *
3340
+ * @example
3341
+ * // Record to video file
3342
+ * const recorder = new MediaRecorder(sandbox.stream(30));
3343
+ */
3344
+ stream(e) {
3345
+ return this.canvasEl.captureStream(e);
3346
+ }
1132
3347
  /**
1133
3348
  * Destroy sandbox and release all resources.
1134
3349
  * @example
@@ -1141,10 +3356,28 @@ You can handle errors programmatically by providing an onError callback to suppr
1141
3356
  }
1142
3357
  }
1143
3358
  export {
1144
- y as Sandbox,
1145
- w as SandboxContextError,
1146
- u as SandboxError,
1147
- m as SandboxProgramError,
1148
- c as SandboxShaderCompilationError,
1149
- k as SandboxShaderVersionMismatchError
3359
+ I as Sandbox,
3360
+ Q as SandboxAttemptedToImportDefaultFunctionError,
3361
+ K as SandboxAttemptedToImportMainFunctionError,
3362
+ ve as SandboxContextCreationError,
3363
+ h as SandboxError,
3364
+ J as SandboxForbiddenModuleNameError,
3365
+ _ as SandboxGLSLShaderCompilationError,
3366
+ ne as SandboxMentionCouldNotBeReplacedError,
3367
+ te as SandboxMentionFunctionNotFoundError,
3368
+ ee as SandboxMentionUniformNotFoundError,
3369
+ Y as SandboxModuleMethodNotFoundError,
3370
+ X as SandboxModuleNotFoundError,
3371
+ re as SandboxOnHookCallbackError,
3372
+ ie as SandboxOnLoadCallbackError,
3373
+ Z as SandboxOverwriteModuleError,
3374
+ S as SandboxProgramError,
3375
+ j as SandboxShaderDuplicateImportNameError,
3376
+ q as SandboxShaderImportSyntaxError,
3377
+ U as SandboxShaderRequirementMismatchError,
3378
+ z as SandboxShaderVersionMismatchError,
3379
+ H as SandboxShaderWithoutFunctionError,
3380
+ ge as SandboxTextureCreationError,
3381
+ xe as SandboxTextureUnitLimitError,
3382
+ W as SandboxWebGLNotSupportedError
1150
3383
  };