@bigmistqke/view.gl 0.1.8 → 0.1.10

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/.claude/settings.local.json +4 -8
  2. package/README.md +30 -0
  3. package/dev/src/examples/bezier.ts +3 -2
  4. package/dev/src/examples/game-of-life.ts +13 -30
  5. package/dev/src/examples/glsl-composition.ts +3 -3
  6. package/dev/src/examples/grid.ts +20 -14
  7. package/dev/src/examples/pix-sim.ts +29 -22
  8. package/dev/src/examples/ray-casting.ts +455 -0
  9. package/dev/src/examples/sdf.ts +98 -0
  10. package/dev/src/examples/symbol.ts +2 -2
  11. package/dev/src/index.ts +12 -11
  12. package/dev/src/utils.ts +18 -4
  13. package/dist/gl.d.mts +6 -0
  14. package/dist/gl.mjs +39 -0
  15. package/dist/gl.mjs.map +1 -0
  16. package/dist/index.d.mts +2 -2
  17. package/dist/index.mjs +27 -38
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/tag.d.mts +21 -4
  20. package/dist/tag.mjs +140 -110
  21. package/dist/tag.mjs.map +1 -1
  22. package/dist/{types-Bp9VAVe3.d.mts → types-DaV9McmR.d.mts} +18 -14
  23. package/package.json +10 -4
  24. package/src/gl.ts +45 -0
  25. package/src/index.ts +1 -1
  26. package/src/tag.ts +112 -44
  27. package/src/types.ts +36 -22
  28. package/src/utils/index.ts +0 -46
  29. package/test/tag.test.ts +462 -0
  30. package/tsup.config.ts +1 -1
  31. package/dev/dist/404.html +0 -19
  32. package/dev/dist/assets/bezier-DuLTb2d1.js +0 -294
  33. package/dev/dist/assets/game_of_life-D7qHdFcv.js +0 -202
  34. package/dev/dist/assets/grid-Ie22U_9K.js +0 -173
  35. package/dev/dist/assets/index-0qrXMlfv.js +0 -109
  36. package/dev/dist/assets/index-C2N3u8wj.css +0 -12
  37. package/dev/dist/assets/main-BZ1N6pp6.js +0 -32
  38. package/dev/dist/assets/overview-DAxtMIw5.js +0 -78
  39. package/dev/dist/assets/pix_sim-DIhzM_vu.js +0 -501
  40. package/dev/dist/assets/symbol-BouRL-Ni.js +0 -65
  41. package/dev/dist/assets/tag-C_bg62fD.js +0 -516
  42. package/dev/dist/assets/tetris-IMnkSi9p.js +0 -344
  43. package/dev/dist/assets/utils-CvCgsM08.js +0 -50
  44. package/dev/dist/index.html +0 -51
@@ -0,0 +1,455 @@
1
+ import { compile, glsl, uniform } from '../../../src/tag.ts'
2
+ import { dom } from '../utils.ts'
3
+
4
+ // Sphere data: [x, y, z, radius]
5
+ const sphereData = [
6
+ [0, 0, 0, 1.0], // Center sphere
7
+ [3, 1, -2, 0.8], // Right sphere
8
+ [-3, -1, 1, 0.9], // Left sphere
9
+ [1, 3, -1, 0.7], // Top sphere
10
+ [-2, -3, 2, 0.6], // Bottom sphere
11
+ [4, -2, -3, 0.5], // Far right sphere
12
+ [-4, 2, 3, 0.8], // Far left sphere
13
+ [0, -1, -4, 1.2], // Back sphere
14
+ ] as const
15
+
16
+ // Sphere colors (RGB)
17
+ const sphereColors = [
18
+ [1.0, 0.3, 0.3], // Red
19
+ [0.3, 1.0, 0.3], // Green
20
+ [0.3, 0.3, 1.0], // Blue
21
+ [1.0, 1.0, 0.3], // Yellow
22
+ [1.0, 0.3, 1.0], // Magenta
23
+ [0.3, 1.0, 1.0], // Cyan
24
+ [1.0, 0.7, 0.3], // Orange
25
+ [0.7, 0.3, 1.0], // Purple
26
+ ] as const
27
+
28
+ // Box data: [x, y, z, size] (size is half-extents for all dimensions)
29
+ const boxData = [
30
+ [2, 0, -1, 0.8], // Right box
31
+ [-2, 0, 1, 0.6], // Left box
32
+ [0, -2, -2, 0.7], // Bottom box
33
+ ] as const
34
+
35
+ // Box colors (RGB)
36
+ const boxColors = [
37
+ [0.8, 0.8, 0.2], // Yellow-green
38
+ [0.2, 0.8, 0.8], // Cyan
39
+ [0.8, 0.2, 0.8], // Magenta
40
+ ] as const
41
+
42
+ let SHAPE_COUNTER = 0
43
+
44
+ const controller = new AbortController()
45
+ const canvas = dom('canvas', { parentElement: document.body })
46
+
47
+ const gl = canvas.getContext('webgl2')!
48
+
49
+ const NUM_SPHERES = 8
50
+ const NUM_BOXES = 3
51
+
52
+ const SHAPE_SPHERE = Symbol('SHAPE_SPHERE')
53
+
54
+ const sphereModule = glsl`
55
+ #define NUM_SPHERES ${NUM_SPHERES}
56
+ #define ${SHAPE_SPHERE} ${SHAPE_COUNTER++}
57
+
58
+ struct Sphere {
59
+ vec3 position;
60
+ float radius;
61
+ vec3 color;
62
+ };
63
+
64
+ ${uniform.vec4('sphereData', { size: NUM_SPHERES })}
65
+ ${uniform.vec3('sphereColors', { size: NUM_SPHERES })}
66
+
67
+ float sphereIntersection(in vec3 rayOrigin, in vec3 rayDirection, in Sphere sphere)
68
+ {
69
+ // We're shooting a ray from the camera and checking if it hits a sphere
70
+ // Think of it like shining a laser pointer at a ball
71
+
72
+ vec3 rayToSphere = rayOrigin - sphere.position;
73
+
74
+ // Step 1: Find the closest point on the ray to the sphere's position
75
+ // This is like finding where the laser beam passes nearest to the ball's position
76
+ float closestApproach = dot(rayToSphere, rayDirection);
77
+
78
+ // Step 2: Check if we're already inside the sphere
79
+ // If negative, we're outside and pointing away from sphere position
80
+ float rayStartDistanceSquared = dot(rayToSphere, rayToSphere) - sphere.radius * sphere.radius;
81
+
82
+ // Step 3: Use geometry to check if the ray can reach the sphere
83
+ // If this value is negative, the ray misses the sphere entirely
84
+ float canReachSphere = closestApproach * closestApproach - rayStartDistanceSquared;
85
+
86
+ if(canReachSphere < 0.0) {
87
+ return -1.0; // Ray misses the sphere
88
+ }
89
+
90
+ // Step 4: Calculate how far along the ray we hit the sphere
91
+ // We want the nearest intersection point (there can be two when ray goes through)
92
+ float hitDistance = -closestApproach - sqrt(canReachSphere);
93
+
94
+ return hitDistance;
95
+ }
96
+
97
+ // Calculate normal vector at hit point on sphere surface
98
+ vec3 sphereNormal(in vec3 hitPosition, in Sphere sphere)
99
+ {
100
+ return (hitPosition - sphere.position) / sphere.radius;
101
+ }
102
+
103
+ // Find closest sphere intersection, returns distance and sets hitIndex and hitType
104
+ float findClosestSphere(in vec3 rayOrigin, in vec3 rayDirection, in float maxDistance, out int hitIndex, out int hitType)
105
+ {
106
+ float closestDistance = maxDistance;
107
+ hitIndex = -1;
108
+ hitType = -1;
109
+
110
+ for(int i = 0; i < NUM_SPHERES; i++) {
111
+ Sphere sphere = Sphere(sphereData[i].xyz, sphereData[i].w, sphereColors[i]);
112
+ float distance = sphereIntersection(rayOrigin, rayDirection, sphere);
113
+
114
+ if(distance > 0.0 && distance < closestDistance) {
115
+ closestDistance = distance;
116
+ hitIndex = i;
117
+ hitType = ${SHAPE_SPHERE};
118
+ }
119
+ }
120
+
121
+ return hitIndex >= 0 ? closestDistance : -1.0;
122
+ }
123
+
124
+ // Calculate sphere normal and color at hit point
125
+ void calculateSphereHit(in vec3 hitPosition, in int hitIndex, out vec3 normal, out vec3 color)
126
+ {
127
+ Sphere hitSphere = Sphere(sphereData[hitIndex].xyz, sphereData[hitIndex].w, sphereColors[hitIndex]);
128
+ normal = sphereNormal(hitPosition, hitSphere);
129
+ color = sphereColors[hitIndex];
130
+ }`
131
+
132
+ const SHAPE_BOX = Symbol('SHAPE_BOX')
133
+
134
+ const boxModule = glsl`
135
+ #define ${SHAPE_BOX} ${SHAPE_COUNTER++}
136
+ #define NUM_BOXES ${NUM_BOXES}
137
+
138
+ struct Box {
139
+ vec3 position;
140
+ float size;
141
+ vec3 color;
142
+ };
143
+
144
+ ${uniform.vec4('boxData', { size: NUM_BOXES })}
145
+ ${uniform.vec3('boxColors', { size: NUM_BOXES })}
146
+
147
+ float boxIntersection(in vec3 rayOrigin, in vec3 rayDirection, in Box box)
148
+ {
149
+ // Ray-box intersection using slab method
150
+ // A box is defined by 6 planes, we find where the ray enters and exits each pair of parallel planes
151
+
152
+ vec3 boxMin = box.position - vec3(box.size);
153
+ vec3 boxMax = box.position + vec3(box.size);
154
+
155
+ // Calculate intersection distances for each axis
156
+ vec3 invDir = 1.0 / rayDirection;
157
+ vec3 t1 = (boxMin - rayOrigin) * invDir;
158
+ vec3 t2 = (boxMax - rayOrigin) * invDir;
159
+
160
+ // Find the min and max for each axis (handles negative direction)
161
+ vec3 tMin = min(t1, t2);
162
+ vec3 tMax = max(t1, t2);
163
+
164
+ // The ray enters the box at the maximum of all tMin values
165
+ float tNear = max(max(tMin.x, tMin.y), tMin.z);
166
+
167
+ // The ray exits the box at the minimum of all tMax values
168
+ float tFar = min(min(tMax.x, tMax.y), tMax.z);
169
+
170
+ // If tNear > tFar, ray misses the box
171
+ // If tFar < 0, box is behind the ray
172
+ if(tNear > tFar || tFar < 0.0) {
173
+ return -1.0;
174
+ }
175
+
176
+ // Return the nearest intersection (tNear, unless we're inside the box)
177
+ return tNear > 0.0 ? tNear : tFar;
178
+ }
179
+
180
+ // Calculate normal vector at hit point on box surface
181
+ vec3 boxNormal(in vec3 hitPosition, in Box box)
182
+ {
183
+ // Find which face of the box we hit by checking which coordinate is closest to the box boundary
184
+ vec3 localPos = hitPosition - box.position;
185
+ vec3 absLocal = abs(localPos);
186
+
187
+ // Return normal for the face that's closest to the box size
188
+ if(absLocal.x > absLocal.y && absLocal.x > absLocal.z) {
189
+ return vec3(sign(localPos.x), 0.0, 0.0);
190
+ } else if(absLocal.y > absLocal.z) {
191
+ return vec3(0.0, sign(localPos.y), 0.0);
192
+ } else {
193
+ return vec3(0.0, 0.0, sign(localPos.z));
194
+ }
195
+ }
196
+
197
+ // Find closest box intersection, returns distance and sets hitIndex and hitType
198
+ float findClosestBox(in vec3 rayOrigin, in vec3 rayDirection, in float maxDistance, out int hitIndex, out int hitType)
199
+ {
200
+ float closestDistance = maxDistance;
201
+ hitIndex = -1;
202
+ hitType = -1;
203
+
204
+ for(int i = 0; i < NUM_BOXES; i++) {
205
+ Box box = Box(boxData[i].xyz, boxData[i].w, boxColors[i]);
206
+ float distance = boxIntersection(rayOrigin, rayDirection, box);
207
+
208
+ if(distance > 0.0 && distance < closestDistance) {
209
+ closestDistance = distance;
210
+ hitIndex = i;
211
+ hitType = ${SHAPE_BOX};
212
+ }
213
+ }
214
+
215
+ return hitIndex >= 0 ? closestDistance : -1.0;
216
+ }
217
+
218
+ // Calculate box normal and color at hit point
219
+ void calculateBoxHit(in vec3 hitPosition, in int hitIndex, out vec3 normal, out vec3 color)
220
+ {
221
+ Box hitBox = Box(boxData[hitIndex].xyz, boxData[hitIndex].w, boxColors[hitIndex]);
222
+ normal = boxNormal(hitPosition, hitBox);
223
+ color = boxColors[hitIndex];
224
+ }
225
+
226
+ `
227
+
228
+ const fragment = glsl`#version 300 es
229
+ precision mediump float;
230
+
231
+ struct HitInfo {
232
+ bool hit;
233
+ float distance;
234
+ vec3 position;
235
+ vec3 normal;
236
+ vec3 color;
237
+ };
238
+
239
+ ${sphereModule}
240
+ ${boxModule}
241
+
242
+ ${uniform.float('aspectRatio')}
243
+
244
+ in vec2 v_uv;
245
+ out vec4 fragColor;
246
+
247
+ vec3 lightPos = vec3(5.0, 5.0, 5.0);
248
+
249
+ vec3 calculateLighting(in vec3 position, in vec3 normal, in vec3 color)
250
+ {
251
+ vec3 lightDirection = normalize(lightPos - position);
252
+ float diffuse = max(0.0, dot(normal, lightDirection));
253
+ return color * (diffuse * 0.8 + 0.2); // 80% diffuse + 20% ambient
254
+ }
255
+
256
+ void main()
257
+ {
258
+ // Ray from camera through pixel
259
+ vec3 rayOrigin = vec3(0.0, 0.0, 4.0);
260
+ vec3 rayDirection = normalize(vec3(v_uv.x * aspectRatio, v_uv.y, -1.0));
261
+
262
+ // Find closest intersection across all shapes
263
+ float closestDistance = 1000.0;
264
+ int hitType = -1;
265
+ int hitIndex = -1;
266
+
267
+ // Check spheres
268
+ int sphereIndex, sphereType;
269
+ float sphereDistance = findClosestSphere(rayOrigin, rayDirection, closestDistance, sphereIndex, sphereType);
270
+ if(sphereDistance > 0.0) {
271
+ closestDistance = sphereDistance;
272
+ hitType = sphereType;
273
+ hitIndex = sphereIndex;
274
+ }
275
+
276
+ // Check boxes
277
+ int boxIndex, boxType;
278
+ float boxDistance = findClosestBox(rayOrigin, rayDirection, closestDistance, boxIndex, boxType);
279
+ if(boxDistance > 0.0) {
280
+ closestDistance = boxDistance;
281
+ hitType = boxType;
282
+ hitIndex = boxIndex;
283
+ }
284
+
285
+ vec3 color = vec3(0.1, 0.1, 0.2); // Background color
286
+
287
+ // Calculate normal and lighting only for the closest hit
288
+ if(hitType >= 0) {
289
+ vec3 hitPosition = rayOrigin + closestDistance * rayDirection;
290
+ vec3 normal;
291
+ vec3 hitColor;
292
+
293
+ switch(hitType) {
294
+ case ${SHAPE_SPHERE}:
295
+ calculateSphereHit(hitPosition, hitIndex, normal, hitColor);
296
+ color = calculateLighting(hitPosition, normal, hitColor);
297
+ break;
298
+ case ${SHAPE_BOX}:
299
+ calculateBoxHit(hitPosition, hitIndex, normal, hitColor);
300
+ color = calculateLighting(hitPosition, normal, hitColor);
301
+ break;
302
+ }
303
+ }
304
+
305
+ // Gamma correction
306
+ color = pow(color, vec3(1.0 / 2.2));
307
+
308
+ fragColor = vec4(color, 1.0);
309
+ }`
310
+
311
+ const { program, view } = compile.toQuad(gl, fragment)
312
+
313
+ gl.useProgram(program)
314
+
315
+ // Bind quad buffer
316
+ view.attributes.a_quad.bind()
317
+
318
+ // Create typed arrays for sphere data and colors
319
+ const spheresArray = new Float32Array(
320
+ (function* () {
321
+ for (const sphere of sphereData) {
322
+ yield sphere[0]
323
+ yield sphere[1]
324
+ yield sphere[2]
325
+ yield sphere[3]
326
+ }
327
+ })(),
328
+ )
329
+ const sphereColorsArray = new Float32Array(
330
+ (function* () {
331
+ for (const color of sphereColors) {
332
+ yield color[0]
333
+ yield color[1]
334
+ yield color[2]
335
+ }
336
+ })(),
337
+ )
338
+
339
+ // Create typed arrays for box data and colors
340
+ const boxesArray = new Float32Array(
341
+ (function* () {
342
+ for (const box of boxData) {
343
+ yield box[0]
344
+ yield box[1]
345
+ yield box[2]
346
+ yield box[3]
347
+ }
348
+ })(),
349
+ )
350
+ const boxColorsArray = new Float32Array(
351
+ (function* () {
352
+ for (const color of boxColors) {
353
+ yield color[0]
354
+ yield color[1]
355
+ yield color[2]
356
+ }
357
+ })(),
358
+ )
359
+
360
+ // Set the uniform arrays
361
+ view.uniforms.sphereData.set(spheresArray)
362
+ view.uniforms.sphereColors.set(sphereColorsArray)
363
+ view.uniforms.boxData.set(boxesArray)
364
+ view.uniforms.boxColors.set(boxColorsArray)
365
+
366
+ // Set initial aspect ratio
367
+ view.uniforms.aspectRatio.set(canvas.width / canvas.height)
368
+
369
+ function animate(timestamp: number) {
370
+ const time = timestamp / 1000
371
+
372
+ // Make the center sphere pulse
373
+ const pulseFactor = 0.8 + 0.4 * Math.sin(time * 2)
374
+ spheresArray[3] = pulseFactor // Update radius of sphere 0
375
+
376
+ // Orbit some spheres
377
+ const orbitRadius = 2.5
378
+
379
+ // Update sphere 1
380
+ spheresArray[4] = Math.cos(time * 0.8) * orbitRadius
381
+ spheresArray[5] = Math.sin(time * 1.2) * 1.5
382
+ spheresArray[6] = Math.sin(time * 0.8) * orbitRadius
383
+
384
+ // Update sphere 2
385
+ spheresArray[8] = Math.cos(time * 0.6 + Math.PI) * orbitRadius
386
+ spheresArray[9] = Math.cos(time * 0.9) * 1.2
387
+ spheresArray[10] = Math.sin(time * 0.6 + Math.PI) * orbitRadius
388
+
389
+ // Sphere 3 - vertical bobbing
390
+ spheresArray[13] = 1 + Math.sin(time * 1.5) * 2 // y position
391
+
392
+ // Sphere 4 - figure-8 motion
393
+ spheresArray[16] = Math.sin(time * 0.7) * 2.5 // x
394
+ spheresArray[17] = Math.sin(time * 1.4) * 1.5 // y
395
+ spheresArray[18] = -2 + Math.cos(time * 0.7) // z
396
+
397
+ // Sphere 5 - circular motion + size change
398
+ spheresArray[20] = Math.cos(time) * 3.5 // x
399
+ spheresArray[22] = Math.sin(time) * 3.5 // z
400
+ spheresArray[23] = 0.4 + 0.2 * Math.sin(time * 3) // radius
401
+
402
+ // Sphere 6 - diagonal wave motion
403
+ spheresArray[24] = -4 + Math.sin(time * 0.8) * 1.5 // x
404
+ spheresArray[25] = 2 + Math.sin(time * 1.2) * 1 // y
405
+
406
+ // Sphere 7 - rotating in back
407
+ spheresArray[28] = Math.cos(time * 0.5) * 2 // x
408
+ spheresArray[29] = -1 + Math.sin(time * 0.5) * 0.5 // y
409
+
410
+ // Animate colors for more spheres
411
+ sphereColorsArray[0] = 0.5 + 0.5 * Math.sin(time * 3) // Red channel of sphere 0
412
+ sphereColorsArray[4] = 0.5 + 0.5 * Math.cos(time * 2) // Green channel of sphere 1
413
+ sphereColorsArray[9] = 0.5 + 0.5 * Math.sin(time * 2.5) // Blue channel of sphere 3
414
+ sphereColorsArray[15] = 0.3 + 0.7 * Math.abs(Math.sin(time * 1.8)) // All channels sphere 5 (pulsing cyan)
415
+ sphereColorsArray[16] = 0.3 + 0.7 * Math.abs(Math.sin(time * 1.8))
416
+ sphereColorsArray[17] = 0.3 + 0.7 * Math.abs(Math.sin(time * 1.8))
417
+
418
+ // Animate some boxes
419
+ // Box 0 - rotate around Y axis
420
+ boxesArray[0] = 2 + Math.cos(time * 0.7) * 0.5 // x
421
+ boxesArray[2] = -1 + Math.sin(time * 0.7) * 0.5 // z
422
+
423
+ // Box 1 - scale up and down
424
+ boxesArray[7] = 0.6 + 0.3 * Math.sin(time * 2) // size
425
+
426
+ // Update the uniform arrays with the animated data
427
+ view.uniforms.sphereData?.set(spheresArray)
428
+ view.uniforms.sphereColors?.set(sphereColorsArray)
429
+ view.uniforms.boxData?.set(boxesArray)
430
+ view.uniforms.boxColors?.set(boxColorsArray)
431
+ }
432
+
433
+ // Animation loop
434
+ function draw(timestamp: number) {
435
+ if (controller.signal.aborted) return
436
+
437
+ animate(timestamp)
438
+
439
+ gl.drawArrays(gl.TRIANGLES, 0, 6)
440
+
441
+ requestAnimationFrame(draw)
442
+ }
443
+
444
+ requestAnimationFrame(draw)
445
+
446
+ // Handle canvas resizing
447
+ new ResizeObserver(() => {
448
+ canvas.width = window.innerWidth
449
+ canvas.height = window.innerHeight
450
+ gl.viewport(0, 0, canvas.width, canvas.height)
451
+
452
+ // Update aspect ratio uniform
453
+ view.uniforms.aspectRatio.set(canvas.width / canvas.height)
454
+ draw(performance.now())
455
+ }).observe(document.body)
@@ -0,0 +1,98 @@
1
+ import { attribute, compile, glsl, uniform } from 'view.gl/tag'
2
+ import { GLSLToken, GLSLToView, View } from '../../../src'
3
+ import { dom } from '../utils'
4
+
5
+ function createModule<TTag>(callback: (symbol: symbol) => TTag) {
6
+ const symbol = Symbol()
7
+ return {
8
+ pointer: symbol,
9
+ module: callback(symbol),
10
+ }
11
+ }
12
+ const controller = new AbortController()
13
+ const canvas = dom('canvas', { parentElement: document.body }) // (<canvas />) as HTMLCanvasElement;
14
+
15
+ const gl = canvas.getContext('webgl2')!
16
+ gl.viewport(0, 0, canvas.width, canvas.height)
17
+
18
+ new ResizeObserver(() => {
19
+ canvas.width = window.innerWidth
20
+ canvas.height = window.innerHeight
21
+ gl.viewport(0, 0, canvas.width, canvas.height)
22
+ draw(performance.now())
23
+ }).observe(canvas)
24
+
25
+ const circle = createModule(
26
+ symbol => glsl`
27
+ ${uniform.vec2('u_base')}
28
+
29
+ bool ${symbol}(vec2 position){
30
+ return distance(position + u_base, vec2(0., 0.)) < 1.0;
31
+ }`,
32
+ )
33
+
34
+ const vertex = glsl`
35
+ precision mediump float;
36
+
37
+ ${attribute.vec2('a_vertex')}
38
+
39
+ varying vec2 position;
40
+
41
+ void main() {
42
+ position = a_vertex;
43
+ gl_Position = vec4(position, 0.0, 1.0);
44
+ }`
45
+
46
+ const fragment = glsl`
47
+ precision mediump float;
48
+ varying vec2 position;
49
+ ${uniform.vec3('u_color')}
50
+
51
+ ${circle.module}
52
+
53
+ void main() {
54
+ if(${circle.pointer}(position) == false){
55
+ discard;
56
+ }
57
+ gl_FragColor = vec4(${'u_color'}, 1.0);
58
+ }`
59
+
60
+ const {
61
+ program,
62
+ view: { attributes, uniforms },
63
+ } = compile(gl, vertex, fragment)
64
+
65
+ gl.useProgram(program)
66
+
67
+ uniforms.u_color.set(0, 255, 0)
68
+
69
+ uniforms.u_base.set(1, 1)
70
+
71
+ // Create triangle vertex buffer
72
+ attributes.a_vertex.set(new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1])).bind()
73
+
74
+ function draw(delta: number) {
75
+ if (controller.signal.aborted) return
76
+ requestAnimationFrame(draw)
77
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, 100)
78
+ }
79
+ requestAnimationFrame(draw)
80
+
81
+ abstract class Module {
82
+ symbol = Symbol()
83
+ abstract view?: View
84
+ abstract template: GLSLToken
85
+ }
86
+
87
+ class CircleModule extends Module {
88
+ view?: GLSLToView<typeof this.template>
89
+ base = Symbol()
90
+ template = glsl`
91
+ ${uniform.vec2(this.base)}
92
+ bool ${this.symbol}(vec2 position){
93
+ return distance(position + ${this.base}, vec2(0., 0.)) < 1.0;
94
+ }`
95
+ setBase(x: number, y: number) {
96
+ this.view?.uniforms[this.base]?.set(x, y)
97
+ }
98
+ }
@@ -1,7 +1,7 @@
1
1
  import { attribute, compile, glsl, uniform } from 'view.gl/tag'
2
- import { createElement } from '../utils'
2
+ import { dom } from '../utils'
3
3
 
4
- const canvas = createElement('canvas', {
4
+ const canvas = dom('canvas', {
5
5
  width: window.innerWidth,
6
6
  height: window.innerHeight,
7
7
  parentElement: document.body,
package/dev/src/index.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import css from './index.css?url'
2
- import { createElement } from './utils'
2
+ import { dom } from './utils'
3
3
 
4
4
  const iframe = document.querySelector('iframe')!
5
5
  const nav = document.querySelector('nav')!
6
6
 
7
- createElement('h3', { innerText: 'Examples', parentElement: nav })
7
+ nav.append(dom('h3', { innerText: 'Examples' }))
8
8
 
9
9
  const urls = Object.fromEntries(
10
10
  Object.entries(import.meta.glob('./examples/*.ts', { eager: false })).map(([key, entry]) => {
@@ -15,15 +15,16 @@ const urls = Object.fromEntries(
15
15
  )
16
16
 
17
17
  Object.entries(urls).forEach(([name, localUrl]) => {
18
- createElement('button', {
19
- onclick(event) {
20
- event.preventDefault()
21
- load(name, localUrl)
22
- },
23
- innerHTML: name.replaceAll('-', ' '),
24
- parentElement: nav,
25
- 'data-route': name,
26
- })
18
+ nav.append(
19
+ dom('button', {
20
+ onclick(event) {
21
+ event.preventDefault()
22
+ load(name, localUrl)
23
+ },
24
+ innerHTML: name.replaceAll('-', ' '),
25
+ 'data-route': name,
26
+ }),
27
+ )
27
28
  })
28
29
 
29
30
  function load(name: string, url: string) {
package/dev/src/utils.ts CHANGED
@@ -43,25 +43,39 @@ export function cursor(event: PointerEvent, callback: (event: CursorEvent) => vo
43
43
  return promise
44
44
  }
45
45
 
46
- export function createElement<T extends keyof HTMLElementTagNameMap>(
46
+ export function dom<T extends keyof HTMLElementTagNameMap>(
47
47
  tag: T,
48
48
  {
49
49
  parentElement,
50
50
  ...options
51
- }: Partial<Omit<HTMLElementTagNameMap[T], 'style'> & { style: string }> &
51
+ }: Partial<Omit<HTMLElementTagNameMap[T], 'style'> & { style: Partial<CSSStyleDeclaration> }> &
52
52
  Record<`data-${string}`, string> = {},
53
53
  ) {
54
54
  const element = document.createElement(tag)
55
55
 
56
56
  for (const key in options) {
57
57
  if (key.startsWith('data-')) {
58
- element.setAttribute(key, options[key])
58
+ element.setAttribute(
59
+ key,
60
+ // @ts-expect-error
61
+ options[key],
62
+ )
63
+ } else if (key == 'style') {
64
+ if (typeof options.style === 'string') {
65
+ element.style = options.style
66
+ } else {
67
+ for (const prop in options.style!) {
68
+ // @ts-expect-error
69
+ element.style[prop] = options.style![prop as any]
70
+ }
71
+ }
59
72
  } else {
73
+ // @ts-expect-error
60
74
  element[key] = options[key]
61
75
  }
62
76
  }
63
77
 
64
- ;(parentElement ?? document.body).appendChild(element)
78
+ parentElement?.appendChild(element)
65
79
 
66
80
  return element
67
81
  }
package/dist/gl.d.mts ADDED
@@ -0,0 +1,6 @@
1
+ import { G as GL } from './types-DaV9McmR.mjs';
2
+
3
+ declare function createShader(gl: GL, type: number, source: string): WebGLShader;
4
+ declare function createProgram(gl: GL, vertexSource: string, fragmentSource: string): WebGLProgram;
5
+
6
+ export { createProgram, createShader };
package/dist/gl.mjs ADDED
@@ -0,0 +1,39 @@
1
+ // src/gl.ts
2
+ function createShader(gl, type, source) {
3
+ const shader = gl.createShader(type);
4
+ if (!shader) throw new Error("Failed to create shader");
5
+ gl.shaderSource(shader, source);
6
+ gl.compileShader(shader);
7
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
8
+ const info = gl.getShaderInfoLog(shader);
9
+ gl.deleteShader(shader);
10
+ throw new Error(
11
+ `Failed to compile ${type === gl.VERTEX_SHADER ? "vertex" : "fragment"} shader: ${info}`
12
+ );
13
+ }
14
+ return shader;
15
+ }
16
+ function createProgram(gl, vertexSource, fragmentSource) {
17
+ const program = gl.createProgram();
18
+ if (!program) throw new Error("Failed to create WebGL program");
19
+ const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
20
+ const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
21
+ gl.attachShader(program, vertexShader);
22
+ gl.attachShader(program, fragmentShader);
23
+ gl.linkProgram(program);
24
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
25
+ const info = gl.getProgramInfoLog(program);
26
+ gl.deleteProgram(program);
27
+ gl.deleteShader(vertexShader);
28
+ gl.deleteShader(fragmentShader);
29
+ throw new Error(`Failed to link program: ${info}`);
30
+ }
31
+ gl.deleteShader(vertexShader);
32
+ gl.deleteShader(fragmentShader);
33
+ return program;
34
+ }
35
+ export {
36
+ createProgram,
37
+ createShader
38
+ };
39
+ //# sourceMappingURL=gl.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/gl.ts"],"sourcesContent":["import { GL } from \"./types\"\n\nexport function createShader(gl: GL, type: number, source: string): WebGLShader {\n const shader = gl.createShader(type)\n if (!shader) throw new Error('Failed to create shader')\n\n gl.shaderSource(shader, source)\n gl.compileShader(shader)\n\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const info = gl.getShaderInfoLog(shader)\n gl.deleteShader(shader)\n throw new Error(\n `Failed to compile ${type === gl.VERTEX_SHADER ? 'vertex' : 'fragment'} shader: ${info}`,\n )\n }\n\n return shader\n}\n\nexport function createProgram(gl: GL, vertexSource: string, fragmentSource: string): WebGLProgram {\n const program = gl.createProgram()\n if (!program) throw new Error('Failed to create WebGL program')\n\n const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource)\n const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)\n\n gl.attachShader(program, vertexShader)\n gl.attachShader(program, fragmentShader)\n gl.linkProgram(program)\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const info = gl.getProgramInfoLog(program)\n gl.deleteProgram(program)\n gl.deleteShader(vertexShader)\n gl.deleteShader(fragmentShader)\n throw new Error(`Failed to link program: ${info}`)\n }\n\n // Clean up shaders after linking\n gl.deleteShader(vertexShader)\n gl.deleteShader(fragmentShader)\n\n return program\n}"],"mappings":";AAEO,SAAS,aAAa,IAAQ,MAAc,QAA6B;AAC9E,QAAM,SAAS,GAAG,aAAa,IAAI;AACnC,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,yBAAyB;AAEtD,KAAG,aAAa,QAAQ,MAAM;AAC9B,KAAG,cAAc,MAAM;AAEvB,MAAI,CAAC,GAAG,mBAAmB,QAAQ,GAAG,cAAc,GAAG;AACrD,UAAM,OAAO,GAAG,iBAAiB,MAAM;AACvC,OAAG,aAAa,MAAM;AACtB,UAAM,IAAI;AAAA,MACR,qBAAqB,SAAS,GAAG,gBAAgB,WAAW,UAAU,YAAY,IAAI;AAAA,IACxF;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,cAAc,IAAQ,cAAsB,gBAAsC;AAChG,QAAM,UAAU,GAAG,cAAc;AACjC,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,gCAAgC;AAE9D,QAAM,eAAe,aAAa,IAAI,GAAG,eAAe,YAAY;AACpE,QAAM,iBAAiB,aAAa,IAAI,GAAG,iBAAiB,cAAc;AAE1E,KAAG,aAAa,SAAS,YAAY;AACrC,KAAG,aAAa,SAAS,cAAc;AACvC,KAAG,YAAY,OAAO;AAEtB,MAAI,CAAC,GAAG,oBAAoB,SAAS,GAAG,WAAW,GAAG;AACpD,UAAM,OAAO,GAAG,kBAAkB,OAAO;AACzC,OAAG,cAAc,OAAO;AACxB,OAAG,aAAa,YAAY;AAC5B,OAAG,aAAa,cAAc;AAC9B,UAAM,IAAI,MAAM,2BAA2B,IAAI,EAAE;AAAA,EACnD;AAGA,KAAG,aAAa,YAAY;AAC5B,KAAG,aAAa,cAAc;AAE9B,SAAO;AACT;","names":[]}