bloody-engine 1.0.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.
@@ -0,0 +1,2117 @@
1
+ import sdl from "@kmamal/sdl";
2
+ import fs from "fs";
3
+ import { execSync } from "child_process";
4
+ import path__default from "path";
5
+ import createGL from "gl";
6
+ class Texture {
7
+ /**
8
+ * Create a texture from pixel data
9
+ * @param gl WebGL context
10
+ * @param width Texture width
11
+ * @param height Texture height
12
+ * @param data Pixel data (Uint8Array RGBA)
13
+ */
14
+ constructor(gl2, width, height, data) {
15
+ this.gl = gl2;
16
+ this.width = width;
17
+ this.height = height;
18
+ const tex = gl2.createTexture();
19
+ if (!tex) {
20
+ throw new Error("Failed to create texture");
21
+ }
22
+ this.texture = tex;
23
+ gl2.bindTexture(gl2.TEXTURE_2D, this.texture);
24
+ gl2.texParameteri(gl2.TEXTURE_2D, gl2.TEXTURE_WRAP_S, gl2.CLAMP_TO_EDGE);
25
+ gl2.texParameteri(gl2.TEXTURE_2D, gl2.TEXTURE_WRAP_T, gl2.CLAMP_TO_EDGE);
26
+ gl2.texParameteri(gl2.TEXTURE_2D, gl2.TEXTURE_MIN_FILTER, gl2.LINEAR);
27
+ gl2.texParameteri(gl2.TEXTURE_2D, gl2.TEXTURE_MAG_FILTER, gl2.LINEAR);
28
+ if (data) {
29
+ gl2.texImage2D(
30
+ gl2.TEXTURE_2D,
31
+ 0,
32
+ gl2.RGBA,
33
+ width,
34
+ height,
35
+ 0,
36
+ gl2.RGBA,
37
+ gl2.UNSIGNED_BYTE,
38
+ data
39
+ );
40
+ } else {
41
+ gl2.texImage2D(
42
+ gl2.TEXTURE_2D,
43
+ 0,
44
+ gl2.RGBA,
45
+ width,
46
+ height,
47
+ 0,
48
+ gl2.RGBA,
49
+ gl2.UNSIGNED_BYTE,
50
+ null
51
+ );
52
+ }
53
+ gl2.bindTexture(gl2.TEXTURE_2D, null);
54
+ }
55
+ /**
56
+ * Create a solid color texture
57
+ * @param gl WebGL context
58
+ * @param width Texture width
59
+ * @param height Texture height
60
+ * @param r Red (0-255)
61
+ * @param g Green (0-255)
62
+ * @param b Blue (0-255)
63
+ * @param a Alpha (0-255)
64
+ */
65
+ static createSolid(gl2, width, height, r, g, b, a = 255) {
66
+ const pixelCount = width * height;
67
+ const data = new Uint8Array(pixelCount * 4);
68
+ for (let i = 0; i < pixelCount; i++) {
69
+ const offset = i * 4;
70
+ data[offset] = r;
71
+ data[offset + 1] = g;
72
+ data[offset + 2] = b;
73
+ data[offset + 3] = a;
74
+ }
75
+ return new Texture(gl2, width, height, data);
76
+ }
77
+ /**
78
+ * Create a checkerboard texture
79
+ * @param gl WebGL context
80
+ * @param width Texture width
81
+ * @param height Texture height
82
+ * @param squareSize Size of each square
83
+ */
84
+ static createCheckerboard(gl2, width, height, squareSize = 32) {
85
+ const data = new Uint8Array(width * height * 4);
86
+ for (let y = 0; y < height; y++) {
87
+ for (let x = 0; x < width; x++) {
88
+ const squareX = Math.floor(x / squareSize);
89
+ const squareY = Math.floor(y / squareSize);
90
+ const isWhite = (squareX + squareY) % 2 === 0;
91
+ const offset = (y * width + x) * 4;
92
+ const color = isWhite ? 255 : 0;
93
+ data[offset] = color;
94
+ data[offset + 1] = color;
95
+ data[offset + 2] = color;
96
+ data[offset + 3] = 255;
97
+ }
98
+ }
99
+ return new Texture(gl2, width, height, data);
100
+ }
101
+ /**
102
+ * Create a gradient texture
103
+ * @param gl WebGL context
104
+ * @param width Texture width
105
+ * @param height Texture height
106
+ */
107
+ static createGradient(gl2, width, height) {
108
+ const data = new Uint8Array(width * height * 4);
109
+ for (let y = 0; y < height; y++) {
110
+ for (let x = 0; x < width; x++) {
111
+ const offset = (y * width + x) * 4;
112
+ data[offset] = Math.floor(x / width * 255);
113
+ data[offset + 1] = Math.floor(y / height * 255);
114
+ data[offset + 2] = 128;
115
+ data[offset + 3] = 255;
116
+ }
117
+ }
118
+ return new Texture(gl2, width, height, data);
119
+ }
120
+ /**
121
+ * Bind this texture to a texture unit
122
+ * @param unit Texture unit (0-7 typically)
123
+ */
124
+ bind(unit = 0) {
125
+ this.gl.activeTexture(this.gl.TEXTURE0 + unit);
126
+ this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
127
+ }
128
+ /**
129
+ * Unbind texture
130
+ */
131
+ unbind() {
132
+ this.gl.bindTexture(this.gl.TEXTURE_2D, null);
133
+ }
134
+ /**
135
+ * Get the underlying WebGL texture
136
+ */
137
+ getHandle() {
138
+ return this.texture;
139
+ }
140
+ /**
141
+ * Get texture dimensions
142
+ */
143
+ getDimensions() {
144
+ return { width: this.width, height: this.height };
145
+ }
146
+ /**
147
+ * Clean up texture resources
148
+ */
149
+ dispose() {
150
+ this.gl.deleteTexture(this.texture);
151
+ }
152
+ }
153
+ const texture = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
154
+ __proto__: null,
155
+ Texture
156
+ }, Symbol.toStringTag, { value: "Module" }));
157
+ class VertexBuffer {
158
+ constructor(gl2, data, stride = 0) {
159
+ this.gl = gl2;
160
+ this.stride = stride;
161
+ const floatsPerVertex = stride > 0 ? stride / 4 : 3;
162
+ this.vertexCount = data.length / floatsPerVertex;
163
+ const buf = gl2.createBuffer();
164
+ if (!buf) {
165
+ throw new Error("Failed to create vertex buffer");
166
+ }
167
+ this.buffer = buf;
168
+ gl2.bindBuffer(gl2.ARRAY_BUFFER, this.buffer);
169
+ gl2.bufferData(gl2.ARRAY_BUFFER, data, gl2.STATIC_DRAW);
170
+ gl2.bindBuffer(gl2.ARRAY_BUFFER, null);
171
+ }
172
+ /**
173
+ * Bind buffer for rendering
174
+ */
175
+ bind() {
176
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
177
+ }
178
+ /**
179
+ * Unbind buffer
180
+ */
181
+ unbind() {
182
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
183
+ }
184
+ /**
185
+ * Get vertex count
186
+ */
187
+ getVertexCount() {
188
+ return this.vertexCount;
189
+ }
190
+ /**
191
+ * Get stride
192
+ */
193
+ getStride() {
194
+ return this.stride;
195
+ }
196
+ /**
197
+ * Clean up resources
198
+ */
199
+ dispose() {
200
+ this.gl.deleteBuffer(this.buffer);
201
+ }
202
+ }
203
+ const buffer = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
204
+ __proto__: null,
205
+ VertexBuffer
206
+ }, Symbol.toStringTag, { value: "Module" }));
207
+ class SDLWindow {
208
+ constructor(width, height, title = "Bloody Engine") {
209
+ this.closed = false;
210
+ this.width = width;
211
+ this.height = height;
212
+ this.title = title;
213
+ try {
214
+ this.window = sdl.video.createWindow({
215
+ width: this.width,
216
+ height: this.height,
217
+ title: this.title
218
+ });
219
+ if (!this.window) {
220
+ throw new Error("Failed to create SDL window");
221
+ }
222
+ this.window.on("close", () => {
223
+ this.closed = true;
224
+ });
225
+ console.log(`✓ SDL Window created (${width}x${height}): "${title}"`);
226
+ } catch (error) {
227
+ this.cleanup();
228
+ throw new Error(`Window creation failed: ${error}`);
229
+ }
230
+ }
231
+ /**
232
+ * Get window dimensions
233
+ */
234
+ getDimensions() {
235
+ return { width: this.width, height: this.height };
236
+ }
237
+ /**
238
+ * Display pixel data in the window
239
+ * @param pixels Uint8Array of RGBA pixel data
240
+ */
241
+ updatePixels(pixels) {
242
+ if (!this.window || this.closed) {
243
+ return;
244
+ }
245
+ try {
246
+ const buffer2 = Buffer.from(pixels);
247
+ const stride = this.width * 4;
248
+ this.window.render(this.width, this.height, stride, "rgba32", buffer2);
249
+ } catch (error) {
250
+ console.error("Failed to update pixels:", error);
251
+ }
252
+ }
253
+ /**
254
+ * Register an event handler
255
+ */
256
+ on(eventName, handler) {
257
+ if (!this.window || this.closed) {
258
+ return;
259
+ }
260
+ try {
261
+ this.window.on(eventName, (event) => {
262
+ try {
263
+ handler(event);
264
+ } catch (error) {
265
+ console.error(`Error in ${eventName} handler:`, error);
266
+ }
267
+ });
268
+ } catch (error) {
269
+ console.error(`Error registering ${eventName} handler:`, error);
270
+ }
271
+ }
272
+ /**
273
+ * Check if window is still open
274
+ */
275
+ isOpen() {
276
+ return this.window !== null && !this.closed;
277
+ }
278
+ /**
279
+ * Cleanup and close window
280
+ */
281
+ cleanup() {
282
+ if (this.window && !this.closed) {
283
+ try {
284
+ this.window.destroy();
285
+ } catch (error) {
286
+ console.warn("Error destroying window:", error);
287
+ }
288
+ this.window = null;
289
+ this.closed = true;
290
+ }
291
+ console.log("✓ SDL Window cleaned up");
292
+ }
293
+ /**
294
+ * Destroy the window (alias for cleanup)
295
+ */
296
+ destroy() {
297
+ this.cleanup();
298
+ }
299
+ }
300
+ class ProjectionConfig {
301
+ // Scale factor for height (vertical exaggeration)
302
+ constructor(tileWidth = 64, tileHeight = 32, zScale = 1) {
303
+ this.tileWidth = tileWidth;
304
+ this.tileHeight = tileHeight;
305
+ this.zScale = zScale;
306
+ }
307
+ }
308
+ function gridToScreen(gridCoord, config) {
309
+ const halfTileWidth = config.tileWidth / 2;
310
+ const halfTileHeight = config.tileHeight / 2;
311
+ const xscreen = (gridCoord.xgrid - gridCoord.ygrid) * halfTileWidth;
312
+ const yscreen = (gridCoord.xgrid + gridCoord.ygrid) * halfTileHeight - gridCoord.zheight * config.zScale;
313
+ return { xscreen, yscreen };
314
+ }
315
+ function getCellCenterOffset(config) {
316
+ return {
317
+ xscreen: 0,
318
+ yscreen: config.tileHeight / 2
319
+ };
320
+ }
321
+ function createDefaultProjection() {
322
+ return new ProjectionConfig(64, 32, 1);
323
+ }
324
+ const SCENE_CONFIG = {
325
+ width: 800,
326
+ height: 600,
327
+ targetFPS: 60
328
+ };
329
+ const PROJECTION_CONFIG = new ProjectionConfig(
330
+ 64,
331
+ // tileWidth: 64 pixels
332
+ 32,
333
+ // tileHeight: 32 pixels
334
+ 1
335
+ // zScale: 1:1 height scale
336
+ );
337
+ const PROJECTION_ENTITIES = [
338
+ {
339
+ id: "player",
340
+ name: "Player",
341
+ gridPos: { xgrid: 5, ygrid: 5, zheight: 0 },
342
+ color: [0.2, 1, 0.2],
343
+ // Green
344
+ size: 1
345
+ },
346
+ {
347
+ id: "enemy1",
348
+ name: "Enemy",
349
+ gridPos: { xgrid: 10, ygrid: 8, zheight: 0 },
350
+ color: [1, 0.2, 0.2],
351
+ // Red
352
+ size: 0.9
353
+ },
354
+ {
355
+ id: "chest",
356
+ name: "Treasure",
357
+ gridPos: { xgrid: 8, ygrid: 6, zheight: 0 },
358
+ color: [1, 1, 0.2],
359
+ // Yellow
360
+ size: 0.7
361
+ },
362
+ {
363
+ id: "tower",
364
+ name: "Tower",
365
+ gridPos: { xgrid: 12, ygrid: 12, zheight: 3 },
366
+ color: [0.5, 0.5, 1],
367
+ // Light Blue
368
+ size: 0.8
369
+ },
370
+ {
371
+ id: "floating",
372
+ name: "Floating Object",
373
+ gridPos: { xgrid: 3, ygrid: 10, zheight: 5 },
374
+ color: [1, 0.5, 1],
375
+ // Magenta
376
+ size: 0.6
377
+ }
378
+ ];
379
+ const GEOMETRY = {
380
+ quad: {
381
+ name: "quad",
382
+ vertices: new Float32Array([
383
+ // Position TexCoord
384
+ -0.5,
385
+ -0.5,
386
+ 0,
387
+ 0,
388
+ 0,
389
+ // Bottom-left
390
+ 0.5,
391
+ -0.5,
392
+ 0,
393
+ 1,
394
+ 0,
395
+ // Bottom-right
396
+ 0.5,
397
+ 0.5,
398
+ 0,
399
+ 1,
400
+ 1,
401
+ // Top-right
402
+ 0.5,
403
+ 0.5,
404
+ 0,
405
+ 1,
406
+ 1,
407
+ // Top-right
408
+ -0.5,
409
+ 0.5,
410
+ 0,
411
+ 0,
412
+ 1,
413
+ // Top-left
414
+ -0.5,
415
+ -0.5,
416
+ 0,
417
+ 0,
418
+ 0
419
+ // Bottom-left
420
+ ]),
421
+ stride: 5 * 4
422
+ // 5 floats per vertex × 4 bytes
423
+ },
424
+ triangle: {
425
+ name: "triangle",
426
+ vertices: new Float32Array([
427
+ // Position TexCoord
428
+ 0,
429
+ 0.5,
430
+ 0,
431
+ 0.5,
432
+ 1,
433
+ // Top
434
+ -0.5,
435
+ -0.5,
436
+ 0,
437
+ 0,
438
+ 0,
439
+ // Bottom-left
440
+ 0.5,
441
+ -0.5,
442
+ 0,
443
+ 1,
444
+ 0
445
+ // Bottom-right
446
+ ]),
447
+ stride: 5 * 4
448
+ // 5 floats per vertex × 4 bytes
449
+ }
450
+ };
451
+ const SHADERS_V2 = {
452
+ vertex: `
453
+ attribute vec3 aPosition;
454
+ attribute vec2 aTexCoord;
455
+ attribute vec4 aColor;
456
+ attribute float aTexIndex;
457
+
458
+ varying vec2 vTexCoord;
459
+ varying vec4 vColor;
460
+ varying float vTexIndex;
461
+
462
+ uniform mat4 uMatrix;
463
+
464
+ void main() {
465
+ gl_Position = uMatrix * vec4(aPosition, 1.0);
466
+ vTexCoord = aTexCoord;
467
+ vColor = aColor;
468
+ vTexIndex = aTexIndex;
469
+ }
470
+ `,
471
+ fragment: `
472
+ precision mediump float;
473
+
474
+ varying vec2 vTexCoord;
475
+ varying vec4 vColor;
476
+ varying float vTexIndex;
477
+
478
+ uniform sampler2D uTexture;
479
+
480
+ void main() {
481
+ // Sample texture
482
+ vec4 texColor = texture2D(uTexture, vTexCoord);
483
+
484
+ // Apply vertex color tint
485
+ gl_FragColor = texColor * vColor;
486
+ }
487
+ `
488
+ };
489
+ const TEXTURE_CONFIG = {
490
+ size: 256,
491
+ type: "gradient"
492
+ // gradient, solid, etc.
493
+ };
494
+ const ANIMATION_CONFIG = {
495
+ background: {
496
+ offsetR: 0.1,
497
+ amplitudeR: 0.1,
498
+ speedR: 1,
499
+ offsetG: 0.1,
500
+ amplitudeG: 0.1,
501
+ speedG: 0.7,
502
+ offsetB: 0.15,
503
+ amplitudeB: 0.1,
504
+ speedB: 0.5
505
+ },
506
+ quads: {
507
+ count: 3,
508
+ scale: 0.4,
509
+ baseRadius: 0.5,
510
+ radiusVariation: 0.2,
511
+ radiusSpeed: 0.3,
512
+ orbitalSpeed: 0.5,
513
+ rotationSpeed: 1,
514
+ colors: [
515
+ [1, 0.2, 0.2],
516
+ // Red
517
+ [0.2, 1, 0.2],
518
+ // Green
519
+ [0.2, 0.2, 1]
520
+ // Blue
521
+ ]
522
+ },
523
+ triangles: {
524
+ count: 2,
525
+ scale: 0.3,
526
+ baseRadius: 0.3,
527
+ radiusVariation: 0.15,
528
+ radiusSpeed: 0.4,
529
+ orbitalSpeed: 0.7,
530
+ rotationSpeed: 1.5,
531
+ rotationDirection: -1,
532
+ // -1 for reverse
533
+ colors: [
534
+ [1, 1, 0.2],
535
+ // Yellow
536
+ [0.2, 1, 1]
537
+ // Cyan
538
+ ]
539
+ }
540
+ };
541
+ function getBackgroundColor(elapsedSeconds) {
542
+ const cfg = ANIMATION_CONFIG.background;
543
+ return {
544
+ r: cfg.offsetR + cfg.amplitudeR * Math.sin(elapsedSeconds * cfg.speedR),
545
+ g: cfg.offsetG + cfg.amplitudeG * Math.cos(elapsedSeconds * cfg.speedG),
546
+ b: cfg.offsetB + cfg.amplitudeB * Math.sin(elapsedSeconds * cfg.speedB),
547
+ a: 1
548
+ };
549
+ }
550
+ function getQuadTransforms(elapsedSeconds) {
551
+ const cfg = ANIMATION_CONFIG.quads;
552
+ const transforms = [];
553
+ for (let i = 0; i < cfg.count; i++) {
554
+ const angle = i / cfg.count * Math.PI * 2 + elapsedSeconds * cfg.orbitalSpeed;
555
+ const radius = cfg.baseRadius + cfg.radiusVariation * Math.sin(elapsedSeconds * cfg.radiusSpeed + i);
556
+ const posX = radius * Math.cos(angle);
557
+ const posY = radius * Math.sin(angle);
558
+ const rotation = elapsedSeconds * cfg.rotationSpeed + i * Math.PI * 2 / cfg.count;
559
+ const cos = Math.cos(rotation);
560
+ const sin = Math.sin(rotation);
561
+ const matrix = new Float32Array([
562
+ cos * cfg.scale,
563
+ sin * cfg.scale,
564
+ 0,
565
+ 0,
566
+ -sin * cfg.scale,
567
+ cos * cfg.scale,
568
+ 0,
569
+ 0,
570
+ 0,
571
+ 0,
572
+ 1,
573
+ 0,
574
+ posX,
575
+ posY,
576
+ 0,
577
+ 1
578
+ ]);
579
+ transforms.push({
580
+ matrix,
581
+ color: cfg.colors[i],
582
+ index: i
583
+ });
584
+ }
585
+ return transforms;
586
+ }
587
+ function getTriangleTransforms(elapsedSeconds) {
588
+ const cfg = ANIMATION_CONFIG.triangles;
589
+ const transforms = [];
590
+ for (let i = 0; i < cfg.count; i++) {
591
+ const angle = i / cfg.count * Math.PI * 2 + elapsedSeconds * cfg.orbitalSpeed;
592
+ const radius = cfg.baseRadius + cfg.radiusVariation * Math.cos(elapsedSeconds * cfg.radiusSpeed + i);
593
+ const posX = radius * Math.cos(angle);
594
+ const posY = radius * Math.sin(angle);
595
+ const rotation = cfg.rotationDirection * elapsedSeconds * cfg.rotationSpeed + i * Math.PI * 2 / cfg.count;
596
+ const cos = Math.cos(rotation);
597
+ const sin = Math.sin(rotation);
598
+ const matrix = new Float32Array([
599
+ cos * cfg.scale,
600
+ sin * cfg.scale,
601
+ 0,
602
+ 0,
603
+ -sin * cfg.scale,
604
+ cos * cfg.scale,
605
+ 0,
606
+ 0,
607
+ 0,
608
+ 0,
609
+ 1,
610
+ 0,
611
+ posX,
612
+ posY,
613
+ 0,
614
+ 1
615
+ ]);
616
+ transforms.push({
617
+ matrix,
618
+ color: cfg.colors[i],
619
+ index: i
620
+ });
621
+ }
622
+ return transforms;
623
+ }
624
+ const scene = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
625
+ __proto__: null,
626
+ ANIMATION_CONFIG,
627
+ GEOMETRY,
628
+ PROJECTION_CONFIG,
629
+ PROJECTION_ENTITIES,
630
+ SCENE_CONFIG,
631
+ SHADERS_V2,
632
+ TEXTURE_CONFIG,
633
+ getBackgroundColor,
634
+ getQuadTransforms,
635
+ getTriangleTransforms
636
+ }, Symbol.toStringTag, { value: "Module" }));
637
+ function initializeGameProjection() {
638
+ const projection = createDefaultProjection();
639
+ console.log("✓ Game projection initialized:");
640
+ console.log(
641
+ ` Tile dimensions: ${projection.tileWidth}x${projection.tileHeight}`
642
+ );
643
+ console.log(` Height scale: ${projection.zScale}`);
644
+ return projection;
645
+ }
646
+ function updateEntityScreenPosition(entity, projection) {
647
+ const screenPos = gridToScreen(entity.gridPos, projection);
648
+ const cellCenter = getCellCenterOffset(projection);
649
+ entity.screenPos = {
650
+ xscreen: screenPos.xscreen + cellCenter.xscreen,
651
+ yscreen: screenPos.yscreen + cellCenter.yscreen
652
+ };
653
+ console.log(
654
+ `✓ ${entity.name} at grid (${entity.gridPos.xgrid}, ${entity.gridPos.ygrid}, ${entity.gridPos.zheight}) → screen (${entity.screenPos.xscreen}, ${entity.screenPos.yscreen})`
655
+ );
656
+ }
657
+ function createEntity(id, name, gridPos, projection) {
658
+ const entity = { id, name, gridPos };
659
+ updateEntityScreenPosition(entity, projection);
660
+ return entity;
661
+ }
662
+ function calculateGridDistance(from, to) {
663
+ return Math.abs(to.xgrid - from.xgrid) + Math.abs(to.ygrid - from.ygrid);
664
+ }
665
+ function calculate3DDistance(from, to) {
666
+ const dx = to.xgrid - from.xgrid;
667
+ const dy = to.ygrid - from.ygrid;
668
+ const dz = to.zheight - from.zheight;
669
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
670
+ }
671
+ function moveEntityTowards(entity, target, projection) {
672
+ const dx = Math.sign(target.xgrid - entity.gridPos.xgrid);
673
+ const dy = Math.sign(target.ygrid - entity.gridPos.ygrid);
674
+ if (dx !== 0) entity.gridPos.xgrid += dx;
675
+ if (dy !== 0) entity.gridPos.ygrid += dy;
676
+ updateEntityScreenPosition(entity, projection);
677
+ const distance = calculateGridDistance(entity.gridPos, target);
678
+ console.log(`✓ ${entity.name} moved, distance to target: ${distance} cells`);
679
+ }
680
+ function calculateRenderPriority(gridCoord) {
681
+ return gridCoord.xgrid + gridCoord.ygrid;
682
+ }
683
+ function sortEntitiesByDepth(entities) {
684
+ return [...entities].sort(
685
+ (a, b) => calculateRenderPriority(a.gridPos) - calculateRenderPriority(b.gridPos)
686
+ );
687
+ }
688
+ function createTerrainHeightMap() {
689
+ return (x, y) => {
690
+ const centerX = 10;
691
+ const centerY = 10;
692
+ const radius = 5;
693
+ const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
694
+ if (distance < radius) {
695
+ return Math.max(0, 5 * (1 - distance / radius));
696
+ }
697
+ return 0;
698
+ };
699
+ }
700
+ function placeEntityOnTerrain(entity, heightMap, projection) {
701
+ entity.gridPos.zheight = heightMap(
702
+ entity.gridPos.xgrid,
703
+ entity.gridPos.ygrid
704
+ );
705
+ updateEntityScreenPosition(entity, projection);
706
+ console.log(
707
+ `✓ ${entity.name} placed on terrain at height ${entity.gridPos.zheight}`
708
+ );
709
+ }
710
+ function createCamera(centerGrid, viewWidthPixels, viewHeightPixels, projection) {
711
+ return {
712
+ center: centerGrid,
713
+ viewWidth: viewWidthPixels,
714
+ viewHeight: viewHeightPixels
715
+ };
716
+ }
717
+ function isEntityInView(entity, camera, projection, margin = 50) {
718
+ if (!entity.screenPos) return false;
719
+ const cameraScreen = gridToScreen(camera.center, projection);
720
+ const halfWidth = camera.viewWidth / 2 + margin;
721
+ const halfHeight = camera.viewHeight / 2 + margin;
722
+ return Math.abs(entity.screenPos.xscreen - cameraScreen.xscreen) < halfWidth && Math.abs(entity.screenPos.yscreen - cameraScreen.yscreen) < halfHeight;
723
+ }
724
+ function runExampleScenario() {
725
+ console.log("\n" + "=".repeat(60));
726
+ console.log("🎮 2.5D Projection System - Example Scenario");
727
+ console.log("=".repeat(60));
728
+ const projection = initializeGameProjection();
729
+ console.log("\n📍 Creating game entities:");
730
+ const player = createEntity(
731
+ "player",
732
+ "Player",
733
+ { xgrid: 5, ygrid: 5, zheight: 0 },
734
+ projection
735
+ );
736
+ const enemy = createEntity(
737
+ "enemy1",
738
+ "Enemy",
739
+ { xgrid: 10, ygrid: 8, zheight: 0 },
740
+ projection
741
+ );
742
+ const chest = createEntity(
743
+ "chest1",
744
+ "Treasure Chest",
745
+ { xgrid: 8, ygrid: 6, zheight: 0 },
746
+ projection
747
+ );
748
+ console.log("\n🚶 Movement example:");
749
+ for (let i = 0; i < 3; i++) {
750
+ moveEntityTowards(player, chest.gridPos, projection);
751
+ }
752
+ console.log("\n📏 Distance calculations:");
753
+ const dist = calculateGridDistance(player.gridPos, enemy.gridPos);
754
+ const dist3d = calculate3DDistance(player.gridPos, enemy.gridPos);
755
+ console.log(` Manhattan distance: ${dist} cells`);
756
+ console.log(` 3D distance: ${dist3d.toFixed(2)} units`);
757
+ console.log("\n🎨 Render order (depth sorting):");
758
+ const entities = [player, enemy, chest];
759
+ const sorted = sortEntitiesByDepth(entities);
760
+ sorted.forEach((e, i) => {
761
+ console.log(
762
+ ` ${i + 1}. ${e.name} (priority: ${calculateRenderPriority(e.gridPos)})`
763
+ );
764
+ });
765
+ console.log("\n🏔️ Terrain integration:");
766
+ const heightMap = createTerrainHeightMap();
767
+ placeEntityOnTerrain(enemy, heightMap, projection);
768
+ console.log("\n📷 Camera example:");
769
+ const camera = createCamera(player.gridPos, 800, 600);
770
+ console.log(
771
+ ` Camera centered at grid (${camera.center.xgrid}, ${camera.center.ygrid})`
772
+ );
773
+ console.log(` View size: ${camera.viewWidth}x${camera.viewHeight} pixels`);
774
+ console.log(
775
+ ` Player in view: ${isEntityInView(player, camera, projection)}`
776
+ );
777
+ console.log(` Enemy in view: ${isEntityInView(enemy, camera, projection)}`);
778
+ console.log("\n" + "=".repeat(60));
779
+ }
780
+ const BASIC_SHADER = {
781
+ vertex: `
782
+ attribute vec3 aPosition;
783
+ attribute vec2 aTexCoord;
784
+
785
+ varying vec2 vTexCoord;
786
+
787
+ uniform mat4 uMatrix;
788
+
789
+ void main() {
790
+ gl_Position = uMatrix * vec4(aPosition, 1.0);
791
+ vTexCoord = aTexCoord;
792
+ }
793
+ `,
794
+ fragment: `
795
+ varying vec2 vTexCoord;
796
+ uniform sampler2D uTexture;
797
+ uniform vec3 uColor;
798
+
799
+ void main() {
800
+ vec4 texColor = texture2D(uTexture, vTexCoord);
801
+ gl_FragColor = vec4(texColor.rgb * uColor, texColor.a);
802
+ }
803
+ `,
804
+ uniforms: ["uMatrix", "uTexture", "uColor"],
805
+ attributes: ["aPosition", "aTexCoord"]
806
+ };
807
+ const GLOW_SHADER = {
808
+ vertex: `
809
+ attribute vec3 aPosition;
810
+ attribute vec2 aTexCoord;
811
+
812
+ varying vec2 vTexCoord;
813
+ varying float vDistance;
814
+
815
+ uniform mat4 uMatrix;
816
+
817
+ void main() {
818
+ gl_Position = uMatrix * vec4(aPosition, 1.0);
819
+ vTexCoord = aTexCoord;
820
+ // Distance from center for glow falloff
821
+ vDistance = length(aTexCoord - vec2(0.5, 0.5));
822
+ }
823
+ `,
824
+ fragment: `
825
+ varying vec2 vTexCoord;
826
+ varying float vDistance;
827
+ uniform sampler2D uTexture;
828
+ uniform vec3 uColor;
829
+ uniform float uGlowIntensity;
830
+
831
+ void main() {
832
+ vec4 texColor = texture2D(uTexture, vTexCoord);
833
+ float glow = 1.0 - vDistance * 2.0;
834
+ glow = max(0.0, glow);
835
+ vec3 glowColor = texColor.rgb * uColor * glow * uGlowIntensity;
836
+ gl_FragColor = vec4(glowColor, texColor.a);
837
+ }
838
+ `,
839
+ uniforms: ["uMatrix", "uTexture", "uColor", "uGlowIntensity"],
840
+ attributes: ["aPosition", "aTexCoord"]
841
+ };
842
+ const WAVE_SHADER = {
843
+ vertex: `
844
+ attribute vec3 aPosition;
845
+ attribute vec2 aTexCoord;
846
+
847
+ varying vec2 vTexCoord;
848
+
849
+ uniform mat4 uMatrix;
850
+ uniform float uTime;
851
+ uniform float uWaveAmount;
852
+
853
+ void main() {
854
+ vec3 pos = aPosition;
855
+ // Apply wave deformation
856
+ pos.y += sin(aPosition.x * 3.14159 + uTime) * uWaveAmount;
857
+ pos.x += cos(aPosition.y * 3.14159 + uTime * 0.7) * uWaveAmount * 0.5;
858
+
859
+ gl_Position = uMatrix * vec4(pos, 1.0);
860
+ vTexCoord = aTexCoord;
861
+ }
862
+ `,
863
+ fragment: `
864
+ varying vec2 vTexCoord;
865
+ uniform sampler2D uTexture;
866
+ uniform vec3 uColor;
867
+
868
+ void main() {
869
+ vec4 texColor = texture2D(uTexture, vTexCoord);
870
+ gl_FragColor = vec4(texColor.rgb * uColor, texColor.a);
871
+ }
872
+ `,
873
+ uniforms: ["uMatrix", "uTexture", "uColor", "uTime", "uWaveAmount"],
874
+ attributes: ["aPosition", "aTexCoord"]
875
+ };
876
+ const NEON_SHADER = {
877
+ vertex: `
878
+ attribute vec3 aPosition;
879
+ attribute vec2 aTexCoord;
880
+
881
+ varying vec2 vTexCoord;
882
+ varying vec3 vNormal;
883
+
884
+ uniform mat4 uMatrix;
885
+
886
+ void main() {
887
+ gl_Position = uMatrix * vec4(aPosition, 1.0);
888
+ vTexCoord = aTexCoord;
889
+ // Simple normal based on position
890
+ vNormal = normalize(aPosition);
891
+ }
892
+ `,
893
+ fragment: `
894
+ varying vec2 vTexCoord;
895
+ varying vec3 vNormal;
896
+ uniform sampler2D uTexture;
897
+ uniform vec3 uColor;
898
+
899
+ void main() {
900
+ vec4 texColor = texture2D(uTexture, vTexCoord);
901
+
902
+ // High contrast
903
+ vec3 enhanced = pow(texColor.rgb * uColor, vec3(0.5));
904
+
905
+ // Edge highlight
906
+ float edge = abs(vNormal.x) + abs(vNormal.y);
907
+ edge = smoothstep(0.3, 1.0, edge) * 0.5;
908
+
909
+ vec3 neonColor = enhanced + edge * uColor;
910
+ gl_FragColor = vec4(neonColor, texColor.a);
911
+ }
912
+ `,
913
+ uniforms: ["uMatrix", "uTexture", "uColor"],
914
+ attributes: ["aPosition", "aTexCoord"]
915
+ };
916
+ const HOLOGRAM_SHADER = {
917
+ vertex: `
918
+ attribute vec3 aPosition;
919
+ attribute vec2 aTexCoord;
920
+
921
+ varying vec2 vTexCoord;
922
+
923
+ uniform mat4 uMatrix;
924
+
925
+ void main() {
926
+ gl_Position = uMatrix * vec4(aPosition, 1.0);
927
+ vTexCoord = aTexCoord;
928
+ }
929
+ `,
930
+ fragment: `
931
+ varying vec2 vTexCoord;
932
+ uniform sampler2D uTexture;
933
+ uniform vec3 uColor;
934
+ uniform float uTime;
935
+
936
+ void main() {
937
+ vec4 texColor = texture2D(uTexture, vTexCoord);
938
+
939
+ // Scan lines
940
+ float scanLine = sin(vTexCoord.y * 50.0) * 0.1 + 0.9;
941
+
942
+ // Flicker transparency
943
+ float flicker = 0.8 + 0.2 * sin(uTime * 5.0 + vTexCoord.x * 10.0);
944
+
945
+ vec3 hologramColor = texColor.rgb * uColor * scanLine;
946
+ gl_FragColor = vec4(hologramColor, texColor.a * flicker);
947
+ }
948
+ `,
949
+ uniforms: ["uMatrix", "uTexture", "uColor", "uTime"],
950
+ attributes: ["aPosition", "aTexCoord"]
951
+ };
952
+ const CHROMATIC_SHADER = {
953
+ vertex: `
954
+ attribute vec3 aPosition;
955
+ attribute vec2 aTexCoord;
956
+
957
+ varying vec2 vTexCoord;
958
+
959
+ uniform mat4 uMatrix;
960
+
961
+ void main() {
962
+ gl_Position = uMatrix * vec4(aPosition, 1.0);
963
+ vTexCoord = aTexCoord;
964
+ }
965
+ `,
966
+ fragment: `
967
+ varying vec2 vTexCoord;
968
+ uniform sampler2D uTexture;
969
+ uniform vec3 uColor;
970
+ uniform float uAberration;
971
+
972
+ void main() {
973
+ float offset = uAberration * 0.01;
974
+
975
+ float r = texture2D(uTexture, vTexCoord + vec2(offset, 0.0)).r;
976
+ float g = texture2D(uTexture, vTexCoord).g;
977
+ float b = texture2D(uTexture, vTexCoord - vec2(offset, 0.0)).b;
978
+
979
+ vec3 aberrated = vec3(r, g, b) * uColor;
980
+ gl_FragColor = vec4(aberrated, 1.0);
981
+ }
982
+ `,
983
+ uniforms: ["uMatrix", "uTexture", "uColor", "uAberration"],
984
+ attributes: ["aPosition", "aTexCoord"]
985
+ };
986
+ const PSYCHEDELIC_SHADER = {
987
+ vertex: `
988
+ attribute vec3 aPosition;
989
+ attribute vec2 aTexCoord;
990
+
991
+ varying vec2 vTexCoord;
992
+
993
+ uniform mat4 uMatrix;
994
+
995
+ void main() {
996
+ gl_Position = uMatrix * vec4(aPosition, 1.0);
997
+ vTexCoord = aTexCoord;
998
+ }
999
+ `,
1000
+ fragment: `
1001
+ varying vec2 vTexCoord;
1002
+ uniform sampler2D uTexture;
1003
+ uniform float uTime;
1004
+
1005
+ void main() {
1006
+ vec4 texColor = texture2D(uTexture, vTexCoord);
1007
+
1008
+ // Rainbow shift based on position and time
1009
+ float hue = atan(vTexCoord.y - 0.5, vTexCoord.x - 0.5) / 3.14159;
1010
+ hue += uTime;
1011
+
1012
+ float r = sin(hue) * 0.5 + 0.5;
1013
+ float g = sin(hue + 2.094) * 0.5 + 0.5;
1014
+ float b = sin(hue + 4.188) * 0.5 + 0.5;
1015
+
1016
+ vec3 rainbow = vec3(r, g, b);
1017
+ gl_FragColor = vec4(texColor.rgb * rainbow, texColor.a);
1018
+ }
1019
+ `,
1020
+ uniforms: ["uMatrix", "uTexture", "uTime"],
1021
+ attributes: ["aPosition", "aTexCoord"]
1022
+ };
1023
+ const SHADER_LIBRARY = {
1024
+ BASIC: BASIC_SHADER,
1025
+ GLOW: GLOW_SHADER,
1026
+ WAVE: WAVE_SHADER,
1027
+ NEON: NEON_SHADER,
1028
+ HOLOGRAM: HOLOGRAM_SHADER,
1029
+ CHROMATIC: CHROMATIC_SHADER,
1030
+ PSYCHEDELIC: PSYCHEDELIC_SHADER
1031
+ };
1032
+ class BrowserRenderingContext {
1033
+ constructor(options) {
1034
+ this.isBrowser = true;
1035
+ if (!options.canvas) {
1036
+ this.canvas = document.createElement("canvas");
1037
+ document.body.appendChild(this.canvas);
1038
+ } else {
1039
+ this.canvas = options.canvas;
1040
+ }
1041
+ this.width = options.width;
1042
+ this.height = options.height;
1043
+ this.canvas.width = this.width;
1044
+ this.canvas.height = this.height;
1045
+ const contextAttributes = {
1046
+ alpha: false,
1047
+ ...options.contextAttributes
1048
+ };
1049
+ const glContext = this.canvas.getContext("webgl", contextAttributes);
1050
+ if (!glContext) {
1051
+ throw new Error("Failed to initialize WebGL context in browser");
1052
+ }
1053
+ this.glContext = glContext;
1054
+ }
1055
+ resize(width, height) {
1056
+ this.width = width;
1057
+ this.height = height;
1058
+ this.canvas.width = width;
1059
+ this.canvas.height = height;
1060
+ this.glContext.viewport(0, 0, width, height);
1061
+ }
1062
+ getViewport() {
1063
+ return { width: this.width, height: this.height };
1064
+ }
1065
+ clear(color) {
1066
+ if (color) {
1067
+ this.glContext.clearColor(color.r, color.g, color.b, color.a);
1068
+ }
1069
+ this.glContext.clear(
1070
+ this.glContext.COLOR_BUFFER_BIT | this.glContext.DEPTH_BUFFER_BIT
1071
+ );
1072
+ }
1073
+ present() {
1074
+ }
1075
+ dispose() {
1076
+ if (this.canvas.parentElement) {
1077
+ this.canvas.parentElement.removeChild(this.canvas);
1078
+ }
1079
+ }
1080
+ /**
1081
+ * Get the underlying canvas element (browser-specific)
1082
+ */
1083
+ getCanvas() {
1084
+ return this.canvas;
1085
+ }
1086
+ }
1087
+ class NodeRenderingContext {
1088
+ constructor(options) {
1089
+ this.isBrowser = false;
1090
+ this.width = options.width;
1091
+ this.height = options.height;
1092
+ const glContext = createGL(this.width, this.height, {
1093
+ preserveDrawingBuffer: options.preserveDrawingBuffer ?? true,
1094
+ ...options.contextAttributes
1095
+ });
1096
+ if (!glContext) {
1097
+ throw new Error("Failed to initialize WebGL context in Node.js");
1098
+ }
1099
+ this.glContext = glContext;
1100
+ }
1101
+ resize(width, height) {
1102
+ this.width = width;
1103
+ this.height = height;
1104
+ console.warn(
1105
+ "NodeRenderingContext: Resize requested but not supported. Consider recreating context."
1106
+ );
1107
+ }
1108
+ getViewport() {
1109
+ return { width: this.width, height: this.height };
1110
+ }
1111
+ clear(color) {
1112
+ if (color) {
1113
+ this.glContext.clearColor(color.r, color.g, color.b, color.a);
1114
+ }
1115
+ this.glContext.clear(
1116
+ this.glContext.COLOR_BUFFER_BIT | this.glContext.DEPTH_BUFFER_BIT
1117
+ );
1118
+ }
1119
+ present() {
1120
+ this.glContext.flush();
1121
+ }
1122
+ dispose() {
1123
+ this.glContext.flush();
1124
+ }
1125
+ /**
1126
+ * Read the current framebuffer contents as RGBA pixel data
1127
+ * Used for capturing frames for display or saving
1128
+ */
1129
+ readPixels() {
1130
+ const pixelData = new Uint8Array(this.width * this.height * 4);
1131
+ this.glContext.readPixels(
1132
+ 0,
1133
+ 0,
1134
+ this.width,
1135
+ this.height,
1136
+ this.glContext.RGBA,
1137
+ this.glContext.UNSIGNED_BYTE,
1138
+ pixelData
1139
+ );
1140
+ return pixelData;
1141
+ }
1142
+ }
1143
+ class RenderingContextFactory {
1144
+ /**
1145
+ * Detect if running in a browser environment
1146
+ */
1147
+ static isBrowserEnvironment() {
1148
+ return typeof window !== "undefined" && typeof document !== "undefined";
1149
+ }
1150
+ /**
1151
+ * Create a rendering context appropriate for the current environment
1152
+ */
1153
+ static createContext(options) {
1154
+ if (this.isBrowserEnvironment()) {
1155
+ return new BrowserRenderingContext(options);
1156
+ } else {
1157
+ return new NodeRenderingContext(options);
1158
+ }
1159
+ }
1160
+ /**
1161
+ * Create a browser-specific rendering context
1162
+ */
1163
+ static createBrowserContext(options) {
1164
+ return new BrowserRenderingContext(options);
1165
+ }
1166
+ /**
1167
+ * Create a Node.js-specific rendering context
1168
+ */
1169
+ static createNodeContext(options) {
1170
+ return new NodeRenderingContext(options);
1171
+ }
1172
+ }
1173
+ class Shader {
1174
+ /**
1175
+ * Create a new shader program
1176
+ * @param gl WebGL rendering context
1177
+ * @param vertexSource Raw vertex shader source code
1178
+ * @param fragmentSource Raw fragment shader source code
1179
+ * @param isBrowser Whether running in browser environment (affects precision header)
1180
+ */
1181
+ constructor(gl2, vertexSource, fragmentSource, isBrowser) {
1182
+ this.gl = gl2;
1183
+ const processedVertexSource = this.injectPrecisionHeader(
1184
+ vertexSource,
1185
+ isBrowser
1186
+ );
1187
+ const processedFragmentSource = this.injectPrecisionHeader(
1188
+ fragmentSource,
1189
+ isBrowser
1190
+ );
1191
+ this.vertexShader = this.compileShader(
1192
+ processedVertexSource,
1193
+ gl2.VERTEX_SHADER
1194
+ );
1195
+ this.fragmentShader = this.compileShader(
1196
+ processedFragmentSource,
1197
+ gl2.FRAGMENT_SHADER
1198
+ );
1199
+ this.program = this.linkProgram(this.vertexShader, this.fragmentShader);
1200
+ }
1201
+ /**
1202
+ * Inject precision header for ES and desktop OpenGL differences
1203
+ * @param source Original shader source
1204
+ * @param isBrowser Whether in browser (WebGL ES) or Node (desktop OpenGL)
1205
+ * @returns Processed shader source with precision header
1206
+ */
1207
+ injectPrecisionHeader(source, isBrowser) {
1208
+ if (source.includes("#ifdef GL_ES") || source.includes("precision")) {
1209
+ return source;
1210
+ }
1211
+ if (isBrowser) {
1212
+ const precisionHeader = `#ifdef GL_ES
1213
+ precision highp float;
1214
+ #endif
1215
+ `;
1216
+ return precisionHeader + source;
1217
+ } else {
1218
+ const precisionHeader = `#ifdef GL_ES
1219
+ precision highp float;
1220
+ #endif
1221
+ `;
1222
+ return precisionHeader + source;
1223
+ }
1224
+ }
1225
+ /**
1226
+ * Compile a single shader (vertex or fragment)
1227
+ * @param source Shader source code
1228
+ * @param type gl.VERTEX_SHADER or gl.FRAGMENT_SHADER
1229
+ * @returns Compiled shader
1230
+ */
1231
+ compileShader(source, type) {
1232
+ const shader2 = this.gl.createShader(type);
1233
+ if (!shader2) {
1234
+ throw new Error(`Failed to create shader of type ${type}`);
1235
+ }
1236
+ this.gl.shaderSource(shader2, source);
1237
+ this.gl.compileShader(shader2);
1238
+ const compiled = this.gl.getShaderParameter(shader2, this.gl.COMPILE_STATUS);
1239
+ if (!compiled) {
1240
+ const infoLog = this.gl.getShaderInfoLog(shader2);
1241
+ const shaderType = type === this.gl.VERTEX_SHADER ? "vertex" : "fragment";
1242
+ this.gl.deleteShader(shader2);
1243
+ throw new Error(
1244
+ `Failed to compile ${shaderType} shader:
1245
+ ${infoLog}
1246
+
1247
+ Source:
1248
+ ${source}`
1249
+ );
1250
+ }
1251
+ return shader2;
1252
+ }
1253
+ /**
1254
+ * Link vertex and fragment shaders into a program
1255
+ * @param vertexShader Compiled vertex shader
1256
+ * @param fragmentShader Compiled fragment shader
1257
+ * @returns Linked shader program
1258
+ */
1259
+ linkProgram(vertexShader, fragmentShader) {
1260
+ const program = this.gl.createProgram();
1261
+ if (!program) {
1262
+ throw new Error("Failed to create shader program");
1263
+ }
1264
+ this.gl.attachShader(program, vertexShader);
1265
+ this.gl.attachShader(program, fragmentShader);
1266
+ this.gl.linkProgram(program);
1267
+ const linked = this.gl.getProgramParameter(program, this.gl.LINK_STATUS);
1268
+ if (!linked) {
1269
+ const infoLog = this.gl.getProgramInfoLog(program);
1270
+ this.gl.deleteProgram(program);
1271
+ this.gl.deleteShader(vertexShader);
1272
+ this.gl.deleteShader(fragmentShader);
1273
+ throw new Error(`Failed to link shader program:
1274
+ ${infoLog}`);
1275
+ }
1276
+ return program;
1277
+ }
1278
+ /**
1279
+ * Get the compiled shader program
1280
+ */
1281
+ getProgram() {
1282
+ return this.program;
1283
+ }
1284
+ /**
1285
+ * Get uniform location by name
1286
+ * @param name Uniform variable name
1287
+ */
1288
+ getUniformLocation(name) {
1289
+ return this.gl.getUniformLocation(this.program, name);
1290
+ }
1291
+ /**
1292
+ * Get attribute location by name
1293
+ * @param name Attribute variable name
1294
+ */
1295
+ getAttributeLocation(name) {
1296
+ return this.gl.getAttribLocation(this.program, name);
1297
+ }
1298
+ /**
1299
+ * Use this shader program
1300
+ */
1301
+ use() {
1302
+ this.gl.useProgram(this.program);
1303
+ }
1304
+ /**
1305
+ * Clean up shader resources
1306
+ */
1307
+ dispose() {
1308
+ this.gl.deleteProgram(this.program);
1309
+ this.gl.deleteShader(this.vertexShader);
1310
+ this.gl.deleteShader(this.fragmentShader);
1311
+ }
1312
+ }
1313
+ const shader = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1314
+ __proto__: null,
1315
+ Shader
1316
+ }, Symbol.toStringTag, { value: "Module" }));
1317
+ class GraphicsDevice {
1318
+ constructor(width, height) {
1319
+ this.context = RenderingContextFactory.createContext({
1320
+ width,
1321
+ height,
1322
+ preserveDrawingBuffer: true
1323
+ });
1324
+ }
1325
+ /**
1326
+ * Get the underlying WebGL rendering context
1327
+ */
1328
+ getGLContext() {
1329
+ return this.context.glContext;
1330
+ }
1331
+ /**
1332
+ * Get the rendering context
1333
+ */
1334
+ getRenderingContext() {
1335
+ return this.context;
1336
+ }
1337
+ /**
1338
+ * Get current width
1339
+ */
1340
+ getWidth() {
1341
+ return this.context.width;
1342
+ }
1343
+ /**
1344
+ * Get current height
1345
+ */
1346
+ getHeight() {
1347
+ return this.context.height;
1348
+ }
1349
+ /**
1350
+ * Get viewport dimensions
1351
+ */
1352
+ getViewport() {
1353
+ return this.context.getViewport();
1354
+ }
1355
+ /**
1356
+ * Check if running in browser
1357
+ */
1358
+ isBrowser() {
1359
+ return this.context.isBrowser;
1360
+ }
1361
+ /**
1362
+ * Resize the graphics device
1363
+ */
1364
+ resize(width, height) {
1365
+ this.context.resize(width, height);
1366
+ }
1367
+ /**
1368
+ * Clear the rendering surface
1369
+ */
1370
+ clear(color) {
1371
+ this.context.clear(color);
1372
+ }
1373
+ /**
1374
+ * Present the rendered frame
1375
+ */
1376
+ present() {
1377
+ this.context.present();
1378
+ }
1379
+ /**
1380
+ * Cleanup and release resources
1381
+ */
1382
+ dispose() {
1383
+ this.context.dispose();
1384
+ }
1385
+ /**
1386
+ * Create a shader program
1387
+ * @param vertexSource Vertex shader source code
1388
+ * @param fragmentSource Fragment shader source code
1389
+ * @returns Compiled and linked shader program
1390
+ */
1391
+ createShader(vertexSource, fragmentSource) {
1392
+ return new Shader(
1393
+ this.context.glContext,
1394
+ vertexSource,
1395
+ fragmentSource,
1396
+ this.context.isBrowser
1397
+ );
1398
+ }
1399
+ }
1400
+ const grahpicDevice = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1401
+ __proto__: null,
1402
+ GraphicsDevice
1403
+ }, Symbol.toStringTag, { value: "Module" }));
1404
+ console.log(
1405
+ "🩸 Bloody Engine - Texture & Shader Demo + Projection Visualization + Resource Loader"
1406
+ );
1407
+ const WIDTH = SCENE_CONFIG.width;
1408
+ const HEIGHT = SCENE_CONFIG.height;
1409
+ async function runResourceLoaderDemo() {
1410
+ console.log("\n" + "=".repeat(60));
1411
+ console.log("📦 RESOURCE LOADER DEMO (Node.js)");
1412
+ console.log("=".repeat(60));
1413
+ const { ResourceLoaderFactory, Environment } = await import("./resource-loader-factory-DQ-PAVcN.js");
1414
+ const { createResourcePipeline } = await import("./resource-pipeline-Dac9qRso.js");
1415
+ const { GraphicsDevice: GraphicsDevice2 } = await Promise.resolve().then(() => grahpicDevice);
1416
+ const { Shader: Shader2 } = await Promise.resolve().then(() => shader);
1417
+ const { Texture: Texture2 } = await Promise.resolve().then(() => texture);
1418
+ const { VertexBuffer: VertexBuffer2 } = await Promise.resolve().then(() => buffer);
1419
+ console.log(
1420
+ `✓ Environment detected: ${ResourceLoaderFactory.detectEnvironment()}`
1421
+ );
1422
+ const pipeline = await createResourcePipeline({
1423
+ concurrency: 5,
1424
+ cache: true,
1425
+ baseDir: process.cwd()
1426
+ });
1427
+ console.log("✓ Resource pipeline created for Node.js");
1428
+ const shaders = [
1429
+ {
1430
+ name: "basic",
1431
+ vertex: "resources/shaders/basic.vert",
1432
+ fragment: "resources/shaders/basic.frag"
1433
+ },
1434
+ {
1435
+ name: "glow",
1436
+ vertex: "resources/shaders/glow.vert",
1437
+ fragment: "resources/shaders/glow.frag"
1438
+ }
1439
+ ];
1440
+ console.log(`
1441
+ 📝 Loading ${shaders.length} shaders from disk...`);
1442
+ const loadedShaders = await pipeline.loadShaders(shaders);
1443
+ for (const shader2 of loadedShaders) {
1444
+ console.log(` ✓ ${shader2.name}:`);
1445
+ console.log(` Vertex: ${shader2.vertex.length} chars`);
1446
+ console.log(` Fragment: ${shader2.fragment.length} chars`);
1447
+ }
1448
+ console.log(`
1449
+ 💾 Cache size: ${pipeline.getCacheSize()} resources`);
1450
+ const gdevice2 = new GraphicsDevice2(800, 600);
1451
+ const gl2 = gdevice2.getGLContext();
1452
+ console.log(`✓ Graphics device initialized (800x600)`);
1453
+ const glowShader = loadedShaders.find((s) => s.name === "glow");
1454
+ const shader$1 = gdevice2.createShader(glowShader.vertex, glowShader.fragment);
1455
+ console.log("✓ Shader compiled from loaded files");
1456
+ const texture$1 = Texture2.createGradient(gl2, 256, 256);
1457
+ console.log("✓ Gradient texture created");
1458
+ const quadBuffer = new VertexBuffer2(
1459
+ gl2,
1460
+ GEOMETRY.quad.vertices,
1461
+ GEOMETRY.quad.stride
1462
+ );
1463
+ console.log(
1464
+ `✓ Quad buffer created (${quadBuffer.getVertexCount()} vertices)`
1465
+ );
1466
+ shader$1.use();
1467
+ const posAttr = shader$1.getAttributeLocation("aPosition");
1468
+ const texCoordAttr = shader$1.getAttributeLocation("aTexCoord");
1469
+ const textureUniform = shader$1.getUniformLocation("uTexture");
1470
+ const matrixUniform = shader$1.getUniformLocation("uMatrix");
1471
+ const colorUniform = shader$1.getUniformLocation("uColor");
1472
+ const glowIntensityUniform = shader$1.getUniformLocation("uGlowIntensity");
1473
+ quadBuffer.bind();
1474
+ gl2.enableVertexAttribArray(posAttr);
1475
+ gl2.vertexAttribPointer(posAttr, 3, gl2.FLOAT, false, GEOMETRY.quad.stride, 0);
1476
+ gl2.enableVertexAttribArray(texCoordAttr);
1477
+ gl2.vertexAttribPointer(
1478
+ texCoordAttr,
1479
+ 2,
1480
+ gl2.FLOAT,
1481
+ false,
1482
+ GEOMETRY.quad.stride,
1483
+ 3 * 4
1484
+ );
1485
+ texture$1.bind(0);
1486
+ gl2.uniform1i(textureUniform, 0);
1487
+ gdevice2.clear({ r: 0.1, g: 0.1, b: 0.1, a: 1 });
1488
+ const testQuads = [
1489
+ { x: -0.3, y: 0.3, color: [1, 0.2, 0.2], glow: 1.5 },
1490
+ { x: 0.3, y: 0.3, color: [0.2, 1, 0.2], glow: 1.8 },
1491
+ { x: -0.3, y: -0.3, color: [0.2, 0.5, 1], glow: 2 },
1492
+ { x: 0.3, y: -0.3, color: [1, 1, 0.2], glow: 1.6 }
1493
+ ];
1494
+ for (const quad of testQuads) {
1495
+ const matrix = createIdentityMatrix();
1496
+ translateMatrix(matrix, quad.x, quad.y, 0);
1497
+ scaleMatrix(matrix, 0.4, 0.4, 1);
1498
+ if (matrixUniform) gl2.uniformMatrix4fv(matrixUniform, false, matrix);
1499
+ if (colorUniform)
1500
+ gl2.uniform3f(colorUniform, quad.color[0], quad.color[1], quad.color[2]);
1501
+ if (glowIntensityUniform) gl2.uniform1f(glowIntensityUniform, quad.glow);
1502
+ gl2.drawArrays(gl2.TRIANGLES, 0, quadBuffer.getVertexCount());
1503
+ }
1504
+ const renderingContext2 = gdevice2.getRenderingContext();
1505
+ const pixelData = renderingContext2.readPixels();
1506
+ const ppmPath = "./resource-loader-demo-output.ppm";
1507
+ savePPM(pixelData, 800, 600, ppmPath);
1508
+ console.log(`✓ Test frame rendered and saved to ${ppmPath}`);
1509
+ console.log("✓ Resource loader demo complete!\n");
1510
+ quadBuffer.dispose();
1511
+ texture$1.dispose();
1512
+ shader$1.dispose();
1513
+ gdevice2.dispose();
1514
+ }
1515
+ function savePPM(pixelData, width, height, outputPath) {
1516
+ const ppmHeader = `P6
1517
+ ${width} ${height}
1518
+ 255
1519
+ `;
1520
+ const ppmData = Buffer.alloc(3 * width * height);
1521
+ for (let i = 0; i < width * height; i++) {
1522
+ const srcIdx = i * 4;
1523
+ const dstIdx = i * 3;
1524
+ ppmData[dstIdx] = pixelData[srcIdx];
1525
+ ppmData[dstIdx + 1] = pixelData[srcIdx + 1];
1526
+ ppmData[dstIdx + 2] = pixelData[srcIdx + 2];
1527
+ }
1528
+ fs.writeFileSync(outputPath, ppmHeader);
1529
+ fs.appendFileSync(outputPath, ppmData);
1530
+ }
1531
+ function createIdentityMatrix() {
1532
+ return new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
1533
+ }
1534
+ function translateMatrix(mat, x, y, z) {
1535
+ mat[12] += x;
1536
+ mat[13] += y;
1537
+ mat[14] += z;
1538
+ }
1539
+ function scaleMatrix(mat, x, y, z) {
1540
+ mat[0] *= x;
1541
+ mat[5] *= y;
1542
+ mat[10] *= z;
1543
+ }
1544
+ runResourceLoaderDemo().catch((error) => {
1545
+ console.error("❌ Resource loader demo failed:", error);
1546
+ });
1547
+ const gdevice = new GraphicsDevice(WIDTH, HEIGHT);
1548
+ const gl = gdevice.getGLContext();
1549
+ const renderingContext = gdevice.getRenderingContext();
1550
+ let sdlWindow = null;
1551
+ try {
1552
+ sdlWindow = new SDLWindow(WIDTH, HEIGHT, "Bloody Engine - Live Rendering");
1553
+ } catch (error) {
1554
+ console.warn("⚠ SDL window creation failed:", error);
1555
+ console.log("Running in headless mode - output will be saved to file only");
1556
+ }
1557
+ console.log(`✓ Graphics device initialized (${WIDTH}x${HEIGHT})`);
1558
+ console.log(
1559
+ `✓ Environment: ${gdevice.isBrowser() ? "Browser (WebGL)" : "Node.js (headless-gl)"}`
1560
+ );
1561
+ console.log("\n✨ Setting up Projection Visualization System...");
1562
+ console.log("\n📊 Running projection example scenario:");
1563
+ runExampleScenario();
1564
+ const projectionEntities = PROJECTION_ENTITIES.map(
1565
+ (entity) => {
1566
+ const screenPos = gridToScreen(entity.gridPos, PROJECTION_CONFIG);
1567
+ return {
1568
+ gridPos: entity.gridPos,
1569
+ screenPos,
1570
+ color: entity.color,
1571
+ size: entity.size,
1572
+ name: entity.name
1573
+ };
1574
+ }
1575
+ );
1576
+ console.log(
1577
+ `✓ ${projectionEntities.length} projection entities prepared for visualization`
1578
+ );
1579
+ console.log("\n📍 Projection Entities:");
1580
+ projectionEntities.forEach((entity) => {
1581
+ console.log(
1582
+ ` • ${entity.name} at grid (${entity.gridPos.xgrid}, ${entity.gridPos.ygrid}, ${entity.gridPos.zheight}) → screen (${entity.screenPos.xscreen.toFixed(0)}, ${entity.screenPos.yscreen.toFixed(0)})`
1583
+ );
1584
+ });
1585
+ const ACTIVE_SHADER = "PSYCHEDELIC";
1586
+ const shaderPreset = SHADER_LIBRARY[ACTIVE_SHADER];
1587
+ const vertexShaderSource = shaderPreset.vertex;
1588
+ const fragmentShaderSource = shaderPreset.fragment;
1589
+ async function runSpriteBatchRendererV2Demo() {
1590
+ console.log("\n" + "=".repeat(60));
1591
+ console.log("🎨 SPRITE BATCH RENDERER V2 DEMO (2.5D Sprites)");
1592
+ console.log("=".repeat(60));
1593
+ const { SpriteBatchRenderer } = await import("./batch-renderer-JqZ4TYcL.js");
1594
+ const { Camera } = await import("./camera-A8EGrk7U.js");
1595
+ const gdevice2 = new GraphicsDevice(WIDTH, HEIGHT);
1596
+ const gl2 = gdevice2.getGLContext();
1597
+ const renderingContext2 = gdevice2.getRenderingContext();
1598
+ let sdlWindow2 = null;
1599
+ try {
1600
+ sdlWindow2 = new SDLWindow(WIDTH, HEIGHT, "Bloody Engine - V2 Sprites + Camera");
1601
+ } catch (error) {
1602
+ console.warn("⚠ SDL window creation failed, running in headless mode");
1603
+ }
1604
+ console.log(`✓ Graphics device initialized (${WIDTH}x${HEIGHT})`);
1605
+ const { SHADERS_V2: SHADERS_V22 } = await Promise.resolve().then(() => scene);
1606
+ const shader2 = gdevice2.createShader(SHADERS_V22.vertex, SHADERS_V22.fragment);
1607
+ console.log("✓ V2 Shader compiled (supports color tint and texture index)");
1608
+ const texture2 = Texture.createGradient(gl2, 256, 256);
1609
+ console.log("✓ Gradient texture created");
1610
+ const spriteBatchRenderer = new SpriteBatchRenderer(gl2, shader2, 1e3);
1611
+ spriteBatchRenderer.setTexture(texture2);
1612
+ console.log("✓ Sprite batch renderer created (V2)");
1613
+ const camera = new Camera(0, 0, 1);
1614
+ console.log("✓ Camera created");
1615
+ console.log("\n🎮 Camera Controls:");
1616
+ console.log(" • WASD / Arrow Keys - Move camera");
1617
+ console.log(" • Q / E - Zoom out / in");
1618
+ console.log(" • R - Reset camera");
1619
+ console.log(" • ESC - Exit demo");
1620
+ const keys = /* @__PURE__ */ new Set();
1621
+ const moveSpeed = 0.5;
1622
+ const zoomSpeed = 2;
1623
+ let startTime = Date.now();
1624
+ let frameCount = 0;
1625
+ let lastTime = startTime;
1626
+ if (sdlWindow2) {
1627
+ sdlWindow2.on("keyDown", (event) => {
1628
+ keys.add(event.key.toLowerCase());
1629
+ if (event.key.toLowerCase() === "r") {
1630
+ camera.reset();
1631
+ console.log("📷 Camera reset to default position");
1632
+ }
1633
+ });
1634
+ sdlWindow2.on("keyUp", (event) => {
1635
+ keys.delete(event.key.toLowerCase());
1636
+ });
1637
+ }
1638
+ function getSpriteQuadsForFrame(elapsedSeconds) {
1639
+ const quads = [];
1640
+ const angle1 = elapsedSeconds * 2;
1641
+ quads.push({
1642
+ x: 0,
1643
+ y: 0,
1644
+ z: -0.5,
1645
+ width: 0.3,
1646
+ height: 0.3,
1647
+ rotation: angle1,
1648
+ color: { r: 1, g: 0.2, b: 0.2, a: 0.7 },
1649
+ texIndex: 0
1650
+ });
1651
+ const angle2 = elapsedSeconds * 1.5;
1652
+ const orbitRadius2 = 0.4;
1653
+ quads.push({
1654
+ x: Math.cos(angle2) * orbitRadius2,
1655
+ y: Math.sin(angle2) * orbitRadius2,
1656
+ z: 0,
1657
+ width: 0.25,
1658
+ height: 0.25,
1659
+ rotation: -angle2,
1660
+ color: { r: 0.2, g: 1, b: 0.2, a: 1 },
1661
+ texIndex: 0
1662
+ });
1663
+ const angle3 = elapsedSeconds * 0.8;
1664
+ const orbitRadius3 = 0.5;
1665
+ quads.push({
1666
+ x: Math.cos(angle3 * 2) * orbitRadius3,
1667
+ y: Math.sin(angle3) * orbitRadius3,
1668
+ z: 0.5,
1669
+ width: 0.2,
1670
+ height: 0.2,
1671
+ rotation: angle3 * 3,
1672
+ color: { r: 0.2, g: 0.5, b: 1, a: 0.9 },
1673
+ texIndex: 0
1674
+ });
1675
+ const bounce = 0.3 * Math.sin(elapsedSeconds * 3);
1676
+ quads.push({
1677
+ x: -0.4,
1678
+ y: bounce,
1679
+ z: 0.2,
1680
+ width: 0.2,
1681
+ height: 0.2,
1682
+ rotation: elapsedSeconds * 4,
1683
+ color: { r: 1, g: 1, b: 0.2, a: 1 },
1684
+ uvRect: { uMin: 0, vMin: 0, uMax: 0.5, vMax: 0.5 },
1685
+ texIndex: 0
1686
+ });
1687
+ const pulse = 0.15 + 0.1 * Math.sin(elapsedSeconds * 2.5);
1688
+ const pulseColor = 0.5 + 0.5 * Math.sin(elapsedSeconds * 3);
1689
+ quads.push({
1690
+ x: 0.4,
1691
+ y: 0,
1692
+ z: -0.2,
1693
+ width: pulse,
1694
+ height: pulse,
1695
+ rotation: -elapsedSeconds,
1696
+ color: { r: 0.2, g: pulseColor, b: pulseColor, a: 1 },
1697
+ texIndex: 0
1698
+ });
1699
+ const angle6 = elapsedSeconds * 1.2;
1700
+ const r6 = 0.35 + 0.15 * Math.sin(elapsedSeconds * 2);
1701
+ quads.push({
1702
+ x: Math.cos(angle6) * r6,
1703
+ y: Math.sin(angle6 * 2) * r6,
1704
+ z: 0.3,
1705
+ width: 0.18,
1706
+ height: 0.18,
1707
+ rotation: elapsedSeconds * 2.5,
1708
+ color: { r: 1, g: 0.2, b: 1, a: 0.8 },
1709
+ uvRect: { uMin: 0.5, vMin: 0.5, uMax: 1, vMax: 1 },
1710
+ texIndex: 1
1711
+ });
1712
+ return quads;
1713
+ }
1714
+ function renderFrame() {
1715
+ const now = Date.now();
1716
+ const deltaTime = (now - lastTime) / 1e3;
1717
+ const elapsedSeconds = (now - startTime) / 1e3;
1718
+ lastTime = now;
1719
+ let cameraMoved = false;
1720
+ const moveAmount = moveSpeed * deltaTime;
1721
+ if (keys.has("w") || keys.has("arrowup")) {
1722
+ camera.y += moveAmount;
1723
+ cameraMoved = true;
1724
+ }
1725
+ if (keys.has("s") || keys.has("arrowdown")) {
1726
+ camera.y -= moveAmount;
1727
+ cameraMoved = true;
1728
+ }
1729
+ if (keys.has("a") || keys.has("arrowleft")) {
1730
+ camera.x -= moveAmount;
1731
+ cameraMoved = true;
1732
+ }
1733
+ if (keys.has("d") || keys.has("arrowright")) {
1734
+ camera.x += moveAmount;
1735
+ cameraMoved = true;
1736
+ }
1737
+ const zoomFactor = 1 + (zoomSpeed - 1) * deltaTime;
1738
+ if (keys.has("q")) {
1739
+ camera.zoomBy(1 / zoomFactor);
1740
+ cameraMoved = true;
1741
+ }
1742
+ if (keys.has("e")) {
1743
+ camera.zoomBy(zoomFactor);
1744
+ cameraMoved = true;
1745
+ }
1746
+ if (cameraMoved && frameCount % 30 === 0) {
1747
+ console.log(
1748
+ `📷 Camera: x=${camera.x.toFixed(2)}, y=${camera.y.toFixed(2)}, zoom=${camera.zoom.toFixed(2)}x`
1749
+ );
1750
+ }
1751
+ const quads = getSpriteQuadsForFrame(elapsedSeconds);
1752
+ spriteBatchRenderer.clear();
1753
+ for (const quad of quads) {
1754
+ spriteBatchRenderer.addQuad(quad);
1755
+ }
1756
+ gdevice2.clear({ r: 0.1, g: 0.1, b: 0.12, a: 1 });
1757
+ spriteBatchRenderer.render(camera);
1758
+ gdevice2.present();
1759
+ if (sdlWindow2 && sdlWindow2.isOpen()) {
1760
+ const pixelData = renderingContext2.readPixels();
1761
+ sdlWindow2.updatePixels(pixelData);
1762
+ }
1763
+ frameCount++;
1764
+ }
1765
+ console.log("\n🎬 Starting render loop (press ESC to exit)...");
1766
+ const frames = [];
1767
+ const renderStart = Date.now();
1768
+ let running = true;
1769
+ if (sdlWindow2) {
1770
+ sdlWindow2.on("close", () => {
1771
+ running = false;
1772
+ });
1773
+ sdlWindow2.on("keyDown", (event) => {
1774
+ if (event.key === "escape") {
1775
+ running = false;
1776
+ console.log("\n✓ Exiting demo...");
1777
+ }
1778
+ });
1779
+ }
1780
+ while (running && (!sdlWindow2 || sdlWindow2.isOpen())) {
1781
+ renderFrame();
1782
+ if (frameCount === 1) {
1783
+ const pixelData = renderingContext2.readPixels();
1784
+ frames.push(Buffer.from(pixelData));
1785
+ }
1786
+ await new Promise((resolve) => setTimeout(resolve, 16));
1787
+ }
1788
+ const renderEnd = Date.now();
1789
+ const renderTime = (renderEnd - renderStart) / 1e3;
1790
+ const avgFPS = frameCount / renderTime;
1791
+ console.log(`✓ Rendered ${frameCount} frames in ${renderTime.toFixed(2)}s`);
1792
+ console.log(`✓ Average FPS: ${avgFPS.toFixed(1)}`);
1793
+ console.log(
1794
+ `✓ Sprite batch renderer tested with ${spriteBatchRenderer.getQuadCount()} sprites`
1795
+ );
1796
+ if (frames.length > 0) {
1797
+ const pixelData = frames[0];
1798
+ const ppmHeader = `P6
1799
+ ${WIDTH} ${HEIGHT}
1800
+ 255
1801
+ `;
1802
+ const ppmData = Buffer.alloc(3 * WIDTH * HEIGHT);
1803
+ for (let i = 0; i < WIDTH * HEIGHT; i++) {
1804
+ const srcIdx = i * 4;
1805
+ const dstIdx = i * 3;
1806
+ ppmData[dstIdx] = pixelData[srcIdx];
1807
+ ppmData[dstIdx + 1] = pixelData[srcIdx + 1];
1808
+ ppmData[dstIdx + 2] = pixelData[srcIdx + 2];
1809
+ }
1810
+ const ppmPath = "./sprite-batch-renderer-v2-demo.ppm";
1811
+ fs.writeFileSync(ppmPath, ppmHeader);
1812
+ fs.appendFileSync(ppmPath, ppmData);
1813
+ console.log(`✓ First frame saved to ${ppmPath}`);
1814
+ }
1815
+ spriteBatchRenderer.dispose();
1816
+ gdevice2.dispose();
1817
+ if (sdlWindow2) {
1818
+ sdlWindow2.cleanup();
1819
+ }
1820
+ console.log("✓ Sprite batch renderer demo complete!\n");
1821
+ }
1822
+ (async () => {
1823
+ try {
1824
+ console.log("\n--- Setting up Textured Quad ---");
1825
+ const shader2 = gdevice.createShader(
1826
+ vertexShaderSource,
1827
+ fragmentShaderSource
1828
+ );
1829
+ console.log(`✓ Shader compiled and linked (${ACTIVE_SHADER})`);
1830
+ const quadVertices = GEOMETRY.quad.vertices;
1831
+ const vertexBuffer = new VertexBuffer(
1832
+ gl,
1833
+ quadVertices,
1834
+ GEOMETRY.quad.stride
1835
+ );
1836
+ console.log(
1837
+ `✓ Vertex buffer created (${vertexBuffer.getVertexCount()} vertices)`
1838
+ );
1839
+ const triangleVertices = GEOMETRY.triangle.vertices;
1840
+ const triangleBuffer = new VertexBuffer(
1841
+ gl,
1842
+ triangleVertices,
1843
+ GEOMETRY.triangle.stride
1844
+ );
1845
+ console.log(
1846
+ `✓ Triangle buffer created (${triangleBuffer.getVertexCount()} vertices)`
1847
+ );
1848
+ const texture2 = Texture.createGradient(
1849
+ gl,
1850
+ TEXTURE_CONFIG.size,
1851
+ TEXTURE_CONFIG.size
1852
+ );
1853
+ console.log("✓ Gradient texture created (256x256)");
1854
+ const posAttr = shader2.getAttributeLocation("aPosition");
1855
+ const texCoordAttr = shader2.getAttributeLocation("aTexCoord");
1856
+ const textureUniform = shader2.getUniformLocation("uTexture");
1857
+ const matrixUniform = shader2.getUniformLocation("uMatrix");
1858
+ const colorUniform = shader2.getUniformLocation("uColor");
1859
+ const glowIntensityUniform = shader2.getUniformLocation("uGlowIntensity");
1860
+ const timeUniform = shader2.getUniformLocation("uTime");
1861
+ console.log(
1862
+ `✓ Attributes located (position=${posAttr}, texCoord=${texCoordAttr})`
1863
+ );
1864
+ shader2.use();
1865
+ vertexBuffer.bind();
1866
+ gl.enableVertexAttribArray(posAttr);
1867
+ gl.vertexAttribPointer(posAttr, 3, gl.FLOAT, false, 5 * 4, 0);
1868
+ gl.enableVertexAttribArray(texCoordAttr);
1869
+ gl.vertexAttribPointer(texCoordAttr, 2, gl.FLOAT, false, 5 * 4, 3 * 4);
1870
+ console.log("✓ Vertex attributes configured");
1871
+ texture2.bind(0);
1872
+ gl.uniform1i(textureUniform, 0);
1873
+ console.log("✓ Texture bound to unit 0");
1874
+ const identityMatrix = new Float32Array([
1875
+ 1,
1876
+ 0,
1877
+ 0,
1878
+ 0,
1879
+ 0,
1880
+ 1,
1881
+ 0,
1882
+ 0,
1883
+ 0,
1884
+ 0,
1885
+ 1,
1886
+ 0,
1887
+ 0,
1888
+ 0,
1889
+ 0,
1890
+ 1
1891
+ ]);
1892
+ if (matrixUniform) {
1893
+ gl.uniformMatrix4fv(matrixUniform, false, identityMatrix);
1894
+ }
1895
+ gdevice.clear({ r: 0.2, g: 0.2, b: 0.2, a: 1 });
1896
+ console.log("✓ Screen cleared");
1897
+ gl.drawArrays(gl.TRIANGLES, 0, vertexBuffer.getVertexCount());
1898
+ console.log(`✓ Rendered ${vertexBuffer.getVertexCount()} vertices`);
1899
+ gdevice.present();
1900
+ console.log("✓ Frame presented");
1901
+ const pixelData = renderingContext.readPixels();
1902
+ console.log(
1903
+ `✓ Captured frame (${WIDTH}x${HEIGHT}, ${pixelData.length} bytes)`
1904
+ );
1905
+ if (sdlWindow && sdlWindow.isOpen()) {
1906
+ sdlWindow.updatePixels(pixelData);
1907
+ }
1908
+ const ppmHeader = `P6
1909
+ ${WIDTH} ${HEIGHT}
1910
+ 255
1911
+ `;
1912
+ const ppmData = Buffer.alloc(3 * WIDTH * HEIGHT);
1913
+ for (let i = 0; i < WIDTH * HEIGHT; i++) {
1914
+ const srcIdx = i * 4;
1915
+ const dstIdx = i * 3;
1916
+ ppmData[dstIdx] = pixelData[srcIdx];
1917
+ ppmData[dstIdx + 1] = pixelData[srcIdx + 1];
1918
+ ppmData[dstIdx + 2] = pixelData[srcIdx + 2];
1919
+ }
1920
+ const ppmPath = "./rendered-textured-quad.ppm";
1921
+ fs.writeFileSync(ppmPath, ppmHeader);
1922
+ fs.appendFileSync(ppmPath, ppmData);
1923
+ console.log(`✓ Frame saved to ${ppmPath}`);
1924
+ if (!sdlWindow) {
1925
+ try {
1926
+ const ppmAbsPath = path__default.resolve(ppmPath);
1927
+ if (process.platform === "win32") {
1928
+ execSync(`start "" "${ppmAbsPath}"`);
1929
+ } else if (process.platform === "darwin") {
1930
+ execSync(`open "${ppmAbsPath}"`);
1931
+ } else {
1932
+ execSync(`xdg-open "${ppmAbsPath}"`);
1933
+ }
1934
+ console.log("✓ Image opened in default viewer");
1935
+ } catch (error) {
1936
+ console.warn(
1937
+ "⚠ Could not auto-open image in viewer (no default handler)"
1938
+ );
1939
+ }
1940
+ }
1941
+ console.log(`
1942
+ ✓ Sample pixels (RGBA):`);
1943
+ for (let i = 0; i < 4; i++) {
1944
+ const offset = i * 4;
1945
+ console.log(
1946
+ ` Pixel ${i}: R=${pixelData[offset]}, G=${pixelData[offset + 1]}, B=${pixelData[offset + 2]}, A=${pixelData[offset + 3]}`
1947
+ );
1948
+ }
1949
+ if (sdlWindow && sdlWindow.isOpen()) {
1950
+ console.log("\n✓ SDL Window open - press ESC or close window to exit");
1951
+ console.log("💡 Window is interactive and rendering at 60 FPS");
1952
+ let frameCount = 0;
1953
+ const startTime = Date.now();
1954
+ const targetFPS = SCENE_CONFIG.targetFPS;
1955
+ const frameTimeMs = 1e3 / targetFPS;
1956
+ let running = true;
1957
+ sdlWindow.on("close", () => {
1958
+ running = false;
1959
+ });
1960
+ sdlWindow.on("keyDown", (event) => {
1961
+ if (event.key === "escape") {
1962
+ running = false;
1963
+ sdlWindow.destroy();
1964
+ }
1965
+ });
1966
+ const renderLoop = () => {
1967
+ if (!running || !sdlWindow.isOpen()) {
1968
+ return;
1969
+ }
1970
+ const frameStartTime = Date.now();
1971
+ const elapsedSeconds = frameCount * frameTimeMs / 1e3;
1972
+ const bgColor = getBackgroundColor(elapsedSeconds);
1973
+ gdevice.clear(bgColor);
1974
+ const quadTransforms = getQuadTransforms(elapsedSeconds);
1975
+ for (const transform of quadTransforms) {
1976
+ const matrixUniform2 = shader2.getUniformLocation("uMatrix");
1977
+ if (matrixUniform2) {
1978
+ gl.uniformMatrix4fv(matrixUniform2, false, transform.matrix);
1979
+ }
1980
+ const colorUniform2 = shader2.getUniformLocation("uColor");
1981
+ if (colorUniform2) {
1982
+ gl.uniform3f(
1983
+ colorUniform2,
1984
+ transform.color[0],
1985
+ transform.color[1],
1986
+ transform.color[2]
1987
+ );
1988
+ }
1989
+ if (glowIntensityUniform) {
1990
+ gl.uniform1f(
1991
+ glowIntensityUniform,
1992
+ 1.5 + Math.sin(elapsedSeconds) * 0.5
1993
+ );
1994
+ }
1995
+ if (timeUniform) {
1996
+ gl.uniform1f(timeUniform, elapsedSeconds);
1997
+ }
1998
+ gl.drawArrays(gl.TRIANGLES, 0, vertexBuffer.getVertexCount());
1999
+ }
2000
+ const triangleTransforms = getTriangleTransforms(elapsedSeconds);
2001
+ for (const transform of triangleTransforms) {
2002
+ const matrixUniform2 = shader2.getUniformLocation("uMatrix");
2003
+ if (matrixUniform2) {
2004
+ gl.uniformMatrix4fv(matrixUniform2, false, transform.matrix);
2005
+ }
2006
+ const colorUniform2 = shader2.getUniformLocation("uColor");
2007
+ if (colorUniform2) {
2008
+ gl.uniform3f(
2009
+ colorUniform2,
2010
+ transform.color[0],
2011
+ transform.color[1],
2012
+ transform.color[2]
2013
+ );
2014
+ }
2015
+ if (glowIntensityUniform) {
2016
+ gl.uniform1f(
2017
+ glowIntensityUniform,
2018
+ 2 + Math.cos(elapsedSeconds * 0.7) * 0.8
2019
+ );
2020
+ }
2021
+ if (timeUniform) {
2022
+ gl.uniform1f(timeUniform, elapsedSeconds);
2023
+ }
2024
+ gl.drawArrays(gl.TRIANGLES, 0, triangleBuffer.getVertexCount());
2025
+ }
2026
+ for (const entity of projectionEntities) {
2027
+ const screenX = entity.screenPos.xscreen / WIDTH * 2 - 1;
2028
+ const screenY = 1 - entity.screenPos.yscreen / HEIGHT * 2;
2029
+ const scale = entity.size * 0.1;
2030
+ const entityMatrix = new Float32Array([
2031
+ scale,
2032
+ 0,
2033
+ 0,
2034
+ 0,
2035
+ 0,
2036
+ scale,
2037
+ 0,
2038
+ 0,
2039
+ 0,
2040
+ 0,
2041
+ 1,
2042
+ 0,
2043
+ screenX,
2044
+ screenY,
2045
+ 0,
2046
+ 1
2047
+ ]);
2048
+ const matrixUniform2 = shader2.getUniformLocation("uMatrix");
2049
+ if (matrixUniform2) {
2050
+ gl.uniformMatrix4fv(matrixUniform2, false, entityMatrix);
2051
+ }
2052
+ const colorUniform2 = shader2.getUniformLocation("uColor");
2053
+ if (colorUniform2) {
2054
+ gl.uniform3f(
2055
+ colorUniform2,
2056
+ entity.color[0],
2057
+ entity.color[1],
2058
+ entity.color[2]
2059
+ );
2060
+ }
2061
+ gl.drawArrays(gl.TRIANGLES, 0, vertexBuffer.getVertexCount());
2062
+ }
2063
+ gdevice.present();
2064
+ const framePixels = renderingContext.readPixels();
2065
+ sdlWindow.updatePixels(framePixels);
2066
+ frameCount++;
2067
+ if (frameCount % 60 === 0) {
2068
+ const totalElapsed = Date.now() - startTime;
2069
+ const fps = frameCount / totalElapsed * 1e3;
2070
+ console.log(`FPS: ${fps.toFixed(1)} | Frames: ${frameCount}`);
2071
+ }
2072
+ const elapsed = Date.now() - frameStartTime;
2073
+ const sleepTime = frameTimeMs - elapsed;
2074
+ if (sleepTime > 0) {
2075
+ setTimeout(renderLoop, sleepTime);
2076
+ } else {
2077
+ setImmediate(renderLoop);
2078
+ }
2079
+ };
2080
+ renderLoop();
2081
+ await new Promise((resolve) => {
2082
+ const checkInterval = setInterval(() => {
2083
+ if (!sdlWindow.isOpen() || !running) {
2084
+ clearInterval(checkInterval);
2085
+ resolve(null);
2086
+ }
2087
+ }, 100);
2088
+ });
2089
+ const totalTime = (Date.now() - startTime) / 1e3;
2090
+ console.log(
2091
+ `✓ Rendered ${frameCount} frames in ${totalTime.toFixed(2)}s`
2092
+ );
2093
+ }
2094
+ vertexBuffer.dispose();
2095
+ texture2.dispose();
2096
+ shader2.dispose();
2097
+ console.log("\n✓ Resources cleaned up");
2098
+ if (sdlWindow) {
2099
+ sdlWindow.cleanup();
2100
+ }
2101
+ gdevice.dispose();
2102
+ console.log("✓ Graphics device disposed");
2103
+ console.log("\n=== Rendering Complete ===\n");
2104
+ await runSpriteBatchRendererV2Demo();
2105
+ } catch (error) {
2106
+ console.error("✗ Rendering failed:", error);
2107
+ if (sdlWindow) {
2108
+ try {
2109
+ sdlWindow.cleanup();
2110
+ } catch (e) {
2111
+ console.error("Error cleaning up SDL window:", e);
2112
+ }
2113
+ }
2114
+ gdevice.dispose();
2115
+ process.exit(1);
2116
+ }
2117
+ })();