@bigmistqke/view.gl 0.1.13 → 0.1.15

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 (38) hide show
  1. package/.claude/settings.local.json +4 -9
  2. package/.vscode/settings.json +2 -0
  3. package/dist/{chunk-Z4F7UTKN.mjs → chunk-4YLGYQ3G.mjs} +36 -111
  4. package/dist/chunk-4YLGYQ3G.mjs.map +1 -0
  5. package/dist/chunk-BZU5QEWD.mjs +72 -0
  6. package/dist/chunk-BZU5QEWD.mjs.map +1 -0
  7. package/dist/gl.d.mts +12 -0
  8. package/dist/gl.mjs +13 -0
  9. package/dist/gl.mjs.map +1 -0
  10. package/dist/index.d.mts +2 -2
  11. package/dist/index.mjs +2 -1
  12. package/dist/tag.d.mts +1 -1
  13. package/dist/tag.mjs +4 -2
  14. package/dist/tag.mjs.map +1 -1
  15. package/dist/{types-Cggy6Gkm.d.mts → types-lm9fTTy8.d.mts} +4 -1
  16. package/package.json +5 -1
  17. package/src/gl.ts +128 -0
  18. package/src/index.ts +11 -19
  19. package/src/tag.ts +2 -1
  20. package/src/{utils/index.ts → utils.ts} +29 -47
  21. package/tsup.config.ts +1 -1
  22. package/dev/dist/404.html +0 -19
  23. package/dev/dist/assets/bezier-DuLTb2d1.js +0 -294
  24. package/dev/dist/assets/game_of_life-D7qHdFcv.js +0 -202
  25. package/dev/dist/assets/grid-Ie22U_9K.js +0 -173
  26. package/dev/dist/assets/index-0qrXMlfv.js +0 -109
  27. package/dev/dist/assets/index-C2N3u8wj.css +0 -12
  28. package/dev/dist/assets/main-BZ1N6pp6.js +0 -32
  29. package/dev/dist/assets/overview-DAxtMIw5.js +0 -78
  30. package/dev/dist/assets/pix_sim-DIhzM_vu.js +0 -501
  31. package/dev/dist/assets/symbol-BouRL-Ni.js +0 -65
  32. package/dev/dist/assets/tag-C_bg62fD.js +0 -516
  33. package/dev/dist/assets/tetris-IMnkSi9p.js +0 -344
  34. package/dev/dist/assets/utils-CvCgsM08.js +0 -50
  35. package/dev/dist/index.html +0 -51
  36. package/dist/chunk-Z4F7UTKN.mjs.map +0 -1
  37. package/src/features.ts +0 -60
  38. package/src/utils/object.ts +0 -27
package/src/gl.ts ADDED
@@ -0,0 +1,128 @@
1
+ import { RemoveSuffix } from './type-utils'
2
+ import type { GL } from './types'
3
+
4
+ /**********************************************************************************/
5
+ /* */
6
+ /* Create Shader */
7
+ /* */
8
+ /**********************************************************************************/
9
+
10
+ export function createShader(gl: GL, type: number, source: string): WebGLShader {
11
+ const shader = gl.createShader(type)
12
+ if (!shader) throw new Error('Failed to create shader')
13
+
14
+ gl.shaderSource(shader, source)
15
+ gl.compileShader(shader)
16
+
17
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
18
+ const info = gl.getShaderInfoLog(shader)
19
+ gl.deleteShader(shader)
20
+ throw new Error(
21
+ `Failed to compile ${type === gl.VERTEX_SHADER ? 'vertex' : 'fragment'} shader: ${info}`,
22
+ )
23
+ }
24
+
25
+ return shader
26
+ }
27
+
28
+ /**********************************************************************************/
29
+ /* */
30
+ /* Create Program */
31
+ /* */
32
+ /**********************************************************************************/
33
+
34
+ export function createProgram(gl: GL, vertexSource: string, fragmentSource: string): WebGLProgram {
35
+ const program = gl.createProgram()
36
+ if (!program) throw new Error('Failed to create WebGL program')
37
+
38
+ const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource)
39
+ const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)
40
+
41
+ gl.attachShader(program, vertexShader)
42
+ gl.attachShader(program, fragmentShader)
43
+ gl.linkProgram(program)
44
+
45
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
46
+ const info = gl.getProgramInfoLog(program)
47
+ gl.deleteProgram(program)
48
+ gl.deleteShader(vertexShader)
49
+ gl.deleteShader(fragmentShader)
50
+ throw new Error(`Failed to link program: ${info}`)
51
+ }
52
+
53
+ // Clean up shaders after linking
54
+ gl.deleteShader(vertexShader)
55
+ gl.deleteShader(fragmentShader)
56
+
57
+ return program
58
+ }
59
+
60
+ /**********************************************************************************/
61
+ /* */
62
+ /* Get Instanced Arrays */
63
+ /* */
64
+ /**********************************************************************************/
65
+
66
+ const INSTANCED_ARRAYS_WRAPPER_MAP = new WeakMap<
67
+ GL,
68
+ RemoveSuffix<
69
+ Pick<
70
+ ANGLE_instanced_arrays,
71
+ 'drawArraysInstancedANGLE' | 'drawElementsInstancedANGLE' | 'vertexAttribDivisorANGLE'
72
+ >,
73
+ 'ANGLE'
74
+ > | null
75
+ >()
76
+ export function getInstancedArrays(gl: GL) {
77
+ if (gl instanceof WebGL2RenderingContext) return gl
78
+
79
+ const cached = INSTANCED_ARRAYS_WRAPPER_MAP.get(gl)
80
+ if (cached) return cached
81
+
82
+ const ext = gl.getExtension('ANGLE_instanced_arrays')
83
+ if (!ext) return undefined
84
+
85
+ const wrapper = {
86
+ drawArraysInstanced: ext.drawArraysInstancedANGLE.bind(ext),
87
+ drawElementsInstanced: ext.drawElementsInstancedANGLE.bind(ext),
88
+ vertexAttribDivisor: ext.vertexAttribDivisorANGLE.bind(ext),
89
+ }
90
+ INSTANCED_ARRAYS_WRAPPER_MAP.set(gl, wrapper)
91
+
92
+ return wrapper
93
+ }
94
+
95
+ /**********************************************************************************/
96
+ /* */
97
+ /* Get Vertex Array Object */
98
+ /* */
99
+ /**********************************************************************************/
100
+
101
+ const VERTEX_ARRAY_OBJECT_WRAPPER_MAP = new WeakMap<
102
+ GL,
103
+ RemoveSuffix<
104
+ Pick<
105
+ OES_vertex_array_object,
106
+ 'bindVertexArrayOES' | 'createVertexArrayOES' | 'deleteVertexArrayOES'
107
+ >,
108
+ 'OES'
109
+ > | null
110
+ >()
111
+ export function getVertexArrayObject(gl: GL) {
112
+ if (gl instanceof WebGL2RenderingContext) return gl
113
+
114
+ const cached = VERTEX_ARRAY_OBJECT_WRAPPER_MAP.get(gl)
115
+ if (cached) return cached
116
+
117
+ const ext = gl.getExtension('OES_vertex_array_object')
118
+ if (!ext) return null
119
+
120
+ const wrapper = {
121
+ bindVertexArray: ext.bindVertexArrayOES.bind(ext),
122
+ createVertexArray: ext.createVertexArrayOES.bind(ext),
123
+ deleteVertexArray: ext.deleteVertexArrayOES.bind(ext),
124
+ }
125
+ VERTEX_ARRAY_OBJECT_WRAPPER_MAP.set(gl, wrapper)
126
+
127
+ return wrapper
128
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { getInstancedArrays, getVertexArrayObject } from './features'
1
+ import { getInstancedArrays, getVertexArrayObject } from './gl'
2
2
  import type {
3
3
  AttributeMethods,
4
4
  AttributeSchema,
@@ -15,15 +15,8 @@ import type {
15
15
  ViewOptions,
16
16
  ViewSchema,
17
17
  } from './types'
18
- import {
19
- assertedNotNullish,
20
- createUpsertMap,
21
- isMatKind,
22
- isSamplerKind,
23
- kindToSize,
24
- kindToUniformFnName,
25
- ObjectUtils,
26
- } from './utils'
18
+ import { isSamplerKind, kindToUniformFnName, isMatKind, assertedNotNullish, kindToSize, forEachObject } from './utils'
19
+ import { createUpsertMap, mapObject } from './utils'
27
20
  export * from './types'
28
21
 
29
22
  /**********************************************************************************/
@@ -94,7 +87,7 @@ export function uniformView<T extends UniformSchema>(
94
87
  schema: T,
95
88
  ): InferUniformView<T> {
96
89
  // @ts-expect-error - conditional return type based on size is hard to infer
97
- return ObjectUtils.map(schema, ({ kind, size }, key) => {
90
+ return mapObject(schema, ({ kind, size }, key) => {
98
91
  const name = toID(key)
99
92
 
100
93
  if (isSamplerKind(kind)) {
@@ -191,7 +184,7 @@ export function attributeView<T extends AttributeSchema>(
191
184
  schema: T,
192
185
  { signal }: ViewOptions = {},
193
186
  ): AttributeView<T> {
194
- const attributes = ObjectUtils.map(
187
+ const attributes = mapObject(
195
188
  schema,
196
189
  (
197
190
  { kind, instanced, buffer = assertedNotNullish(gl.createBuffer()) },
@@ -226,7 +219,7 @@ export function attributeView<T extends AttributeSchema>(
226
219
  )
227
220
 
228
221
  signal?.addEventListener('abort', function dispose() {
229
- ObjectUtils.forEach(attributes, value => value.dispose())
222
+ forEachObject(attributes, value => value.dispose())
230
223
  })
231
224
 
232
225
  return attributes
@@ -245,7 +238,7 @@ export function interleavedAttributeView<T extends InterleavedAttributeSchema>(
245
238
  { signal }: ViewOptions = {},
246
239
  ): InterleavedAttributeView<T> {
247
240
  // Initialize interleaved attributes
248
- const interleavedAttributes = ObjectUtils.map(schema, ({ layout, instanced }) => {
241
+ const interleavedAttributes = mapObject(schema, ({ layout, instanced }) => {
249
242
  // Increment number to keep track of offset
250
243
  let index = 0
251
244
 
@@ -325,9 +318,8 @@ export function interleavedAttributeView<T extends InterleavedAttributeSchema>(
325
318
  set(value, usage = 'STATIC_DRAW') {
326
319
  if (vao) {
327
320
  vao.bind()
328
- } else {
329
- gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
330
321
  }
322
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
331
323
  gl.bufferData(gl.ARRAY_BUFFER, value, gl[usage])
332
324
  if (vao) {
333
325
  vao.unbind()
@@ -337,7 +329,7 @@ export function interleavedAttributeView<T extends InterleavedAttributeSchema>(
337
329
  })
338
330
 
339
331
  signal?.addEventListener('abort', function dispose() {
340
- ObjectUtils.forEach(interleavedAttributes, value => value.dispose())
332
+ forEachObject(interleavedAttributes, value => value.dispose())
341
333
  })
342
334
 
343
335
  return interleavedAttributes
@@ -355,7 +347,7 @@ export function bufferView<T extends BufferSchema>(
355
347
  { signal }: ViewOptions = {},
356
348
  ): BufferView<T> {
357
349
  // Initialize buffers
358
- const buffers = ObjectUtils.map(schema, ({ target = 'ARRAY_BUFFER', usage = 'STATIC_DRAW' }) => {
350
+ const buffers = mapObject(schema, ({ target = 'ARRAY_BUFFER', usage = 'STATIC_DRAW' }) => {
359
351
  const buffer = assertedNotNullish(gl.createBuffer())
360
352
  return {
361
353
  bind() {
@@ -372,7 +364,7 @@ export function bufferView<T extends BufferSchema>(
372
364
  })
373
365
 
374
366
  signal?.addEventListener('abort', function dispose() {
375
- ObjectUtils.forEach(buffers, value => value.dispose())
367
+ forEachObject(buffers, value => value.dispose())
376
368
  })
377
369
 
378
370
  return buffers
package/src/tag.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { toID, view } from '.'
2
+ import { createProgram } from './gl'
2
3
  import type { Prettify } from './type-utils'
3
4
  import type {
4
5
  AttributeDefinition,
@@ -17,7 +18,7 @@ import type {
17
18
  ViewSchema,
18
19
  ViewSchemaPartial,
19
20
  } from './types'
20
- import { createProgram, createUpsertMap } from './utils'
21
+ import { createUpsertMap } from './utils'
21
22
 
22
23
  export function glsl<TSlot extends GLSLSlot, const TSlots extends TSlot[]>(
23
24
  template: TemplateStringsArray,
@@ -6,59 +6,13 @@ import type {
6
6
  KIND_TO_UNIFORM_FN_NAME_MAP,
7
7
  TextureDefinition,
8
8
  UniformKind,
9
- } from '../types'
10
-
11
- export * as ObjectUtils from './object'
9
+ } from './types'
12
10
 
13
11
  export function assertedNotNullish<T>(value: T, message?: string): NonNullable<T> {
14
12
  if (value === undefined || value === null) throw new Error(message)
15
13
  return value
16
14
  }
17
15
 
18
- function createGLShader(gl: GL, type: number, source: string): WebGLShader {
19
- const shader = gl.createShader(type)
20
- if (!shader) throw new Error('Failed to create shader')
21
-
22
- gl.shaderSource(shader, source)
23
- gl.compileShader(shader)
24
-
25
- if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
26
- const info = gl.getShaderInfoLog(shader)
27
- gl.deleteShader(shader)
28
- throw new Error(
29
- `Failed to compile ${type === gl.VERTEX_SHADER ? 'vertex' : 'fragment'} shader: ${info}`,
30
- )
31
- }
32
-
33
- return shader
34
- }
35
-
36
- export function createProgram(gl: GL, vertexSource: string, fragmentSource: string): WebGLProgram {
37
- const program = gl.createProgram()
38
- if (!program) throw new Error('Failed to create WebGL program')
39
-
40
- const vertexShader = createGLShader(gl, gl.VERTEX_SHADER, vertexSource)
41
- const fragmentShader = createGLShader(gl, gl.FRAGMENT_SHADER, fragmentSource)
42
-
43
- gl.attachShader(program, vertexShader)
44
- gl.attachShader(program, fragmentShader)
45
- gl.linkProgram(program)
46
-
47
- if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
48
- const info = gl.getProgramInfoLog(program)
49
- gl.deleteProgram(program)
50
- gl.deleteShader(vertexShader)
51
- gl.deleteShader(fragmentShader)
52
- throw new Error(`Failed to link program: ${info}`)
53
- }
54
-
55
- // Clean up shaders after linking
56
- gl.deleteShader(vertexShader)
57
- gl.deleteShader(fragmentShader)
58
-
59
- return program
60
- }
61
-
62
16
  /**********************************************************************************/
63
17
  /* */
64
18
  /* Kind To Uniform Name */
@@ -292,3 +246,31 @@ export function createUpsertMap<T extends UpsertMapKind<any, any> = Map<any, any
292
246
  },
293
247
  })
294
248
  }
249
+
250
+ export function forEachObject<T extends Record<string, any>>(
251
+ value: T,
252
+ callback: (value: T[keyof T], key: keyof T, index: number) => void,
253
+ ) {
254
+ let index = 0
255
+ for (const key in value) {
256
+ callback(value[key], key, index)
257
+ index++
258
+ }
259
+ for (const key of Object.getOwnPropertySymbols(value)) {
260
+ callback(value[key as keyof T], key as keyof T, index)
261
+ index++
262
+ }
263
+ }
264
+
265
+ export function mapObject<T extends Record<string, any>, TReturn>(
266
+ value: T,
267
+ callback: (value: T[keyof T], key: keyof T, index: number) => TReturn,
268
+ ): { [TKey in keyof T]: TReturn } {
269
+ const result = {} as { [TKey in keyof T]: TReturn }
270
+
271
+ forEachObject(value, (value, key, index) => {
272
+ result[key] = callback(value, key, index)
273
+ })
274
+
275
+ return result
276
+ }
package/tsup.config.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { defineConfig } from 'tsup'
2
2
 
3
3
  export default defineConfig({
4
- entry: ['src/index.ts', 'src/tag.ts'],
4
+ entry: ['src/index.ts', 'src/tag.ts', 'src/gl.ts'],
5
5
  format: ['esm'],
6
6
  splitting: true,
7
7
  sourcemap: true,
package/dev/dist/404.html DELETED
@@ -1,19 +0,0 @@
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>
@@ -1,294 +0,0 @@
1
- import { i as interleave, u as uniform, a as attribute, g as glsl, c as compile, v as view } from './tag-C_bg62fD.js';
2
- import { c as createElement, a as cursor } from './utils-CvCgsM08.js';
3
-
4
- const canvas = createElement("canvas");
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, schema: curveSchema } = compile(gl, curveVertex, curveFragment);
196
- const curveView = view(gl, curveProgram, curveSchema);
197
- const { program: pointProgram, schema: pointSchema } = compile(gl, pointVertex, pointFragment);
198
- const pointView = view(gl, pointProgram, pointSchema);
199
- const { program: lineProgram, schema: lineSchema } = compile(gl, lineVertex, lineFragment);
200
- const lineView = view(gl, lineProgram, lineSchema);
201
- const curveSegments = 100;
202
- function generateTriangleStrip() {
203
- return new Float32Array(
204
- function* () {
205
- for (let i = 0; i <= curveSegments; i++) {
206
- const t = i / curveSegments;
207
- yield t;
208
- yield -1;
209
- yield t;
210
- yield 1;
211
- }
212
- }()
213
- );
214
- }
215
- const stripData = generateTriangleStrip();
216
- curveView.interleavedAttributes.data.set(stripData);
217
- const pointVertices = new Float32Array([
218
- -1,
219
- -1,
220
- // Bottom-left
221
- 1,
222
- -1,
223
- // Bottom-right
224
- -1,
225
- 1,
226
- // Top-left
227
- -1,
228
- 1,
229
- // Top-left (repeated for second triangle)
230
- 1,
231
- -1,
232
- // Bottom-right (repeated for second triangle)
233
- 1,
234
- 1
235
- // Top-right
236
- ]);
237
- pointView.attributes.a_vertex.set(pointVertices);
238
- function updateLineData() {
239
- const lineVerticesData = new Float32Array([
240
- state.controlPoints[0].x,
241
- state.controlPoints[0].y,
242
- // P0
243
- state.controlPoints[1].x,
244
- state.controlPoints[1].y,
245
- // P1
246
- state.controlPoints[1].x,
247
- state.controlPoints[1].y,
248
- // P1 (repeated)
249
- state.controlPoints[2].x,
250
- state.controlPoints[2].y
251
- // P2
252
- ]);
253
- lineView.attributes.a_position.set(lineVerticesData);
254
- }
255
- updateLineData();
256
- function render() {
257
- if (canvas.width !== window.innerWidth || canvas.height !== window.innerHeight) {
258
- canvas.width = window.innerWidth;
259
- canvas.height = window.innerHeight;
260
- }
261
- gl.viewport(0, 0, canvas.width, canvas.height);
262
- gl.clearColor(0.1, 0.1, 0.15, 1);
263
- gl.clear(gl.COLOR_BUFFER_BIT);
264
- gl.enable(gl.BLEND);
265
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
266
- updateLineData();
267
- gl.useProgram(lineProgram);
268
- lineView.attributes.a_position.bind();
269
- lineView.uniforms.u_resolution.set(canvas.width, canvas.height);
270
- lineView.uniforms.u_offset.set(state.position.x, state.position.y);
271
- lineView.uniforms.u_scale.set(state.scale);
272
- gl.drawArrays(gl.LINES, 0, 4);
273
- gl.useProgram(curveProgram);
274
- curveView.interleavedAttributes.data.bind();
275
- curveView.uniforms.u_resolution.set(canvas.width, canvas.height);
276
- curveView.uniforms.u_offset.set(state.position.x, state.position.y);
277
- curveView.uniforms.u_scale.set(state.scale);
278
- curveView.uniforms.u_p0.set(state.controlPoints[0].x, state.controlPoints[0].y);
279
- curveView.uniforms.u_p1.set(state.controlPoints[1].x, state.controlPoints[1].y);
280
- curveView.uniforms.u_p2.set(state.controlPoints[2].x, state.controlPoints[2].y);
281
- curveView.uniforms.u_thickness.set(5);
282
- gl.drawArrays(gl.TRIANGLE_STRIP, 0, (curveSegments + 1) * 2);
283
- curveView.interleavedAttributes.data.unbind();
284
- gl.useProgram(pointProgram);
285
- pointView.attributes.a_vertex.bind();
286
- pointView.uniforms.u_resolution.set(canvas.width, canvas.height);
287
- pointView.uniforms.u_offset.set(state.position.x, state.position.y);
288
- pointView.uniforms.u_scale.set(state.scale);
289
- for (const point of state.controlPoints) {
290
- pointView.uniforms.u_pointPos.set(point.x, point.y);
291
- gl.drawArrays(gl.TRIANGLES, 0, 6);
292
- }
293
- }
294
- render();