@bigmistqke/view.gl 0.2.0 → 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 (43) 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-EYXOSM7B.mjs → chunk-X4M3LV4R.mjs} +60 -133
  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 +13 -4
  27. package/dist/index.mjs +7 -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-CyGyGi18.d.mts → types-CNdqh7tB.d.mts} +34 -4
  32. package/package.json +1 -1
  33. package/src/gl.ts +13 -2
  34. package/src/index.ts +55 -39
  35. package/src/tag.ts +18 -2
  36. package/src/types.ts +33 -3
  37. package/src/utils.ts +45 -10
  38. package/test/index.test.ts +111 -29
  39. package/test/mocks/webgl.ts +14 -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-EYXOSM7B.mjs.map +0 -1
package/README.md CHANGED
@@ -60,7 +60,7 @@ bun add @bigmistqke/view.gl
60
60
 
61
61
  ## 👁️ view.gl
62
62
 
63
- The view system provides type-safe WebGL resource management for uniforms, attributes, and buffers
63
+ The view system provides type-safe WebGL resource management for uniforms and attributes
64
64
 
65
65
  ### 🚀 Basic Usage
66
66
 
@@ -74,9 +74,6 @@ const { uniforms, attributes } = view(gl, program, {
74
74
  position: { kind: 'vec3' },
75
75
  uv: { kind: 'vec2' },
76
76
  },
77
- buffers: {
78
- indices: { target: 'ELEMENT_ARRAY_BUFFER' },
79
- },
80
77
  })
81
78
 
82
79
  // Type-safe uniform setting
@@ -93,7 +90,7 @@ attributes.position.bind()
93
90
  The `view()` function creates type-safe WebGL resource managers from a schema.
94
91
 
95
92
  ```typescript
96
- const { uniforms, attributes, buffers } = view(gl, program, schema)
93
+ const { uniforms, attributes } = view(gl, program, schema)
97
94
  ```
98
95
 
99
96
  **Parameters:**
@@ -106,14 +103,18 @@ const { uniforms, attributes, buffers } = view(gl, program, schema)
106
103
 
107
104
  - `uniforms`: Type-safe uniform setters
108
105
  - `attributes`: Attribute managers with buffer handling
109
- - `buffers`: Generic buffer managers
106
+ - `dispose`: Deletes everything the view made — see [Disposal](#-disposal)
110
107
 
111
108
  #### 📋 ViewSchema
112
109
  The complete schema object that defines all WebGL resources. Contains mappings for:
113
110
  - [`UniformSchema`](#-uniformschema) - uniform variable definitions
114
111
  - [`AttributeSchema`](#-attributeschema) - vertex attribute definitions
115
112
  - [`InterleavedAttributeSchema`](#-interleavedattributeschema) - interleaved vertex data definitions
116
- - [`BufferSchema`](#-bufferschema) - generic buffer definitions
113
+
114
+ A schema names what the program has names for, so buffers are not part of it: a
115
+ buffer has no name in the source to resolve against the program, and its lifetime
116
+ is rarely a program's — the same buffer often feeds several. Build one with
117
+ [`bufferView`](#-bufferview) and hand it to `vao()` alongside the attributes.
117
118
 
118
119
  <details>
119
120
  <summary>TypeScript Types</summary>
@@ -123,12 +124,52 @@ interface ViewSchema {
123
124
  uniforms: UniformSchema
124
125
  attributes: AttributeSchema
125
126
  interleavedAttributes: InterleavedAttributeSchema
126
- buffers: BufferSchema
127
127
  }
128
128
  ```
129
129
 
130
130
  </details>
131
131
 
132
+ ### 🗑️ Disposal
133
+
134
+ Everything that creates a WebGL resource takes an `AbortSignal` and offers a
135
+ `dispose()`, and the two are the same teardown reached two ways:
136
+
137
+ ```typescript
138
+ const controller = new AbortController()
139
+
140
+ const { view, dispose } = compile(gl, vertex, fragment, { signal: controller.signal })
141
+ const buffers = bufferView(gl, { indices: {} }, { signal: controller.signal })
142
+ const target = createFramebuffer(gl, definition, { signal: controller.signal })
143
+
144
+ controller.abort() // deletes the program, the buffers, the vertex arrays, the framebuffer
145
+ ```
146
+
147
+ Two rules make this safe to reach for.
148
+
149
+ **Only what it made.** A resource handed in from outside is borrowed, never
150
+ deleted: a buffer named by an attribute schema, a texture passed to
151
+ `createFramebuffer`. Those belong to whoever created them, and the same buffer
152
+ often feeds several views. Delete one out from under its owner and WebGL does
153
+ not fault where it happened — the object survives while it is bound, so the
154
+ handle is invalidated and the *other* view fails later, on its next upload.
155
+
156
+ **Once.** Every `dispose()` is idempotent. Disposing by hand and then aborting,
157
+ or aborting twice, deletes each resource exactly once.
158
+
159
+ | creates | disposed by |
160
+ | --- | --- |
161
+ | `view()` | its own attribute and layout buffers, and vertex arrays still held from `vao()` |
162
+ | `compile()` | the program, and its view |
163
+ | `attributeView` / `interleavedAttributeView` | the buffer, unless the schema named one |
164
+ | `bufferView` | its buffers |
165
+ | `vaoView` | the vertex array, never its participants |
166
+ | `createProgram` | the program |
167
+ | `createTexture` | the texture |
168
+ | `createFramebuffer` | the framebuffer, and the texture unless one was passed in |
169
+
170
+ `uniformView` takes no signal: a uniform location is not a resource, so there is
171
+ nothing to delete.
172
+
132
173
  ### 👀 Resource Views
133
174
 
134
175
  Each view type can be imported individually.
@@ -316,8 +357,10 @@ const buffers = bufferView(gl, {
316
357
  data: { target: 'ARRAY_BUFFER', usage: 'DYNAMIC_DRAW' },
317
358
  })
318
359
 
319
- buffers.indices.set(indexData).bind()
320
- buffers.data.set(dynamicData).bind()
360
+ buffers.indices.set(indexData)
361
+ buffers.data.set(dynamicData)
362
+
363
+ const unbind = buffers.indices.bind()
321
364
 
322
365
  // bind() returns a disposer restoring the previous binding for that target.
323
366
  // Worth calling for ELEMENT_ARRAY_BUFFER, whose binding is recorded in the
@@ -628,19 +671,22 @@ You can provide an optional override schema to enhance or override the automatic
628
671
 
629
672
  ```typescript
630
673
  const { program, schema, view } = compile(gl, vertexShader, fragmentShader, {
631
- uniforms: {
632
- // Add additional uniforms not automatically inferred
633
- customTime: { kind: 'float' },
634
- },
635
- buffers: {
636
- // Add buffer definitions
637
- indices: { target: 'ELEMENT_ARRAY_BUFFER' },
674
+ schema: {
675
+ uniforms: {
676
+ // Add additional uniforms not automatically inferred
677
+ customTime: { kind: 'float' },
678
+ },
638
679
  },
639
680
  })
640
681
 
641
682
  // The override schema is merged with the extracted schema
642
683
  view.uniforms.customTime.set(123.45)
643
- view.buffers.indices.set(indexData).bind()
684
+
685
+ // An index buffer is its own thing — no program names it, and it outlives any
686
+ // one of them. Build it with bufferView and pass it into the vertex array.
687
+ const buffers = bufferView(gl, { indices: { target: 'ELEMENT_ARRAY_BUFFER' } })
688
+ buffers.indices.set(indexData)
689
+ const vao = view.vao([view.attributes.position, buffers.indices])
644
690
  ```
645
691
 
646
692
  **Returns**:
@@ -773,7 +819,7 @@ uniforms[u_time].set(performance.now())
773
819
  Creates and links a WebGL program from vertex and fragment shader sources.
774
820
 
775
821
  ```typescript
776
- const program = createProgram(gl, vertexShaderSource, fragmentShaderSource)
822
+ const program = createProgram(gl, vertexShaderSource, fragmentShaderSource, { signal })
777
823
  ```
778
824
 
779
825
  ### 🖼️ createTexture
@@ -795,6 +841,7 @@ const texture = createTexture(
795
841
  wrapT: 'CLAMP_TO_EDGE',
796
842
  },
797
843
  data,
844
+ { signal },
798
845
  )
799
846
  ```
800
847
 
@@ -805,18 +852,26 @@ Automatically validates WebGL2-only formats and provides fallbacks for WebGL1.
805
852
  Creates a framebuffer with attached texture for render-to-texture operations.
806
853
 
807
854
  ```typescript
808
- const { framebuffer, texture } = createFramebuffer(gl, {
809
- width: 512,
810
- height: 512,
811
- attachment: 'color',
812
- internalFormat: 'RGBA',
813
- format: 'RGBA',
814
- type: 'UNSIGNED_BYTE',
815
- })
855
+ const { framebuffer, texture, dispose } = createFramebuffer(
856
+ gl,
857
+ {
858
+ width: 512,
859
+ height: 512,
860
+ attachment: 'color',
861
+ internalFormat: 'RGBA',
862
+ format: 'RGBA',
863
+ type: 'UNSIGNED_BYTE',
864
+ },
865
+ { signal },
866
+ )
816
867
  ```
817
868
 
818
869
  Supports color, depth, stencil, and combined depth-stencil attachments with completeness validation.
819
870
 
871
+ Pass a `texture` to attach one you already have — ping-ponging a pair of
872
+ framebuffers over shared textures, say. `dispose()` then deletes the framebuffer
873
+ and leaves that texture alone, because it is not the framebuffer's to delete.
874
+
820
875
  ## 🔍 WebGL Type Compatibility
821
876
 
822
877
  ### 🎯 Uniform Types
@@ -0,0 +1,19 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>view.gl</title>
6
+ <script>
7
+ // Redirect to index.html with the current path as a query parameter
8
+ const path = window.location.pathname.slice(1); // Remove leading slash
9
+ if (path) {
10
+ window.location.replace(window.location.origin + '/?redirect=' + path);
11
+ } else {
12
+ window.location.replace(window.location.origin);
13
+ }
14
+ </script>
15
+ </head>
16
+ <body>
17
+ Redirecting...
18
+ </body>
19
+ </html>
@@ -0,0 +1,292 @@
1
+ import { i as interleave, u as uniform, a as attribute, g as glsl, c as compile } from './tag-C0Vskpp8.js';
2
+ import { d as dom, c as cursor } from './utils-2dzuv_bW.js';
3
+
4
+ const canvas = dom("canvas", { parentElement: document.body });
5
+ canvas.width = window.innerWidth;
6
+ canvas.height = window.innerHeight;
7
+ const gl = canvas.getContext("webgl2", { antialias: true });
8
+ if (!gl) {
9
+ throw new Error("WebGL not supported");
10
+ }
11
+ const state = {
12
+ position: { x: 0, y: 0 },
13
+ // Pan offset in screen pixels
14
+ scale: 1,
15
+ // Zoom level
16
+ controlPoints: [
17
+ { x: 200, y: 400 },
18
+ // P0: Start point of the bezier curve
19
+ { x: 400, y: 100 },
20
+ // P1: Control point (influences curvature)
21
+ { x: 600, y: 400 }
22
+ // P2: End point of the bezier curve
23
+ ],
24
+ dragIndex: -1
25
+ // Index of control point being dragged (-1 = none)
26
+ };
27
+ function screenToWorld(screenX, screenY) {
28
+ return {
29
+ x: (screenX - state.position.x) / state.scale,
30
+ y: (screenY - state.position.y) / state.scale
31
+ };
32
+ }
33
+ function worldToScreen(worldX, worldY) {
34
+ return {
35
+ x: worldX * state.scale + state.position.x,
36
+ y: worldY * state.scale + state.position.y
37
+ };
38
+ }
39
+ function getControlPointAt(x, y, threshold = 20) {
40
+ for (let i = 0; i < state.controlPoints.length; i++) {
41
+ const screen = worldToScreen(state.controlPoints[i].x, state.controlPoints[i].y);
42
+ const dx = x - screen.x;
43
+ const dy = y - screen.y;
44
+ if (dx * dx + dy * dy < threshold * threshold) {
45
+ return i;
46
+ }
47
+ }
48
+ return -1;
49
+ }
50
+ canvas.addEventListener("pointerdown", (event) => {
51
+ const hitIndex = getControlPointAt(event.clientX, event.clientY);
52
+ if (hitIndex >= 0) {
53
+ state.dragIndex = hitIndex;
54
+ cursor(event, (event2) => {
55
+ const world = screenToWorld(event2.clientX, event2.clientY);
56
+ state.controlPoints[state.dragIndex] = world;
57
+ render();
58
+ });
59
+ } else {
60
+ cursor(event, (event2) => {
61
+ state.position.x -= event2.deltaX;
62
+ state.position.y -= event2.deltaY;
63
+ render();
64
+ });
65
+ }
66
+ });
67
+ canvas.addEventListener(
68
+ "wheel",
69
+ (event) => {
70
+ const rect = canvas.getBoundingClientRect();
71
+ const mouseX = event.clientX - rect.left;
72
+ const mouseY = event.clientY - rect.top;
73
+ const scaleFactor = event.deltaY > 0 ? 0.95 : 1.05;
74
+ const oldScale = state.scale;
75
+ const newScale = Math.max(0.1, Math.min(10, oldScale * scaleFactor));
76
+ const worldX = (mouseX - state.position.x) / oldScale;
77
+ const worldY = (mouseY - state.position.y) / oldScale;
78
+ state.scale = newScale;
79
+ state.position.x = mouseX - worldX * newScale;
80
+ state.position.y = mouseY - worldY * newScale;
81
+ render();
82
+ },
83
+ { passive: true }
84
+ );
85
+ window.addEventListener("resize", () => {
86
+ canvas.width = window.innerWidth;
87
+ canvas.height = window.innerHeight;
88
+ render();
89
+ });
90
+ const curveVertex = glsl`#version 300 es
91
+ // Attributes (per-vertex data)
92
+ ${interleave("data", [
93
+ attribute.float("a_t"),
94
+ attribute.float("a_side")
95
+ ])} // Bezier parameter t ∈ [0,1]
96
+
97
+ // Uniforms (shared data for all vertices)
98
+ ${uniform.vec2("u_resolution")} // Canvas size in pixels
99
+ ${uniform.vec2("u_offset")} // Pan offset in pixels
100
+ ${uniform.float("u_scale")} // Zoom level
101
+ ${uniform.vec2("u_p0")} // Control point P0 (start)
102
+ ${uniform.vec2("u_p1")} // Control point P1 (control)
103
+ ${uniform.vec2("u_p2")} // Control point P2 (end)
104
+ ${uniform.float("u_thickness")} // Curve thickness in world units
105
+
106
+ void main() {
107
+ // STEP 1: Evaluate the quadratic bezier curve at parameter t
108
+ // Quadratic bezier formula: B(t) = (1-t)²P0 + 2t(1-t)P1 + t²P2
109
+ float t = a_t;
110
+ float s = 1.0 - t; // s is often used for (1-t) for brevity
111
+
112
+ // Calculate the position on the curve
113
+ vec2 position = s * s * u_p0 + // (1-t)² * P0
114
+ 2.0 * s * t * u_p1 + // 2(1-t)t * P1
115
+ t * t * u_p2; // t² * P2
116
+
117
+ // STEP 2: Calculate the tangent vector at this point
118
+ // The derivative of the bezier curve gives us the tangent
119
+ // B'(t) = 2(1-t)(P1-P0) + 2t(P2-P1)
120
+ vec2 tangent = 2.0 * s * (u_p1 - u_p0) + 2.0 * t * (u_p2 - u_p1);
121
+
122
+ // STEP 3: Create a normal vector (perpendicular to tangent)
123
+ // Rotate tangent 90 degrees: (x,y) → (-y,x)
124
+ vec2 normal = normalize(vec2(-tangent.y, tangent.x));
125
+
126
+ // STEP 4: Offset the curve position to create thickness
127
+ // Move the vertex perpendicular to the curve by thickness * side
128
+ vec2 offsetPosition = position + normal * u_thickness * a_side;
129
+
130
+ // STEP 5: Transform from world space to screen space
131
+ vec2 screenPos = offsetPosition * u_scale + u_offset;
132
+
133
+ // STEP 6: Convert to clip space (OpenGL's coordinate system)
134
+ // Screen space: (0,0) is top-left, (width,height) is bottom-right
135
+ // Clip space: (-1,-1) is bottom-left, (1,1) is top-right
136
+ vec2 clipPos = (screenPos / u_resolution) * 2.0 - 1.0;
137
+ clipPos.y = -clipPos.y; // Flip Y to match screen coordinates
138
+
139
+ gl_Position = vec4(clipPos, 0, 1);
140
+ }`;
141
+ const curveFragment = glsl`#version 300 es
142
+ precision mediump float;
143
+ out vec4 fragColor;
144
+
145
+ void main() {
146
+ // Output a solid blue color for the curve
147
+ vec3 curveColor = vec3(0.2, 0.6, 1.0);
148
+ fragColor = vec4(curveColor, 1.0);
149
+ }`;
150
+ const pointVertex = glsl`#version 300 es
151
+ ${attribute.vec2("a_vertex")} // Quad vertex position (-1 to 1)
152
+ ${uniform.vec2("u_resolution")} // Canvas size
153
+ ${uniform.vec2("u_offset")} // Pan offset
154
+ ${uniform.float("u_scale")} // Zoom level
155
+ ${uniform.vec2("u_pointPos")} // Control point position in world space
156
+
157
+ void main() {
158
+ // Create a small square around the control point position
159
+ vec2 screenPos = (u_pointPos * u_scale + u_offset) + a_vertex * 8.0;
160
+
161
+ // Transform to clip space
162
+ vec2 clipPos = (screenPos / u_resolution) * 2.0 - 1.0;
163
+ clipPos.y = -clipPos.y;
164
+ gl_Position = vec4(clipPos, 0, 1);
165
+ }`;
166
+ const pointFragment = glsl`#version 300 es
167
+ precision mediump float;
168
+ out vec4 fragColor;
169
+
170
+ void main() {
171
+ // Red color for control points
172
+ fragColor = vec4(1.0, 0.3, 0.3, 1.0);
173
+ }`;
174
+ const lineVertex = glsl`#version 300 es
175
+ ${attribute.vec2("a_position")} // Line vertex position
176
+ ${uniform.vec2("u_resolution")} // Canvas size
177
+ ${uniform.vec2("u_offset")} // Pan offset
178
+ ${uniform.float("u_scale")} // Zoom level
179
+
180
+ void main() {
181
+ // Transform world position to screen space then clip space
182
+ vec2 screenPos = a_position * u_scale + u_offset;
183
+ vec2 clipPos = (screenPos / u_resolution) * 2.0 - 1.0;
184
+ clipPos.y = -clipPos.y;
185
+ gl_Position = vec4(clipPos, 0, 1);
186
+ }`;
187
+ const lineFragment = glsl`#version 300 es
188
+ precision mediump float;
189
+ out vec4 fragColor;
190
+
191
+ void main() {
192
+ // Semi-transparent gray for control polygon
193
+ fragColor = vec4(0.5, 0.5, 0.5, 0.5);
194
+ }`;
195
+ const { program: curveProgram, view: curveView } = compile(gl, curveVertex, curveFragment);
196
+ const { program: pointProgram, view: pointView } = compile(gl, pointVertex, pointFragment);
197
+ const { program: lineProgram, view: lineView } = compile(gl, lineVertex, lineFragment);
198
+ const curveSegments = 100;
199
+ function generateTriangleStrip() {
200
+ return new Float32Array(
201
+ function* () {
202
+ for (let i = 0; i <= curveSegments; i++) {
203
+ const t = i / curveSegments;
204
+ yield t;
205
+ yield -1;
206
+ yield t;
207
+ yield 1;
208
+ }
209
+ }()
210
+ );
211
+ }
212
+ const stripData = generateTriangleStrip();
213
+ curveView.interleavedAttributes.data.set(stripData);
214
+ const pointVertices = new Float32Array([
215
+ -1,
216
+ -1,
217
+ // Bottom-left
218
+ 1,
219
+ -1,
220
+ // Bottom-right
221
+ -1,
222
+ 1,
223
+ // Top-left
224
+ -1,
225
+ 1,
226
+ // Top-left (repeated for second triangle)
227
+ 1,
228
+ -1,
229
+ // Bottom-right (repeated for second triangle)
230
+ 1,
231
+ 1
232
+ // Top-right
233
+ ]);
234
+ pointView.attributes.a_vertex.set(pointVertices);
235
+ function updateLineData() {
236
+ const lineVerticesData = new Float32Array([
237
+ state.controlPoints[0].x,
238
+ state.controlPoints[0].y,
239
+ // P0
240
+ state.controlPoints[1].x,
241
+ state.controlPoints[1].y,
242
+ // P1
243
+ state.controlPoints[1].x,
244
+ state.controlPoints[1].y,
245
+ // P1 (repeated)
246
+ state.controlPoints[2].x,
247
+ state.controlPoints[2].y
248
+ // P2
249
+ ]);
250
+ lineView.attributes.a_position.set(lineVerticesData);
251
+ }
252
+ updateLineData();
253
+ function render() {
254
+ console.log("render!");
255
+ if (canvas.width !== window.innerWidth || canvas.height !== window.innerHeight) {
256
+ canvas.width = window.innerWidth;
257
+ canvas.height = window.innerHeight;
258
+ }
259
+ gl.viewport(0, 0, canvas.width, canvas.height);
260
+ gl.clearColor(0.1, 0.1, 0.15, 1);
261
+ gl.clear(gl.COLOR_BUFFER_BIT);
262
+ gl.enable(gl.BLEND);
263
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
264
+ updateLineData();
265
+ gl.useProgram(lineProgram);
266
+ lineView.attributes.a_position.bind();
267
+ lineView.uniforms.u_resolution.set(canvas.width, canvas.height);
268
+ lineView.uniforms.u_offset.set(state.position.x, state.position.y);
269
+ lineView.uniforms.u_scale.set(state.scale);
270
+ gl.drawArrays(gl.LINES, 0, 4);
271
+ gl.useProgram(curveProgram);
272
+ curveView.interleavedAttributes.data.bind();
273
+ curveView.uniforms.u_resolution.set(canvas.width, canvas.height);
274
+ curveView.uniforms.u_offset.set(state.position.x, state.position.y);
275
+ curveView.uniforms.u_scale.set(state.scale);
276
+ curveView.uniforms.u_p0.set(state.controlPoints[0].x, state.controlPoints[0].y);
277
+ curveView.uniforms.u_p1.set(state.controlPoints[1].x, state.controlPoints[1].y);
278
+ curveView.uniforms.u_p2.set(state.controlPoints[2].x, state.controlPoints[2].y);
279
+ curveView.uniforms.u_thickness.set(5);
280
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, (curveSegments + 1) * 2);
281
+ curveView.interleavedAttributes.data.unbind();
282
+ gl.useProgram(pointProgram);
283
+ pointView.attributes.a_vertex.bind();
284
+ pointView.uniforms.u_resolution.set(canvas.width, canvas.height);
285
+ pointView.uniforms.u_offset.set(state.position.x, state.position.y);
286
+ pointView.uniforms.u_scale.set(state.scale);
287
+ for (const point of state.controlPoints) {
288
+ pointView.uniforms.u_pointPos.set(point.x, point.y);
289
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
290
+ }
291
+ }
292
+ render();
@@ -0,0 +1,186 @@
1
+ import { b as createFramebuffer, u as uniform, g as glsl, c as compile } from './tag-C0Vskpp8.js';
2
+ import { d as dom } from './utils-2dzuv_bW.js';
3
+
4
+ let playing = false;
5
+ const canvas = dom("canvas");
6
+ const gl = canvas.getContext("webgl", { antialias: false });
7
+ if (!gl) {
8
+ throw new Error("WebGL not supported");
9
+ }
10
+ document.body.append(canvas);
11
+ function spray() {
12
+ return Math.floor(Math.random() * 8) * (Math.random() < 0.5 ? 1 : -1);
13
+ }
14
+ canvas.addEventListener("mousedown", (event) => {
15
+ const rect = canvas.getBoundingClientRect();
16
+ const controller = new AbortController();
17
+ playing = false;
18
+ window.addEventListener(
19
+ "mousemove",
20
+ (event2) => {
21
+ const x = Math.floor((event2.clientX - rect.left - spray()) * (canvas.width / rect.width));
22
+ const y = Math.floor((rect.bottom - event2.clientY - spray()) * (canvas.height / rect.height));
23
+ const dimensions = [2, 2];
24
+ const data = new Uint8Array(
25
+ function* () {
26
+ for (let i = 0; i < dimensions[0] * dimensions[1]; i++) {
27
+ yield 255;
28
+ yield 0;
29
+ yield 0;
30
+ yield 255;
31
+ }
32
+ }()
33
+ );
34
+ gl.bindTexture(gl.TEXTURE_2D, read.texture);
35
+ gl.texSubImage2D(
36
+ gl.TEXTURE_2D,
37
+ 0,
38
+ x,
39
+ y,
40
+ dimensions[0],
41
+ dimensions[1],
42
+ gl.RGBA,
43
+ gl.UNSIGNED_BYTE,
44
+ data
45
+ );
46
+ gl.bindTexture(gl.TEXTURE_2D, write.texture);
47
+ gl.texSubImage2D(
48
+ gl.TEXTURE_2D,
49
+ 0,
50
+ x,
51
+ y,
52
+ dimensions[0],
53
+ dimensions[1],
54
+ gl.RGBA,
55
+ gl.UNSIGNED_BYTE,
56
+ data
57
+ );
58
+ render();
59
+ },
60
+ controller
61
+ );
62
+ window.addEventListener(
63
+ "mouseup",
64
+ () => {
65
+ controller.abort();
66
+ playing = true;
67
+ animate();
68
+ },
69
+ controller
70
+ );
71
+ });
72
+ canvas.width = 256 * 2;
73
+ canvas.height = 256 * 2;
74
+ const WIDTH = canvas.width;
75
+ const HEIGHT = canvas.height;
76
+ const stepFragment = glsl`
77
+ precision mediump float;
78
+ varying vec2 v_uv;
79
+ ${uniform.sampler2D("u_texture")}
80
+ ${uniform.vec2("u_texelSize")}
81
+
82
+ int getCell(vec2 uv) {
83
+ // Wrap around (toroidal)
84
+ uv = mod(uv, 1.0);
85
+ float val = texture2D(u_texture, uv).r;
86
+ return int(step(0.5, val));
87
+ }
88
+
89
+ void main() {
90
+ vec2 uv = v_uv * 0.5 + 0.5;
91
+
92
+ int sum = 0;
93
+ // Iterate neighbors -1,0,1
94
+ for (int y = -1; y <= 1; y++) {
95
+ for (int x = -1; x <= 1; x++) {
96
+ if (x == 0 && y == 0) continue;
97
+ sum += getCell(uv + vec2(float(x), float(y)) * u_texelSize);
98
+ }
99
+ }
100
+ int current = getCell(uv);
101
+
102
+ int nextState = 0;
103
+ if (current == 1) {
104
+ if (sum == 2 || sum == 3) nextState = 1;
105
+ else nextState = 0;
106
+ } else {
107
+ if (sum == 3) nextState = 1;
108
+ else nextState = 0;
109
+ }
110
+
111
+ gl_FragColor = vec4(float(nextState), 0.0, 0.0, 1.0);
112
+ }`;
113
+ const renderFragment = glsl`
114
+ precision mediump float;
115
+ varying vec2 v_uv;
116
+ ${uniform.sampler2D("u_texture")}
117
+
118
+ void main() {
119
+ vec2 uv = v_uv * 0.5 + 0.5;
120
+ float cell = texture2D(u_texture, uv).r;
121
+ gl_FragColor = vec4(vec3(cell), 1.0);
122
+ }`;
123
+ const { program: stepProgram, view: stepView } = compile.toQuad(gl, stepFragment);
124
+ const { program: renderProgram, view: renderView } = compile.toQuad(gl, renderFragment);
125
+ const framebufferOptions = {
126
+ attachment: "color",
127
+ width: WIDTH,
128
+ height: HEIGHT,
129
+ type: "UNSIGNED_BYTE",
130
+ magFilter: "NEAREST",
131
+ minFilter: "NEAREST"
132
+ };
133
+ let read = createFramebuffer(gl, framebufferOptions);
134
+ let write = createFramebuffer(gl, framebufferOptions);
135
+ gl.bindTexture(gl.TEXTURE_2D, read.texture);
136
+ gl.texSubImage2D(
137
+ gl.TEXTURE_2D,
138
+ 0,
139
+ 0,
140
+ 0,
141
+ WIDTH,
142
+ HEIGHT,
143
+ gl.RGBA,
144
+ gl.UNSIGNED_BYTE,
145
+ new Uint8Array(
146
+ function* () {
147
+ for (let i = 0; i < WIDTH * HEIGHT; i++) {
148
+ yield 0;
149
+ yield 0;
150
+ yield 0;
151
+ yield 255;
152
+ }
153
+ }()
154
+ )
155
+ );
156
+ function step() {
157
+ gl.bindFramebuffer(gl.FRAMEBUFFER, write.framebuffer);
158
+ gl.viewport(0, 0, WIDTH, HEIGHT);
159
+ gl.useProgram(stepProgram);
160
+ gl.activeTexture(gl.TEXTURE0);
161
+ gl.bindTexture(gl.TEXTURE_2D, read.texture);
162
+ stepView.uniforms.u_texture.set(0);
163
+ stepView.uniforms.u_texelSize.set(1 / WIDTH, 1 / HEIGHT);
164
+ stepView.attributes.a_quad.bind();
165
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
166
+ }
167
+ function render() {
168
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
169
+ gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
170
+ gl.useProgram(renderProgram);
171
+ gl.activeTexture(gl.TEXTURE0);
172
+ gl.bindTexture(gl.TEXTURE_2D, write.texture);
173
+ renderView.uniforms.u_texture.set(0);
174
+ renderView.attributes.a_quad.bind();
175
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
176
+ }
177
+ function animate() {
178
+ if (playing) {
179
+ step();
180
+ render();
181
+ [read, write] = [write, read];
182
+ requestAnimationFrame(animate);
183
+ }
184
+ }
185
+ playing = true;
186
+ animate();