@bigmistqke/view.gl 0.1.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.
- package/.claude/settings.local.json +8 -0
- package/.prettierrc +10 -0
- package/README.md +338 -0
- package/dev/bezier.ts +428 -0
- package/dev/game_of_life.ts +250 -0
- package/dev/grid.ts +223 -0
- package/dev/index.html +20 -0
- package/dev/index.ts +94 -0
- package/dev/pix_sim.ts +549 -0
- package/dev/tetris.ts +439 -0
- package/dev/tsconfig.json +4 -0
- package/dev/utils.ts +61 -0
- package/dev/vite.config.ts +42 -0
- package/package.json +29 -0
- package/src/features.ts +59 -0
- package/src/index.ts +313 -0
- package/src/tag.ts +193 -0
- package/src/types.ts +718 -0
- package/src/utils.ts +266 -0
- package/tsconfig.json +30 -0
- package/tsup.config.ts +11 -0
package/dev/tetris.ts
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import { GL } from 'src/types'
|
|
2
|
+
import { attributeView, uniformView } from 'view.gl'
|
|
3
|
+
import { attribute, compile, glsl, uniform } from 'view.gl/tag'
|
|
4
|
+
|
|
5
|
+
/**********************************************************************************/
|
|
6
|
+
/* */
|
|
7
|
+
/* Utils */
|
|
8
|
+
/* */
|
|
9
|
+
/**********************************************************************************/
|
|
10
|
+
|
|
11
|
+
function rotate(data: Float32Array, size: number, clockwise: boolean = true): void {
|
|
12
|
+
for (let layer = 0; layer < Math.floor(size / 2); layer++) {
|
|
13
|
+
const first = layer
|
|
14
|
+
const last = size - 1 - layer
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < last - first; i++) {
|
|
17
|
+
const top = getColorIndex(data, first, first + i, size)
|
|
18
|
+
const right = getColorIndex(data, first + i, last, size)
|
|
19
|
+
const bottom = getColorIndex(data, last, last - i, size)
|
|
20
|
+
const left = getColorIndex(data, last - i, first, size)
|
|
21
|
+
|
|
22
|
+
if (clockwise) {
|
|
23
|
+
// top -> right, right -> bottom, bottom -> left, left -> top
|
|
24
|
+
setColorIndex(data, first + i, last, size, top)
|
|
25
|
+
setColorIndex(data, last, last - i, size, right)
|
|
26
|
+
setColorIndex(data, last - i, first, size, bottom)
|
|
27
|
+
setColorIndex(data, first, first + i, size, left)
|
|
28
|
+
} else {
|
|
29
|
+
// top -> left, left -> bottom, bottom -> right, right -> top
|
|
30
|
+
setColorIndex(data, last - i, first, size, top)
|
|
31
|
+
setColorIndex(data, last, last - i, size, left)
|
|
32
|
+
setColorIndex(data, first + i, last, size, bottom)
|
|
33
|
+
setColorIndex(data, first, first + i, size, right)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getColorIndex(buf: Float32Array, x: number, y: number, size: number) {
|
|
39
|
+
return buf[y * size + x]!
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function setColorIndex(buf: Float32Array, x: number, y: number, size: number, pixel: number) {
|
|
43
|
+
buf[y * size + x] = pixel
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createTimeout() {
|
|
48
|
+
let timeout: number
|
|
49
|
+
return {
|
|
50
|
+
set(callback: () => void, time: number) {
|
|
51
|
+
clearTimeout(timeout)
|
|
52
|
+
timeout = setTimeout(callback, time)
|
|
53
|
+
},
|
|
54
|
+
clear() {
|
|
55
|
+
clearTimeout(timeout)
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**********************************************************************************/
|
|
61
|
+
/* */
|
|
62
|
+
/* Constants */
|
|
63
|
+
/* */
|
|
64
|
+
/**********************************************************************************/
|
|
65
|
+
|
|
66
|
+
const WIDTH = 10
|
|
67
|
+
const HEIGHT = 20
|
|
68
|
+
|
|
69
|
+
export const TETROMINO = {
|
|
70
|
+
I: {
|
|
71
|
+
dimensions: [4, 4] as const,
|
|
72
|
+
pixels: () => new Float32Array([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]),
|
|
73
|
+
color: [0, 1, 1], // Cyan
|
|
74
|
+
},
|
|
75
|
+
O: {
|
|
76
|
+
dimensions: [2, 2] as const,
|
|
77
|
+
pixels: () => new Float32Array([2, 2, 2, 2]),
|
|
78
|
+
color: [1, 1, 0], // Yellow
|
|
79
|
+
},
|
|
80
|
+
T: {
|
|
81
|
+
dimensions: [3, 3] as const,
|
|
82
|
+
pixels: () => new Float32Array([0, 3, 0, 3, 3, 3, 0, 0, 0]),
|
|
83
|
+
color: [0.6, 0, 1], // Purple
|
|
84
|
+
},
|
|
85
|
+
S: {
|
|
86
|
+
dimensions: [3, 3] as const,
|
|
87
|
+
pixels: () => new Float32Array([0, 4, 4, 4, 4, 0, 0, 0, 0]),
|
|
88
|
+
color: [0, 1, 0],
|
|
89
|
+
},
|
|
90
|
+
Z: {
|
|
91
|
+
dimensions: [3, 3] as const,
|
|
92
|
+
pixels: () => new Float32Array([5, 5, 0, 0, 5, 5, 0, 0, 0]),
|
|
93
|
+
color: [1, 0, 0],
|
|
94
|
+
},
|
|
95
|
+
J: {
|
|
96
|
+
dimensions: [3, 3] as const,
|
|
97
|
+
pixels: () => new Float32Array([6, 0, 0, 6, 6, 6, 0, 0, 0]),
|
|
98
|
+
color: [0, 0, 1],
|
|
99
|
+
},
|
|
100
|
+
L: {
|
|
101
|
+
dimensions: [3, 3] as const,
|
|
102
|
+
pixels: () => new Float32Array([0, 0, 7, 7, 7, 7, 0, 0, 0]),
|
|
103
|
+
color: [1, 0.5, 0],
|
|
104
|
+
},
|
|
105
|
+
} as const
|
|
106
|
+
|
|
107
|
+
const TETROMINO_KIND = Object.keys(TETROMINO) as Array<keyof typeof TETROMINO>
|
|
108
|
+
|
|
109
|
+
/**********************************************************************************/
|
|
110
|
+
/* */
|
|
111
|
+
/* Create Tetromino */
|
|
112
|
+
/* */
|
|
113
|
+
/**********************************************************************************/
|
|
114
|
+
|
|
115
|
+
function getRandomTetrominoKind() {
|
|
116
|
+
return TETROMINO_KIND[Math.floor(Math.random() * TETROMINO_KIND.length)]!
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function createTetromino(
|
|
120
|
+
gl: GL,
|
|
121
|
+
program: WebGLProgram,
|
|
122
|
+
schema: {
|
|
123
|
+
a_index: { kind: 'float'; instanced: true }
|
|
124
|
+
a_pixel: { kind: 'float'; instanced: true }
|
|
125
|
+
},
|
|
126
|
+
) {
|
|
127
|
+
const { a_index, a_pixel } = attributeView(gl, program, schema)
|
|
128
|
+
|
|
129
|
+
let pixels: Float32Array = null!
|
|
130
|
+
let dimensions: [number, number] = null!
|
|
131
|
+
let offset: [number, number] = null!
|
|
132
|
+
let pixelCount: number = null!
|
|
133
|
+
|
|
134
|
+
function next(kind = getRandomTetrominoKind()) {
|
|
135
|
+
const template = TETROMINO[kind]
|
|
136
|
+
pixels = template.pixels()
|
|
137
|
+
pixelCount = template.dimensions[0] * template.dimensions[1]
|
|
138
|
+
offset = [Math.ceil((WIDTH - template.dimensions[0]) / 2), HEIGHT - template.dimensions[1]] as [
|
|
139
|
+
number,
|
|
140
|
+
number,
|
|
141
|
+
]
|
|
142
|
+
dimensions = template.dimensions as [number, number]
|
|
143
|
+
|
|
144
|
+
a_index.set(new Float32Array(Array.from({ length: pixelCount }, (_, index) => index)))
|
|
145
|
+
a_pixel.set(pixels)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
next()
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
get dimensions() {
|
|
152
|
+
return dimensions
|
|
153
|
+
},
|
|
154
|
+
get pixels() {
|
|
155
|
+
return pixels
|
|
156
|
+
},
|
|
157
|
+
get offset() {
|
|
158
|
+
return offset
|
|
159
|
+
},
|
|
160
|
+
get pixelCount() {
|
|
161
|
+
return pixelCount
|
|
162
|
+
},
|
|
163
|
+
bindAttributes() {
|
|
164
|
+
a_index.bind()
|
|
165
|
+
a_pixel.bind()
|
|
166
|
+
},
|
|
167
|
+
next,
|
|
168
|
+
rotate(clockwise = true) {
|
|
169
|
+
rotate(pixels, dimensions[1], clockwise)
|
|
170
|
+
a_pixel.set(pixels).bind()
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**********************************************************************************/
|
|
176
|
+
/* */
|
|
177
|
+
/* Create Board */
|
|
178
|
+
/* */
|
|
179
|
+
/**********************************************************************************/
|
|
180
|
+
|
|
181
|
+
function createBoard(
|
|
182
|
+
gl: GL,
|
|
183
|
+
program: WebGLProgram,
|
|
184
|
+
schema: {
|
|
185
|
+
a_index: { kind: 'float'; instanced: true }
|
|
186
|
+
a_pixel: { kind: 'float'; instanced: true }
|
|
187
|
+
},
|
|
188
|
+
) {
|
|
189
|
+
const { a_index, a_pixel } = attributeView(gl, program, schema)
|
|
190
|
+
const array = new Float32Array(Array.from({ length: WIDTH * HEIGHT }, () => 0))
|
|
191
|
+
|
|
192
|
+
a_index.set(new Float32Array(Array.from({ length: WIDTH * HEIGHT }, (_, index) => index)))
|
|
193
|
+
a_pixel.set(array)
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
bindAttributes() {
|
|
197
|
+
a_index.bind()
|
|
198
|
+
a_pixel.bind()
|
|
199
|
+
},
|
|
200
|
+
array,
|
|
201
|
+
blit({ pixelCount, offset, dimensions, pixels }: ReturnType<typeof createTetromino>) {
|
|
202
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
203
|
+
const pixel = pixels[i]!
|
|
204
|
+
|
|
205
|
+
if (pixel) {
|
|
206
|
+
const x = offset[0] + (i % dimensions[0])
|
|
207
|
+
const y = offset[1] + Math.floor(i / dimensions[0]) + 1
|
|
208
|
+
const index = x + y * WIDTH
|
|
209
|
+
array[index] = pixel
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let writeRow = 0
|
|
214
|
+
|
|
215
|
+
for (let y = 0; y < HEIGHT; y++) {
|
|
216
|
+
let isComplete = true
|
|
217
|
+
|
|
218
|
+
for (let x = 0; x < WIDTH; x++) {
|
|
219
|
+
if (array[x + y * WIDTH] === 0) {
|
|
220
|
+
isComplete = false
|
|
221
|
+
break
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!isComplete) {
|
|
226
|
+
if (writeRow !== y) {
|
|
227
|
+
for (let x = 0; x < WIDTH; x++) {
|
|
228
|
+
array[x + writeRow * WIDTH] = array[x + y * WIDTH]!
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
writeRow++
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
a_pixel.set(array)
|
|
236
|
+
},
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**********************************************************************************/
|
|
241
|
+
/* */
|
|
242
|
+
/* Create Tetris */
|
|
243
|
+
/* */
|
|
244
|
+
/**********************************************************************************/
|
|
245
|
+
|
|
246
|
+
const vertex = glsl`
|
|
247
|
+
precision mediump float;
|
|
248
|
+
|
|
249
|
+
${attribute.float('a_index', { instanced: true })}
|
|
250
|
+
${attribute.float('a_pixel', { instanced: true })}
|
|
251
|
+
${attribute.vec2('a_vertex')}
|
|
252
|
+
${uniform.vec2('u_offset')}
|
|
253
|
+
${uniform.vec2('u_dimensions')}
|
|
254
|
+
${uniform.vec3('u_palette', { size: 7 })}
|
|
255
|
+
|
|
256
|
+
varying vec3 v_color;
|
|
257
|
+
|
|
258
|
+
void main(){
|
|
259
|
+
float columns = mod(a_index, u_dimensions.x);
|
|
260
|
+
float rows = floor(a_index / u_dimensions.x);
|
|
261
|
+
|
|
262
|
+
vec2 offset = (vec2(columns, rows) + u_offset);
|
|
263
|
+
|
|
264
|
+
vec2 base = offset + 0.5;
|
|
265
|
+
vec2 pos = (a_vertex + base) / vec2(10., 20.);
|
|
266
|
+
|
|
267
|
+
gl_Position = vec4(pos * 2.0 - 1.0, 0.0, 1.0);
|
|
268
|
+
|
|
269
|
+
if(a_pixel == 0.){
|
|
270
|
+
v_color = vec3(0., 0., 0.);
|
|
271
|
+
}else{
|
|
272
|
+
v_color = u_palette[int(a_pixel) - 1];
|
|
273
|
+
}
|
|
274
|
+
}`
|
|
275
|
+
|
|
276
|
+
const fragment = glsl`
|
|
277
|
+
precision mediump float;
|
|
278
|
+
|
|
279
|
+
varying vec3 v_color;
|
|
280
|
+
|
|
281
|
+
void main() {
|
|
282
|
+
if(v_color.x + v_color.y + v_color.z == 0.){
|
|
283
|
+
discard;
|
|
284
|
+
}
|
|
285
|
+
gl_FragColor = vec4(v_color, 1.0);
|
|
286
|
+
}`
|
|
287
|
+
|
|
288
|
+
function createTetris() {
|
|
289
|
+
// Setup canvas
|
|
290
|
+
const canvas = document.createElement('canvas')
|
|
291
|
+
canvas.width = WIDTH * 30
|
|
292
|
+
canvas.height = HEIGHT * 30
|
|
293
|
+
document.body.append(canvas)
|
|
294
|
+
|
|
295
|
+
const animationTimeout = createTimeout()
|
|
296
|
+
|
|
297
|
+
// Setup gl
|
|
298
|
+
const gl = canvas.getContext('webgl2')!
|
|
299
|
+
gl.viewport(0, 0, canvas.width, canvas.height)
|
|
300
|
+
|
|
301
|
+
// Setup program + schema
|
|
302
|
+
const { program, schema } = compile(gl, vertex, fragment)
|
|
303
|
+
gl.useProgram(program)
|
|
304
|
+
|
|
305
|
+
// Setup views
|
|
306
|
+
const { u_dimensions, u_offset, u_palette } = uniformView(gl, program, schema.uniforms)
|
|
307
|
+
const { a_vertex, ...localSchema } = schema.attributes
|
|
308
|
+
const globalAttributes = attributeView(gl, program, { a_vertex })
|
|
309
|
+
|
|
310
|
+
// Setup board
|
|
311
|
+
const board = createBoard(gl, program, localSchema)
|
|
312
|
+
|
|
313
|
+
// Setup tetromino
|
|
314
|
+
const tetromino = createTetromino(gl, program, localSchema)
|
|
315
|
+
|
|
316
|
+
// Inititalize static uniforms/attributes
|
|
317
|
+
u_palette.set(new Float32Array(Object.values(TETROMINO).flatMap(({ color }) => color)))
|
|
318
|
+
globalAttributes.a_vertex
|
|
319
|
+
.set(
|
|
320
|
+
new Float32Array([
|
|
321
|
+
// triangle 1
|
|
322
|
+
-0.5, -0.5, 0.5, -0.5, -0.5, 0.5,
|
|
323
|
+
// triangle 2
|
|
324
|
+
-0.5, 0.5, 0.5, -0.5, 0.5, 0.5,
|
|
325
|
+
]),
|
|
326
|
+
)
|
|
327
|
+
.bind()
|
|
328
|
+
|
|
329
|
+
function checkCollision() {
|
|
330
|
+
for (let i = 0; i < tetromino.pixelCount; i++) {
|
|
331
|
+
const x = (i % tetromino.dimensions[0]) + tetromino.offset[0]
|
|
332
|
+
const y = Math.floor(i / tetromino.dimensions[0]) + tetromino.offset[1]
|
|
333
|
+
|
|
334
|
+
let pixel = tetromino.pixels[i]!
|
|
335
|
+
|
|
336
|
+
if (pixel > 0) {
|
|
337
|
+
if (x < 0) return true
|
|
338
|
+
if (x >= WIDTH) return true
|
|
339
|
+
if (y < 0) return true
|
|
340
|
+
|
|
341
|
+
const boardPixel = board.array[x + y * WIDTH]
|
|
342
|
+
|
|
343
|
+
if (boardPixel !== 0) {
|
|
344
|
+
return true
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function next() {
|
|
351
|
+
board.blit(tetromino)
|
|
352
|
+
tetromino.next()
|
|
353
|
+
|
|
354
|
+
if (checkCollision()) {
|
|
355
|
+
document.removeEventListener('keydown', handleKeyDown)
|
|
356
|
+
animationTimeout.clear()
|
|
357
|
+
} else {
|
|
358
|
+
render()
|
|
359
|
+
animationTimeout.set(animate, 1_000)
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Initialize event listeners
|
|
364
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
365
|
+
switch (event.key) {
|
|
366
|
+
case 'ArrowUp':
|
|
367
|
+
tetromino.rotate()
|
|
368
|
+
if (checkCollision()) {
|
|
369
|
+
tetromino.rotate(false)
|
|
370
|
+
} else {
|
|
371
|
+
render()
|
|
372
|
+
}
|
|
373
|
+
break
|
|
374
|
+
case 'ArrowDown':
|
|
375
|
+
tetromino.offset[1]--
|
|
376
|
+
if (checkCollision()) {
|
|
377
|
+
next()
|
|
378
|
+
} else {
|
|
379
|
+
render()
|
|
380
|
+
}
|
|
381
|
+
break
|
|
382
|
+
case 'ArrowLeft':
|
|
383
|
+
tetromino.offset[0]--
|
|
384
|
+
if (checkCollision()) {
|
|
385
|
+
tetromino.offset[0]++
|
|
386
|
+
} else {
|
|
387
|
+
render()
|
|
388
|
+
}
|
|
389
|
+
break
|
|
390
|
+
case 'ArrowRight':
|
|
391
|
+
tetromino.offset[0]++
|
|
392
|
+
if (checkCollision()) {
|
|
393
|
+
tetromino.offset[0]--
|
|
394
|
+
} else {
|
|
395
|
+
render()
|
|
396
|
+
}
|
|
397
|
+
break
|
|
398
|
+
case ' ':
|
|
399
|
+
while (true) {
|
|
400
|
+
tetromino.offset[1]--
|
|
401
|
+
if (checkCollision()) {
|
|
402
|
+
next()
|
|
403
|
+
break
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
document.addEventListener('keydown', handleKeyDown)
|
|
409
|
+
|
|
410
|
+
function render() {
|
|
411
|
+
gl.clearColor(0.0, 0.5, 0.0, 0.3)
|
|
412
|
+
gl.clear(gl.COLOR_BUFFER_BIT)
|
|
413
|
+
|
|
414
|
+
// Draw board
|
|
415
|
+
u_offset.set(0, 0)
|
|
416
|
+
u_dimensions.set(WIDTH, HEIGHT)
|
|
417
|
+
board.bindAttributes()
|
|
418
|
+
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, WIDTH * HEIGHT)
|
|
419
|
+
|
|
420
|
+
// Draw tetromino
|
|
421
|
+
u_offset.set(...tetromino.offset)
|
|
422
|
+
u_dimensions.set(...tetromino.dimensions)
|
|
423
|
+
tetromino.bindAttributes()
|
|
424
|
+
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, tetromino.pixelCount)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function animate() {
|
|
428
|
+
tetromino.offset[1]--
|
|
429
|
+
if (checkCollision()) {
|
|
430
|
+
next()
|
|
431
|
+
}
|
|
432
|
+
animationTimeout.set(animate, 1_000)
|
|
433
|
+
}
|
|
434
|
+
setTimeout(animate, 1_000)
|
|
435
|
+
|
|
436
|
+
render()
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
createTetris()
|
package/dev/utils.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
type CursorEvent = PointerEvent & {
|
|
2
|
+
distanceX: number
|
|
3
|
+
distanceY: number
|
|
4
|
+
deltaX: number
|
|
5
|
+
deltaY: number
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function cursor(event: PointerEvent, callback: (event: CursorEvent) => void) {
|
|
9
|
+
const { promise, resolve } = Promise.withResolvers<void>()
|
|
10
|
+
const controller = new AbortController()
|
|
11
|
+
|
|
12
|
+
const initialX = event.clientX
|
|
13
|
+
const initialY = event.clientY
|
|
14
|
+
|
|
15
|
+
let previousX = event.clientX
|
|
16
|
+
let previousY = event.clientY
|
|
17
|
+
|
|
18
|
+
window.addEventListener(
|
|
19
|
+
'pointermove',
|
|
20
|
+
event => {
|
|
21
|
+
callback(
|
|
22
|
+
Object.assign(event, {
|
|
23
|
+
distanceX: initialX - event.clientX,
|
|
24
|
+
distanceY: initialY - event.clientY,
|
|
25
|
+
deltaX: previousX - event.clientX,
|
|
26
|
+
deltaY: previousY - event.clientY,
|
|
27
|
+
}),
|
|
28
|
+
)
|
|
29
|
+
previousX = event.clientX
|
|
30
|
+
previousY = event.clientY
|
|
31
|
+
},
|
|
32
|
+
controller,
|
|
33
|
+
)
|
|
34
|
+
window.addEventListener(
|
|
35
|
+
'pointerup',
|
|
36
|
+
() => {
|
|
37
|
+
controller.abort()
|
|
38
|
+
resolve()
|
|
39
|
+
},
|
|
40
|
+
controller,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return promise
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createElement<T extends keyof HTMLElementTagNameMap>(
|
|
47
|
+
tag: T,
|
|
48
|
+
{
|
|
49
|
+
parentElement,
|
|
50
|
+
...options
|
|
51
|
+
}: Partial<Omit<HTMLElementTagNameMap[T], 'style'> & { style: string }> = {},
|
|
52
|
+
) {
|
|
53
|
+
const element = document.createElement(tag)
|
|
54
|
+
for (const key in options) {
|
|
55
|
+
element[key] = options[key]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
;(parentElement ?? document.body).appendChild(element)
|
|
59
|
+
|
|
60
|
+
return element
|
|
61
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import tsconfigPaths from 'vite-tsconfig-paths'
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
base: './',
|
|
6
|
+
assetsInclude: ['assets/**/*'],
|
|
7
|
+
plugins: [
|
|
8
|
+
tsconfigPaths(),
|
|
9
|
+
{
|
|
10
|
+
name: 'Reaplace env variables',
|
|
11
|
+
transform(code, id) {
|
|
12
|
+
if (id.includes('node_modules')) {
|
|
13
|
+
return code
|
|
14
|
+
}
|
|
15
|
+
return code
|
|
16
|
+
.replace(/process\.env\.SSR/g, 'false')
|
|
17
|
+
.replace(/process\.env\.DEV/g, 'true')
|
|
18
|
+
.replace(/process\.env\.PROD/g, 'false')
|
|
19
|
+
.replace(/process\.env\.NODE_ENV/g, '"development"')
|
|
20
|
+
.replace(/import\.meta\.env\.SSR/g, 'false')
|
|
21
|
+
.replace(/import\.meta\.env\.DEV/g, 'true')
|
|
22
|
+
.replace(/import\.meta\.env\.PROD/g, 'false')
|
|
23
|
+
.replace(/import\.meta\.env\.NODE_ENV/g, '"development"')
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
optimizeDeps: {
|
|
28
|
+
esbuildOptions: {
|
|
29
|
+
define: {
|
|
30
|
+
global: 'globalThis',
|
|
31
|
+
},
|
|
32
|
+
plugins: [],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
server: {
|
|
36
|
+
port: 3000,
|
|
37
|
+
},
|
|
38
|
+
build: {
|
|
39
|
+
target: 'esnext',
|
|
40
|
+
minify: false,
|
|
41
|
+
},
|
|
42
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bigmistqke/view.gl",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"import": "./index.mjs",
|
|
8
|
+
"types": "./index.d.mts"
|
|
9
|
+
},
|
|
10
|
+
"./tag": {
|
|
11
|
+
"import": "./tag.mjs",
|
|
12
|
+
"types": "./tag.d.mts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"keywords": [],
|
|
16
|
+
"author": "",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"dependencies": {},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"tsup": "^8.5.0",
|
|
21
|
+
"typescript": "^5.8.3",
|
|
22
|
+
"vite": "^7.0.2",
|
|
23
|
+
"vite-tsconfig-paths": "^5.1.4"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"dev": "vite serve dev",
|
|
27
|
+
"build": "tsup"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/features.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { GL, RemoveSuffix } from './types'
|
|
2
|
+
|
|
3
|
+
const INSTANCED_ARRAYS_WRAPPER_MAP = new WeakMap<
|
|
4
|
+
GL,
|
|
5
|
+
RemoveSuffix<
|
|
6
|
+
Pick<
|
|
7
|
+
ANGLE_instanced_arrays,
|
|
8
|
+
'drawArraysInstancedANGLE' | 'drawElementsInstancedANGLE' | 'vertexAttribDivisorANGLE'
|
|
9
|
+
>,
|
|
10
|
+
'ANGLE'
|
|
11
|
+
> | null
|
|
12
|
+
>()
|
|
13
|
+
export function getInstancedArrays(gl: GL) {
|
|
14
|
+
if (gl instanceof WebGL2RenderingContext) return gl
|
|
15
|
+
|
|
16
|
+
const cached = INSTANCED_ARRAYS_WRAPPER_MAP.get(gl)
|
|
17
|
+
if (cached) return cached
|
|
18
|
+
|
|
19
|
+
const ext = gl.getExtension('ANGLE_instanced_arrays')
|
|
20
|
+
if (!ext) return undefined
|
|
21
|
+
|
|
22
|
+
const wrapper = {
|
|
23
|
+
drawArraysInstanced: ext.drawArraysInstancedANGLE.bind(ext),
|
|
24
|
+
drawElementsInstanced: ext.drawElementsInstancedANGLE.bind(ext),
|
|
25
|
+
vertexAttribDivisor: ext.vertexAttribDivisorANGLE.bind(ext),
|
|
26
|
+
}
|
|
27
|
+
INSTANCED_ARRAYS_WRAPPER_MAP.set(gl, wrapper)
|
|
28
|
+
|
|
29
|
+
return wrapper
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const VERTEX_ARRAY_OBJECT_WRAPPER_MAP = new WeakMap<
|
|
33
|
+
GL,
|
|
34
|
+
RemoveSuffix<
|
|
35
|
+
Pick<
|
|
36
|
+
OES_vertex_array_object,
|
|
37
|
+
'bindVertexArrayOES' | 'createVertexArrayOES' | 'deleteVertexArrayOES'
|
|
38
|
+
>,
|
|
39
|
+
'OES'
|
|
40
|
+
> | null
|
|
41
|
+
>()
|
|
42
|
+
export function getVertexArrayObject(gl: GL) {
|
|
43
|
+
if (gl instanceof WebGL2RenderingContext) return gl
|
|
44
|
+
|
|
45
|
+
const cached = VERTEX_ARRAY_OBJECT_WRAPPER_MAP.get(gl)
|
|
46
|
+
if (cached) return cached
|
|
47
|
+
|
|
48
|
+
const ext = gl.getExtension('OES_vertex_array_object')
|
|
49
|
+
if (!ext) return null
|
|
50
|
+
|
|
51
|
+
const wrapper = {
|
|
52
|
+
bindVertexArray: ext.bindVertexArrayOES.bind(ext),
|
|
53
|
+
createVertexArray: ext.createVertexArrayOES.bind(ext),
|
|
54
|
+
deleteVertexArray: ext.deleteVertexArrayOES.bind(ext),
|
|
55
|
+
}
|
|
56
|
+
VERTEX_ARRAY_OBJECT_WRAPPER_MAP.set(gl, wrapper)
|
|
57
|
+
|
|
58
|
+
return wrapper
|
|
59
|
+
}
|