@bigmistqke/view.gl 0.1.1 → 0.1.3

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 (52) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/.github/ISSUE_TEMPLATE/bug-report.yml +31 -0
  3. package/.github/ISSUE_TEMPLATE/feature-request.yml +27 -0
  4. package/.github/workflows/codeql.yml +46 -0
  5. package/.github/workflows/deploy-page.yml +38 -0
  6. package/.github/workflows/format.yml +40 -0
  7. package/README.md +286 -84
  8. package/dev/404.html +19 -0
  9. package/dev/dist/404.html +19 -0
  10. package/dev/dist/assets/bezier-DuLTb2d1.js +294 -0
  11. package/dev/dist/assets/game_of_life-D7qHdFcv.js +202 -0
  12. package/dev/dist/assets/grid-Ie22U_9K.js +173 -0
  13. package/dev/dist/assets/index-0qrXMlfv.js +109 -0
  14. package/dev/dist/assets/index-C2N3u8wj.css +12 -0
  15. package/dev/dist/assets/main-BZ1N6pp6.js +32 -0
  16. package/dev/dist/assets/overview-DAxtMIw5.js +78 -0
  17. package/dev/dist/assets/pix_sim-DIhzM_vu.js +501 -0
  18. package/dev/dist/assets/symbol-BouRL-Ni.js +65 -0
  19. package/dev/dist/assets/tag-C_bg62fD.js +516 -0
  20. package/dev/dist/assets/tetris-IMnkSi9p.js +344 -0
  21. package/dev/dist/assets/utils-CvCgsM08.js +50 -0
  22. package/dev/dist/index.html +51 -0
  23. package/dev/index.html +39 -10
  24. package/dev/{bezier.ts → src/examples/bezier.ts} +1 -1
  25. package/dev/{grid.ts → src/examples/grid.ts} +1 -1
  26. package/dev/{pix_sim.ts → src/examples/pix_sim.ts} +112 -97
  27. package/dev/src/examples/symbol.ts +79 -0
  28. package/dev/{tetris.ts → src/examples/tetris.ts} +1 -0
  29. package/dev/src/index.css +12 -0
  30. package/dev/src/index.ts +69 -0
  31. package/dev/{utils.ts → src/utils.ts} +8 -2
  32. package/dev/vite.config.ts +9 -1
  33. package/dist/index.d.mts +3 -2
  34. package/dist/index.mjs +81 -27
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/tag.d.mts +9 -9
  37. package/dist/tag.mjs +46 -17
  38. package/dist/tag.mjs.map +1 -1
  39. package/dist/{types-DxpS9Bae.d.mts → types-DoFAJoh-.d.mts} +18 -18
  40. package/package.json +20 -7
  41. package/src/index.ts +39 -17
  42. package/src/tag.ts +47 -39
  43. package/src/types.ts +24 -24
  44. package/src/{utils.ts → utils/index.ts} +39 -14
  45. package/src/utils/object.ts +27 -0
  46. package/test/index.test.ts +1017 -0
  47. package/test/mocks/webgl.ts +248 -0
  48. package/test/setup.ts +13 -0
  49. package/test/tag.test.ts +790 -0
  50. package/vitest.config.ts +15 -0
  51. /package/dev/{game_of_life.ts → src/examples/game_of_life.ts} +0 -0
  52. /package/dev/{index.ts → src/examples/overview.ts} +0 -0
@@ -0,0 +1,344 @@
1
+ import { a as attribute, u as uniform, g as glsl, c as compile, d as uniformView, e as attributeView } from './tag-C_bg62fD.js';
2
+
3
+ function rotate(data, size, clockwise = true) {
4
+ for (let layer = 0; layer < Math.floor(size / 2); layer++) {
5
+ const first = layer;
6
+ const last = size - 1 - layer;
7
+ for (let i = 0; i < last - first; i++) {
8
+ const top = getColorIndex(data, first, first + i, size);
9
+ const right = getColorIndex(data, first + i, last, size);
10
+ const bottom = getColorIndex(data, last, last - i, size);
11
+ const left = getColorIndex(data, last - i, first, size);
12
+ if (clockwise) {
13
+ setColorIndex(data, first + i, last, size, top);
14
+ setColorIndex(data, last, last - i, size, right);
15
+ setColorIndex(data, last - i, first, size, bottom);
16
+ setColorIndex(data, first, first + i, size, left);
17
+ } else {
18
+ setColorIndex(data, last - i, first, size, top);
19
+ setColorIndex(data, last, last - i, size, left);
20
+ setColorIndex(data, first + i, last, size, bottom);
21
+ setColorIndex(data, first, first + i, size, right);
22
+ }
23
+ }
24
+ }
25
+ function getColorIndex(buf, x, y, size2) {
26
+ return buf[y * size2 + x];
27
+ }
28
+ function setColorIndex(buf, x, y, size2, pixel) {
29
+ buf[y * size2 + x] = pixel;
30
+ }
31
+ }
32
+ function createTimeout() {
33
+ let timeout;
34
+ return {
35
+ set(callback, time) {
36
+ clearTimeout(timeout);
37
+ timeout = setTimeout(callback, time);
38
+ },
39
+ clear() {
40
+ clearTimeout(timeout);
41
+ }
42
+ };
43
+ }
44
+ const WIDTH = 10;
45
+ const HEIGHT = 20;
46
+ const TETROMINO = {
47
+ I: {
48
+ dimensions: [4, 4],
49
+ pixels: () => new Float32Array([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]),
50
+ color: [0, 1, 1]
51
+ // Cyan
52
+ },
53
+ O: {
54
+ dimensions: [2, 2],
55
+ pixels: () => new Float32Array([2, 2, 2, 2]),
56
+ color: [1, 1, 0]
57
+ // Yellow
58
+ },
59
+ T: {
60
+ dimensions: [3, 3],
61
+ pixels: () => new Float32Array([0, 3, 0, 3, 3, 3, 0, 0, 0]),
62
+ color: [0.6, 0, 1]
63
+ // Purple
64
+ },
65
+ S: {
66
+ dimensions: [3, 3],
67
+ pixels: () => new Float32Array([0, 4, 4, 4, 4, 0, 0, 0, 0]),
68
+ color: [0, 1, 0]
69
+ },
70
+ Z: {
71
+ dimensions: [3, 3],
72
+ pixels: () => new Float32Array([5, 5, 0, 0, 5, 5, 0, 0, 0]),
73
+ color: [1, 0, 0]
74
+ },
75
+ J: {
76
+ dimensions: [3, 3],
77
+ pixels: () => new Float32Array([6, 0, 0, 6, 6, 6, 0, 0, 0]),
78
+ color: [0, 0, 1]
79
+ },
80
+ L: {
81
+ dimensions: [3, 3],
82
+ pixels: () => new Float32Array([0, 0, 7, 7, 7, 7, 0, 0, 0]),
83
+ color: [1, 0.5, 0]
84
+ }
85
+ };
86
+ const TETROMINO_KIND = Object.keys(TETROMINO);
87
+ function getRandomTetrominoKind() {
88
+ return TETROMINO_KIND[Math.floor(Math.random() * TETROMINO_KIND.length)];
89
+ }
90
+ function createTetromino(gl, program, schema) {
91
+ const { a_index, a_pixel } = attributeView(gl, program, schema);
92
+ let pixels = null;
93
+ let dimensions = null;
94
+ let offset = null;
95
+ let pixelCount = null;
96
+ function next(kind = getRandomTetrominoKind()) {
97
+ const template = TETROMINO[kind];
98
+ pixels = template.pixels();
99
+ pixelCount = template.dimensions[0] * template.dimensions[1];
100
+ offset = [Math.ceil((WIDTH - template.dimensions[0]) / 2), HEIGHT - template.dimensions[1]];
101
+ dimensions = template.dimensions;
102
+ a_index.set(new Float32Array(Array.from({ length: pixelCount }, (_, index) => index)));
103
+ a_pixel.set(pixels);
104
+ }
105
+ next();
106
+ return {
107
+ get dimensions() {
108
+ return dimensions;
109
+ },
110
+ get pixels() {
111
+ return pixels;
112
+ },
113
+ get offset() {
114
+ return offset;
115
+ },
116
+ get pixelCount() {
117
+ return pixelCount;
118
+ },
119
+ bindAttributes() {
120
+ a_index.bind();
121
+ a_pixel.bind();
122
+ },
123
+ next,
124
+ rotate(clockwise = true) {
125
+ rotate(pixels, dimensions[1], clockwise);
126
+ a_pixel.set(pixels).bind();
127
+ }
128
+ };
129
+ }
130
+ function createBoard(gl, program, schema) {
131
+ const { a_index, a_pixel } = attributeView(gl, program, schema);
132
+ const array = new Float32Array(Array.from({ length: WIDTH * HEIGHT }, () => 0));
133
+ a_index.set(new Float32Array(Array.from({ length: WIDTH * HEIGHT }, (_, index) => index)));
134
+ a_pixel.set(array);
135
+ return {
136
+ bindAttributes() {
137
+ a_index.bind();
138
+ a_pixel.bind();
139
+ },
140
+ array,
141
+ blit({ pixelCount, offset, dimensions, pixels }) {
142
+ for (let i = 0; i < pixelCount; i++) {
143
+ const pixel = pixels[i];
144
+ if (pixel) {
145
+ const x = offset[0] + i % dimensions[0];
146
+ const y = offset[1] + Math.floor(i / dimensions[0]) + 1;
147
+ const index = x + y * WIDTH;
148
+ array[index] = pixel;
149
+ }
150
+ }
151
+ let writeRow = 0;
152
+ for (let y = 0; y < HEIGHT; y++) {
153
+ let isComplete = true;
154
+ for (let x = 0; x < WIDTH; x++) {
155
+ if (array[x + y * WIDTH] === 0) {
156
+ isComplete = false;
157
+ break;
158
+ }
159
+ }
160
+ if (!isComplete) {
161
+ if (writeRow !== y) {
162
+ for (let x = 0; x < WIDTH; x++) {
163
+ array[x + writeRow * WIDTH] = array[x + y * WIDTH];
164
+ }
165
+ }
166
+ writeRow++;
167
+ }
168
+ }
169
+ a_pixel.set(array);
170
+ }
171
+ };
172
+ }
173
+ const vertex = glsl`
174
+ precision mediump float;
175
+
176
+ ${attribute.float("a_index", { instanced: true })}
177
+ ${attribute.float("a_pixel", { instanced: true })}
178
+ ${attribute.vec2("a_vertex")}
179
+ ${uniform.vec2("u_offset")}
180
+ ${uniform.vec2("u_dimensions")}
181
+ ${uniform.vec3("u_palette", { size: 7 })}
182
+
183
+ varying vec3 v_color;
184
+
185
+ void main(){
186
+ float columns = mod(a_index, u_dimensions.x);
187
+ float rows = floor(a_index / u_dimensions.x);
188
+
189
+ vec2 offset = (vec2(columns, rows) + u_offset);
190
+
191
+ vec2 base = offset + 0.5;
192
+ vec2 pos = (a_vertex + base) / vec2(10., 20.);
193
+
194
+ gl_Position = vec4(pos * 2.0 - 1.0, 0.0, 1.0);
195
+
196
+ if(a_pixel == 0.){
197
+ v_color = vec3(0., 0., 0.);
198
+ }else{
199
+ v_color = u_palette[int(a_pixel) - 1];
200
+ }
201
+ }`;
202
+ const fragment = glsl`
203
+ precision mediump float;
204
+
205
+ varying vec3 v_color;
206
+
207
+ void main() {
208
+ if(v_color.x + v_color.y + v_color.z == 0.){
209
+ discard;
210
+ }
211
+ gl_FragColor = vec4(v_color, 1.0);
212
+ }`;
213
+ function createTetris() {
214
+ const canvas = document.createElement("canvas");
215
+ canvas.width = WIDTH * 30;
216
+ canvas.height = HEIGHT * 30;
217
+ document.body.append(canvas);
218
+ const animationTimeout = createTimeout();
219
+ const gl = canvas.getContext("webgl2");
220
+ gl.viewport(0, 0, canvas.width, canvas.height);
221
+ const { program, schema } = compile(gl, vertex, fragment);
222
+ gl.useProgram(program);
223
+ const { u_dimensions, u_offset, u_palette } = uniformView(gl, program, schema.uniforms);
224
+ const { a_vertex, ...localSchema } = schema.attributes;
225
+ const globalAttributes = attributeView(gl, program, { a_vertex });
226
+ const board = createBoard(gl, program, localSchema);
227
+ const tetromino = createTetromino(gl, program, localSchema);
228
+ u_palette.set(new Float32Array(Object.values(TETROMINO).flatMap(({ color }) => color)));
229
+ globalAttributes.a_vertex.set(
230
+ new Float32Array([
231
+ // triangle 1
232
+ -0.5,
233
+ -0.5,
234
+ 0.5,
235
+ -0.5,
236
+ -0.5,
237
+ 0.5,
238
+ // triangle 2
239
+ -0.5,
240
+ 0.5,
241
+ 0.5,
242
+ -0.5,
243
+ 0.5,
244
+ 0.5
245
+ ])
246
+ ).bind();
247
+ function checkCollision() {
248
+ for (let i = 0; i < tetromino.pixelCount; i++) {
249
+ const x = i % tetromino.dimensions[0] + tetromino.offset[0];
250
+ const y = Math.floor(i / tetromino.dimensions[0]) + tetromino.offset[1];
251
+ let pixel = tetromino.pixels[i];
252
+ if (pixel > 0) {
253
+ if (x < 0) return true;
254
+ if (x >= WIDTH) return true;
255
+ if (y < 0) return true;
256
+ const boardPixel = board.array[x + y * WIDTH];
257
+ if (boardPixel !== 0) {
258
+ return true;
259
+ }
260
+ }
261
+ }
262
+ }
263
+ function next() {
264
+ board.blit(tetromino);
265
+ tetromino.next();
266
+ if (checkCollision()) {
267
+ document.removeEventListener("keydown", handleKeyDown);
268
+ animationTimeout.clear();
269
+ } else {
270
+ render();
271
+ animationTimeout.set(animate, 1e3);
272
+ }
273
+ }
274
+ function handleKeyDown(event) {
275
+ switch (event.key) {
276
+ case "ArrowUp":
277
+ tetromino.rotate();
278
+ if (checkCollision()) {
279
+ tetromino.rotate(false);
280
+ } else {
281
+ render();
282
+ }
283
+ break;
284
+ case "ArrowDown":
285
+ tetromino.offset[1]--;
286
+ if (checkCollision()) {
287
+ next();
288
+ } else {
289
+ render();
290
+ }
291
+ break;
292
+ case "ArrowLeft":
293
+ tetromino.offset[0]--;
294
+ if (checkCollision()) {
295
+ tetromino.offset[0]++;
296
+ } else {
297
+ render();
298
+ }
299
+ break;
300
+ case "ArrowRight":
301
+ tetromino.offset[0]++;
302
+ if (checkCollision()) {
303
+ tetromino.offset[0]--;
304
+ } else {
305
+ render();
306
+ }
307
+ break;
308
+ case " ":
309
+ while (true) {
310
+ tetromino.offset[1]--;
311
+ if (checkCollision()) {
312
+ next();
313
+ break;
314
+ }
315
+ }
316
+ }
317
+ }
318
+ document.addEventListener("keydown", handleKeyDown);
319
+ function render() {
320
+ gl.clearColor(0, 0.5, 0, 0.3);
321
+ gl.clear(gl.COLOR_BUFFER_BIT);
322
+ u_offset.set(0, 0);
323
+ u_dimensions.set(WIDTH, HEIGHT);
324
+ board.bindAttributes();
325
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, WIDTH * HEIGHT);
326
+ u_offset.set(...tetromino.offset);
327
+ u_dimensions.set(...tetromino.dimensions);
328
+ tetromino.bindAttributes();
329
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, tetromino.pixelCount);
330
+ }
331
+ function animate() {
332
+ tetromino.offset[1]--;
333
+ if (checkCollision()) {
334
+ next();
335
+ }
336
+ render();
337
+ animationTimeout.set(animate, 1e3);
338
+ }
339
+ setTimeout(animate, 1e3);
340
+ render();
341
+ }
342
+ createTetris();
343
+
344
+ export { TETROMINO };
@@ -0,0 +1,50 @@
1
+ function cursor(event, callback) {
2
+ const { promise, resolve } = Promise.withResolvers();
3
+ const controller = new AbortController();
4
+ const initialX = event.clientX;
5
+ const initialY = event.clientY;
6
+ let previousX = event.clientX;
7
+ let previousY = event.clientY;
8
+ window.addEventListener(
9
+ "pointermove",
10
+ (event2) => {
11
+ callback(
12
+ Object.assign(event2, {
13
+ distanceX: initialX - event2.clientX,
14
+ distanceY: initialY - event2.clientY,
15
+ deltaX: previousX - event2.clientX,
16
+ deltaY: previousY - event2.clientY
17
+ })
18
+ );
19
+ previousX = event2.clientX;
20
+ previousY = event2.clientY;
21
+ },
22
+ controller
23
+ );
24
+ window.addEventListener(
25
+ "pointerup",
26
+ () => {
27
+ controller.abort();
28
+ resolve();
29
+ },
30
+ controller
31
+ );
32
+ return promise;
33
+ }
34
+ function createElement(tag, {
35
+ parentElement,
36
+ ...options
37
+ } = {}) {
38
+ const element = document.createElement(tag);
39
+ for (const key in options) {
40
+ if (key.startsWith("data-")) {
41
+ element.setAttribute(key, options[key]);
42
+ } else {
43
+ element[key] = options[key];
44
+ }
45
+ }
46
+ (parentElement ?? document.body).appendChild(element);
47
+ return element;
48
+ }
49
+
50
+ export { cursor as a, createElement as c };
@@ -0,0 +1,51 @@
1
+ <html>
2
+ <head>
3
+ <style>
4
+ body {
5
+ --color-foreground: #e9e9e9;
6
+ --color-background: rgb(33, 33, 47);
7
+ --color-border: rgb(87, 87, 87);
8
+ --font-family: monospace;
9
+
10
+ display: grid;
11
+ grid-template-columns: 175px 1fr;
12
+ margin: 0px;
13
+ background-color: var(--color-background);
14
+ color: var(--color-foreground);
15
+ font-family: var(--font-family);
16
+ }
17
+ iframe {
18
+ border: none;
19
+ width: 100%;
20
+ height: 100%;
21
+ }
22
+ nav {
23
+ display: flex;
24
+ flex-direction: column;
25
+ border-inline-end: 1px solid var(--color-border);
26
+
27
+ & > * {
28
+ margin: 0px;
29
+ border: none;
30
+ border-block-end: 1px solid var(--color-border);
31
+ background: none;
32
+ padding: 7px;
33
+ color: var(--color-foreground);
34
+ font-family: var(--font-family);
35
+ text-align: left;
36
+
37
+ &.active {
38
+ background-color: rgba(255, 255, 255, 0.1);
39
+ }
40
+ }
41
+ }
42
+ </style>
43
+ <script type="module" crossorigin src="/assets/main-BZ1N6pp6.js"></script>
44
+ <link rel="modulepreload" crossorigin href="/assets/utils-CvCgsM08.js">
45
+ <link rel="modulepreload" crossorigin href="/assets/index-0qrXMlfv.js">
46
+ </head>
47
+ <body>
48
+ <nav></nav>
49
+ <iframe></iframe>
50
+ </body>
51
+ </html>
package/dev/index.html CHANGED
@@ -1,20 +1,49 @@
1
1
  <html>
2
2
  <head>
3
- <script src="./bezier.ts" type="module"></script>
3
+ <script src="./src/index.ts" type="module"></script>
4
4
  <style>
5
5
  body {
6
- display: flex;
7
- justify-content: center;
8
- align-items: center;
6
+ --color-foreground: #e9e9e9;
7
+ --color-background: rgb(33, 33, 47);
8
+ --color-border: rgb(87, 87, 87);
9
+ --font-family: monospace;
10
+
11
+ display: grid;
12
+ grid-template-columns: 175px 1fr;
9
13
  margin: 0px;
10
- height: 100vh;
11
- overscroll-behavior-y: none;
14
+ background-color: var(--color-background);
15
+ color: var(--color-foreground);
16
+ font-family: var(--font-family);
17
+ }
18
+ iframe {
19
+ border: none;
20
+ width: 100%;
21
+ height: 100%;
12
22
  }
13
- canvas {
14
- margin: auto;
15
- image-rendering: pixelated;
23
+ nav {
24
+ display: flex;
25
+ flex-direction: column;
26
+ border-inline-end: 1px solid var(--color-border);
27
+
28
+ & > * {
29
+ margin: 0px;
30
+ border: none;
31
+ border-block-end: 1px solid var(--color-border);
32
+ background: none;
33
+ padding: 7px;
34
+ color: var(--color-foreground);
35
+ font-family: var(--font-family);
36
+ text-align: left;
37
+
38
+ &.active {
39
+ background-color: rgba(255, 255, 255, 0.1);
40
+ }
41
+ }
16
42
  }
17
43
  </style>
18
44
  </head>
19
- <body></body>
45
+ <body>
46
+ <nav></nav>
47
+ <iframe></iframe>
48
+ </body>
20
49
  </html>
@@ -1,6 +1,6 @@
1
1
  import { view } from 'view.gl'
2
2
  import { attribute, compile, glsl, interleave, uniform } from 'view.gl/tag'
3
- import { createElement, cursor } from './utils'
3
+ import { createElement, cursor } from '../utils'
4
4
 
5
5
  // Create canvas and WebGL context
6
6
  const canvas = createElement('canvas')
@@ -1,6 +1,6 @@
1
1
  import { view } from 'view.gl'
2
2
  import { attribute, compile, glsl, uniform } from 'view.gl/tag'
3
- import { createElement, cursor } from './utils'
3
+ import { createElement, cursor } from '../utils'
4
4
 
5
5
  const canvas = createElement('canvas', { width: window.innerWidth, height: window.innerHeight })
6
6
  const gl = canvas.getContext('webgl2', { antialias: false })!