@bigmistqke/view.gl 0.1.19 → 0.3.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.
Files changed (44) hide show
  1. package/README.md +82 -27
  2. package/dev/dist/404.html +19 -0
  3. package/dev/dist/assets/bezier-DrA4cU_H.js +292 -0
  4. package/dev/dist/assets/game-of-life-BYlzWhEL.js +186 -0
  5. package/dev/dist/assets/glsl-composition-D_BaFFij.js +97 -0
  6. package/dev/dist/assets/grid-DfXHNLBm.js +181 -0
  7. package/dev/dist/assets/index-C2N3u8wj.css +12 -0
  8. package/dev/dist/assets/index-D-N0FDYs.js +110 -0
  9. package/dev/dist/assets/main-CSE2fki3.js +32 -0
  10. package/dev/dist/assets/overview-IeUhUflE.js +80 -0
  11. package/dev/dist/assets/pix-sim-DMJJA_WO.js +500 -0
  12. package/dev/dist/assets/ray-casting-DTQetDPd.js +411 -0
  13. package/dev/dist/assets/ray-casting-modules-Dkfw4tdc.js +765 -0
  14. package/dev/dist/assets/sdf-CS0uBonx.js +66 -0
  15. package/dev/dist/assets/symbol-BL_7GJ0M.js +67 -0
  16. package/dev/dist/assets/tag-C0Vskpp8.js +833 -0
  17. package/dev/dist/assets/tetris-D8mokodr.js +344 -0
  18. package/dev/dist/assets/utils-2dzuv_bW.js +62 -0
  19. package/dev/dist/index.html +51 -0
  20. package/dist/chunk-BZDGEIOT.mjs +278 -0
  21. package/dist/chunk-BZDGEIOT.mjs.map +1 -0
  22. package/dist/chunk-X4M3LV4R.mjs +433 -0
  23. package/dist/chunk-X4M3LV4R.mjs.map +1 -0
  24. package/dist/gl.d.mts +2 -2
  25. package/dist/gl.mjs +1 -1
  26. package/dist/index.d.mts +23 -6
  27. package/dist/index.mjs +9 -2
  28. package/dist/tag.d.mts +6 -1
  29. package/dist/tag.mjs +13 -5
  30. package/dist/tag.mjs.map +1 -1
  31. package/dist/{types-DTSPQclW.d.mts → types-CNdqh7tB.d.mts} +121 -8
  32. package/package.json +1 -1
  33. package/src/gl.ts +13 -2
  34. package/src/index.ts +294 -122
  35. package/src/tag.ts +18 -2
  36. package/src/types.ts +127 -7
  37. package/src/utils.ts +45 -10
  38. package/test/index.test.ts +339 -84
  39. package/test/mocks/webgl.ts +20 -0
  40. package/test/tag.test.ts +41 -0
  41. package/dist/chunk-CJTT7WPV.mjs +0 -75
  42. package/dist/chunk-CJTT7WPV.mjs.map +0 -1
  43. package/dist/chunk-ZP4HP5H7.mjs +0 -405
  44. package/dist/chunk-ZP4HP5H7.mjs.map +0 -1
@@ -0,0 +1,500 @@
1
+ import { b as createFramebuffer, a as attribute, g as glsl, u as uniform, c as compile } from './tag-C0Vskpp8.js';
2
+ import { d as dom } from './utils-2dzuv_bW.js';
3
+
4
+ const MATERIALS = {
5
+ SAND: {
6
+ color: [1, 0.8, 0],
7
+ // Yellow sand
8
+ id: 1
9
+ },
10
+ WATER: {
11
+ color: [0, 0.5, 1],
12
+ // Blue water
13
+ id: 2
14
+ },
15
+ STONE: {
16
+ color: [0.5, 0.5, 0.5],
17
+ // Gray stone
18
+ id: 3
19
+ }
20
+ };
21
+ const MATERIAL_SIZE = Object.keys(MATERIALS).length;
22
+ let playing = false;
23
+ let currentMaterial = MATERIALS.SAND.id;
24
+ const container = dom("div", {
25
+ style: {
26
+ display: "flex",
27
+ flexDirection: "column",
28
+ alignItems: "center",
29
+ gap: "10px"
30
+ },
31
+ parentElement: document.body
32
+ });
33
+ const buttonContainer = dom("div", {
34
+ style: "display: flex; gap: 10px; margin-bottom: 10px;",
35
+ parentElement: container
36
+ });
37
+ Object.entries(MATERIALS).forEach(([name, material]) => {
38
+ dom("button", {
39
+ "data-name": name,
40
+ "data-id": material.id.toString(),
41
+ textContent: name.toLowerCase(),
42
+ style: {
43
+ padding: "5px 10px",
44
+ fontSize: "14px",
45
+ fontFamily: "arial",
46
+ cursor: "pointer",
47
+ color: "black",
48
+ background: currentMaterial === material.id ? `rgb(${material.color.map((c) => Math.floor(c * 255)).join(",")})` : `rgba(${material.color.map((c) => Math.floor(c * 255)).join(",")}, 0.75)`,
49
+ borderColor: name === "SAND" ? "black" : "white",
50
+ border: `2px solid rgb(${material.color.map((c) => Math.floor(c * 255)).join(",")})`,
51
+ borderRadius: "2px"
52
+ },
53
+ onclick() {
54
+ currentMaterial = material.id;
55
+ buttonContainer.querySelectorAll("button").forEach((btn) => {
56
+ const name2 = btn.getAttribute("data-name");
57
+ const id = btn.getAttribute("data-id");
58
+ if (id === currentMaterial.toString()) {
59
+ btn.style.background = `rgb(${MATERIALS[name2].color.map((c) => Math.floor(c * 255)).join(",")})`;
60
+ } else {
61
+ btn.style.background = `rgba(${MATERIALS[name2].color.map((c) => Math.floor(c * 255)).join(",")}, 0.75)`;
62
+ }
63
+ });
64
+ },
65
+ parentElement: buttonContainer
66
+ });
67
+ });
68
+ function spray() {
69
+ return Math.floor(Math.random() * 8) * (Math.random() < 0.5 ? 1 : -1);
70
+ }
71
+ const canvas = dom("canvas", {
72
+ parentElement: container,
73
+ onmousedown() {
74
+ const rect = canvas.getBoundingClientRect();
75
+ const controller = new AbortController();
76
+ window.addEventListener(
77
+ "mousemove",
78
+ (event) => {
79
+ const x = Math.floor((event.clientX - rect.left + spray()) * (canvas.width / rect.width));
80
+ const y = Math.floor(
81
+ (rect.bottom - event.clientY + spray()) * (canvas.height / rect.height)
82
+ );
83
+ const dimensions = [8, 8];
84
+ const data = new Uint8Array(
85
+ function* () {
86
+ for (let i = 0; i < dimensions[0] * dimensions[1]; i++) {
87
+ yield (currentMaterial & 1) * 255;
88
+ yield (currentMaterial >> 1 & 1) * 255;
89
+ yield (currentMaterial >> 2 & 1) * 255;
90
+ yield 255;
91
+ }
92
+ }()
93
+ );
94
+ gl.bindTexture(gl.TEXTURE_2D, read.texture);
95
+ gl.texSubImage2D(
96
+ gl.TEXTURE_2D,
97
+ 0,
98
+ Math.max(0, Math.min(WIDTH - dimensions[0], x)),
99
+ Math.max(0, Math.min(HEIGHT - dimensions[1], y)),
100
+ dimensions[0],
101
+ dimensions[1],
102
+ gl.RGBA,
103
+ gl.UNSIGNED_BYTE,
104
+ data
105
+ );
106
+ gl.bindTexture(gl.TEXTURE_2D, write.texture);
107
+ gl.texSubImage2D(
108
+ gl.TEXTURE_2D,
109
+ 0,
110
+ Math.max(0, Math.min(WIDTH - dimensions[0], x)),
111
+ Math.max(0, Math.min(HEIGHT - dimensions[1], y)),
112
+ dimensions[0],
113
+ dimensions[1],
114
+ gl.RGBA,
115
+ gl.UNSIGNED_BYTE,
116
+ data
117
+ );
118
+ render();
119
+ },
120
+ controller
121
+ );
122
+ window.addEventListener(
123
+ "mouseup",
124
+ () => {
125
+ controller.abort();
126
+ },
127
+ controller
128
+ );
129
+ }
130
+ });
131
+ const gl = canvas.getContext("webgl2", { antialias: false });
132
+ if (!gl) {
133
+ throw new Error("WebGL not supported");
134
+ }
135
+ canvas.width = 256 * 2;
136
+ canvas.height = 256 * 2;
137
+ const WIDTH = canvas.width;
138
+ const HEIGHT = canvas.height;
139
+ const vertex = glsl`#version 300 es
140
+ ${attribute.vec2("a_vertex")}
141
+ out vec2 v_uv;
142
+ void main() {
143
+ v_uv = a_vertex * 0.5 + 0.5;
144
+ gl_Position = vec4(a_vertex, 0, 1);
145
+ }`;
146
+ const stepFragment = glsl`#version 300 es
147
+ precision highp float;
148
+ in vec2 v_uv;
149
+ ${uniform.sampler2D("u_texture")}
150
+ ${uniform.vec2("u_texelSize")}
151
+ out vec4 fragColor;
152
+
153
+ // Material constants (like enums)
154
+ const int MAT_EMPTY = 0;
155
+ const int MAT_SAND = 1;
156
+ const int MAT_WATER = 2;
157
+ const int MAT_STONE = 3;
158
+
159
+ // Direction constants
160
+ const vec2 DIR_UP = vec2(0.0, 1.0);
161
+ const vec2 DIR_DOWN = vec2(0.0, -1.0);
162
+ const vec2 DIR_LEFT = vec2(-1.0, 0.0);
163
+ const vec2 DIR_RIGHT = vec2(1.0, 0.0);
164
+
165
+ // Helper function to decode material from RGB
166
+ int getMaterial(vec4 color) {
167
+ return int(color.r + color.g * 2.0 + color.b * 4.0);
168
+ }
169
+
170
+ // Helper function to encode material to RGB
171
+ vec4 encodeMaterial(int material) {
172
+ return vec4(
173
+ float(material & 1),
174
+ float((material >> 1) & 1),
175
+ float((material >> 2) & 1),
176
+ 1.0
177
+ );
178
+ }
179
+
180
+ void main() {
181
+ // Sample the current state
182
+ vec4 current = texture(u_texture, v_uv);
183
+
184
+ // Check boundaries
185
+ bool atBottom = v_uv.y - u_texelSize.y < 0.0;
186
+ bool atLeft = v_uv.x - u_texelSize.x < 0.0;
187
+ bool atRight = v_uv.x + u_texelSize.x > 1.0;
188
+ bool atTop = v_uv.y + u_texelSize.y > 1.0;
189
+
190
+ // Default: keep current state
191
+ fragColor = current;
192
+
193
+ int material = getMaterial(current);
194
+
195
+ int below = getMaterial(texture(u_texture, v_uv + DIR_DOWN * u_texelSize));
196
+ int above = getMaterial(texture(u_texture, v_uv + DIR_UP * u_texelSize));
197
+ int aboveLeft = getMaterial(texture(u_texture, v_uv + (DIR_UP + DIR_LEFT) * u_texelSize));
198
+ int aboveRight = getMaterial(texture(u_texture, v_uv + (DIR_UP + DIR_RIGHT) * u_texelSize));
199
+ int belowLeft = getMaterial(texture(u_texture, v_uv + (DIR_DOWN + DIR_LEFT) * u_texelSize));
200
+ int belowRight = getMaterial(texture(u_texture, v_uv + (DIR_DOWN + DIR_RIGHT) * u_texelSize));
201
+
202
+ // Physics simulation using switch
203
+ switch (material) {
204
+ case MAT_EMPTY:{
205
+ // Empty cell - check what can fall/flow into it
206
+
207
+ // Check for sand from above (highest priority - sand is denser)
208
+ bool sandDirectlyAbove = !atTop && above == MAT_SAND;
209
+ if (sandDirectlyAbove) {
210
+ fragColor = encodeMaterial(MAT_SAND);
211
+ break;
212
+ }
213
+
214
+ // Check for sand sliding from diagonal
215
+ bool sandAboveLeft = !atTop && !atRight && aboveLeft == MAT_SAND;
216
+ bool sandAboveRight = !atTop && !atLeft && aboveRight == MAT_SAND;
217
+
218
+ if (sandAboveLeft) {
219
+ vec4 left = texture(u_texture, v_uv + DIR_LEFT * u_texelSize);
220
+ bool leftBlocked = getMaterial(left) != MAT_EMPTY;
221
+ bool canSlideFromLeft = leftBlocked || atBottom;
222
+ if (canSlideFromLeft) {
223
+ fragColor = encodeMaterial(MAT_SAND);
224
+ break;
225
+ }
226
+ }
227
+
228
+ if (sandAboveRight) {
229
+ vec4 right = texture(u_texture, v_uv + DIR_RIGHT * u_texelSize);
230
+ bool rightBlocked = getMaterial(right) != MAT_EMPTY;
231
+ bool canSlideFromRight = rightBlocked || atBottom;
232
+ if (canSlideFromRight) {
233
+ fragColor = encodeMaterial(MAT_SAND);
234
+ break;
235
+ }
236
+ }
237
+
238
+ // Check for water from above
239
+ bool waterDirectlyAbove = !atTop && above == MAT_WATER;
240
+ if (waterDirectlyAbove) {
241
+ fragColor = encodeMaterial(MAT_WATER);
242
+ break;
243
+ }
244
+
245
+ // Check for water sliding from diagonal
246
+ bool waterAboveLeft = !atTop && !atRight && aboveLeft == MAT_WATER;
247
+ bool waterAboveRight = !atTop && !atLeft && aboveRight == MAT_WATER;
248
+
249
+ if (waterAboveLeft) {
250
+ vec4 left = texture(u_texture, v_uv + DIR_LEFT * u_texelSize);
251
+ bool sandToLeft = getMaterial(left) == MAT_SAND;
252
+ if (!sandToLeft) {
253
+ fragColor = encodeMaterial(MAT_WATER);
254
+ break;
255
+ }
256
+ }
257
+
258
+ if (waterAboveRight) {
259
+ vec4 right = texture(u_texture, v_uv + DIR_RIGHT * u_texelSize);
260
+ bool sandToRight = getMaterial(right) == MAT_SAND;
261
+ if (!sandToRight) {
262
+ fragColor = encodeMaterial(MAT_WATER);
263
+ break;
264
+ }
265
+ }
266
+
267
+ // Check for water flowing horizontally
268
+ vec4 left = texture(u_texture, v_uv + DIR_LEFT * u_texelSize);
269
+ vec4 right = texture(u_texture, v_uv + DIR_RIGHT * u_texelSize);
270
+ int matLeft = getMaterial(left);
271
+ int matRight = getMaterial(right);
272
+
273
+ bool waterToLeft = !atRight && matLeft == MAT_WATER;
274
+ bool waterToRight = !atLeft && matRight == MAT_WATER;
275
+
276
+ if (!waterToLeft && !waterToRight) break;
277
+
278
+ // Random choice for symmetric flow
279
+ float rand = fract(sin(dot(v_uv * 1000.0, vec2(12.9898, 78.233))) * 43758.5453);
280
+ bool preferLeft = rand < 0.5;
281
+
282
+ if (
283
+ waterToLeft && waterToRight ||
284
+ waterToLeft ||
285
+ waterToRight
286
+ ) {
287
+ fragColor = encodeMaterial(MAT_WATER);
288
+ break;
289
+ }
290
+ }
291
+
292
+ case MAT_SAND: {
293
+ // Sand particle - check if it should fall
294
+ bool canFallThrough = below == MAT_EMPTY || below == MAT_WATER;
295
+ bool canFallDown = !atBottom && canFallThrough;
296
+
297
+ if (canFallDown) {
298
+ fragColor = encodeMaterial(MAT_EMPTY);
299
+ break;
300
+ }
301
+
302
+ // Check diagonal falling
303
+ bool canFallThroughLeft = belowLeft == MAT_EMPTY || belowLeft == MAT_WATER;
304
+ bool canFallThroughRight = belowRight == MAT_EMPTY || belowRight == MAT_WATER;
305
+
306
+ bool canFallDownLeft = !atBottom && !atLeft && canFallThroughLeft;
307
+ bool canFallDownRight = !atBottom && !atRight && canFallThroughRight;
308
+
309
+ if (canFallDownLeft || canFallDownRight) {
310
+ fragColor = encodeMaterial(MAT_EMPTY);
311
+ break;
312
+ }
313
+
314
+ // Sand stays in place
315
+ break;
316
+ }
317
+
318
+ case MAT_WATER: {
319
+ // Water - check if being displaced by sand first
320
+ bool sandFallingFromAbove = !atTop && above == MAT_SAND;
321
+ if (sandFallingFromAbove) {
322
+ fragColor = encodeMaterial(MAT_SAND);
323
+ break;
324
+ }
325
+
326
+ // Check sand sliding into water from diagonals
327
+ bool sandFromAboveLeft = !atTop && !atRight && aboveLeft == MAT_SAND;
328
+ bool sandFromAboveRight = !atTop && !atLeft && aboveRight == MAT_SAND;
329
+
330
+ if (sandFromAboveLeft) {
331
+ vec4 left = texture(u_texture, v_uv + DIR_LEFT * u_texelSize);
332
+ bool leftNotEmpty = getMaterial(left) != MAT_EMPTY;
333
+ bool sandCanSlideIn = leftNotEmpty || atBottom;
334
+
335
+ if (sandCanSlideIn) {
336
+ fragColor = encodeMaterial(MAT_SAND);
337
+ break;
338
+ }
339
+ }
340
+
341
+ if (sandFromAboveRight) {
342
+ vec4 right = texture(u_texture, v_uv + DIR_RIGHT * u_texelSize);
343
+ bool rightNotEmpty = getMaterial(right) != MAT_EMPTY;
344
+ bool sandCanSlideIn = rightNotEmpty || atBottom;
345
+
346
+ if (sandCanSlideIn) {
347
+ fragColor = encodeMaterial(MAT_SAND);
348
+ break;
349
+ }
350
+ }
351
+
352
+ // Normal water physics - falling
353
+ bool canFallDown = !atBottom && below == MAT_EMPTY;
354
+ bool canFlowDownLeft = !atBottom && !atLeft && belowLeft == MAT_EMPTY;
355
+ bool canFlowDownRight = !atBottom && !atRight && belowRight == MAT_EMPTY;
356
+
357
+ if (canFallDown || canFlowDownLeft || canFlowDownRight) {
358
+ fragColor = encodeMaterial(MAT_EMPTY);
359
+ break;
360
+ }
361
+
362
+ // Horizontal spreading
363
+ vec4 left = texture(u_texture, v_uv + DIR_LEFT * u_texelSize);
364
+ vec4 right = texture(u_texture, v_uv + DIR_RIGHT * u_texelSize);
365
+ int matLeft = getMaterial(left);
366
+ int matRight = getMaterial(right);
367
+
368
+ bool canFlowLeft = !atLeft && matLeft == MAT_EMPTY;
369
+ bool canFlowRight = !atRight && matRight == MAT_EMPTY;
370
+
371
+ if (!canFlowLeft && !canFlowRight) {
372
+ break;
373
+ }
374
+
375
+ // Random horizontal movement for fluid behavior
376
+ float rand = fract(sin(dot(v_uv * 1000.0, vec2(12.9898, 78.233))) * 43758.5453);
377
+ bool preferLeft = rand < 0.5;
378
+
379
+ if (
380
+ canFlowLeft && preferLeft ||
381
+ canFlowRight && !preferLeft ||
382
+ canFlowLeft ||
383
+ canFlowDownRight
384
+ ) {
385
+ fragColor = encodeMaterial(MAT_EMPTY);
386
+ break;
387
+ }
388
+
389
+ }
390
+
391
+ case MAT_STONE:
392
+ // Stone doesn't move
393
+ break;
394
+
395
+ default:
396
+ // Unknown material - keep as is
397
+ break;
398
+ }
399
+ }`;
400
+ const renderFragment = glsl`#version 300 es
401
+ precision mediump float;
402
+ in vec2 v_uv;
403
+ ${uniform.sampler2D("u_texture")}
404
+ ${uniform.vec3("u_palette", { size: MATERIAL_SIZE })}
405
+ out vec4 fragColor;
406
+
407
+ // Material constants (must match stepFragment)
408
+ const int MAT_EMPTY = 0;
409
+ const int MAT_SAND = 1;
410
+ const int MAT_WATER = 2;
411
+ const int MAT_STONE = 3;
412
+
413
+ // Helper function to decode material from RGB
414
+ int getMaterial(vec4 color) {
415
+ return int(color.r + color.g * 2.0 + color.b * 4.0);
416
+ }
417
+
418
+ void main() {
419
+ vec4 texel = texture(u_texture, v_uv);
420
+ int material = getMaterial(texel);
421
+
422
+ if (material == MAT_EMPTY) {
423
+ fragColor = vec4(0.0, 0.0, 0.0, 1.0); // Black background
424
+ } else if (material > 0 && material <= ${MATERIAL_SIZE}) {
425
+ fragColor = vec4(u_palette[material - 1], 1.0);
426
+ } else {
427
+ fragColor = vec4(1.0, 0.0, 1.0, 1.0); // Magenta for unknown materials
428
+ }
429
+ }`;
430
+ const { program: stepProgram, view: stepView } = compile(gl, vertex, stepFragment);
431
+ const { program: renderProgram, view: renderView } = compile(gl, vertex, renderFragment, {
432
+ schema: {
433
+ attributes: { a_vertex: { buffer: stepView.attributes.a_vertex.buffer } }
434
+ }
435
+ });
436
+ gl.useProgram(renderProgram);
437
+ renderView.uniforms.u_palette.set(
438
+ new Float32Array(Object.values(MATERIALS).flatMap(({ color }) => color))
439
+ );
440
+ gl.useProgram(stepProgram);
441
+ stepView.attributes.a_vertex.set(new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]));
442
+ const framebufferOptions = {
443
+ attachment: "color",
444
+ width: WIDTH,
445
+ height: HEIGHT,
446
+ type: "UNSIGNED_BYTE",
447
+ magFilter: "NEAREST",
448
+ minFilter: "NEAREST",
449
+ internalFormat: "RGBA",
450
+ format: "RGBA"
451
+ };
452
+ let read = createFramebuffer(gl, framebufferOptions);
453
+ let write = createFramebuffer(gl, framebufferOptions);
454
+ const initData = new Uint8Array(
455
+ function* () {
456
+ for (let i = 0; i < WIDTH * HEIGHT; i++) {
457
+ yield 0;
458
+ yield 0;
459
+ yield 0;
460
+ yield 255;
461
+ }
462
+ }()
463
+ );
464
+ gl.bindTexture(gl.TEXTURE_2D, read.texture);
465
+ gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, WIDTH, HEIGHT, gl.RGBA, gl.UNSIGNED_BYTE, initData);
466
+ gl.bindTexture(gl.TEXTURE_2D, write.texture);
467
+ gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, WIDTH, HEIGHT, gl.RGBA, gl.UNSIGNED_BYTE, initData);
468
+ function step() {
469
+ gl.bindFramebuffer(gl.FRAMEBUFFER, write.framebuffer);
470
+ gl.viewport(0, 0, WIDTH, HEIGHT);
471
+ gl.useProgram(stepProgram);
472
+ gl.activeTexture(gl.TEXTURE0);
473
+ gl.bindTexture(gl.TEXTURE_2D, read.texture);
474
+ stepView.uniforms.u_texture.set(0);
475
+ stepView.uniforms.u_texelSize.set(1 / WIDTH, 1 / HEIGHT);
476
+ stepView.attributes.a_vertex.bind();
477
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
478
+ }
479
+ function render() {
480
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
481
+ gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
482
+ gl.useProgram(renderProgram);
483
+ gl.activeTexture(gl.TEXTURE0);
484
+ gl.bindTexture(gl.TEXTURE_2D, write.texture);
485
+ renderView.uniforms.u_texture.set(0);
486
+ renderView.attributes.a_vertex.bind();
487
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
488
+ }
489
+ function animate() {
490
+ if (playing) {
491
+ step();
492
+ render();
493
+ [read, write] = [write, read];
494
+ requestAnimationFrame(animate);
495
+ }
496
+ }
497
+ playing = true;
498
+ animate();
499
+
500
+ export { MATERIALS };