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.
- package/LICENSE +21 -0
- package/README.md +92 -0
- package/dist/node/batch-renderer-JqZ4TYcL.js +308 -0
- package/dist/node/browser-resource-loader-D51BD3k_.js +146 -0
- package/dist/node/camera-A8EGrk7U.js +271 -0
- package/dist/node/index-node.js +2117 -0
- package/dist/node/node-resource-loader-MzkD-IGo.js +166 -0
- package/dist/node/resource-loader-factory-DQ-PAVcN.js +93 -0
- package/dist/node/resource-pipeline-Dac9qRso.js +211 -0
- package/dist/web/index.js +1940 -0
- package/dist/web/index.umd.js +56 -0
- package/package.json +58 -0
|
@@ -0,0 +1,1940 @@
|
|
|
1
|
+
import X from "gl";
|
|
2
|
+
import * as C from "fs/promises";
|
|
3
|
+
import * as T from "path";
|
|
4
|
+
class Y {
|
|
5
|
+
constructor(e) {
|
|
6
|
+
this.isBrowser = !0, e.canvas ? this.canvas = e.canvas : (this.canvas = document.createElement("canvas"), document.body.appendChild(this.canvas)), this.width = e.width, this.height = e.height, this.canvas.width = this.width, this.canvas.height = this.height;
|
|
7
|
+
const t = {
|
|
8
|
+
alpha: !1,
|
|
9
|
+
...e.contextAttributes
|
|
10
|
+
}, r = this.canvas.getContext("webgl", t);
|
|
11
|
+
if (!r)
|
|
12
|
+
throw new Error("Failed to initialize WebGL context in browser");
|
|
13
|
+
this.glContext = r;
|
|
14
|
+
}
|
|
15
|
+
resize(e, t) {
|
|
16
|
+
this.width = e, this.height = t, this.canvas.width = e, this.canvas.height = t, this.glContext.viewport(0, 0, e, t);
|
|
17
|
+
}
|
|
18
|
+
getViewport() {
|
|
19
|
+
return { width: this.width, height: this.height };
|
|
20
|
+
}
|
|
21
|
+
clear(e) {
|
|
22
|
+
e && this.glContext.clearColor(e.r, e.g, e.b, e.a), this.glContext.clear(
|
|
23
|
+
this.glContext.COLOR_BUFFER_BIT | this.glContext.DEPTH_BUFFER_BIT
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
present() {
|
|
27
|
+
}
|
|
28
|
+
dispose() {
|
|
29
|
+
this.canvas.parentElement && this.canvas.parentElement.removeChild(this.canvas);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get the underlying canvas element (browser-specific)
|
|
33
|
+
*/
|
|
34
|
+
getCanvas() {
|
|
35
|
+
return this.canvas;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
class G {
|
|
39
|
+
constructor(e) {
|
|
40
|
+
this.isBrowser = !1, this.width = e.width, this.height = e.height;
|
|
41
|
+
const t = X(this.width, this.height, {
|
|
42
|
+
preserveDrawingBuffer: e.preserveDrawingBuffer ?? !0,
|
|
43
|
+
...e.contextAttributes
|
|
44
|
+
});
|
|
45
|
+
if (!t)
|
|
46
|
+
throw new Error("Failed to initialize WebGL context in Node.js");
|
|
47
|
+
this.glContext = t;
|
|
48
|
+
}
|
|
49
|
+
resize(e, t) {
|
|
50
|
+
this.width = e, this.height = t, console.warn(
|
|
51
|
+
"NodeRenderingContext: Resize requested but not supported. Consider recreating context."
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
getViewport() {
|
|
55
|
+
return { width: this.width, height: this.height };
|
|
56
|
+
}
|
|
57
|
+
clear(e) {
|
|
58
|
+
e && this.glContext.clearColor(e.r, e.g, e.b, e.a), this.glContext.clear(
|
|
59
|
+
this.glContext.COLOR_BUFFER_BIT | this.glContext.DEPTH_BUFFER_BIT
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
present() {
|
|
63
|
+
this.glContext.flush();
|
|
64
|
+
}
|
|
65
|
+
dispose() {
|
|
66
|
+
this.glContext.flush();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Read the current framebuffer contents as RGBA pixel data
|
|
70
|
+
* Used for capturing frames for display or saving
|
|
71
|
+
*/
|
|
72
|
+
readPixels() {
|
|
73
|
+
const e = new Uint8Array(this.width * this.height * 4);
|
|
74
|
+
return this.glContext.readPixels(
|
|
75
|
+
0,
|
|
76
|
+
0,
|
|
77
|
+
this.width,
|
|
78
|
+
this.height,
|
|
79
|
+
this.glContext.RGBA,
|
|
80
|
+
this.glContext.UNSIGNED_BYTE,
|
|
81
|
+
e
|
|
82
|
+
), e;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
class k {
|
|
86
|
+
/**
|
|
87
|
+
* Detect if running in a browser environment
|
|
88
|
+
*/
|
|
89
|
+
static isBrowserEnvironment() {
|
|
90
|
+
return typeof window < "u" && typeof document < "u";
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Create a rendering context appropriate for the current environment
|
|
94
|
+
*/
|
|
95
|
+
static createContext(e) {
|
|
96
|
+
return this.isBrowserEnvironment() ? new Y(e) : new G(e);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Create a browser-specific rendering context
|
|
100
|
+
*/
|
|
101
|
+
static createBrowserContext(e) {
|
|
102
|
+
return new Y(e);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Create a Node.js-specific rendering context
|
|
106
|
+
*/
|
|
107
|
+
static createNodeContext(e) {
|
|
108
|
+
return new G(e);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
class H {
|
|
112
|
+
/**
|
|
113
|
+
* Create a new shader program
|
|
114
|
+
* @param gl WebGL rendering context
|
|
115
|
+
* @param vertexSource Raw vertex shader source code
|
|
116
|
+
* @param fragmentSource Raw fragment shader source code
|
|
117
|
+
* @param isBrowser Whether running in browser environment (affects precision header)
|
|
118
|
+
*/
|
|
119
|
+
constructor(e, t, r, s) {
|
|
120
|
+
this.gl = e;
|
|
121
|
+
const i = this.injectPrecisionHeader(
|
|
122
|
+
t,
|
|
123
|
+
s
|
|
124
|
+
), o = this.injectPrecisionHeader(
|
|
125
|
+
r,
|
|
126
|
+
s
|
|
127
|
+
);
|
|
128
|
+
this.vertexShader = this.compileShader(
|
|
129
|
+
i,
|
|
130
|
+
e.VERTEX_SHADER
|
|
131
|
+
), this.fragmentShader = this.compileShader(
|
|
132
|
+
o,
|
|
133
|
+
e.FRAGMENT_SHADER
|
|
134
|
+
), this.program = this.linkProgram(this.vertexShader, this.fragmentShader);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Inject precision header for ES and desktop OpenGL differences
|
|
138
|
+
* @param source Original shader source
|
|
139
|
+
* @param isBrowser Whether in browser (WebGL ES) or Node (desktop OpenGL)
|
|
140
|
+
* @returns Processed shader source with precision header
|
|
141
|
+
*/
|
|
142
|
+
injectPrecisionHeader(e, t) {
|
|
143
|
+
return e.includes("#ifdef GL_ES") || e.includes("precision") ? e : `#ifdef GL_ES
|
|
144
|
+
precision highp float;
|
|
145
|
+
#endif
|
|
146
|
+
` + e;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Compile a single shader (vertex or fragment)
|
|
150
|
+
* @param source Shader source code
|
|
151
|
+
* @param type gl.VERTEX_SHADER or gl.FRAGMENT_SHADER
|
|
152
|
+
* @returns Compiled shader
|
|
153
|
+
*/
|
|
154
|
+
compileShader(e, t) {
|
|
155
|
+
const r = this.gl.createShader(t);
|
|
156
|
+
if (!r)
|
|
157
|
+
throw new Error(`Failed to create shader of type ${t}`);
|
|
158
|
+
if (this.gl.shaderSource(r, e), this.gl.compileShader(r), !this.gl.getShaderParameter(r, this.gl.COMPILE_STATUS)) {
|
|
159
|
+
const i = this.gl.getShaderInfoLog(r), o = t === this.gl.VERTEX_SHADER ? "vertex" : "fragment";
|
|
160
|
+
throw this.gl.deleteShader(r), new Error(
|
|
161
|
+
`Failed to compile ${o} shader:
|
|
162
|
+
${i}
|
|
163
|
+
|
|
164
|
+
Source:
|
|
165
|
+
${e}`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return r;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Link vertex and fragment shaders into a program
|
|
172
|
+
* @param vertexShader Compiled vertex shader
|
|
173
|
+
* @param fragmentShader Compiled fragment shader
|
|
174
|
+
* @returns Linked shader program
|
|
175
|
+
*/
|
|
176
|
+
linkProgram(e, t) {
|
|
177
|
+
const r = this.gl.createProgram();
|
|
178
|
+
if (!r)
|
|
179
|
+
throw new Error("Failed to create shader program");
|
|
180
|
+
if (this.gl.attachShader(r, e), this.gl.attachShader(r, t), this.gl.linkProgram(r), !this.gl.getProgramParameter(r, this.gl.LINK_STATUS)) {
|
|
181
|
+
const i = this.gl.getProgramInfoLog(r);
|
|
182
|
+
throw this.gl.deleteProgram(r), this.gl.deleteShader(e), this.gl.deleteShader(t), new Error(`Failed to link shader program:
|
|
183
|
+
${i}`);
|
|
184
|
+
}
|
|
185
|
+
return r;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get the compiled shader program
|
|
189
|
+
*/
|
|
190
|
+
getProgram() {
|
|
191
|
+
return this.program;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get uniform location by name
|
|
195
|
+
* @param name Uniform variable name
|
|
196
|
+
*/
|
|
197
|
+
getUniformLocation(e) {
|
|
198
|
+
return this.gl.getUniformLocation(this.program, e);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get attribute location by name
|
|
202
|
+
* @param name Attribute variable name
|
|
203
|
+
*/
|
|
204
|
+
getAttributeLocation(e) {
|
|
205
|
+
return this.gl.getAttribLocation(this.program, e);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Use this shader program
|
|
209
|
+
*/
|
|
210
|
+
use() {
|
|
211
|
+
this.gl.useProgram(this.program);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Clean up shader resources
|
|
215
|
+
*/
|
|
216
|
+
dispose() {
|
|
217
|
+
this.gl.deleteProgram(this.program), this.gl.deleteShader(this.vertexShader), this.gl.deleteShader(this.fragmentShader);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
class W {
|
|
221
|
+
constructor(e, t) {
|
|
222
|
+
this.context = k.createContext({
|
|
223
|
+
width: e,
|
|
224
|
+
height: t,
|
|
225
|
+
preserveDrawingBuffer: !0
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get the underlying WebGL rendering context
|
|
230
|
+
*/
|
|
231
|
+
getGLContext() {
|
|
232
|
+
return this.context.glContext;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get the rendering context
|
|
236
|
+
*/
|
|
237
|
+
getRenderingContext() {
|
|
238
|
+
return this.context;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get current width
|
|
242
|
+
*/
|
|
243
|
+
getWidth() {
|
|
244
|
+
return this.context.width;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get current height
|
|
248
|
+
*/
|
|
249
|
+
getHeight() {
|
|
250
|
+
return this.context.height;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Get viewport dimensions
|
|
254
|
+
*/
|
|
255
|
+
getViewport() {
|
|
256
|
+
return this.context.getViewport();
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Check if running in browser
|
|
260
|
+
*/
|
|
261
|
+
isBrowser() {
|
|
262
|
+
return this.context.isBrowser;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Resize the graphics device
|
|
266
|
+
*/
|
|
267
|
+
resize(e, t) {
|
|
268
|
+
this.context.resize(e, t);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Clear the rendering surface
|
|
272
|
+
*/
|
|
273
|
+
clear(e) {
|
|
274
|
+
this.context.clear(e);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Present the rendered frame
|
|
278
|
+
*/
|
|
279
|
+
present() {
|
|
280
|
+
this.context.present();
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Cleanup and release resources
|
|
284
|
+
*/
|
|
285
|
+
dispose() {
|
|
286
|
+
this.context.dispose();
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Create a shader program
|
|
290
|
+
* @param vertexSource Vertex shader source code
|
|
291
|
+
* @param fragmentSource Fragment shader source code
|
|
292
|
+
* @returns Compiled and linked shader program
|
|
293
|
+
*/
|
|
294
|
+
createShader(e, t) {
|
|
295
|
+
return new H(
|
|
296
|
+
this.context.glContext,
|
|
297
|
+
e,
|
|
298
|
+
t,
|
|
299
|
+
this.context.isBrowser
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
class B {
|
|
304
|
+
/**
|
|
305
|
+
* Create a texture from pixel data
|
|
306
|
+
* @param gl WebGL context
|
|
307
|
+
* @param width Texture width
|
|
308
|
+
* @param height Texture height
|
|
309
|
+
* @param data Pixel data (Uint8Array RGBA)
|
|
310
|
+
*/
|
|
311
|
+
constructor(e, t, r, s) {
|
|
312
|
+
this.gl = e, this.width = t, this.height = r;
|
|
313
|
+
const i = e.createTexture();
|
|
314
|
+
if (!i)
|
|
315
|
+
throw new Error("Failed to create texture");
|
|
316
|
+
this.texture = i, e.bindTexture(e.TEXTURE_2D, this.texture), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_S, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_T, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MIN_FILTER, e.LINEAR), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MAG_FILTER, e.LINEAR), s ? e.texImage2D(
|
|
317
|
+
e.TEXTURE_2D,
|
|
318
|
+
0,
|
|
319
|
+
e.RGBA,
|
|
320
|
+
t,
|
|
321
|
+
r,
|
|
322
|
+
0,
|
|
323
|
+
e.RGBA,
|
|
324
|
+
e.UNSIGNED_BYTE,
|
|
325
|
+
s
|
|
326
|
+
) : e.texImage2D(
|
|
327
|
+
e.TEXTURE_2D,
|
|
328
|
+
0,
|
|
329
|
+
e.RGBA,
|
|
330
|
+
t,
|
|
331
|
+
r,
|
|
332
|
+
0,
|
|
333
|
+
e.RGBA,
|
|
334
|
+
e.UNSIGNED_BYTE,
|
|
335
|
+
null
|
|
336
|
+
), e.bindTexture(e.TEXTURE_2D, null);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Create a solid color texture
|
|
340
|
+
* @param gl WebGL context
|
|
341
|
+
* @param width Texture width
|
|
342
|
+
* @param height Texture height
|
|
343
|
+
* @param r Red (0-255)
|
|
344
|
+
* @param g Green (0-255)
|
|
345
|
+
* @param b Blue (0-255)
|
|
346
|
+
* @param a Alpha (0-255)
|
|
347
|
+
*/
|
|
348
|
+
static createSolid(e, t, r, s, i, o, n = 255) {
|
|
349
|
+
const a = t * r, h = new Uint8Array(a * 4);
|
|
350
|
+
for (let x = 0; x < a; x++) {
|
|
351
|
+
const l = x * 4;
|
|
352
|
+
h[l] = s, h[l + 1] = i, h[l + 2] = o, h[l + 3] = n;
|
|
353
|
+
}
|
|
354
|
+
return new B(e, t, r, h);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Create a checkerboard texture
|
|
358
|
+
* @param gl WebGL context
|
|
359
|
+
* @param width Texture width
|
|
360
|
+
* @param height Texture height
|
|
361
|
+
* @param squareSize Size of each square
|
|
362
|
+
*/
|
|
363
|
+
static createCheckerboard(e, t, r, s = 32) {
|
|
364
|
+
const i = new Uint8Array(t * r * 4);
|
|
365
|
+
for (let o = 0; o < r; o++)
|
|
366
|
+
for (let n = 0; n < t; n++) {
|
|
367
|
+
const a = Math.floor(n / s), h = Math.floor(o / s), x = (a + h) % 2 === 0, l = (o * t + n) * 4, d = x ? 255 : 0;
|
|
368
|
+
i[l] = d, i[l + 1] = d, i[l + 2] = d, i[l + 3] = 255;
|
|
369
|
+
}
|
|
370
|
+
return new B(e, t, r, i);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Create a gradient texture
|
|
374
|
+
* @param gl WebGL context
|
|
375
|
+
* @param width Texture width
|
|
376
|
+
* @param height Texture height
|
|
377
|
+
*/
|
|
378
|
+
static createGradient(e, t, r) {
|
|
379
|
+
const s = new Uint8Array(t * r * 4);
|
|
380
|
+
for (let i = 0; i < r; i++)
|
|
381
|
+
for (let o = 0; o < t; o++) {
|
|
382
|
+
const n = (i * t + o) * 4;
|
|
383
|
+
s[n] = Math.floor(o / t * 255), s[n + 1] = Math.floor(i / r * 255), s[n + 2] = 128, s[n + 3] = 255;
|
|
384
|
+
}
|
|
385
|
+
return new B(e, t, r, s);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Bind this texture to a texture unit
|
|
389
|
+
* @param unit Texture unit (0-7 typically)
|
|
390
|
+
*/
|
|
391
|
+
bind(e = 0) {
|
|
392
|
+
this.gl.activeTexture(this.gl.TEXTURE0 + e), this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Unbind texture
|
|
396
|
+
*/
|
|
397
|
+
unbind() {
|
|
398
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, null);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Get the underlying WebGL texture
|
|
402
|
+
*/
|
|
403
|
+
getHandle() {
|
|
404
|
+
return this.texture;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Get texture dimensions
|
|
408
|
+
*/
|
|
409
|
+
getDimensions() {
|
|
410
|
+
return { width: this.width, height: this.height };
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Clean up texture resources
|
|
414
|
+
*/
|
|
415
|
+
dispose() {
|
|
416
|
+
this.gl.deleteTexture(this.texture);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
class Q {
|
|
420
|
+
constructor(e, t, r = 0) {
|
|
421
|
+
this.gl = e, this.stride = r;
|
|
422
|
+
const s = r > 0 ? r / 4 : 3;
|
|
423
|
+
this.vertexCount = t.length / s;
|
|
424
|
+
const i = e.createBuffer();
|
|
425
|
+
if (!i)
|
|
426
|
+
throw new Error("Failed to create vertex buffer");
|
|
427
|
+
this.buffer = i, e.bindBuffer(e.ARRAY_BUFFER, this.buffer), e.bufferData(e.ARRAY_BUFFER, t, e.STATIC_DRAW), e.bindBuffer(e.ARRAY_BUFFER, null);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Bind buffer for rendering
|
|
431
|
+
*/
|
|
432
|
+
bind() {
|
|
433
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Unbind buffer
|
|
437
|
+
*/
|
|
438
|
+
unbind() {
|
|
439
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Get vertex count
|
|
443
|
+
*/
|
|
444
|
+
getVertexCount() {
|
|
445
|
+
return this.vertexCount;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Get stride
|
|
449
|
+
*/
|
|
450
|
+
getStride() {
|
|
451
|
+
return this.stride;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Clean up resources
|
|
455
|
+
*/
|
|
456
|
+
dispose() {
|
|
457
|
+
this.gl.deleteBuffer(this.buffer);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
class j {
|
|
461
|
+
constructor(e, t) {
|
|
462
|
+
this.gl = e, this.indexCount = t.length;
|
|
463
|
+
const r = e.createBuffer();
|
|
464
|
+
if (!r)
|
|
465
|
+
throw new Error("Failed to create index buffer");
|
|
466
|
+
this.buffer = r, e.bindBuffer(e.ELEMENT_ARRAY_BUFFER, this.buffer), e.bufferData(e.ELEMENT_ARRAY_BUFFER, t, e.STATIC_DRAW), e.bindBuffer(e.ELEMENT_ARRAY_BUFFER, null);
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Bind buffer for rendering
|
|
470
|
+
*/
|
|
471
|
+
bind() {
|
|
472
|
+
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffer);
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Unbind buffer
|
|
476
|
+
*/
|
|
477
|
+
unbind() {
|
|
478
|
+
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, null);
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Get index count
|
|
482
|
+
*/
|
|
483
|
+
getIndexCount() {
|
|
484
|
+
return this.indexCount;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Clean up resources
|
|
488
|
+
*/
|
|
489
|
+
dispose() {
|
|
490
|
+
this.gl.deleteBuffer(this.buffer);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const K = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
494
|
+
__proto__: null,
|
|
495
|
+
IndexBuffer: j,
|
|
496
|
+
VertexBuffer: Q
|
|
497
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
498
|
+
class de {
|
|
499
|
+
/**
|
|
500
|
+
* Create a new batch renderer (V1)
|
|
501
|
+
* @param gl WebGL rendering context
|
|
502
|
+
* @param shader Shader program to use
|
|
503
|
+
* @param maxQuads Maximum number of quads to batch (default 1000)
|
|
504
|
+
*/
|
|
505
|
+
constructor(e, t, r = 1e3) {
|
|
506
|
+
this.vertexBuffer = null, this.quads = [], this.isDirty = !1, this.verticesPerQuad = 6, this.floatsPerVertex = 5, this.texture = null, this.gl = e, this.shader = t, this.maxQuads = r;
|
|
507
|
+
const s = r * this.verticesPerQuad * this.floatsPerVertex;
|
|
508
|
+
this.vertexData = new Float32Array(s);
|
|
509
|
+
const i = e.createBuffer();
|
|
510
|
+
if (!i)
|
|
511
|
+
throw new Error("Failed to create vertex buffer");
|
|
512
|
+
this.vertexBuffer = i, e.bindBuffer(e.ARRAY_BUFFER, this.vertexBuffer), e.bufferData(e.ARRAY_BUFFER, this.vertexData.byteLength, e.DYNAMIC_DRAW), e.bindBuffer(e.ARRAY_BUFFER, null);
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Set the texture for batch rendering
|
|
516
|
+
* @param texture The texture to use when rendering
|
|
517
|
+
*/
|
|
518
|
+
setTexture(e) {
|
|
519
|
+
this.texture = e;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Add a quad to the batch
|
|
523
|
+
* @param quad Quad instance to add
|
|
524
|
+
*/
|
|
525
|
+
addQuad(e) {
|
|
526
|
+
if (this.quads.length >= this.maxQuads) {
|
|
527
|
+
console.warn(`Batch renderer at max capacity (${this.maxQuads})`);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
this.quads.push(e), this.isDirty = !0;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Clear all quads from the batch
|
|
534
|
+
*/
|
|
535
|
+
clear() {
|
|
536
|
+
this.quads = [], this.isDirty = !0;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Get number of quads currently in batch
|
|
540
|
+
*/
|
|
541
|
+
getQuadCount() {
|
|
542
|
+
return this.quads.length;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Update the batch - rebuilds vertex buffer if quads changed
|
|
546
|
+
*/
|
|
547
|
+
update() {
|
|
548
|
+
if (!this.isDirty || this.quads.length === 0)
|
|
549
|
+
return;
|
|
550
|
+
let e = 0;
|
|
551
|
+
for (const t of this.quads) {
|
|
552
|
+
const r = this.generateQuadVertices(t);
|
|
553
|
+
for (const s of r)
|
|
554
|
+
this.vertexData[e++] = s[0], this.vertexData[e++] = s[1], this.vertexData[e++] = s[2], this.vertexData[e++] = s[3], this.vertexData[e++] = s[4];
|
|
555
|
+
}
|
|
556
|
+
this.vertexBuffer && (this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer), this.gl.bufferSubData(
|
|
557
|
+
this.gl.ARRAY_BUFFER,
|
|
558
|
+
0,
|
|
559
|
+
this.vertexData.subarray(0, e)
|
|
560
|
+
), this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null)), this.isDirty = !1;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Render the batch
|
|
564
|
+
* @param camera Optional camera for view transform (defaults to identity matrix)
|
|
565
|
+
*/
|
|
566
|
+
render(e) {
|
|
567
|
+
if (this.quads.length !== 0 && (this.update(), this.shader.use(), this.vertexBuffer)) {
|
|
568
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
569
|
+
const t = this.shader.getAttributeLocation("aPosition"), r = this.shader.getAttributeLocation("aTexCoord");
|
|
570
|
+
if (t !== -1 && (this.gl.enableVertexAttribArray(t), this.gl.vertexAttribPointer(
|
|
571
|
+
t,
|
|
572
|
+
3,
|
|
573
|
+
// 3 floats (x, y, z)
|
|
574
|
+
this.gl.FLOAT,
|
|
575
|
+
!1,
|
|
576
|
+
this.floatsPerVertex * 4,
|
|
577
|
+
// stride
|
|
578
|
+
0
|
|
579
|
+
// offset
|
|
580
|
+
)), r !== -1 && (this.gl.enableVertexAttribArray(r), this.gl.vertexAttribPointer(
|
|
581
|
+
r,
|
|
582
|
+
2,
|
|
583
|
+
// 2 floats (u, v)
|
|
584
|
+
this.gl.FLOAT,
|
|
585
|
+
!1,
|
|
586
|
+
this.floatsPerVertex * 4,
|
|
587
|
+
// stride
|
|
588
|
+
12
|
|
589
|
+
// offset after position
|
|
590
|
+
)), this.texture) {
|
|
591
|
+
this.texture.bind(0);
|
|
592
|
+
const o = this.shader.getUniformLocation("uTexture");
|
|
593
|
+
o !== null && this.gl.uniform1i(o, 0);
|
|
594
|
+
}
|
|
595
|
+
const s = this.shader.getUniformLocation("uMatrix");
|
|
596
|
+
if (s !== null) {
|
|
597
|
+
const o = e ? e.getViewMatrix() : new Float32Array([
|
|
598
|
+
1,
|
|
599
|
+
0,
|
|
600
|
+
0,
|
|
601
|
+
0,
|
|
602
|
+
0,
|
|
603
|
+
1,
|
|
604
|
+
0,
|
|
605
|
+
0,
|
|
606
|
+
0,
|
|
607
|
+
0,
|
|
608
|
+
1,
|
|
609
|
+
0,
|
|
610
|
+
0,
|
|
611
|
+
0,
|
|
612
|
+
0,
|
|
613
|
+
1
|
|
614
|
+
]);
|
|
615
|
+
this.gl.uniformMatrix4fv(s, !1, o);
|
|
616
|
+
}
|
|
617
|
+
const i = this.quads.length * this.verticesPerQuad;
|
|
618
|
+
this.gl.drawArrays(this.gl.TRIANGLES, 0, i), this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Generate vertices for a quad with rotation applied
|
|
623
|
+
* Returns 6 vertices (2 triangles)
|
|
624
|
+
* @private
|
|
625
|
+
*/
|
|
626
|
+
generateQuadVertices(e) {
|
|
627
|
+
const { x: t, y: r, width: s, height: i, rotation: o } = e, n = s / 2, a = i / 2, h = Math.cos(o), x = Math.sin(o), l = (m, v) => [m * h - v * x, m * x + v * h], d = [
|
|
628
|
+
[-n, -a],
|
|
629
|
+
// bottom-left
|
|
630
|
+
[n, -a],
|
|
631
|
+
// bottom-right
|
|
632
|
+
[n, a],
|
|
633
|
+
// top-right
|
|
634
|
+
[n, a],
|
|
635
|
+
// top-right (duplicate)
|
|
636
|
+
[-n, a],
|
|
637
|
+
// top-left
|
|
638
|
+
[-n, -a]
|
|
639
|
+
// bottom-left (duplicate)
|
|
640
|
+
], f = [
|
|
641
|
+
[0, 0],
|
|
642
|
+
// bottom-left
|
|
643
|
+
[1, 0],
|
|
644
|
+
// bottom-right
|
|
645
|
+
[1, 1],
|
|
646
|
+
// top-right
|
|
647
|
+
[1, 1],
|
|
648
|
+
// top-right
|
|
649
|
+
[0, 1],
|
|
650
|
+
// top-left
|
|
651
|
+
[0, 0]
|
|
652
|
+
// bottom-left
|
|
653
|
+
], y = [];
|
|
654
|
+
for (let m = 0; m < d.length; m++) {
|
|
655
|
+
const [v, E] = d[m], [A, u] = l(v, E), [w, b] = f[m];
|
|
656
|
+
y.push([t + A, r + u, 0, w, b]);
|
|
657
|
+
}
|
|
658
|
+
return y;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Dispose resources
|
|
662
|
+
*/
|
|
663
|
+
dispose() {
|
|
664
|
+
this.vertexBuffer && (this.gl.deleteBuffer(this.vertexBuffer), this.vertexBuffer = null);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
class fe {
|
|
668
|
+
/**
|
|
669
|
+
* Create a new sprite batch renderer (V2)
|
|
670
|
+
* @param gl WebGL rendering context
|
|
671
|
+
* @param shader Shader program to use (should be SHADERS_V2)
|
|
672
|
+
* @param maxQuads Maximum number of quads to batch (default 1000)
|
|
673
|
+
*/
|
|
674
|
+
constructor(e, t, r = 1e3) {
|
|
675
|
+
this.vertexBuffer = null, this.quads = [], this.isDirty = !1, this.verticesPerQuad = 6, this.floatsPerVertex = 10, this.texture = null, this.depthTestEnabled = !0, this.gl = e, this.shader = t, this.maxQuads = r;
|
|
676
|
+
const s = r * this.verticesPerQuad * this.floatsPerVertex;
|
|
677
|
+
this.vertexData = new Float32Array(s);
|
|
678
|
+
const i = e.createBuffer();
|
|
679
|
+
if (!i)
|
|
680
|
+
throw new Error("Failed to create vertex buffer");
|
|
681
|
+
this.vertexBuffer = i, e.bindBuffer(e.ARRAY_BUFFER, this.vertexBuffer), e.bufferData(e.ARRAY_BUFFER, this.vertexData.byteLength, e.DYNAMIC_DRAW), e.bindBuffer(e.ARRAY_BUFFER, null);
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Set the texture for batch rendering
|
|
685
|
+
* @param texture The texture to use when rendering
|
|
686
|
+
*/
|
|
687
|
+
setTexture(e) {
|
|
688
|
+
this.texture = e;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Add a sprite quad to the batch
|
|
692
|
+
* @param quad Sprite quad instance to add
|
|
693
|
+
*/
|
|
694
|
+
addQuad(e) {
|
|
695
|
+
if (this.quads.length >= this.maxQuads) {
|
|
696
|
+
console.warn(`Sprite batch renderer at max capacity (${this.maxQuads})`);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
this.quads.push(e), this.isDirty = !0;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Clear all quads from the batch
|
|
703
|
+
*/
|
|
704
|
+
clear() {
|
|
705
|
+
this.quads = [], this.isDirty = !0;
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Get number of quads currently in batch
|
|
709
|
+
*/
|
|
710
|
+
getQuadCount() {
|
|
711
|
+
return this.quads.length;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Update the batch - rebuilds vertex buffer if quads changed
|
|
715
|
+
*/
|
|
716
|
+
update() {
|
|
717
|
+
if (!this.isDirty || this.quads.length === 0)
|
|
718
|
+
return;
|
|
719
|
+
let e = 0;
|
|
720
|
+
for (const t of this.quads) {
|
|
721
|
+
const {
|
|
722
|
+
x: r,
|
|
723
|
+
y: s,
|
|
724
|
+
z: i = 0,
|
|
725
|
+
width: o,
|
|
726
|
+
height: n,
|
|
727
|
+
rotation: a,
|
|
728
|
+
color: h = { r: 1, g: 1, b: 1, a: 1 },
|
|
729
|
+
uvRect: x = { uMin: 0, vMin: 0, uMax: 1, vMax: 1 },
|
|
730
|
+
texIndex: l = 0
|
|
731
|
+
} = t, d = this.generateQuadVertices({
|
|
732
|
+
x: r,
|
|
733
|
+
y: s,
|
|
734
|
+
z: i,
|
|
735
|
+
width: o,
|
|
736
|
+
height: n,
|
|
737
|
+
rotation: a,
|
|
738
|
+
color: h,
|
|
739
|
+
uvRect: x,
|
|
740
|
+
texIndex: l
|
|
741
|
+
});
|
|
742
|
+
for (const f of d)
|
|
743
|
+
this.vertexData[e++] = f.x, this.vertexData[e++] = f.y, this.vertexData[e++] = f.z, this.vertexData[e++] = f.u, this.vertexData[e++] = f.v, this.vertexData[e++] = f.r, this.vertexData[e++] = f.g, this.vertexData[e++] = f.b, this.vertexData[e++] = f.a, this.vertexData[e++] = f.texIndex;
|
|
744
|
+
}
|
|
745
|
+
this.vertexBuffer && (this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer), this.gl.bufferSubData(
|
|
746
|
+
this.gl.ARRAY_BUFFER,
|
|
747
|
+
0,
|
|
748
|
+
this.vertexData.subarray(0, e)
|
|
749
|
+
), this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null)), this.isDirty = !1;
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Set whether depth testing is enabled
|
|
753
|
+
* When enabled, sprites with lower Z values appear behind sprites with higher Z values
|
|
754
|
+
* @param enabled Whether to enable depth testing (default true)
|
|
755
|
+
*/
|
|
756
|
+
setDepthTestEnabled(e) {
|
|
757
|
+
this.depthTestEnabled = e;
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Render the batch
|
|
761
|
+
* @param camera Optional camera for view transform (defaults to identity matrix)
|
|
762
|
+
*/
|
|
763
|
+
render(e) {
|
|
764
|
+
if (this.quads.length !== 0 && (this.update(), this.shader.use(), this.depthTestEnabled ? (this.gl.enable(this.gl.DEPTH_TEST), this.gl.depthFunc(this.gl.LEQUAL)) : this.gl.disable(this.gl.DEPTH_TEST), this.vertexBuffer)) {
|
|
765
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
766
|
+
const t = this.shader.getAttributeLocation("aPosition"), r = this.shader.getAttributeLocation("aTexCoord"), s = this.shader.getAttributeLocation("aColor"), i = this.shader.getAttributeLocation("aTexIndex"), o = this.floatsPerVertex * 4;
|
|
767
|
+
if (t !== -1 && (this.gl.enableVertexAttribArray(t), this.gl.vertexAttribPointer(
|
|
768
|
+
t,
|
|
769
|
+
3,
|
|
770
|
+
// 3 floats (x, y, z)
|
|
771
|
+
this.gl.FLOAT,
|
|
772
|
+
!1,
|
|
773
|
+
o,
|
|
774
|
+
0
|
|
775
|
+
// offset
|
|
776
|
+
)), r !== -1 && (this.gl.enableVertexAttribArray(r), this.gl.vertexAttribPointer(
|
|
777
|
+
r,
|
|
778
|
+
2,
|
|
779
|
+
// 2 floats (u, v)
|
|
780
|
+
this.gl.FLOAT,
|
|
781
|
+
!1,
|
|
782
|
+
o,
|
|
783
|
+
12
|
|
784
|
+
// offset after position
|
|
785
|
+
)), s !== -1 && (this.gl.enableVertexAttribArray(s), this.gl.vertexAttribPointer(
|
|
786
|
+
s,
|
|
787
|
+
4,
|
|
788
|
+
// 4 floats (r, g, b, a)
|
|
789
|
+
this.gl.FLOAT,
|
|
790
|
+
!1,
|
|
791
|
+
o,
|
|
792
|
+
20
|
|
793
|
+
// offset after texCoord
|
|
794
|
+
)), i !== -1 && (this.gl.enableVertexAttribArray(i), this.gl.vertexAttribPointer(
|
|
795
|
+
i,
|
|
796
|
+
1,
|
|
797
|
+
// 1 float (texIndex)
|
|
798
|
+
this.gl.FLOAT,
|
|
799
|
+
!1,
|
|
800
|
+
o,
|
|
801
|
+
36
|
|
802
|
+
// offset after color
|
|
803
|
+
)), this.texture) {
|
|
804
|
+
this.texture.bind(0);
|
|
805
|
+
const h = this.shader.getUniformLocation("uTexture");
|
|
806
|
+
h !== null && this.gl.uniform1i(h, 0);
|
|
807
|
+
}
|
|
808
|
+
const n = this.shader.getUniformLocation("uMatrix");
|
|
809
|
+
if (n !== null) {
|
|
810
|
+
const h = e ? e.getViewMatrix() : new Float32Array([
|
|
811
|
+
1,
|
|
812
|
+
0,
|
|
813
|
+
0,
|
|
814
|
+
0,
|
|
815
|
+
0,
|
|
816
|
+
1,
|
|
817
|
+
0,
|
|
818
|
+
0,
|
|
819
|
+
0,
|
|
820
|
+
0,
|
|
821
|
+
1,
|
|
822
|
+
0,
|
|
823
|
+
0,
|
|
824
|
+
0,
|
|
825
|
+
0,
|
|
826
|
+
1
|
|
827
|
+
]);
|
|
828
|
+
this.gl.uniformMatrix4fv(n, !1, h);
|
|
829
|
+
}
|
|
830
|
+
const a = this.quads.length * this.verticesPerQuad;
|
|
831
|
+
this.gl.drawArrays(this.gl.TRIANGLES, 0, a), this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Generate vertices for a quad with rotation applied
|
|
836
|
+
* Returns 6 vertices (2 triangles)
|
|
837
|
+
* @private
|
|
838
|
+
*/
|
|
839
|
+
generateQuadVertices(e) {
|
|
840
|
+
const { x: t, y: r, z: s, width: i, height: o, rotation: n, color: a, uvRect: h, texIndex: x } = e, l = i / 2, d = o / 2, f = Math.cos(n), y = Math.sin(n), m = (u, w) => [u * f - w * y, u * y + w * f], v = [
|
|
841
|
+
[-l, -d],
|
|
842
|
+
// bottom-left
|
|
843
|
+
[l, -d],
|
|
844
|
+
// bottom-right
|
|
845
|
+
[l, d],
|
|
846
|
+
// top-right
|
|
847
|
+
[l, d],
|
|
848
|
+
// top-right (duplicate)
|
|
849
|
+
[-l, d],
|
|
850
|
+
// top-left
|
|
851
|
+
[-l, -d]
|
|
852
|
+
// bottom-left (duplicate)
|
|
853
|
+
], E = [
|
|
854
|
+
[h.uMin, h.vMin],
|
|
855
|
+
// bottom-left
|
|
856
|
+
[h.uMax, h.vMin],
|
|
857
|
+
// bottom-right
|
|
858
|
+
[h.uMax, h.vMax],
|
|
859
|
+
// top-right
|
|
860
|
+
[h.uMax, h.vMax],
|
|
861
|
+
// top-right
|
|
862
|
+
[h.uMin, h.vMax],
|
|
863
|
+
// top-left
|
|
864
|
+
[h.uMin, h.vMin]
|
|
865
|
+
// bottom-left
|
|
866
|
+
], A = [];
|
|
867
|
+
for (let u = 0; u < v.length; u++) {
|
|
868
|
+
const [w, b] = v[u], [_, D] = m(w, b), [U, g] = E[u];
|
|
869
|
+
A.push({
|
|
870
|
+
x: t + _,
|
|
871
|
+
y: r + D,
|
|
872
|
+
z: s,
|
|
873
|
+
u: U,
|
|
874
|
+
v: g,
|
|
875
|
+
r: a.r,
|
|
876
|
+
g: a.g,
|
|
877
|
+
b: a.b,
|
|
878
|
+
a: a.a,
|
|
879
|
+
texIndex: x
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
return A;
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Dispose resources
|
|
886
|
+
*/
|
|
887
|
+
dispose() {
|
|
888
|
+
this.vertexBuffer && (this.gl.deleteBuffer(this.vertexBuffer), this.vertexBuffer = null);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
class F {
|
|
892
|
+
/**
|
|
893
|
+
* Create an identity matrix
|
|
894
|
+
* @returns 4x4 identity matrix in column-major order
|
|
895
|
+
*/
|
|
896
|
+
static identity() {
|
|
897
|
+
return new Float32Array([
|
|
898
|
+
1,
|
|
899
|
+
0,
|
|
900
|
+
0,
|
|
901
|
+
0,
|
|
902
|
+
// column 0
|
|
903
|
+
0,
|
|
904
|
+
1,
|
|
905
|
+
0,
|
|
906
|
+
0,
|
|
907
|
+
// column 1
|
|
908
|
+
0,
|
|
909
|
+
0,
|
|
910
|
+
1,
|
|
911
|
+
0,
|
|
912
|
+
// column 2
|
|
913
|
+
0,
|
|
914
|
+
0,
|
|
915
|
+
0,
|
|
916
|
+
1
|
|
917
|
+
// column 3
|
|
918
|
+
]);
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Create a translation matrix
|
|
922
|
+
* @param x Translation along X axis
|
|
923
|
+
* @param y Translation along Y axis
|
|
924
|
+
* @param z Translation along Z axis (default 0)
|
|
925
|
+
* @returns 4x4 translation matrix in column-major order
|
|
926
|
+
*/
|
|
927
|
+
static translation(e, t, r = 0) {
|
|
928
|
+
return new Float32Array([
|
|
929
|
+
1,
|
|
930
|
+
0,
|
|
931
|
+
0,
|
|
932
|
+
0,
|
|
933
|
+
// column 0
|
|
934
|
+
0,
|
|
935
|
+
1,
|
|
936
|
+
0,
|
|
937
|
+
0,
|
|
938
|
+
// column 1
|
|
939
|
+
0,
|
|
940
|
+
0,
|
|
941
|
+
1,
|
|
942
|
+
0,
|
|
943
|
+
// column 2
|
|
944
|
+
e,
|
|
945
|
+
t,
|
|
946
|
+
r,
|
|
947
|
+
1
|
|
948
|
+
// column 3
|
|
949
|
+
]);
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Create a scale matrix
|
|
953
|
+
* @param x Scale factor along X axis
|
|
954
|
+
* @param y Scale factor along Y axis
|
|
955
|
+
* @param z Scale factor along Z axis (default 1)
|
|
956
|
+
* @returns 4x4 scale matrix in column-major order
|
|
957
|
+
*/
|
|
958
|
+
static scale(e, t, r = 1) {
|
|
959
|
+
return new Float32Array([
|
|
960
|
+
e,
|
|
961
|
+
0,
|
|
962
|
+
0,
|
|
963
|
+
0,
|
|
964
|
+
// column 0
|
|
965
|
+
0,
|
|
966
|
+
t,
|
|
967
|
+
0,
|
|
968
|
+
0,
|
|
969
|
+
// column 1
|
|
970
|
+
0,
|
|
971
|
+
0,
|
|
972
|
+
r,
|
|
973
|
+
0,
|
|
974
|
+
// column 2
|
|
975
|
+
0,
|
|
976
|
+
0,
|
|
977
|
+
0,
|
|
978
|
+
1
|
|
979
|
+
// column 3
|
|
980
|
+
]);
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Multiply two matrices (result = a * b)
|
|
984
|
+
* @param a First matrix (left operand)
|
|
985
|
+
* @param b Second matrix (right operand)
|
|
986
|
+
* @returns Result of matrix multiplication in column-major order
|
|
987
|
+
*/
|
|
988
|
+
static multiply(e, t) {
|
|
989
|
+
const r = new Float32Array(16);
|
|
990
|
+
for (let s = 0; s < 4; s++)
|
|
991
|
+
for (let i = 0; i < 4; i++) {
|
|
992
|
+
let o = 0;
|
|
993
|
+
for (let n = 0; n < 4; n++)
|
|
994
|
+
o += e[n * 4 + i] * t[s * 4 + n];
|
|
995
|
+
r[s * 4 + i] = o;
|
|
996
|
+
}
|
|
997
|
+
return r;
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Create a view matrix from camera position and zoom
|
|
1001
|
+
* The view matrix transforms world coordinates to camera/eye coordinates
|
|
1002
|
+
*
|
|
1003
|
+
* View = Translation(-cameraX, -cameraY, 0) * Scale(zoom, zoom, 1)
|
|
1004
|
+
*
|
|
1005
|
+
* @param x Camera X position (translation will be negative)
|
|
1006
|
+
* @param y Camera Y position (translation will be negative)
|
|
1007
|
+
* @param zoom Camera zoom level (1.0 = no zoom, >1 = zoom in, <1 = zoom out)
|
|
1008
|
+
* @returns 4x4 view matrix in column-major order
|
|
1009
|
+
*/
|
|
1010
|
+
static createViewMatrix(e, t, r) {
|
|
1011
|
+
const s = F.translation(-e, -t, 0), i = F.scale(r, r, 1);
|
|
1012
|
+
return F.multiply(s, i);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
class ge {
|
|
1016
|
+
/**
|
|
1017
|
+
* Create a new camera
|
|
1018
|
+
* @param x Initial X position (default 0)
|
|
1019
|
+
* @param y Initial Y position (default 0)
|
|
1020
|
+
* @param zoom Initial zoom level (default 1.0)
|
|
1021
|
+
*/
|
|
1022
|
+
constructor(e = 0, t = 0, r = 1) {
|
|
1023
|
+
this._viewMatrix = null, this._viewMatrixDirty = !0, this._x = e, this._y = t, this._zoom = r;
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Get the camera X position
|
|
1027
|
+
*/
|
|
1028
|
+
get x() {
|
|
1029
|
+
return this._x;
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Set the camera X position
|
|
1033
|
+
*/
|
|
1034
|
+
set x(e) {
|
|
1035
|
+
this._x = e, this._viewMatrixDirty = !0;
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Get the camera Y position
|
|
1039
|
+
*/
|
|
1040
|
+
get y() {
|
|
1041
|
+
return this._y;
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Set the camera Y position
|
|
1045
|
+
*/
|
|
1046
|
+
set y(e) {
|
|
1047
|
+
this._y = e, this._viewMatrixDirty = !0;
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Get the camera zoom level
|
|
1051
|
+
*/
|
|
1052
|
+
get zoom() {
|
|
1053
|
+
return this._zoom;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Set the camera zoom level
|
|
1057
|
+
* Values: 1.0 = no zoom, >1 = zoom in, <1 = zoom out
|
|
1058
|
+
*/
|
|
1059
|
+
set zoom(e) {
|
|
1060
|
+
this._zoom = Math.max(1e-3, e), this._viewMatrixDirty = !0;
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Set both X and Y position at once
|
|
1064
|
+
* @param x New X position
|
|
1065
|
+
* @param y New Y position
|
|
1066
|
+
*/
|
|
1067
|
+
setPosition(e, t) {
|
|
1068
|
+
this._x = e, this._y = t, this._viewMatrixDirty = !0;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Move the camera by a relative offset
|
|
1072
|
+
* @param dx X offset to add to current position
|
|
1073
|
+
* @param dy Y offset to add to current position
|
|
1074
|
+
*/
|
|
1075
|
+
move(e, t) {
|
|
1076
|
+
this._x += e, this._y += t, this._viewMatrixDirty = !0;
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Scale the zoom by a factor
|
|
1080
|
+
* @param factor Multiplier for current zoom (e.g., 1.1 to zoom in 10%)
|
|
1081
|
+
*/
|
|
1082
|
+
zoomBy(e) {
|
|
1083
|
+
this._zoom = Math.max(1e-3, this._zoom * e), this._viewMatrixDirty = !0;
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Reset camera to default position and zoom
|
|
1087
|
+
*/
|
|
1088
|
+
reset() {
|
|
1089
|
+
this._x = 0, this._y = 0, this._zoom = 1, this._viewMatrixDirty = !0;
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Get the view matrix for this camera
|
|
1093
|
+
* The view matrix transforms world coordinates to camera space
|
|
1094
|
+
* Caches the result until camera properties change
|
|
1095
|
+
*
|
|
1096
|
+
* @returns 4x4 view matrix in column-major order
|
|
1097
|
+
*/
|
|
1098
|
+
getViewMatrix() {
|
|
1099
|
+
return (this._viewMatrixDirty || this._viewMatrix === null) && (this._viewMatrix = F.createViewMatrix(this._x, this._y, this._zoom), this._viewMatrixDirty = !1), this._viewMatrix;
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Convert screen coordinates to world coordinates
|
|
1103
|
+
* Useful for mouse picking and interaction
|
|
1104
|
+
*
|
|
1105
|
+
* @param screenX Screen X coordinate (pixels)
|
|
1106
|
+
* @param screenY Screen Y coordinate (pixels)
|
|
1107
|
+
* @param viewportWidth Viewport width in pixels
|
|
1108
|
+
* @param viewportHeight Viewport height in pixels
|
|
1109
|
+
* @returns World coordinates {x, y}
|
|
1110
|
+
*/
|
|
1111
|
+
screenToWorld(e, t, r, s) {
|
|
1112
|
+
const i = e - r / 2, o = t - s / 2, n = i / this._zoom + this._x, a = o / this._zoom + this._y;
|
|
1113
|
+
return { x: n, y: a };
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Convert world coordinates to screen coordinates
|
|
1117
|
+
* Useful for UI positioning and debug rendering
|
|
1118
|
+
*
|
|
1119
|
+
* @param worldX World X coordinate
|
|
1120
|
+
* @param worldY World Y coordinate
|
|
1121
|
+
* @param viewportWidth Viewport width in pixels
|
|
1122
|
+
* @param viewportHeight Viewport height in pixels
|
|
1123
|
+
* @returns Screen coordinates {x, y} in pixels
|
|
1124
|
+
*/
|
|
1125
|
+
worldToScreen(e, t, r, s) {
|
|
1126
|
+
const i = (e - this._x) * this._zoom, o = (t - this._y) * this._zoom, n = i + r / 2, a = o + s / 2;
|
|
1127
|
+
return { x: n, y: a };
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
class J {
|
|
1131
|
+
/**
|
|
1132
|
+
* Create a new browser resource loader
|
|
1133
|
+
* @param baseUrl Optional base URL for resolving relative paths (defaults to current origin)
|
|
1134
|
+
* @param timeout Default timeout for requests in milliseconds (default: 10000)
|
|
1135
|
+
*/
|
|
1136
|
+
constructor(e = "", t = 1e4) {
|
|
1137
|
+
this.baseUrl = e || this.getCurrentOrigin(), this.defaultTimeout = t;
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Get the current origin (protocol + host + port)
|
|
1141
|
+
*/
|
|
1142
|
+
getCurrentOrigin() {
|
|
1143
|
+
return typeof window < "u" ? window.location.origin : "http://localhost";
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Resolve a relative path against the base URL
|
|
1147
|
+
* @param path Relative or absolute path
|
|
1148
|
+
* @returns Resolved absolute URL
|
|
1149
|
+
*/
|
|
1150
|
+
resolvePath(e) {
|
|
1151
|
+
try {
|
|
1152
|
+
return e.startsWith("http://") || e.startsWith("https://") ? e : e.startsWith("//") ? window.location.protocol + e : e.startsWith("/") ? this.baseUrl + e : `${this.baseUrl}/${e}`;
|
|
1153
|
+
} catch {
|
|
1154
|
+
return e;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Load a single resource from a URL
|
|
1159
|
+
* @param path URL or relative path to the resource
|
|
1160
|
+
* @param options Optional loading configuration
|
|
1161
|
+
* @returns Promise resolving to the resource content
|
|
1162
|
+
*/
|
|
1163
|
+
async load(e, t) {
|
|
1164
|
+
const r = this.resolvePath(e);
|
|
1165
|
+
try {
|
|
1166
|
+
const s = {
|
|
1167
|
+
credentials: t?.credentials || "same-origin"
|
|
1168
|
+
};
|
|
1169
|
+
t?.headers && (s.headers = t.headers);
|
|
1170
|
+
const i = new AbortController(), o = setTimeout(() => i.abort(), this.defaultTimeout);
|
|
1171
|
+
s.signal = i.signal;
|
|
1172
|
+
const n = await fetch(r, s);
|
|
1173
|
+
if (clearTimeout(o), !n.ok)
|
|
1174
|
+
throw new Error(
|
|
1175
|
+
`HTTP ${n.status}: ${n.statusText} for URL: ${r}`
|
|
1176
|
+
);
|
|
1177
|
+
return await n.text();
|
|
1178
|
+
} catch (s) {
|
|
1179
|
+
throw s instanceof Error ? s.name === "AbortError" ? new Error(
|
|
1180
|
+
`Request timeout after ${this.defaultTimeout}ms for URL: ${r}`
|
|
1181
|
+
) : new Error(`Failed to load resource from ${r}: ${s.message}`) : new Error(`Failed to load resource from ${r}: Unknown error`);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Load multiple resources in parallel
|
|
1186
|
+
* @param paths Array of URLs or paths
|
|
1187
|
+
* @param options Optional loading configuration
|
|
1188
|
+
* @returns Promise resolving to array of load results
|
|
1189
|
+
*/
|
|
1190
|
+
async loadMultiple(e, t) {
|
|
1191
|
+
const r = e.map(async (s) => {
|
|
1192
|
+
try {
|
|
1193
|
+
return {
|
|
1194
|
+
data: await this.load(s, t),
|
|
1195
|
+
path: s,
|
|
1196
|
+
success: !0
|
|
1197
|
+
};
|
|
1198
|
+
} catch (i) {
|
|
1199
|
+
return {
|
|
1200
|
+
data: "",
|
|
1201
|
+
path: s,
|
|
1202
|
+
success: !1,
|
|
1203
|
+
error: i instanceof Error ? i.message : String(i)
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
return Promise.all(r);
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Check if the path is valid for loading in the browser
|
|
1211
|
+
* @param path URL or path to check
|
|
1212
|
+
* @returns true if the path can be loaded
|
|
1213
|
+
*/
|
|
1214
|
+
canLoad(e) {
|
|
1215
|
+
const t = [
|
|
1216
|
+
/^https?:\/\//i,
|
|
1217
|
+
// Absolute HTTP(S) URLs
|
|
1218
|
+
/^\/\//,
|
|
1219
|
+
// Protocol-relative URLs
|
|
1220
|
+
/^\//,
|
|
1221
|
+
// Absolute paths
|
|
1222
|
+
/^\.\.?\//
|
|
1223
|
+
// Relative paths starting with ./ or ../
|
|
1224
|
+
], r = /\.[a-z0-9]+$/i.test(e);
|
|
1225
|
+
return t.some((s) => s.test(e)) || r;
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Set a new base URL for resolving relative paths
|
|
1229
|
+
* @param baseUrl New base URL
|
|
1230
|
+
*/
|
|
1231
|
+
setBaseUrl(e) {
|
|
1232
|
+
this.baseUrl = e;
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Get the current base URL
|
|
1236
|
+
* @returns Current base URL
|
|
1237
|
+
*/
|
|
1238
|
+
getBaseUrl() {
|
|
1239
|
+
return this.baseUrl;
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Set the default request timeout
|
|
1243
|
+
* @param timeout Timeout in milliseconds
|
|
1244
|
+
*/
|
|
1245
|
+
setTimeout(e) {
|
|
1246
|
+
this.defaultTimeout = e;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
const Z = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1250
|
+
__proto__: null,
|
|
1251
|
+
BrowserResourceLoader: J
|
|
1252
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
1253
|
+
class ee {
|
|
1254
|
+
/**
|
|
1255
|
+
* Create a new Node.js resource loader
|
|
1256
|
+
* @param baseDir Optional base directory for resolving relative paths (defaults to current working directory)
|
|
1257
|
+
*/
|
|
1258
|
+
constructor(e = process.cwd()) {
|
|
1259
|
+
this.baseDir = e;
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Resolve a relative path against the base directory
|
|
1263
|
+
* @param filePath Relative or absolute file path
|
|
1264
|
+
* @returns Resolved absolute file path
|
|
1265
|
+
*/
|
|
1266
|
+
resolvePath(e) {
|
|
1267
|
+
return T.isAbsolute(e) ? T.normalize(e) : T.normalize(T.join(this.baseDir, e));
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Load a single resource from a file
|
|
1271
|
+
* @param filePath File path (relative or absolute)
|
|
1272
|
+
* @param options Optional loading configuration
|
|
1273
|
+
* @returns Promise resolving to the file content
|
|
1274
|
+
*/
|
|
1275
|
+
async load(e, t) {
|
|
1276
|
+
const r = this.resolvePath(e), s = t?.encoding || "utf-8";
|
|
1277
|
+
try {
|
|
1278
|
+
return await C.readFile(r, s);
|
|
1279
|
+
} catch (i) {
|
|
1280
|
+
if (i instanceof Error) {
|
|
1281
|
+
const o = i.code;
|
|
1282
|
+
throw o === "ENOENT" ? new Error(
|
|
1283
|
+
`File not found: ${r} (resolved from: ${e})`
|
|
1284
|
+
) : o === "EACCES" ? new Error(
|
|
1285
|
+
`Permission denied reading file: ${r}`
|
|
1286
|
+
) : o === "EISDIR" ? new Error(
|
|
1287
|
+
`Path is a directory, not a file: ${r}`
|
|
1288
|
+
) : new Error(
|
|
1289
|
+
`Failed to load resource from ${r}: ${i.message}`
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
throw new Error(
|
|
1293
|
+
`Failed to load resource from ${r}: Unknown error`
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Load multiple resources in parallel
|
|
1299
|
+
* @param filePaths Array of file paths
|
|
1300
|
+
* @param options Optional loading configuration
|
|
1301
|
+
* @returns Promise resolving to array of load results
|
|
1302
|
+
*/
|
|
1303
|
+
async loadMultiple(e, t) {
|
|
1304
|
+
const r = e.map(async (s) => {
|
|
1305
|
+
try {
|
|
1306
|
+
return {
|
|
1307
|
+
data: await this.load(s, t),
|
|
1308
|
+
path: s,
|
|
1309
|
+
success: !0
|
|
1310
|
+
};
|
|
1311
|
+
} catch (i) {
|
|
1312
|
+
return {
|
|
1313
|
+
data: "",
|
|
1314
|
+
path: s,
|
|
1315
|
+
success: !1,
|
|
1316
|
+
error: i instanceof Error ? i.message : String(i)
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1320
|
+
return Promise.all(r);
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Check if the path is valid for loading in Node.js
|
|
1324
|
+
* @param filePath File path to check
|
|
1325
|
+
* @returns true if the path can be loaded
|
|
1326
|
+
*/
|
|
1327
|
+
canLoad(e) {
|
|
1328
|
+
return [
|
|
1329
|
+
/^\//,
|
|
1330
|
+
// Unix absolute paths
|
|
1331
|
+
/^[a-zA-Z]:/,
|
|
1332
|
+
// Windows absolute paths (e.g., C:\)
|
|
1333
|
+
/^\.\.?\//,
|
|
1334
|
+
// Relative paths starting with ./ or ../
|
|
1335
|
+
/^[^/\\]+\//
|
|
1336
|
+
// Relative paths without explicit prefix (e.g., "shaders/")
|
|
1337
|
+
].some((r) => r.test(e));
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Check if a file exists without loading it
|
|
1341
|
+
* @param filePath File path to check
|
|
1342
|
+
* @returns Promise resolving to true if file exists
|
|
1343
|
+
*/
|
|
1344
|
+
async exists(e) {
|
|
1345
|
+
const t = this.resolvePath(e);
|
|
1346
|
+
try {
|
|
1347
|
+
return await C.access(t, C.constants.F_OK), !0;
|
|
1348
|
+
} catch {
|
|
1349
|
+
return !1;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Get file statistics (size, modification time, etc.)
|
|
1354
|
+
* @param filePath File path to check
|
|
1355
|
+
* @returns Promise resolving to file stats
|
|
1356
|
+
*/
|
|
1357
|
+
async getStats(e) {
|
|
1358
|
+
const t = this.resolvePath(e);
|
|
1359
|
+
return C.stat(t);
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Set a new base directory for resolving relative paths
|
|
1363
|
+
* @param baseDir New base directory
|
|
1364
|
+
*/
|
|
1365
|
+
setBaseDir(e) {
|
|
1366
|
+
this.baseDir = e;
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Get the current base directory
|
|
1370
|
+
* @returns Current base directory
|
|
1371
|
+
*/
|
|
1372
|
+
getBaseDir() {
|
|
1373
|
+
return this.baseDir;
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* List all files in a directory
|
|
1377
|
+
* @param dirPath Directory path to list
|
|
1378
|
+
* @param recursive Whether to recursively list subdirectories (default: false)
|
|
1379
|
+
* @returns Promise resolving to array of file paths
|
|
1380
|
+
*/
|
|
1381
|
+
async listDirectory(e, t = !1) {
|
|
1382
|
+
const r = this.resolvePath(e), s = await C.readdir(r, { withFileTypes: !0 }), i = [];
|
|
1383
|
+
for (const o of s) {
|
|
1384
|
+
const n = T.join(r, o.name);
|
|
1385
|
+
if (o.isDirectory() && t) {
|
|
1386
|
+
const a = await this.listDirectory(n, !0);
|
|
1387
|
+
i.push(...a);
|
|
1388
|
+
} else o.isFile() && i.push(n);
|
|
1389
|
+
}
|
|
1390
|
+
return i;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
const te = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1394
|
+
__proto__: null,
|
|
1395
|
+
NodeResourceLoader: ee
|
|
1396
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
1397
|
+
var I = /* @__PURE__ */ ((c) => (c.BROWSER = "browser", c.NODE = "node", c.UNKNOWN = "unknown", c))(I || {});
|
|
1398
|
+
class N {
|
|
1399
|
+
/**
|
|
1400
|
+
* Detect the current runtime environment
|
|
1401
|
+
* @returns The detected environment type
|
|
1402
|
+
*/
|
|
1403
|
+
static detectEnvironment() {
|
|
1404
|
+
return typeof window < "u" && typeof window.document < "u" && typeof fetch < "u" ? "browser" : typeof process < "u" && process.versions != null && process.versions.node != null ? "node" : "unknown";
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Check if the current environment is a browser
|
|
1408
|
+
* @returns true if running in a browser
|
|
1409
|
+
*/
|
|
1410
|
+
static isBrowser() {
|
|
1411
|
+
return this.detectEnvironment() === "browser";
|
|
1412
|
+
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Check if the current environment is Node.js
|
|
1415
|
+
* @returns true if running in Node.js
|
|
1416
|
+
*/
|
|
1417
|
+
static isNode() {
|
|
1418
|
+
return this.detectEnvironment() === "node";
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Create a resource loader for the current environment
|
|
1422
|
+
* @param options Optional factory configuration
|
|
1423
|
+
* @returns A resource loader instance appropriate for the current platform
|
|
1424
|
+
* @throws Error if the environment is not supported
|
|
1425
|
+
*/
|
|
1426
|
+
static async create(e) {
|
|
1427
|
+
const t = e?.forceEnvironment || this.detectEnvironment();
|
|
1428
|
+
switch (t) {
|
|
1429
|
+
case "browser":
|
|
1430
|
+
return await this.createBrowserLoader(e);
|
|
1431
|
+
case "node":
|
|
1432
|
+
return await this.createNodeLoader(e);
|
|
1433
|
+
case "unknown":
|
|
1434
|
+
throw new Error(
|
|
1435
|
+
"Unsupported environment: Unable to determine runtime environment. Please specify forceEnvironment in options."
|
|
1436
|
+
);
|
|
1437
|
+
default:
|
|
1438
|
+
throw new Error(`Unsupported environment: ${t}`);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Create a browser resource loader
|
|
1443
|
+
* @param options Optional factory configuration
|
|
1444
|
+
* @returns A browser resource loader instance
|
|
1445
|
+
*/
|
|
1446
|
+
static async createBrowserLoader(e) {
|
|
1447
|
+
const { BrowserResourceLoader: t } = await Promise.resolve().then(() => Z);
|
|
1448
|
+
return new t(e?.baseUrl, e?.timeout);
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Create a Node.js resource loader
|
|
1452
|
+
* @param options Optional factory configuration
|
|
1453
|
+
* @returns A Node.js resource loader instance
|
|
1454
|
+
*/
|
|
1455
|
+
static async createNodeLoader(e) {
|
|
1456
|
+
const { NodeResourceLoader: t } = await Promise.resolve().then(() => te);
|
|
1457
|
+
return new t(e?.baseDir);
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Create a resource loader with automatic fallback
|
|
1461
|
+
* If the preferred loader is not available, falls back to the available loader
|
|
1462
|
+
* @param preferredEnvironment Preferred environment
|
|
1463
|
+
* @param options Optional factory configuration
|
|
1464
|
+
* @returns A resource loader instance
|
|
1465
|
+
*/
|
|
1466
|
+
static async createWithFallback(e, t) {
|
|
1467
|
+
try {
|
|
1468
|
+
return t = { ...t, forceEnvironment: e }, await this.create(t);
|
|
1469
|
+
} catch {
|
|
1470
|
+
return await this.create({ ...t, forceEnvironment: void 0 });
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
async function re(c) {
|
|
1475
|
+
return await N.create(c);
|
|
1476
|
+
}
|
|
1477
|
+
const se = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1478
|
+
__proto__: null,
|
|
1479
|
+
Environment: I,
|
|
1480
|
+
ResourceLoaderFactory: N,
|
|
1481
|
+
createResourceLoader: re
|
|
1482
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
1483
|
+
class ie {
|
|
1484
|
+
constructor(e = !0) {
|
|
1485
|
+
this.cache = /* @__PURE__ */ new Map(), this.enabled = e;
|
|
1486
|
+
}
|
|
1487
|
+
get(e) {
|
|
1488
|
+
if (this.enabled)
|
|
1489
|
+
return this.cache.get(e);
|
|
1490
|
+
}
|
|
1491
|
+
set(e, t) {
|
|
1492
|
+
this.enabled && this.cache.set(e, t);
|
|
1493
|
+
}
|
|
1494
|
+
has(e) {
|
|
1495
|
+
return this.enabled ? this.cache.has(e) : !1;
|
|
1496
|
+
}
|
|
1497
|
+
clear() {
|
|
1498
|
+
this.cache.clear();
|
|
1499
|
+
}
|
|
1500
|
+
size() {
|
|
1501
|
+
return this.cache.size;
|
|
1502
|
+
}
|
|
1503
|
+
enable() {
|
|
1504
|
+
this.enabled = !0;
|
|
1505
|
+
}
|
|
1506
|
+
disable() {
|
|
1507
|
+
this.enabled = !1;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
class oe {
|
|
1511
|
+
/**
|
|
1512
|
+
* Create a new resource loading pipeline
|
|
1513
|
+
* @param loader Resource loader instance
|
|
1514
|
+
* @param options Pipeline configuration options
|
|
1515
|
+
*/
|
|
1516
|
+
constructor(e, t) {
|
|
1517
|
+
this.loader = e, this.concurrency = t?.concurrency ?? 10, this.cache = new ie(t?.cache ?? !0);
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Load a single resource with caching support
|
|
1521
|
+
* @param path Resource path or URL
|
|
1522
|
+
* @param options Optional loading options
|
|
1523
|
+
* @returns Promise resolving to the resource content
|
|
1524
|
+
*/
|
|
1525
|
+
async load(e, t) {
|
|
1526
|
+
const r = this.cache.get(e);
|
|
1527
|
+
if (r !== void 0)
|
|
1528
|
+
return r;
|
|
1529
|
+
const s = await this.loader.load(e, t);
|
|
1530
|
+
return this.cache.set(e, s), s;
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Load multiple resources with concurrency control
|
|
1534
|
+
* @param paths Array of resource paths
|
|
1535
|
+
* @param options Optional loading options
|
|
1536
|
+
* @returns Promise resolving to batch load result
|
|
1537
|
+
*/
|
|
1538
|
+
async loadBatch(e, t) {
|
|
1539
|
+
const r = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map();
|
|
1540
|
+
for (let i = 0; i < e.length; i += this.concurrency) {
|
|
1541
|
+
const o = e.slice(i, i + this.concurrency), n = await this.loader.loadMultiple(o, t);
|
|
1542
|
+
for (const a of n)
|
|
1543
|
+
a.success ? (r.set(a.path, a.data), this.cache.set(a.path, a.data)) : s.set(a.path, a.error || "Unknown error");
|
|
1544
|
+
}
|
|
1545
|
+
return {
|
|
1546
|
+
succeeded: r,
|
|
1547
|
+
failed: s,
|
|
1548
|
+
total: e.length,
|
|
1549
|
+
successCount: r.size,
|
|
1550
|
+
failureCount: s.size
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Load a shader from separate vertex and fragment files
|
|
1555
|
+
* @param vertexPath Path to vertex shader file
|
|
1556
|
+
* @param fragmentPath Path to fragment shader file
|
|
1557
|
+
* @param options Optional loading options
|
|
1558
|
+
* @returns Promise resolving to shader source code
|
|
1559
|
+
*/
|
|
1560
|
+
async loadShader(e, t, r) {
|
|
1561
|
+
const [s, i] = await Promise.all([
|
|
1562
|
+
this.load(e, r),
|
|
1563
|
+
this.load(t, r)
|
|
1564
|
+
]);
|
|
1565
|
+
return { vertex: s, fragment: i };
|
|
1566
|
+
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Load multiple shaders
|
|
1569
|
+
* @param shaders Array of shader definitions
|
|
1570
|
+
* @param options Optional loading options
|
|
1571
|
+
* @returns Promise resolving to array of named shader sources
|
|
1572
|
+
*/
|
|
1573
|
+
async loadShaders(e, t) {
|
|
1574
|
+
return await Promise.all(
|
|
1575
|
+
e.map(async (s) => {
|
|
1576
|
+
const i = await this.loadShader(
|
|
1577
|
+
s.vertex,
|
|
1578
|
+
s.fragment,
|
|
1579
|
+
t
|
|
1580
|
+
);
|
|
1581
|
+
return {
|
|
1582
|
+
name: s.name,
|
|
1583
|
+
...i
|
|
1584
|
+
};
|
|
1585
|
+
})
|
|
1586
|
+
);
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Load resources from a manifest file
|
|
1590
|
+
* @param manifestPath Path to JSON manifest file
|
|
1591
|
+
* @param options Optional loading options
|
|
1592
|
+
* @returns Promise resolving to batch load result
|
|
1593
|
+
*/
|
|
1594
|
+
async loadFromManifest(e, t) {
|
|
1595
|
+
const r = await this.load(e, t), s = JSON.parse(r);
|
|
1596
|
+
return this.loadBatch(s.resources, t);
|
|
1597
|
+
}
|
|
1598
|
+
/**
|
|
1599
|
+
* Preload resources for faster access later
|
|
1600
|
+
* @param paths Array of resource paths to preload
|
|
1601
|
+
* @param options Optional loading options
|
|
1602
|
+
* @returns Promise resolving when all resources are loaded
|
|
1603
|
+
*/
|
|
1604
|
+
async preload(e, t) {
|
|
1605
|
+
await this.loadBatch(e, t);
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* Check if a resource is cached
|
|
1609
|
+
* @param path Resource path
|
|
1610
|
+
* @returns true if the resource is in the cache
|
|
1611
|
+
*/
|
|
1612
|
+
isCached(e) {
|
|
1613
|
+
return this.cache.has(e);
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Get a resource from cache without loading
|
|
1617
|
+
* @param path Resource path
|
|
1618
|
+
* @returns Cached content or undefined if not cached
|
|
1619
|
+
*/
|
|
1620
|
+
getCached(e) {
|
|
1621
|
+
return this.cache.get(e);
|
|
1622
|
+
}
|
|
1623
|
+
/**
|
|
1624
|
+
* Clear the resource cache
|
|
1625
|
+
*/
|
|
1626
|
+
clearCache() {
|
|
1627
|
+
this.cache.clear();
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Get cache statistics
|
|
1631
|
+
* @returns Number of cached resources
|
|
1632
|
+
*/
|
|
1633
|
+
getCacheSize() {
|
|
1634
|
+
return this.cache.size();
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Enable caching
|
|
1638
|
+
*/
|
|
1639
|
+
enableCache() {
|
|
1640
|
+
this.cache.enable();
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Disable caching
|
|
1644
|
+
*/
|
|
1645
|
+
disableCache() {
|
|
1646
|
+
this.cache.disable();
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Set the maximum concurrency for batch operations
|
|
1650
|
+
* @param concurrency Maximum concurrent loads
|
|
1651
|
+
*/
|
|
1652
|
+
setConcurrency(e) {
|
|
1653
|
+
this.concurrency = Math.max(1, e);
|
|
1654
|
+
}
|
|
1655
|
+
/**
|
|
1656
|
+
* Get the underlying resource loader
|
|
1657
|
+
* @returns The resource loader instance
|
|
1658
|
+
*/
|
|
1659
|
+
getLoader() {
|
|
1660
|
+
return this.loader;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
async function ne(c) {
|
|
1664
|
+
const { ResourceLoaderFactory: e } = await Promise.resolve().then(() => se), t = await e.create({
|
|
1665
|
+
baseUrl: c?.baseUrl,
|
|
1666
|
+
baseDir: c?.baseDir,
|
|
1667
|
+
timeout: c?.timeout
|
|
1668
|
+
});
|
|
1669
|
+
return new oe(t, c);
|
|
1670
|
+
}
|
|
1671
|
+
const L = {
|
|
1672
|
+
width: 800,
|
|
1673
|
+
height: 600
|
|
1674
|
+
}, P = {
|
|
1675
|
+
quad: {
|
|
1676
|
+
vertices: new Float32Array([
|
|
1677
|
+
// Position TexCoord
|
|
1678
|
+
-0.5,
|
|
1679
|
+
-0.5,
|
|
1680
|
+
0,
|
|
1681
|
+
0,
|
|
1682
|
+
0,
|
|
1683
|
+
// Bottom-left
|
|
1684
|
+
0.5,
|
|
1685
|
+
-0.5,
|
|
1686
|
+
0,
|
|
1687
|
+
1,
|
|
1688
|
+
0,
|
|
1689
|
+
// Bottom-right
|
|
1690
|
+
0.5,
|
|
1691
|
+
0.5,
|
|
1692
|
+
0,
|
|
1693
|
+
1,
|
|
1694
|
+
1,
|
|
1695
|
+
// Top-right
|
|
1696
|
+
0.5,
|
|
1697
|
+
0.5,
|
|
1698
|
+
0,
|
|
1699
|
+
1,
|
|
1700
|
+
1,
|
|
1701
|
+
// Top-right
|
|
1702
|
+
-0.5,
|
|
1703
|
+
0.5,
|
|
1704
|
+
0,
|
|
1705
|
+
0,
|
|
1706
|
+
1,
|
|
1707
|
+
// Top-left
|
|
1708
|
+
-0.5,
|
|
1709
|
+
-0.5,
|
|
1710
|
+
0,
|
|
1711
|
+
0,
|
|
1712
|
+
0
|
|
1713
|
+
// Bottom-left
|
|
1714
|
+
]),
|
|
1715
|
+
stride: 20
|
|
1716
|
+
// 5 floats per vertex × 4 bytes
|
|
1717
|
+
}
|
|
1718
|
+
}, S = {
|
|
1719
|
+
size: 256
|
|
1720
|
+
}, M = {
|
|
1721
|
+
shaders: [
|
|
1722
|
+
{
|
|
1723
|
+
name: "basic",
|
|
1724
|
+
vertex: "resources/shaders/basic.vert",
|
|
1725
|
+
fragment: "resources/shaders/basic.frag"
|
|
1726
|
+
},
|
|
1727
|
+
{
|
|
1728
|
+
name: "glow",
|
|
1729
|
+
vertex: "resources/shaders/glow.vert",
|
|
1730
|
+
fragment: "resources/shaders/glow.frag"
|
|
1731
|
+
}
|
|
1732
|
+
],
|
|
1733
|
+
// Additional resources to demonstrate batch loading
|
|
1734
|
+
resources: [
|
|
1735
|
+
"resources/shaders/basic.vert",
|
|
1736
|
+
"resources/shaders/basic.frag",
|
|
1737
|
+
"resources/shaders/glow.vert",
|
|
1738
|
+
"resources/shaders/glow.frag"
|
|
1739
|
+
]
|
|
1740
|
+
};
|
|
1741
|
+
async function ae() {
|
|
1742
|
+
console.log("🩸 Bloody Engine - Resource Loader Demo"), console.log(`==========================================
|
|
1743
|
+
`);
|
|
1744
|
+
const c = N.detectEnvironment();
|
|
1745
|
+
if (console.log(`✓ Environment detected: ${c}`), c !== I.BROWSER) {
|
|
1746
|
+
console.warn("⚠ This demo is designed for browser environment");
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
console.log(`
|
|
1750
|
+
1. Creating Resource Pipeline...`);
|
|
1751
|
+
const e = await ne({
|
|
1752
|
+
concurrency: 5,
|
|
1753
|
+
cache: !0,
|
|
1754
|
+
timeout: 1e4,
|
|
1755
|
+
baseUrl: window.location.origin
|
|
1756
|
+
});
|
|
1757
|
+
console.log("✓ Resource pipeline created"), console.log(" - Concurrency: 5"), console.log(" - Caching: enabled"), console.log(`
|
|
1758
|
+
2. Batch Loading Resources...`), console.log(`Loading ${M.resources.length} resources...`);
|
|
1759
|
+
const t = await e.loadBatch(M.resources);
|
|
1760
|
+
if (console.log("✓ Batch loading complete"), console.log(` - Succeeded: ${t.successCount}`), console.log(` - Failed: ${t.failureCount}`), t.failureCount > 0) {
|
|
1761
|
+
console.log(`
|
|
1762
|
+
❌ Failed resources:`);
|
|
1763
|
+
for (const [g, p] of t.failed)
|
|
1764
|
+
console.log(` - ${g}: ${p}`);
|
|
1765
|
+
console.log(`
|
|
1766
|
+
⚠️ Falling back to inline shaders...`);
|
|
1767
|
+
}
|
|
1768
|
+
console.log(`
|
|
1769
|
+
3. Loading Shaders...`);
|
|
1770
|
+
const r = await e.loadShaders(M.shaders);
|
|
1771
|
+
console.log(`✓ Loaded ${r.length} shaders:`);
|
|
1772
|
+
for (const g of r)
|
|
1773
|
+
console.log(` - ${g.name}:`), console.log(` Vertex: ${g.vertex.length} chars`), console.log(` Fragment: ${g.fragment.length} chars`);
|
|
1774
|
+
console.log(`
|
|
1775
|
+
4. Testing Cache...`);
|
|
1776
|
+
const s = e.getCacheSize();
|
|
1777
|
+
console.log(`✓ Cache contains ${s} resources`);
|
|
1778
|
+
for (const g of M.shaders) {
|
|
1779
|
+
const p = e.isCached(g.vertex), $ = e.isCached(g.fragment);
|
|
1780
|
+
console.log(` - ${g.name}:`), console.log(` Vertex cached: ${p}`), console.log(` Fragment cached: ${$}`);
|
|
1781
|
+
}
|
|
1782
|
+
console.log(`
|
|
1783
|
+
5. Initializing Graphics Device...`);
|
|
1784
|
+
const i = new W(L.width, L.height), o = i.getGLContext();
|
|
1785
|
+
console.log("✓ Graphics device initialized"), console.log(` - Resolution: ${L.width}x${L.height}`), console.log(`
|
|
1786
|
+
6. Creating Shader from Loaded Source...`);
|
|
1787
|
+
let n = r.find((g) => g.name === "glow");
|
|
1788
|
+
(!n || !n.vertex || !n.fragment) && (console.warn("⚠️ Glow shader not loaded or empty, using inline fallback"), n = {
|
|
1789
|
+
name: "glow",
|
|
1790
|
+
vertex: `attribute vec3 aPosition;
|
|
1791
|
+
attribute vec2 aTexCoord;
|
|
1792
|
+
|
|
1793
|
+
varying vec2 vTexCoord;
|
|
1794
|
+
varying float vDistance;
|
|
1795
|
+
|
|
1796
|
+
uniform mat4 uMatrix;
|
|
1797
|
+
|
|
1798
|
+
void main() {
|
|
1799
|
+
gl_Position = uMatrix * vec4(aPosition, 1.0);
|
|
1800
|
+
vTexCoord = aTexCoord;
|
|
1801
|
+
vDistance = length(aTexCoord - vec2(0.5, 0.5));
|
|
1802
|
+
}`,
|
|
1803
|
+
fragment: `precision mediump float;
|
|
1804
|
+
|
|
1805
|
+
varying vec2 vTexCoord;
|
|
1806
|
+
varying float vDistance;
|
|
1807
|
+
uniform sampler2D uTexture;
|
|
1808
|
+
uniform vec3 uColor;
|
|
1809
|
+
uniform float uGlowIntensity;
|
|
1810
|
+
|
|
1811
|
+
void main() {
|
|
1812
|
+
vec4 texColor = texture2D(uTexture, vTexCoord);
|
|
1813
|
+
// Better glow falloff - keeps minimum brightness
|
|
1814
|
+
float glow = 1.0 - (vDistance * 0.7);
|
|
1815
|
+
glow = max(0.5, glow);
|
|
1816
|
+
vec3 glowColor = texColor.rgb * uColor * glow * uGlowIntensity;
|
|
1817
|
+
gl_FragColor = vec4(glowColor, texColor.a);
|
|
1818
|
+
}`
|
|
1819
|
+
});
|
|
1820
|
+
const a = i.createShader(n.vertex, n.fragment);
|
|
1821
|
+
console.log("✓ Shader compiled from loaded source code"), console.log(" - Vertex shader: compiled"), console.log(" - Fragment shader: compiled"), console.log(" - Program: linked"), console.log(`
|
|
1822
|
+
7. Creating Texture...`);
|
|
1823
|
+
const h = B.createGradient(
|
|
1824
|
+
o,
|
|
1825
|
+
S.size,
|
|
1826
|
+
S.size
|
|
1827
|
+
);
|
|
1828
|
+
console.log("✓ Gradient texture created"), console.log(` - Size: ${S.size}x${S.size}`), console.log(`
|
|
1829
|
+
8. Creating Geometry Buffers...`);
|
|
1830
|
+
const { VertexBuffer: x } = await Promise.resolve().then(() => K), l = new x(
|
|
1831
|
+
o,
|
|
1832
|
+
P.quad.vertices,
|
|
1833
|
+
P.quad.stride
|
|
1834
|
+
);
|
|
1835
|
+
console.log("✓ Quad buffer created"), console.log(` - Vertices: ${l.getVertexCount()}`), console.log(`
|
|
1836
|
+
9. Setting up Rendering...`), a.use();
|
|
1837
|
+
const d = a.getAttributeLocation("aPosition"), f = a.getAttributeLocation("aTexCoord"), y = a.getUniformLocation("uTexture"), m = a.getUniformLocation("uMatrix"), v = a.getUniformLocation("uColor"), E = a.getUniformLocation("uGlowIntensity");
|
|
1838
|
+
l.bind(), o.enableVertexAttribArray(d), o.vertexAttribPointer(d, 3, o.FLOAT, !1, P.quad.stride, 0), o.enableVertexAttribArray(f), o.vertexAttribPointer(
|
|
1839
|
+
f,
|
|
1840
|
+
2,
|
|
1841
|
+
o.FLOAT,
|
|
1842
|
+
!1,
|
|
1843
|
+
P.quad.stride,
|
|
1844
|
+
12
|
|
1845
|
+
), console.log("✓ Vertex attributes configured"), h.bind(0), o.uniform1i(y, 0), console.log("✓ Texture bound to unit 0");
|
|
1846
|
+
const u = i.getRenderingContext().canvas;
|
|
1847
|
+
u && (u.style.display = "block", u.style.margin = "0 auto", u.style.border = "2px solid #333", u.style.backgroundColor = "#1a1a1a"), document.body.style.margin = "0", document.body.style.padding = "20px", document.body.style.backgroundColor = "#0a0a0a", document.body.style.fontFamily = "monospace", document.body.style.color = "#aaa";
|
|
1848
|
+
const w = document.createElement("h1");
|
|
1849
|
+
w.textContent = "🩸 Resource Loader Demo", w.style.textAlign = "center", w.style.color = "#fff", u && u.parentNode ? u.parentNode.insertBefore(w, u) : document.body.insertBefore(w, document.body.firstChild);
|
|
1850
|
+
const b = document.createElement("div");
|
|
1851
|
+
b.style.textAlign = "center", b.style.marginTop = "10px", b.style.fontSize = "12px", b.innerHTML = `
|
|
1852
|
+
<div>Environment: <strong>${c}</strong></div>
|
|
1853
|
+
<div>Shaders loaded: <strong>${r.length}</strong></div>
|
|
1854
|
+
<div>Cached resources: <strong>${s}</strong></div>
|
|
1855
|
+
`, document.body.appendChild(b);
|
|
1856
|
+
let _ = 0;
|
|
1857
|
+
const D = Date.now();
|
|
1858
|
+
function U() {
|
|
1859
|
+
const g = Date.now(), p = (g - D) / 1e3;
|
|
1860
|
+
i.clear({ r: 0.1, g: 0.1, b: 0.1, a: 1 });
|
|
1861
|
+
const $ = [
|
|
1862
|
+
{ x: -0.3, y: 0.3, color: [1, 0.2, 0.2], glow: 1.5 },
|
|
1863
|
+
{ x: 0.3, y: 0.3, color: [0.2, 1, 0.2], glow: 1.8 },
|
|
1864
|
+
{ x: -0.3, y: -0.3, color: [0.2, 0.5, 1], glow: 2 },
|
|
1865
|
+
{ x: 0.3, y: -0.3, color: [1, 1, 0.2], glow: 1.6 }
|
|
1866
|
+
];
|
|
1867
|
+
for (const R of $) {
|
|
1868
|
+
const z = ce();
|
|
1869
|
+
if (he(z, R.x, R.y, 0), le(z, 0.4, 0.4, 1), m && o.uniformMatrix4fv(m, !1, z), v && o.uniform3f(
|
|
1870
|
+
v,
|
|
1871
|
+
R.color[0],
|
|
1872
|
+
R.color[1],
|
|
1873
|
+
R.color[2]
|
|
1874
|
+
), E) {
|
|
1875
|
+
const q = R.glow + Math.sin(p * 2) * 0.3;
|
|
1876
|
+
o.uniform1f(E, q);
|
|
1877
|
+
}
|
|
1878
|
+
o.drawArrays(o.TRIANGLES, 0, l.getVertexCount());
|
|
1879
|
+
}
|
|
1880
|
+
i.present(), _++;
|
|
1881
|
+
const V = (g - D) / 1e3, O = _ / V;
|
|
1882
|
+
b.innerHTML = `
|
|
1883
|
+
<div>FPS: <strong>${O.toFixed(1)}</strong> | Frame: <strong>${_}</strong> | Elapsed: <strong>${V.toFixed(2)}s</strong></div>
|
|
1884
|
+
<div>Environment: <strong>${c}</strong> | Shaders loaded: <strong>${r.length}</strong> | Cached: <strong>${s}</strong></div>
|
|
1885
|
+
`, requestAnimationFrame(U);
|
|
1886
|
+
}
|
|
1887
|
+
console.log(`
|
|
1888
|
+
✓ Demo started! Rendering animation...`), U();
|
|
1889
|
+
}
|
|
1890
|
+
function ce() {
|
|
1891
|
+
return new Float32Array([
|
|
1892
|
+
1,
|
|
1893
|
+
0,
|
|
1894
|
+
0,
|
|
1895
|
+
0,
|
|
1896
|
+
0,
|
|
1897
|
+
1,
|
|
1898
|
+
0,
|
|
1899
|
+
0,
|
|
1900
|
+
0,
|
|
1901
|
+
0,
|
|
1902
|
+
1,
|
|
1903
|
+
0,
|
|
1904
|
+
0,
|
|
1905
|
+
0,
|
|
1906
|
+
0,
|
|
1907
|
+
1
|
|
1908
|
+
]);
|
|
1909
|
+
}
|
|
1910
|
+
function he(c, e, t, r) {
|
|
1911
|
+
c[12] += e, c[13] += t, c[14] += r;
|
|
1912
|
+
}
|
|
1913
|
+
function le(c, e, t, r) {
|
|
1914
|
+
c[0] *= e, c[5] *= t, c[10] *= r;
|
|
1915
|
+
}
|
|
1916
|
+
typeof window < "u" && ae().catch((c) => {
|
|
1917
|
+
console.error("❌ Demo failed:", c);
|
|
1918
|
+
});
|
|
1919
|
+
export {
|
|
1920
|
+
de as BatchRenderer,
|
|
1921
|
+
Y as BrowserRenderingContext,
|
|
1922
|
+
J as BrowserResourceLoader,
|
|
1923
|
+
ge as Camera,
|
|
1924
|
+
I as Environment,
|
|
1925
|
+
W as GraphicsDevice,
|
|
1926
|
+
j as IndexBuffer,
|
|
1927
|
+
F as Matrix4,
|
|
1928
|
+
G as NodeRenderingContext,
|
|
1929
|
+
ee as NodeResourceLoader,
|
|
1930
|
+
k as RenderingContextFactory,
|
|
1931
|
+
N as ResourceLoaderFactory,
|
|
1932
|
+
oe as ResourcePipeline,
|
|
1933
|
+
H as Shader,
|
|
1934
|
+
fe as SpriteBatchRenderer,
|
|
1935
|
+
B as Texture,
|
|
1936
|
+
Q as VertexBuffer,
|
|
1937
|
+
re as createResourceLoader,
|
|
1938
|
+
ne as createResourcePipeline,
|
|
1939
|
+
ae as runBrowserResourceLoaderDemo
|
|
1940
|
+
};
|