@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/README.md +286 -47
- package/dist/errors/base.d.ts +6 -0
- package/dist/errors/context.d.ts +7 -0
- package/dist/errors/index.d.ts +7 -0
- package/dist/errors/module.d.ts +42 -0
- package/dist/errors/program.d.ts +5 -0
- package/dist/errors/shader.d.ts +34 -0
- package/dist/errors/texture.d.ts +12 -0
- package/dist/errors/unknown.d.ts +7 -0
- package/dist/globals.d.ts +17 -0
- package/dist/index.cjs.js +834 -8
- package/dist/index.d.ts +99 -1
- package/dist/index.es.js +2718 -485
- package/dist/tools/compilable.d.ts +79 -0
- package/dist/tools/module.d.ts +46 -0
- package/dist/tools/module_registry.d.ts +63 -0
- package/dist/tools/parser.d.ts +32 -0
- package/dist/tools/program.d.ts +1 -12
- package/dist/tools/shader.d.ts +4 -0
- package/dist/tools/texture.d.ts +64 -0
- package/dist/tools/textures.d.ts +48 -0
- package/dist/tools/uniforms.d.ts +0 -2
- package/dist/tools/web_gl.d.ts +16 -2
- package/dist/types.d.ts +123 -1
- package/package.json +8 -5
- package/dist/errors.d.ts +0 -32
package/dist/index.es.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
class
|
|
5
|
-
constructor(t,
|
|
6
|
-
this.target =
|
|
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,
|
|
20
|
-
return
|
|
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
|
|
24
|
-
constructor(t,
|
|
25
|
-
super(t)
|
|
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
|
|
29
|
-
constructor(
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
)
|
|
38
|
+
"Failed to create WebGL context. The GPU may be unavailable.",
|
|
39
|
+
"CONTEXT_ERROR"
|
|
40
|
+
);
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
|
-
class
|
|
38
|
-
constructor(
|
|
43
|
+
class z extends h {
|
|
44
|
+
constructor(e, t) {
|
|
39
45
|
super(
|
|
40
|
-
`Vertex and fragment shader WebGL versions do not match (${
|
|
41
|
-
"
|
|
42
|
-
), this.vertexVersion =
|
|
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
|
|
46
|
-
constructor(
|
|
47
|
-
const
|
|
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
|
-
`${
|
|
55
|
+
`${t} shader compilation failed${s}
|
|
50
56
|
|
|
51
|
-
${
|
|
52
|
-
"
|
|
57
|
+
${i}`,
|
|
58
|
+
"SHADER_ERROR"
|
|
53
59
|
);
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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 [...
|
|
76
|
+
return [...i].sort((r, s) => r - s);
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
|
-
class
|
|
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
|
-
${
|
|
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
|
|
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
|
-
|
|
220
|
+
o(this, "time", 0);
|
|
89
221
|
/** Delta time since last frame in seconds */
|
|
90
|
-
|
|
222
|
+
o(this, "delta", 0);
|
|
91
223
|
/** Frame counter */
|
|
92
|
-
|
|
224
|
+
o(this, "frame", 0);
|
|
93
225
|
/** Is clock running */
|
|
94
|
-
|
|
226
|
+
o(this, "running", !1);
|
|
95
227
|
/** Smoothed frames per second */
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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(
|
|
239
|
+
start(e) {
|
|
108
240
|
if (this.running) return this;
|
|
109
|
-
this.callback =
|
|
110
|
-
const
|
|
111
|
-
return this.frame === 0 ? this.startTime =
|
|
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(
|
|
143
|
-
return this.delta =
|
|
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(
|
|
149
|
-
return this.time =
|
|
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(
|
|
161
|
-
return this.maxFps =
|
|
292
|
+
setMaxFps(e) {
|
|
293
|
+
return this.maxFps = e, this;
|
|
162
294
|
}
|
|
163
295
|
/**
|
|
164
296
|
* Internal animation frame handler.
|
|
165
297
|
*/
|
|
166
|
-
loop(
|
|
298
|
+
loop(e) {
|
|
167
299
|
if (!this.running) return;
|
|
168
300
|
if (this.maxFps > 0) {
|
|
169
|
-
const
|
|
170
|
-
if (
|
|
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 = (
|
|
176
|
-
const
|
|
177
|
-
this.fps = this.fps * 0.95 +
|
|
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
|
|
181
|
-
constructor(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
this.gl =
|
|
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(
|
|
199
|
-
const
|
|
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
|
-
]),
|
|
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
|
|
363
|
+
return t.setup(n, i), t;
|
|
232
364
|
}
|
|
233
365
|
/**
|
|
234
366
|
* Setup geometry from vertex and index data.
|
|
235
367
|
*/
|
|
236
|
-
setup(
|
|
237
|
-
const
|
|
238
|
-
return this.createVAO(), this.bindVAO(), this.vbo =
|
|
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(
|
|
245
|
-
const
|
|
246
|
-
this.bindVAO(),
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
return
|
|
251
|
-
|
|
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
|
-
|
|
385
|
+
t.FLOAT,
|
|
254
386
|
!1,
|
|
255
|
-
|
|
387
|
+
n,
|
|
256
388
|
2 * Float32Array.BYTES_PER_ELEMENT
|
|
257
|
-
)), this.useIndices &&
|
|
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
|
|
276
|
-
return this.bindVAO(), this.useIndices ?
|
|
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
|
|
283
|
-
this.deleteVAO(), this.vbo && (
|
|
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(
|
|
290
|
-
let
|
|
291
|
-
return
|
|
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(
|
|
298
|
-
let
|
|
299
|
-
return
|
|
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
|
|
318
|
-
constructor(
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
361
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
367
|
-
|
|
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
|
-
*
|
|
2414
|
+
* Clear all uniforms.
|
|
371
2415
|
*/
|
|
372
|
-
|
|
373
|
-
|
|
2416
|
+
clear() {
|
|
2417
|
+
this.uniforms.clear();
|
|
374
2418
|
}
|
|
375
2419
|
/**
|
|
376
|
-
* Cleanup
|
|
2420
|
+
* Cleanup.
|
|
377
2421
|
*/
|
|
378
2422
|
destroy() {
|
|
379
|
-
|
|
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
|
-
*
|
|
384
|
-
* @throws ShaderCompilationError if compilation fails
|
|
2426
|
+
* Get all uniform names.
|
|
385
2427
|
*/
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
*
|
|
402
|
-
* @throws ProgramLinkError if linking fails
|
|
2432
|
+
* Get uniform count.
|
|
403
2433
|
*/
|
|
404
|
-
|
|
405
|
-
|
|
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,
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
this
|
|
428
|
-
|
|
429
|
-
this
|
|
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
|
-
*
|
|
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
|
-
|
|
435
|
-
|
|
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
|
|
2462
|
+
* Returns null if the sampler uniform doesn't exist in the shader.
|
|
471
2463
|
*/
|
|
472
|
-
resolveLocation(
|
|
473
|
-
return this.locationResolved || (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
|
-
*
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
*
|
|
2489
|
+
* Cleanup WebGL texture resource.
|
|
489
2490
|
*/
|
|
490
|
-
|
|
491
|
-
|
|
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
|
|
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
|
-
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
-
*
|
|
546
|
-
* Invalidates all cached locations since they're program-specific.
|
|
2509
|
+
* Apply texture wrap and filter parameters.
|
|
547
2510
|
*/
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
e.
|
|
552
|
-
|
|
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
|
-
*
|
|
556
|
-
* Creates the uniform if it doesn't exist, updates if it does.
|
|
2532
|
+
* Resolve wrap mode string to WebGL constant.
|
|
557
2533
|
*/
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
-
*
|
|
2546
|
+
* Resolve texture options with defaults.
|
|
564
2547
|
*/
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
-
*
|
|
2561
|
+
* Check if a source is dynamic (needs re-upload every frame).
|
|
572
2562
|
*/
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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
|
-
*
|
|
2575
|
+
* Attach to a WebGL program.
|
|
2576
|
+
* Invalidates all cached locations since they're program-specific.
|
|
579
2577
|
*/
|
|
580
|
-
|
|
581
|
-
|
|
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
|
-
*
|
|
2585
|
+
* Set a texture.
|
|
2586
|
+
* Creates the texture entry if it doesn't exist, updates source if it does.
|
|
585
2587
|
*/
|
|
586
|
-
|
|
587
|
-
|
|
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
|
-
*
|
|
591
|
-
* Requires a program to be attached.
|
|
2593
|
+
* Get a texture by name.
|
|
592
2594
|
*/
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
*
|
|
602
|
-
* Call this every frame with current values.
|
|
2599
|
+
* Check if a texture exists.
|
|
603
2600
|
*/
|
|
604
|
-
|
|
605
|
-
|
|
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
|
-
*
|
|
2605
|
+
* Remove a texture and free its GPU resources.
|
|
615
2606
|
*/
|
|
616
|
-
|
|
617
|
-
this.
|
|
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
|
-
*
|
|
2612
|
+
* Upload all textures to their respective texture units.
|
|
2613
|
+
* Units are assigned sequentially (0, 1, 2, ...).
|
|
621
2614
|
*/
|
|
622
|
-
|
|
623
|
-
|
|
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
|
|
2624
|
+
* Get texture count.
|
|
627
2625
|
*/
|
|
628
|
-
|
|
629
|
-
return this.
|
|
2626
|
+
get size() {
|
|
2627
|
+
return this.textures.size;
|
|
630
2628
|
}
|
|
631
2629
|
/**
|
|
632
|
-
*
|
|
2630
|
+
* Cleanup all textures.
|
|
633
2631
|
*/
|
|
634
|
-
|
|
635
|
-
|
|
2632
|
+
destroy() {
|
|
2633
|
+
for (const e of this.textures.values())
|
|
2634
|
+
e.destroy();
|
|
2635
|
+
this.textures.clear(), this.program = null;
|
|
636
2636
|
}
|
|
637
|
-
}
|
|
638
|
-
|
|
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
|
-
|
|
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(
|
|
656
|
-
const
|
|
657
|
-
return this.hooks.set(
|
|
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(
|
|
661
|
-
this.hooks.delete(
|
|
2651
|
+
remove(e) {
|
|
2652
|
+
this.hooks.delete(e);
|
|
662
2653
|
}
|
|
663
2654
|
/** Run all hooks with the given state */
|
|
664
|
-
run(
|
|
665
|
-
for (const [
|
|
666
|
-
|
|
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(
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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(
|
|
693
|
-
const
|
|
694
|
-
return
|
|
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
|
|
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
|
-
},
|
|
709
|
-
if (
|
|
710
|
-
return this._version = 2,
|
|
711
|
-
const
|
|
712
|
-
if (
|
|
713
|
-
return this._version = 1,
|
|
714
|
-
const
|
|
715
|
-
throw this.options.onError(
|
|
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,
|
|
727
|
-
return this.canvas.width =
|
|
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(
|
|
733
|
-
return this._clock.setTime(
|
|
2731
|
+
clock(e) {
|
|
2732
|
+
return this._clock.setTime(e), this;
|
|
734
2733
|
}
|
|
735
2734
|
/**
|
|
736
2735
|
* Update mouse position.
|
|
737
2736
|
*/
|
|
738
|
-
mouse(
|
|
739
|
-
return this._mouse = [
|
|
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(
|
|
745
|
-
return this._uniforms.set(
|
|
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(
|
|
751
|
-
return this._uniforms.setMany(
|
|
2749
|
+
uniforms(e) {
|
|
2750
|
+
return this._uniforms.setMany(e), this;
|
|
752
2751
|
}
|
|
753
2752
|
/**
|
|
754
2753
|
* Get current uniform value.
|
|
755
2754
|
*/
|
|
756
|
-
getUniform(
|
|
757
|
-
return this._uniforms.get(
|
|
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(
|
|
2787
|
+
shader(e, t) {
|
|
764
2788
|
try {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
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
|
|
785
|
-
|
|
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(
|
|
821
|
-
const
|
|
822
|
-
|
|
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
|
-
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
}`,
|
|
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
|
|
878
|
-
constructor(
|
|
2941
|
+
class I {
|
|
2942
|
+
constructor(e, t) {
|
|
879
2943
|
/** Active event listeners */
|
|
880
|
-
|
|
2944
|
+
o(this, "listeners", []);
|
|
881
2945
|
/** HTML canvas element */
|
|
882
|
-
|
|
2946
|
+
o(this, "canvasEl");
|
|
883
2947
|
/** Resolved options */
|
|
884
|
-
|
|
2948
|
+
o(this, "options");
|
|
885
2949
|
/** WebGL engine */
|
|
886
|
-
|
|
2950
|
+
o(this, "engine");
|
|
887
2951
|
/** User sets custom vertex shader */
|
|
888
|
-
|
|
889
|
-
this.canvasEl =
|
|
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(
|
|
910
|
-
return new
|
|
2976
|
+
static create(e, t) {
|
|
2977
|
+
return new I(e, t);
|
|
911
2978
|
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
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: (
|
|
3015
|
+
onError: (s) => {
|
|
923
3016
|
console.error(
|
|
924
3017
|
"Oops!",
|
|
925
|
-
|
|
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 (
|
|
937
|
-
|
|
938
|
-
|
|
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 (
|
|
941
|
-
|
|
942
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3048
|
+
b.on(window, "resize", () => {
|
|
950
3049
|
this.setViewport();
|
|
951
3050
|
}),
|
|
952
3051
|
// Canvas resize
|
|
953
|
-
|
|
3052
|
+
b.on(this.canvasEl, "resize", () => {
|
|
954
3053
|
this.setViewport();
|
|
955
3054
|
}),
|
|
956
3055
|
// Visibility check on scroll
|
|
957
3056
|
(() => {
|
|
958
|
-
let
|
|
959
|
-
return
|
|
960
|
-
this.options.pauseWhenHidden && (this.isInViewport() ?
|
|
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
|
-
|
|
965
|
-
this.setMouse(
|
|
3063
|
+
b.on(document, "mousemove", (e) => {
|
|
3064
|
+
this.setMouse(e.clientX || e.pageX, e.clientY || e.pageY);
|
|
966
3065
|
}),
|
|
967
3066
|
// Touch tracking
|
|
968
|
-
|
|
969
|
-
|
|
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((
|
|
3073
|
+
this.listeners.forEach((e) => e()), this.listeners = [];
|
|
975
3074
|
}
|
|
976
3075
|
setViewport() {
|
|
977
|
-
const
|
|
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(
|
|
982
|
-
Math.max(1, Math.floor(
|
|
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
|
|
987
|
-
return
|
|
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(
|
|
990
|
-
const
|
|
991
|
-
|
|
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(
|
|
1001
|
-
return this.engine.uniform(
|
|
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(
|
|
1018
|
-
return this.engine.uniforms(
|
|
3116
|
+
setUniforms(e) {
|
|
3117
|
+
return this.engine.uniforms(e), this;
|
|
1019
3118
|
}
|
|
1020
3119
|
/**
|
|
1021
3120
|
* Get current uniform value.
|
|
1022
3121
|
*/
|
|
1023
|
-
getUniform(
|
|
1024
|
-
return this.engine.getUniform(
|
|
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(
|
|
1032
|
-
return this.options.vertex =
|
|
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(
|
|
1040
|
-
const e =
|
|
1041
|
-
return this.options.fragment = t,
|
|
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(
|
|
1051
|
-
return this.engine.getClock().setMaxFps(
|
|
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(
|
|
1057
|
-
return
|
|
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(
|
|
1069
|
-
return this.engine.clock(
|
|
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(
|
|
1081
|
-
const
|
|
1082
|
-
|
|
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(
|
|
1096
|
-
return this.engine.clock(
|
|
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(
|
|
1112
|
-
return this.engine.clock(
|
|
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
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
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
|
};
|