@rosalana/sandbox 0.0.1
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/LICENCE +21 -0
- package/README.md +361 -0
- package/dist/errors.d.ts +32 -0
- package/dist/index.cjs.js +57 -0
- package/dist/index.d.ts +156 -0
- package/dist/index.es.js +1107 -0
- package/dist/tools/clock.d.ts +54 -0
- package/dist/tools/geometry.d.ts +62 -0
- package/dist/tools/hooks.d.ts +12 -0
- package/dist/tools/listener.d.ts +11 -0
- package/dist/tools/program.d.ts +58 -0
- package/dist/tools/uniform.d.ts +42 -0
- package/dist/tools/uniforms.d.ts +65 -0
- package/dist/tools/web_gl.d.ts +93 -0
- package/dist/types.d.ts +111 -0
- package/package.json +54 -0
package/dist/index.es.js
ADDED
|
@@ -0,0 +1,1107 @@
|
|
|
1
|
+
var S = Object.defineProperty;
|
|
2
|
+
var L = (o, t, e) => t in o ? S(o, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : o[t] = e;
|
|
3
|
+
var i = (o, t, e) => L(o, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
+
class l {
|
|
5
|
+
constructor(t, e, r, s) {
|
|
6
|
+
this.target = t, this.type = e, this.listener = r, this.options = s, this.target.addEventListener(
|
|
7
|
+
this.type,
|
|
8
|
+
this.listener,
|
|
9
|
+
this.options
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
remove() {
|
|
13
|
+
this.target.removeEventListener(
|
|
14
|
+
this.type,
|
|
15
|
+
this.listener,
|
|
16
|
+
this.options
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
static on(t, e, r, s) {
|
|
20
|
+
return t.addEventListener(e, r, s), () => t.removeEventListener(e, r, s);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
class u extends Error {
|
|
24
|
+
constructor(t, e) {
|
|
25
|
+
super(t), this.code = e, this.name = "SandboxError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
class w extends u {
|
|
29
|
+
constructor(t) {
|
|
30
|
+
const e = t === "not_supported" ? "WebGL is not supported in this browser." : "Failed to create WebGL context. The GPU may be unavailable.";
|
|
31
|
+
super(
|
|
32
|
+
e,
|
|
33
|
+
t === "not_supported" ? "WEBGL_NOT_SUPPORTED" : "CONTEXT_CREATION_FAILED"
|
|
34
|
+
), this.name = "SandboxContextError";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
class k extends u {
|
|
38
|
+
constructor(t, e) {
|
|
39
|
+
super(
|
|
40
|
+
`Vertex and fragment shader WebGL versions do not match (${t} vs ${e})`,
|
|
41
|
+
"SHADER_VERSION_MISMATCH"
|
|
42
|
+
), this.vertexVersion = t, this.fragmentVersion = e, this.name = "SandboxShaderVersionMismatchError";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
class f extends u {
|
|
46
|
+
constructor(e, r, s) {
|
|
47
|
+
const n = f.parseErrorLines(s), a = n.length > 0 ? ` at line(s): ${n.join(", ")}` : "";
|
|
48
|
+
super(
|
|
49
|
+
`${e} shader compilation failed${a}
|
|
50
|
+
|
|
51
|
+
${s}`,
|
|
52
|
+
"SHADER_COMPILATION_FAILED"
|
|
53
|
+
);
|
|
54
|
+
/** Line numbers where errors occurred */
|
|
55
|
+
i(this, "lines");
|
|
56
|
+
this.shaderType = e, this.source = r, this.infoLog = s, this.name = "SandboxShaderCompilationError", this.lines = n;
|
|
57
|
+
}
|
|
58
|
+
/** Parse error log to extract line numbers */
|
|
59
|
+
static parseErrorLines(e) {
|
|
60
|
+
const r = [
|
|
61
|
+
/ERROR:\s*\d*:(\d+)/g,
|
|
62
|
+
// Chrome/ANGLE: ERROR: 0:15
|
|
63
|
+
/(\d+):(\d+)\(\d+\):/g,
|
|
64
|
+
// Mesa: 0:15(0):
|
|
65
|
+
/^(\d+):/gm
|
|
66
|
+
// Simple: 15:
|
|
67
|
+
], s = /* @__PURE__ */ new Set();
|
|
68
|
+
for (const n of r) {
|
|
69
|
+
let a;
|
|
70
|
+
for (; (a = n.exec(e)) !== null; ) {
|
|
71
|
+
const c = parseInt(a[1], 10);
|
|
72
|
+
c > 0 && s.add(c);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return [...s].sort((n, a) => n - a);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
class m extends u {
|
|
79
|
+
constructor(t) {
|
|
80
|
+
super(`Shader program linking failed
|
|
81
|
+
|
|
82
|
+
${t}`, "PROGRAM_LINK_FAILED"), this.infoLog = t, this.name = "SandboxProgramError";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
class R {
|
|
86
|
+
constructor() {
|
|
87
|
+
/** Total elapsed time in seconds */
|
|
88
|
+
i(this, "time", 0);
|
|
89
|
+
/** Delta time since last frame in seconds */
|
|
90
|
+
i(this, "delta", 0);
|
|
91
|
+
/** Frame counter */
|
|
92
|
+
i(this, "frame", 0);
|
|
93
|
+
/** Is clock running */
|
|
94
|
+
i(this, "running", !1);
|
|
95
|
+
i(this, "startTime", 0);
|
|
96
|
+
i(this, "lastTime", 0);
|
|
97
|
+
i(this, "rafId", null);
|
|
98
|
+
i(this, "callback", null);
|
|
99
|
+
this.loop = this.loop.bind(this);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Start the animation loop with a render callback.
|
|
103
|
+
*/
|
|
104
|
+
start(t) {
|
|
105
|
+
if (this.running) return this;
|
|
106
|
+
this.callback = t, this.running = !0;
|
|
107
|
+
const e = performance.now();
|
|
108
|
+
return this.startTime = e, this.lastTime = e, this.rafId = requestAnimationFrame(this.loop), this;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Stop the animation loop.
|
|
112
|
+
* Clock state is preserved for resume.
|
|
113
|
+
*/
|
|
114
|
+
stop() {
|
|
115
|
+
return this.running ? (this.running = !1, this.rafId !== null && (cancelAnimationFrame(this.rafId), this.rafId = null), this) : this;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Reset clock to initial state.
|
|
119
|
+
*/
|
|
120
|
+
reset() {
|
|
121
|
+
return this.stop(), this.time = 0, this.delta = 0, this.frame = 0, this;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get current clock state snapshot.
|
|
125
|
+
*/
|
|
126
|
+
getState() {
|
|
127
|
+
return {
|
|
128
|
+
time: this.time,
|
|
129
|
+
delta: this.delta,
|
|
130
|
+
frame: this.frame
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Advance clock by one tick (for single-shot rendering).
|
|
135
|
+
* Useful when autoplay is disabled.
|
|
136
|
+
*/
|
|
137
|
+
tick(t = 0) {
|
|
138
|
+
return this.delta = t, this.time += t, this.frame++, this.callback && this.callback(this.getState()), this;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Set time directly (for deterministic rendering).
|
|
142
|
+
*/
|
|
143
|
+
setTime(t) {
|
|
144
|
+
return this.time = t, this;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Cleanup.
|
|
148
|
+
*/
|
|
149
|
+
destroy() {
|
|
150
|
+
this.stop(), this.callback = null;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Internal animation frame handler.
|
|
154
|
+
*/
|
|
155
|
+
loop(t) {
|
|
156
|
+
this.running && (this.delta = (t - this.lastTime) / 1e3, this.lastTime = t, this.time = (t - this.startTime) / 1e3, this.frame++, this.callback && this.callback(this.getState()), this.rafId = requestAnimationFrame(this.loop));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
class p {
|
|
160
|
+
constructor(t) {
|
|
161
|
+
i(this, "gl");
|
|
162
|
+
i(this, "vao", null);
|
|
163
|
+
i(this, "vbo", null);
|
|
164
|
+
i(this, "ibo", null);
|
|
165
|
+
i(this, "vertexCount", 0);
|
|
166
|
+
i(this, "indexCount", 0);
|
|
167
|
+
i(this, "useIndices", !1);
|
|
168
|
+
// WebGL1 VAO extension (if available)
|
|
169
|
+
i(this, "vaoExt", null);
|
|
170
|
+
i(this, "isWebGL2");
|
|
171
|
+
this.gl = t, this.isWebGL2 = t instanceof WebGL2RenderingContext, this.isWebGL2 || (this.vaoExt = t.getExtension("OES_vertex_array_object"));
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Create a fullscreen quad geometry.
|
|
175
|
+
* This is the most common use case for shader effects.
|
|
176
|
+
*/
|
|
177
|
+
static fullscreenQuad(t) {
|
|
178
|
+
const e = new p(t), r = new Float32Array([
|
|
179
|
+
// position texcoord
|
|
180
|
+
-1,
|
|
181
|
+
-1,
|
|
182
|
+
0,
|
|
183
|
+
0,
|
|
184
|
+
// bottom-left
|
|
185
|
+
1,
|
|
186
|
+
-1,
|
|
187
|
+
1,
|
|
188
|
+
0,
|
|
189
|
+
// bottom-right
|
|
190
|
+
-1,
|
|
191
|
+
1,
|
|
192
|
+
0,
|
|
193
|
+
1,
|
|
194
|
+
// top-left
|
|
195
|
+
1,
|
|
196
|
+
1,
|
|
197
|
+
1,
|
|
198
|
+
1
|
|
199
|
+
// top-right
|
|
200
|
+
]), s = new Uint16Array([
|
|
201
|
+
0,
|
|
202
|
+
1,
|
|
203
|
+
2,
|
|
204
|
+
// first triangle
|
|
205
|
+
2,
|
|
206
|
+
1,
|
|
207
|
+
3
|
|
208
|
+
// second triangle
|
|
209
|
+
]);
|
|
210
|
+
return e.setup(r, s), e;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Setup geometry from vertex and index data.
|
|
214
|
+
*/
|
|
215
|
+
setup(t, e) {
|
|
216
|
+
const r = this.gl;
|
|
217
|
+
return this.createVAO(), this.bindVAO(), this.vbo = r.createBuffer(), r.bindBuffer(r.ARRAY_BUFFER, this.vbo), r.bufferData(r.ARRAY_BUFFER, t, r.STATIC_DRAW), this.vertexCount = t.length / 4, e && (this.ibo = r.createBuffer(), r.bindBuffer(r.ELEMENT_ARRAY_BUFFER, this.ibo), r.bufferData(r.ELEMENT_ARRAY_BUFFER, e, r.STATIC_DRAW), this.indexCount = e.length, this.useIndices = !0), this.unbindVAO(), this;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Link vertex attributes to shader program.
|
|
221
|
+
* Call this after compiling shaders.
|
|
222
|
+
*/
|
|
223
|
+
linkAttributes(t) {
|
|
224
|
+
const e = this.gl;
|
|
225
|
+
this.bindVAO(), e.bindBuffer(e.ARRAY_BUFFER, this.vbo);
|
|
226
|
+
const r = 4 * Float32Array.BYTES_PER_ELEMENT, s = this.getPositionLocation(t);
|
|
227
|
+
s >= 0 && (e.enableVertexAttribArray(s), e.vertexAttribPointer(s, 2, e.FLOAT, !1, r, 0));
|
|
228
|
+
const n = this.getTexcoordLocation(t);
|
|
229
|
+
return n >= 0 && (e.enableVertexAttribArray(n), e.vertexAttribPointer(
|
|
230
|
+
n,
|
|
231
|
+
2,
|
|
232
|
+
e.FLOAT,
|
|
233
|
+
!1,
|
|
234
|
+
r,
|
|
235
|
+
2 * Float32Array.BYTES_PER_ELEMENT
|
|
236
|
+
)), this.useIndices && e.bindBuffer(e.ELEMENT_ARRAY_BUFFER, this.ibo), this.unbindVAO(), this;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Bind geometry for rendering.
|
|
240
|
+
*/
|
|
241
|
+
bind() {
|
|
242
|
+
return this.bindVAO(), this;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Unbind geometry.
|
|
246
|
+
*/
|
|
247
|
+
unbind() {
|
|
248
|
+
return this.unbindVAO(), this;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Draw the geometry.
|
|
252
|
+
*/
|
|
253
|
+
draw() {
|
|
254
|
+
const t = this.gl;
|
|
255
|
+
return this.bindVAO(), this.useIndices ? t.drawElements(t.TRIANGLES, this.indexCount, t.UNSIGNED_SHORT, 0) : t.drawArrays(t.TRIANGLE_STRIP, 0, this.vertexCount), this;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Cleanup all GPU resources.
|
|
259
|
+
*/
|
|
260
|
+
destroy() {
|
|
261
|
+
const t = this.gl;
|
|
262
|
+
this.deleteVAO(), this.vbo && (t.deleteBuffer(this.vbo), this.vbo = null), this.ibo && (t.deleteBuffer(this.ibo), this.ibo = null);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get position attribute location.
|
|
266
|
+
* Tries common naming conventions.
|
|
267
|
+
*/
|
|
268
|
+
getPositionLocation(t) {
|
|
269
|
+
let e = t.getAttribLocation("a_position");
|
|
270
|
+
return e >= 0 || (e = t.getAttribLocation("aPosition"), e >= 0) || (e = t.getAttribLocation("position"), e >= 0) ? e : -1;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Get texcoord attribute location.
|
|
274
|
+
* Tries common naming conventions.
|
|
275
|
+
*/
|
|
276
|
+
getTexcoordLocation(t) {
|
|
277
|
+
let e = t.getAttribLocation("a_texcoord");
|
|
278
|
+
return e >= 0 || (e = t.getAttribLocation("aTexCoord"), e >= 0) || (e = t.getAttribLocation("texcoord"), e >= 0) || (e = t.getAttribLocation("a_uv"), e >= 0) ? e : -1;
|
|
279
|
+
}
|
|
280
|
+
// ============================================================================
|
|
281
|
+
// VAO helpers (handle WebGL1 vs WebGL2 differences)
|
|
282
|
+
// ============================================================================
|
|
283
|
+
createVAO() {
|
|
284
|
+
this.isWebGL2 ? this.vao = this.gl.createVertexArray() : this.vaoExt && (this.vao = this.vaoExt.createVertexArrayOES());
|
|
285
|
+
}
|
|
286
|
+
bindVAO() {
|
|
287
|
+
this.vao && (this.isWebGL2 ? this.gl.bindVertexArray(this.vao) : this.vaoExt && this.vaoExt.bindVertexArrayOES(this.vao));
|
|
288
|
+
}
|
|
289
|
+
unbindVAO() {
|
|
290
|
+
this.isWebGL2 ? this.gl.bindVertexArray(null) : this.vaoExt && this.vaoExt.bindVertexArrayOES(null);
|
|
291
|
+
}
|
|
292
|
+
deleteVAO() {
|
|
293
|
+
this.vao && (this.isWebGL2 ? this.gl.deleteVertexArray(this.vao) : this.vaoExt && this.vaoExt.deleteVertexArrayOES(this.vao), this.vao = null);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
class h {
|
|
297
|
+
constructor(t) {
|
|
298
|
+
i(this, "gl");
|
|
299
|
+
i(this, "program", null);
|
|
300
|
+
i(this, "vertexShader", null);
|
|
301
|
+
i(this, "fragmentShader", null);
|
|
302
|
+
i(this, "version", 1);
|
|
303
|
+
this.gl = t;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Detect WebGL version from shader source.
|
|
307
|
+
* Looks for "#version 300 es" directive.
|
|
308
|
+
*/
|
|
309
|
+
static detectVersion(t) {
|
|
310
|
+
return /^\s*#version\s+300\s+es/m.test(t) ? 2 : 1;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Compile shaders and link program.
|
|
314
|
+
* @throws ShaderCompilationError if compilation fails
|
|
315
|
+
* @throws ProgramLinkError if linking fails
|
|
316
|
+
*/
|
|
317
|
+
compile(t, e) {
|
|
318
|
+
this.destroy();
|
|
319
|
+
const r = h.detectVersion(t), s = h.detectVersion(e);
|
|
320
|
+
if (r != s)
|
|
321
|
+
throw new k(r, s);
|
|
322
|
+
return this.version = Math.max(r, s), this.vertexShader = this.compileShader("vertex", t), this.fragmentShader = this.compileShader("fragment", e), this.linkProgram(), this;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Bind this program for rendering.
|
|
326
|
+
*/
|
|
327
|
+
use() {
|
|
328
|
+
return this.program && this.gl.useProgram(this.program), this;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Get the compiled WebGL program.
|
|
332
|
+
*/
|
|
333
|
+
getProgram() {
|
|
334
|
+
return this.program;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Get detected WebGL version.
|
|
338
|
+
*/
|
|
339
|
+
getVersion() {
|
|
340
|
+
return this.version;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Get attribute location.
|
|
344
|
+
*/
|
|
345
|
+
getAttribLocation(t) {
|
|
346
|
+
return this.program ? this.gl.getAttribLocation(this.program, t) : -1;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Get uniform location.
|
|
350
|
+
*/
|
|
351
|
+
getUniformLocation(t) {
|
|
352
|
+
return this.program ? this.gl.getUniformLocation(this.program, t) : null;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Cleanup all GPU resources.
|
|
356
|
+
*/
|
|
357
|
+
destroy() {
|
|
358
|
+
const t = this.gl;
|
|
359
|
+
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);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Compile a single shader.
|
|
363
|
+
* @throws ShaderCompilationError if compilation fails
|
|
364
|
+
*/
|
|
365
|
+
compileShader(t, e) {
|
|
366
|
+
const r = this.gl, s = t === "vertex" ? r.VERTEX_SHADER : r.FRAGMENT_SHADER, n = r.createShader(s);
|
|
367
|
+
if (!n)
|
|
368
|
+
throw new f(
|
|
369
|
+
t,
|
|
370
|
+
e,
|
|
371
|
+
"Failed to create shader object"
|
|
372
|
+
);
|
|
373
|
+
if (r.shaderSource(n, e), r.compileShader(n), !r.getShaderParameter(n, r.COMPILE_STATUS)) {
|
|
374
|
+
const c = r.getShaderInfoLog(n) || "Unknown error";
|
|
375
|
+
throw r.deleteShader(n), new f(t, e, c);
|
|
376
|
+
}
|
|
377
|
+
return n;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Link vertex and fragment shaders into a program.
|
|
381
|
+
* @throws ProgramLinkError if linking fails
|
|
382
|
+
*/
|
|
383
|
+
linkProgram() {
|
|
384
|
+
const t = this.gl;
|
|
385
|
+
if (!this.vertexShader || !this.fragmentShader)
|
|
386
|
+
throw new m("Shaders not compiled");
|
|
387
|
+
const e = t.createProgram();
|
|
388
|
+
if (!e)
|
|
389
|
+
throw new m("Failed to create program object");
|
|
390
|
+
if (t.attachShader(e, this.vertexShader), t.attachShader(e, this.fragmentShader), t.linkProgram(e), !t.getProgramParameter(e, t.LINK_STATUS)) {
|
|
391
|
+
const s = t.getProgramInfoLog(e) || "Unknown error";
|
|
392
|
+
throw t.deleteProgram(e), new m(s);
|
|
393
|
+
}
|
|
394
|
+
this.program = e;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
class x {
|
|
398
|
+
constructor(t, e) {
|
|
399
|
+
i(this, "name");
|
|
400
|
+
i(this, "method");
|
|
401
|
+
i(this, "isArray");
|
|
402
|
+
i(this, "isMatrix");
|
|
403
|
+
i(this, "location", null);
|
|
404
|
+
i(this, "locationResolved", !1);
|
|
405
|
+
i(this, "value");
|
|
406
|
+
this.name = t, this.value = e;
|
|
407
|
+
const r = x.inferMethodInfo(e);
|
|
408
|
+
this.method = r.method, this.isArray = r.isArray, this.isMatrix = r.isMatrix;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Infer WebGL method and metadata from value type.
|
|
412
|
+
*/
|
|
413
|
+
static inferMethodInfo(t) {
|
|
414
|
+
if (typeof t == "boolean")
|
|
415
|
+
return { method: "uniform1i", isArray: !1, isMatrix: !1 };
|
|
416
|
+
if (typeof t == "number")
|
|
417
|
+
return { method: "uniform1f", isArray: !1, isMatrix: !1 };
|
|
418
|
+
if (!Array.isArray(t))
|
|
419
|
+
return { method: "uniform1f", isArray: !1, isMatrix: !1 };
|
|
420
|
+
const e = t.length, r = t[0];
|
|
421
|
+
if (Array.isArray(r))
|
|
422
|
+
switch (r.length) {
|
|
423
|
+
case 2:
|
|
424
|
+
return { method: "uniform2fv", isArray: !0, isMatrix: !1 };
|
|
425
|
+
case 3:
|
|
426
|
+
return { method: "uniform3fv", isArray: !0, isMatrix: !1 };
|
|
427
|
+
case 4:
|
|
428
|
+
return { method: "uniform4fv", isArray: !0, isMatrix: !1 };
|
|
429
|
+
default:
|
|
430
|
+
return { method: "uniform1fv", isArray: !0, isMatrix: !1 };
|
|
431
|
+
}
|
|
432
|
+
switch (e) {
|
|
433
|
+
case 2:
|
|
434
|
+
return { method: "uniform2fv", isArray: !1, isMatrix: !1 };
|
|
435
|
+
case 3:
|
|
436
|
+
return { method: "uniform3fv", isArray: !1, isMatrix: !1 };
|
|
437
|
+
case 4:
|
|
438
|
+
return { method: "uniform4fv", isArray: !1, isMatrix: !1 };
|
|
439
|
+
case 9:
|
|
440
|
+
return { method: "uniformMatrix3fv", isArray: !1, isMatrix: !0 };
|
|
441
|
+
case 16:
|
|
442
|
+
return { method: "uniformMatrix4fv", isArray: !1, isMatrix: !0 };
|
|
443
|
+
default:
|
|
444
|
+
return { method: "uniform1fv", isArray: !0, isMatrix: !1 };
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Resolve and cache uniform location from program.
|
|
449
|
+
* Returns null if uniform doesn't exist (optimized out by compiler, etc.)
|
|
450
|
+
*/
|
|
451
|
+
resolveLocation(t, e) {
|
|
452
|
+
return this.locationResolved || (this.location = t.getUniformLocation(e, this.name), this.locationResolved = !0), this.location;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Invalidate cached location (call when program changes).
|
|
456
|
+
*/
|
|
457
|
+
invalidateLocation() {
|
|
458
|
+
this.location = null, this.locationResolved = !1;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Update value (doesn't upload to GPU until upload() is called).
|
|
462
|
+
*/
|
|
463
|
+
setValue(t) {
|
|
464
|
+
this.value = t;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Get current value.
|
|
468
|
+
*/
|
|
469
|
+
getValue() {
|
|
470
|
+
return this.value;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Upload current value to GPU.
|
|
474
|
+
* @param gl - WebGL context
|
|
475
|
+
* @param program - Current WebGL program (for location resolution)
|
|
476
|
+
*/
|
|
477
|
+
upload(t, e) {
|
|
478
|
+
const r = this.resolveLocation(t, e);
|
|
479
|
+
if (r === null)
|
|
480
|
+
return;
|
|
481
|
+
const s = this.value;
|
|
482
|
+
let n;
|
|
483
|
+
switch (typeof s == "boolean" ? n = s ? 1 : 0 : typeof s == "number" ? n = s : this.isArray && Array.isArray(s[0]) ? n = new Float32Array(
|
|
484
|
+
s.flat()
|
|
485
|
+
) : n = new Float32Array(s), this.method) {
|
|
486
|
+
case "uniform1f":
|
|
487
|
+
t.uniform1f(r, n);
|
|
488
|
+
break;
|
|
489
|
+
case "uniform1i":
|
|
490
|
+
t.uniform1i(r, n);
|
|
491
|
+
break;
|
|
492
|
+
case "uniform1fv":
|
|
493
|
+
t.uniform1fv(r, n);
|
|
494
|
+
break;
|
|
495
|
+
case "uniform2fv":
|
|
496
|
+
t.uniform2fv(r, n);
|
|
497
|
+
break;
|
|
498
|
+
case "uniform3fv":
|
|
499
|
+
t.uniform3fv(r, n);
|
|
500
|
+
break;
|
|
501
|
+
case "uniform4fv":
|
|
502
|
+
t.uniform4fv(r, n);
|
|
503
|
+
break;
|
|
504
|
+
case "uniformMatrix2fv":
|
|
505
|
+
t.uniformMatrix2fv(r, !1, n);
|
|
506
|
+
break;
|
|
507
|
+
case "uniformMatrix3fv":
|
|
508
|
+
t.uniformMatrix3fv(r, !1, n);
|
|
509
|
+
break;
|
|
510
|
+
case "uniformMatrix4fv":
|
|
511
|
+
t.uniformMatrix4fv(r, !1, n);
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const d = class d {
|
|
517
|
+
constructor(t) {
|
|
518
|
+
i(this, "gl");
|
|
519
|
+
i(this, "program", null);
|
|
520
|
+
i(this, "uniforms", /* @__PURE__ */ new Map());
|
|
521
|
+
this.gl = t;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Attach to a WebGL program.
|
|
525
|
+
* Invalidates all cached locations since they're program-specific.
|
|
526
|
+
*/
|
|
527
|
+
attachProgram(t) {
|
|
528
|
+
this.program = t;
|
|
529
|
+
for (const e of this.uniforms.values())
|
|
530
|
+
e.invalidateLocation();
|
|
531
|
+
return this;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Set a uniform value.
|
|
535
|
+
* Creates the uniform if it doesn't exist, updates if it does.
|
|
536
|
+
*/
|
|
537
|
+
set(t, e) {
|
|
538
|
+
const r = this.uniforms.get(t);
|
|
539
|
+
return r ? r.setValue(e) : this.uniforms.set(t, new x(t, e)), this;
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Set multiple uniforms at once.
|
|
543
|
+
*/
|
|
544
|
+
setMany(t) {
|
|
545
|
+
for (const [e, r] of Object.entries(t))
|
|
546
|
+
this.set(e, r);
|
|
547
|
+
return this;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Get current uniform value.
|
|
551
|
+
*/
|
|
552
|
+
get(t) {
|
|
553
|
+
var e;
|
|
554
|
+
return (e = this.uniforms.get(t)) == null ? void 0 : e.getValue();
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Check if uniform exists.
|
|
558
|
+
*/
|
|
559
|
+
has(t) {
|
|
560
|
+
return this.uniforms.has(t);
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Remove a uniform.
|
|
564
|
+
*/
|
|
565
|
+
delete(t) {
|
|
566
|
+
return this.uniforms.delete(t);
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Upload all uniforms to GPU.
|
|
570
|
+
* Requires a program to be attached.
|
|
571
|
+
*/
|
|
572
|
+
uploadAll() {
|
|
573
|
+
if (!this.program)
|
|
574
|
+
return this;
|
|
575
|
+
for (const t of this.uniforms.values())
|
|
576
|
+
t.upload(this.gl, this.program);
|
|
577
|
+
return this;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Upload only built-in uniforms (u_resolution, u_time, u_delta, u_mouse, u_frame).
|
|
581
|
+
* Call this every frame with current values.
|
|
582
|
+
*/
|
|
583
|
+
uploadBuiltIns(t, e, r) {
|
|
584
|
+
if (this.set("u_resolution", e), this.set("u_time", t.time), this.set("u_delta", t.delta), this.set("u_mouse", r), this.set("u_frame", t.frame), !this.program)
|
|
585
|
+
return this;
|
|
586
|
+
for (const s of d.BUILT_INS) {
|
|
587
|
+
const n = this.uniforms.get(s);
|
|
588
|
+
n && n.upload(this.gl, this.program);
|
|
589
|
+
}
|
|
590
|
+
return this;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Clear all uniforms.
|
|
594
|
+
*/
|
|
595
|
+
clear() {
|
|
596
|
+
this.uniforms.clear();
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Cleanup.
|
|
600
|
+
*/
|
|
601
|
+
destroy() {
|
|
602
|
+
this.uniforms.clear(), this.program = null;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Get all uniform names.
|
|
606
|
+
*/
|
|
607
|
+
keys() {
|
|
608
|
+
return this.uniforms.keys();
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Get uniform count.
|
|
612
|
+
*/
|
|
613
|
+
get size() {
|
|
614
|
+
return this.uniforms.size;
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
/** Built-in uniform names that are handled automatically */
|
|
618
|
+
i(d, "BUILT_INS", /* @__PURE__ */ new Set([
|
|
619
|
+
"u_resolution",
|
|
620
|
+
"u_time",
|
|
621
|
+
"u_delta",
|
|
622
|
+
"u_mouse",
|
|
623
|
+
"u_frame"
|
|
624
|
+
]));
|
|
625
|
+
let v = d;
|
|
626
|
+
class b {
|
|
627
|
+
constructor() {
|
|
628
|
+
i(this, "hooks", /* @__PURE__ */ new Map());
|
|
629
|
+
}
|
|
630
|
+
id() {
|
|
631
|
+
return Math.random().toString(36).substring(2, 10);
|
|
632
|
+
}
|
|
633
|
+
/** Add a new hook, returns a removal function */
|
|
634
|
+
add(t) {
|
|
635
|
+
const e = this.id();
|
|
636
|
+
return this.hooks.set(e, t), () => this.remove(e);
|
|
637
|
+
}
|
|
638
|
+
/** Remove a hook by its ID */
|
|
639
|
+
remove(t) {
|
|
640
|
+
this.hooks.delete(t);
|
|
641
|
+
}
|
|
642
|
+
/** Run all hooks with the given state */
|
|
643
|
+
run(t) {
|
|
644
|
+
for (const [e, r] of this.hooks)
|
|
645
|
+
r(t) === !1 && this.remove(e);
|
|
646
|
+
}
|
|
647
|
+
destroy() {
|
|
648
|
+
this.hooks.clear();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
class _ {
|
|
652
|
+
constructor(t, e) {
|
|
653
|
+
i(this, "canvas");
|
|
654
|
+
i(this, "gl");
|
|
655
|
+
i(this, "options");
|
|
656
|
+
i(this, "onBeforeHooks", new b());
|
|
657
|
+
i(this, "onAfterHooks", new b());
|
|
658
|
+
i(this, "_program");
|
|
659
|
+
i(this, "_geometry");
|
|
660
|
+
i(this, "_uniforms");
|
|
661
|
+
i(this, "_clock");
|
|
662
|
+
i(this, "_resolution", [1, 1]);
|
|
663
|
+
i(this, "_mouse", [0, 0]);
|
|
664
|
+
i(this, "_version", 1);
|
|
665
|
+
i(this, "playing", !1);
|
|
666
|
+
this.canvas = t, this.options = e, this.gl = this.initContext(), this.enableExtensions(), this._program = new h(this.gl), this._geometry = p.fullscreenQuad(this.gl), this._uniforms = new v(this.gl), this._clock = new R(), this.options.onBeforeRender && this.onBeforeHooks.add(this.options.onBeforeRender), this.options.onAfterRender && this.onAfterHooks.add(this.options.onAfterRender), this.onRender = this.onRender.bind(this);
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Factory method to create and setup WebGL instance.
|
|
670
|
+
*/
|
|
671
|
+
static setup(t, e) {
|
|
672
|
+
const r = new _(t, e);
|
|
673
|
+
return e.vertex && e.fragment && r.shader(e.vertex, e.fragment), e.uniforms && r._uniforms.setMany(e.uniforms), r;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Initialize WebGL context.
|
|
677
|
+
* Tries WebGL2 first, falls back to WebGL1.
|
|
678
|
+
* Context errors are fatal but still reported via onError.
|
|
679
|
+
*/
|
|
680
|
+
initContext() {
|
|
681
|
+
const t = {
|
|
682
|
+
antialias: this.options.antialias,
|
|
683
|
+
preserveDrawingBuffer: this.options.preserveDrawingBuffer,
|
|
684
|
+
alpha: !0,
|
|
685
|
+
depth: !1,
|
|
686
|
+
stencil: !1
|
|
687
|
+
}, e = this.canvas.getContext("webgl2", t);
|
|
688
|
+
if (e)
|
|
689
|
+
return this._version = 2, e;
|
|
690
|
+
const r = this.canvas.getContext("webgl", t);
|
|
691
|
+
if (r)
|
|
692
|
+
return this._version = 1, r;
|
|
693
|
+
const s = new w("not_supported");
|
|
694
|
+
throw this.options.onError(s), s;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Enable useful WebGL extensions.
|
|
698
|
+
*/
|
|
699
|
+
enableExtensions() {
|
|
700
|
+
this.gl.getExtension("OES_standard_derivatives"), this.gl.getExtension("OES_texture_float"), this.gl.getExtension("OES_texture_float_linear"), this._version === 1 && this.gl.getExtension("OES_vertex_array_object");
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Set viewport dimensions.
|
|
704
|
+
*/
|
|
705
|
+
viewport(t, e, r, s) {
|
|
706
|
+
return this.canvas.width = r, this.canvas.height = s, this.gl.viewport(t, e, r, s), this._resolution = [r, s], this;
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Set the clock time
|
|
710
|
+
*/
|
|
711
|
+
clock(t) {
|
|
712
|
+
return this._clock.setTime(t), this;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Update mouse position.
|
|
716
|
+
*/
|
|
717
|
+
mouse(t, e) {
|
|
718
|
+
return this._mouse = [t, e], this;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Set a uniform value.
|
|
722
|
+
*/
|
|
723
|
+
uniform(t, e) {
|
|
724
|
+
return this._uniforms.set(t, e), this;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Set multiple uniforms.
|
|
728
|
+
*/
|
|
729
|
+
uniforms(t) {
|
|
730
|
+
return this._uniforms.setMany(t), this;
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Get current uniform value.
|
|
734
|
+
*/
|
|
735
|
+
getUniform(t) {
|
|
736
|
+
return this._uniforms.get(t);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Compile and link shaders.
|
|
740
|
+
* Errors are handled via onError callback, never thrown.
|
|
741
|
+
*/
|
|
742
|
+
shader(t, e) {
|
|
743
|
+
try {
|
|
744
|
+
this._program.compile(t, e), this._version = this._program.getVersion(), this._geometry.linkAttributes(this._program);
|
|
745
|
+
const r = this._program.getProgram();
|
|
746
|
+
r && this._uniforms.attachProgram(r);
|
|
747
|
+
} catch (r) {
|
|
748
|
+
r instanceof u && this.options.onError(r);
|
|
749
|
+
}
|
|
750
|
+
return this;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Start animation loop.
|
|
754
|
+
*/
|
|
755
|
+
play() {
|
|
756
|
+
return this.playing ? this : (this.playing = !0, this._clock.start(this.onRender), this);
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Stop animation loop.
|
|
760
|
+
*/
|
|
761
|
+
pause() {
|
|
762
|
+
if (!this.playing) return this;
|
|
763
|
+
const t = this._clock.getState();
|
|
764
|
+
return this.onBeforeHooks.run(t), this.playing = !1, this._clock.stop(), this.onAfterHooks.run(t), this;
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Render a single frame.
|
|
768
|
+
*/
|
|
769
|
+
render() {
|
|
770
|
+
return this.onRender(this._clock.getState()), this;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Get WebGL context.
|
|
774
|
+
*/
|
|
775
|
+
getContext() {
|
|
776
|
+
return this.gl;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Get detected WebGL version.
|
|
780
|
+
*/
|
|
781
|
+
getVersion() {
|
|
782
|
+
return this._version;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Cleanup all resources.
|
|
786
|
+
*/
|
|
787
|
+
destroy() {
|
|
788
|
+
this.pause(), this._clock.destroy(), this._geometry.destroy(), this._program.destroy(), this._uniforms.destroy(), this.onAfterHooks.destroy(), this.onBeforeHooks.destroy();
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Internal render callback.
|
|
792
|
+
*/
|
|
793
|
+
onRender(t) {
|
|
794
|
+
const e = this.gl;
|
|
795
|
+
this.onBeforeHooks.run(t), e.clearColor(0, 0, 0, 0), e.clear(e.COLOR_BUFFER_BIT), this._program.use(), this._uniforms.uploadBuiltIns(t, this._resolution, this._mouse), this._uniforms.uploadAll(), this._geometry.bind(), this._geometry.draw(), this.onAfterHooks.run(t);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const g = `#ifdef GL_ES
|
|
799
|
+
precision mediump float;
|
|
800
|
+
#endif
|
|
801
|
+
|
|
802
|
+
attribute vec2 a_position;
|
|
803
|
+
attribute vec2 a_texcoord;
|
|
804
|
+
|
|
805
|
+
varying vec2 v_texcoord;
|
|
806
|
+
|
|
807
|
+
void main() {
|
|
808
|
+
v_texcoord = a_texcoord;
|
|
809
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
810
|
+
}
|
|
811
|
+
`, A = `#ifdef GL_ES
|
|
812
|
+
precision mediump float;
|
|
813
|
+
#endif
|
|
814
|
+
|
|
815
|
+
uniform vec2 u_resolution;
|
|
816
|
+
uniform float u_time;
|
|
817
|
+
|
|
818
|
+
varying vec2 v_texcoord;
|
|
819
|
+
|
|
820
|
+
void main() {
|
|
821
|
+
vec2 uv = v_texcoord;
|
|
822
|
+
vec3 color = vec3(uv.x, uv.y, 0.5 + 0.5 * sin(u_time));
|
|
823
|
+
gl_FragColor = vec4(color, 1.0);
|
|
824
|
+
}
|
|
825
|
+
`, y = `#version 300 es
|
|
826
|
+
|
|
827
|
+
in vec2 a_position;
|
|
828
|
+
in vec2 a_texcoord;
|
|
829
|
+
|
|
830
|
+
out vec2 v_texcoord;
|
|
831
|
+
|
|
832
|
+
void main() {
|
|
833
|
+
v_texcoord = a_texcoord;
|
|
834
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
835
|
+
}`, M = `#version 300 es
|
|
836
|
+
precision highp float;
|
|
837
|
+
|
|
838
|
+
uniform vec2 u_resolution;
|
|
839
|
+
uniform float u_time;
|
|
840
|
+
uniform vec2 u_mouse;
|
|
841
|
+
|
|
842
|
+
in vec2 v_texcoord;
|
|
843
|
+
out vec4 fragColor;
|
|
844
|
+
|
|
845
|
+
void main() {
|
|
846
|
+
vec2 uv = gl_FragCoord.xy / u_resolution;
|
|
847
|
+
vec3 color = vec3(uv.x, uv.y, 0.5 + 0.5 * sin(u_time));
|
|
848
|
+
fragColor = vec4(color, 1.0);
|
|
849
|
+
}`;
|
|
850
|
+
class E {
|
|
851
|
+
constructor(t, e) {
|
|
852
|
+
/** Active event listeners */
|
|
853
|
+
i(this, "listeners", []);
|
|
854
|
+
/** HTML canvas element */
|
|
855
|
+
i(this, "canvas");
|
|
856
|
+
/** Resolved options */
|
|
857
|
+
i(this, "options");
|
|
858
|
+
/** WebGL engine */
|
|
859
|
+
i(this, "engine");
|
|
860
|
+
this.canvas = t, this.options = this.resolveOptions(e), this.engine = _.setup(this.canvas, this.options), this.setupListeners(), this.setViewport(), this.options.onLoad(), this.options.autoplay && this.play();
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Sandbox - A lightweight WebGL wrapper for shader effects.
|
|
864
|
+
*
|
|
865
|
+
* @example
|
|
866
|
+
* // Static rendering
|
|
867
|
+
* const sandbox = Sandbox.create(canvas, {
|
|
868
|
+
* fragment: myShader,
|
|
869
|
+
* autoplay: false,
|
|
870
|
+
* });
|
|
871
|
+
* sandbox.setUniforms({ u_time: 1.5 }).render();
|
|
872
|
+
*
|
|
873
|
+
* @example
|
|
874
|
+
* // Animation loop
|
|
875
|
+
* const sandbox = Sandbox.create(canvas, {
|
|
876
|
+
* fragment: myShader,
|
|
877
|
+
* autoplay: true,
|
|
878
|
+
* });
|
|
879
|
+
*/
|
|
880
|
+
static create(t, e) {
|
|
881
|
+
return new E(t, e);
|
|
882
|
+
}
|
|
883
|
+
resolveOptions(t) {
|
|
884
|
+
const e = {
|
|
885
|
+
vertex: g,
|
|
886
|
+
fragment: A,
|
|
887
|
+
autoplay: !0,
|
|
888
|
+
pauseWhenHidden: !0,
|
|
889
|
+
dpr: "auto",
|
|
890
|
+
preserveDrawingBuffer: !1,
|
|
891
|
+
antialias: !0,
|
|
892
|
+
onError: (r) => {
|
|
893
|
+
console.error(
|
|
894
|
+
"Oops!",
|
|
895
|
+
r,
|
|
896
|
+
`
|
|
897
|
+
You can handle errors programmatically by providing an onError callback to suppress this log and implement custom fallback behavior.`
|
|
898
|
+
);
|
|
899
|
+
},
|
|
900
|
+
onLoad: () => {
|
|
901
|
+
},
|
|
902
|
+
onBeforeRender: null,
|
|
903
|
+
onAfterRender: null,
|
|
904
|
+
uniforms: {}
|
|
905
|
+
};
|
|
906
|
+
if (t != null && t.vertex && !(t != null && t.fragment)) {
|
|
907
|
+
const r = h.detectVersion(t.vertex);
|
|
908
|
+
e.vertex = t.vertex, e.fragment = r === 2 ? M : A;
|
|
909
|
+
}
|
|
910
|
+
if (t != null && t.fragment && !(t != null && t.vertex)) {
|
|
911
|
+
const r = h.detectVersion(t.fragment);
|
|
912
|
+
e.fragment = t.fragment, e.vertex = r === 2 ? y : g;
|
|
913
|
+
}
|
|
914
|
+
return { ...e, ...t };
|
|
915
|
+
}
|
|
916
|
+
setupListeners() {
|
|
917
|
+
this.listeners.push(
|
|
918
|
+
// Window resize
|
|
919
|
+
l.on(window, "resize", () => {
|
|
920
|
+
this.setViewport();
|
|
921
|
+
}),
|
|
922
|
+
// Canvas resize
|
|
923
|
+
l.on(this.canvas, "resize", () => {
|
|
924
|
+
this.setViewport();
|
|
925
|
+
}),
|
|
926
|
+
// Visibility check on scroll
|
|
927
|
+
l.on(document, "scroll", () => {
|
|
928
|
+
this.options.pauseWhenHidden && (this.isInViewport() ? this.play() : this.pause());
|
|
929
|
+
}),
|
|
930
|
+
// Mouse tracking
|
|
931
|
+
l.on(document, "mousemove", (t) => {
|
|
932
|
+
this.setMouse(t.clientX || t.pageX, t.clientY || t.pageY);
|
|
933
|
+
}),
|
|
934
|
+
// Touch tracking
|
|
935
|
+
l.on(document, "touchmove", (t) => {
|
|
936
|
+
t.touches.length > 0 && this.setMouse(t.touches[0].clientX, t.touches[0].clientY);
|
|
937
|
+
})
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
destroyListeners() {
|
|
941
|
+
this.listeners.forEach((t) => t()), this.listeners = [];
|
|
942
|
+
}
|
|
943
|
+
setViewport() {
|
|
944
|
+
const t = this.options.dpr === "auto" ? Math.min(2, window.devicePixelRatio || 1) : this.options.dpr, e = this.canvas.clientWidth || this.canvas.width || 1, r = this.canvas.clientHeight || this.canvas.height || 1;
|
|
945
|
+
this.engine.viewport(
|
|
946
|
+
0,
|
|
947
|
+
0,
|
|
948
|
+
Math.max(1, Math.floor(e * t)),
|
|
949
|
+
Math.max(1, Math.floor(r * t))
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
isInViewport() {
|
|
953
|
+
const t = this.canvas.getBoundingClientRect();
|
|
954
|
+
return t.bottom >= 0 && t.right >= 0 && t.top <= (window.innerHeight || document.documentElement.clientHeight) && t.left <= (window.innerWidth || document.documentElement.clientWidth);
|
|
955
|
+
}
|
|
956
|
+
setMouse(t, e) {
|
|
957
|
+
const r = this.canvas.getBoundingClientRect();
|
|
958
|
+
t >= r.left && t <= r.right && e >= r.top && e <= r.bottom && this.engine.mouse(t - r.left, e - r.top);
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Set a single uniform value with type checking.
|
|
962
|
+
* @example
|
|
963
|
+
* sandbox.setUniform<Vec3>("u_color", [1, 0, 0]);
|
|
964
|
+
* sandbox.setUniform<number>("u_time", 1.5);
|
|
965
|
+
* sandbox.setUniform<Vec3[]>("u_colors", [[1, 0, 0], [0, 1, 0]]);
|
|
966
|
+
*/
|
|
967
|
+
setUniform(t, e) {
|
|
968
|
+
return this.engine.uniform(t, e), this;
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Set multiple uniforms at once with type checking.
|
|
972
|
+
* @example
|
|
973
|
+
* interface MyUniforms extends UniformSchema {
|
|
974
|
+
* u_time: number;
|
|
975
|
+
* u_resolution: Vec2;
|
|
976
|
+
* u_colors: Vec3[];
|
|
977
|
+
* }
|
|
978
|
+
* sandbox.setUniforms<MyUniforms>({
|
|
979
|
+
* u_time: 1.5,
|
|
980
|
+
* u_resolution: [800, 600],
|
|
981
|
+
* u_colors: [[1, 0, 0], [0, 1, 0]],
|
|
982
|
+
* });
|
|
983
|
+
*/
|
|
984
|
+
setUniforms(t) {
|
|
985
|
+
return this.engine.uniforms(t), this;
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Get current uniform value.
|
|
989
|
+
*/
|
|
990
|
+
getUniform(t) {
|
|
991
|
+
return this.engine.getUniform(t);
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Update shaders.
|
|
995
|
+
* @example
|
|
996
|
+
* sandbox.setShader(vertexSource, fragmentSource);
|
|
997
|
+
*/
|
|
998
|
+
setShader(t, e) {
|
|
999
|
+
return this.engine.shader(t, e), this;
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Update only fragment shader (uses default vertex).
|
|
1003
|
+
* @example
|
|
1004
|
+
* sandbox.setFragment(fragmentSource);
|
|
1005
|
+
*/
|
|
1006
|
+
setFragment(t) {
|
|
1007
|
+
const r = this.webglVersion() === 1 ? g : y;
|
|
1008
|
+
return this.engine.shader(r, t), this;
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Add a runtime render hook.
|
|
1012
|
+
*/
|
|
1013
|
+
hook(t, e = "before") {
|
|
1014
|
+
return e === "before" ? this.engine.onBeforeHooks.add(t) : this.engine.onAfterHooks.add(t);
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Start animation loop.
|
|
1018
|
+
*/
|
|
1019
|
+
play() {
|
|
1020
|
+
return this.engine.play(), this;
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Start animation loop at specific time (in seconds).
|
|
1024
|
+
*/
|
|
1025
|
+
playAt(t) {
|
|
1026
|
+
return this.engine.clock(t), this.engine.play(), this;
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Stop animation loop.
|
|
1030
|
+
*/
|
|
1031
|
+
pause() {
|
|
1032
|
+
return this.engine.pause(), this;
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Pause animation loop at specific time (in seconds).
|
|
1036
|
+
*/
|
|
1037
|
+
pauseAt(t) {
|
|
1038
|
+
const e = this.hook((r) => {
|
|
1039
|
+
r.time >= t && (e(), this.pause());
|
|
1040
|
+
}, "after");
|
|
1041
|
+
return this;
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Toggle play/pause state.
|
|
1045
|
+
*/
|
|
1046
|
+
toggle() {
|
|
1047
|
+
return this.engine.playing ? this.pause() : this.play(), this;
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Set current time (in seconds).
|
|
1051
|
+
*/
|
|
1052
|
+
time(t) {
|
|
1053
|
+
return this.engine.clock(t), this;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Render a single frame (for static rendering).
|
|
1057
|
+
* @example
|
|
1058
|
+
* sandbox.time(1.4).render();
|
|
1059
|
+
*/
|
|
1060
|
+
render() {
|
|
1061
|
+
return this.engine.render(), this;
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Render at specific time (for deterministic output).
|
|
1065
|
+
* @example
|
|
1066
|
+
* sandbox.renderAt(2.5); // Render as if 2.5 seconds elapsed
|
|
1067
|
+
*/
|
|
1068
|
+
renderAt(t) {
|
|
1069
|
+
return this.engine.clock(t), this.engine.render(), this;
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Check if currently playing.
|
|
1073
|
+
*/
|
|
1074
|
+
isPlaying() {
|
|
1075
|
+
return this.engine.playing;
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Get WebGL version using (1 or 2).
|
|
1079
|
+
*/
|
|
1080
|
+
webglVersion() {
|
|
1081
|
+
return this.engine.getVersion();
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Get canvas element.
|
|
1085
|
+
*/
|
|
1086
|
+
canvasElement() {
|
|
1087
|
+
return this.canvas;
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Destroy sandbox and release all resources.
|
|
1091
|
+
* @example
|
|
1092
|
+
* onUnmounted(() => {
|
|
1093
|
+
* sandbox.destroy();
|
|
1094
|
+
* });
|
|
1095
|
+
*/
|
|
1096
|
+
destroy() {
|
|
1097
|
+
this.destroyListeners(), this.engine.destroy();
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
export {
|
|
1101
|
+
E as Sandbox,
|
|
1102
|
+
w as SandboxContextError,
|
|
1103
|
+
u as SandboxError,
|
|
1104
|
+
m as SandboxProgramError,
|
|
1105
|
+
f as SandboxShaderCompilationError,
|
|
1106
|
+
k as SandboxShaderVersionMismatchError
|
|
1107
|
+
};
|