bloody-engine 1.0.1 → 1.0.2
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/dist/node/index.js +2364 -0
- package/package.json +1 -1
- package/dist/node/batch-renderer-JqZ4TYcL.js +0 -308
- package/dist/node/browser-resource-loader-D51BD3k_.js +0 -146
- package/dist/node/camera-A8EGrk7U.js +0 -271
- package/dist/node/index-node.js +0 -2117
- package/dist/node/node-resource-loader-MzkD-IGo.js +0 -166
- package/dist/node/resource-loader-factory-DQ-PAVcN.js +0 -93
- package/dist/node/resource-pipeline-Dac9qRso.js +0 -211
package/package.json
CHANGED
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
class SpriteBatchRenderer {
|
|
2
|
-
/**
|
|
3
|
-
* Create a new sprite batch renderer (V2)
|
|
4
|
-
* @param gl WebGL rendering context
|
|
5
|
-
* @param shader Shader program to use (should be SHADERS_V2)
|
|
6
|
-
* @param maxQuads Maximum number of quads to batch (default 1000)
|
|
7
|
-
*/
|
|
8
|
-
constructor(gl, shader, maxQuads = 1e3) {
|
|
9
|
-
this.vertexBuffer = null;
|
|
10
|
-
this.quads = [];
|
|
11
|
-
this.isDirty = false;
|
|
12
|
-
this.verticesPerQuad = 6;
|
|
13
|
-
this.floatsPerVertex = 10;
|
|
14
|
-
this.texture = null;
|
|
15
|
-
this.depthTestEnabled = true;
|
|
16
|
-
this.gl = gl;
|
|
17
|
-
this.shader = shader;
|
|
18
|
-
this.maxQuads = maxQuads;
|
|
19
|
-
const totalFloats = maxQuads * this.verticesPerQuad * this.floatsPerVertex;
|
|
20
|
-
this.vertexData = new Float32Array(totalFloats);
|
|
21
|
-
const buf = gl.createBuffer();
|
|
22
|
-
if (!buf) {
|
|
23
|
-
throw new Error("Failed to create vertex buffer");
|
|
24
|
-
}
|
|
25
|
-
this.vertexBuffer = buf;
|
|
26
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
27
|
-
gl.bufferData(gl.ARRAY_BUFFER, this.vertexData.byteLength, gl.DYNAMIC_DRAW);
|
|
28
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Set the texture for batch rendering
|
|
32
|
-
* @param texture The texture to use when rendering
|
|
33
|
-
*/
|
|
34
|
-
setTexture(texture) {
|
|
35
|
-
this.texture = texture;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Add a sprite quad to the batch
|
|
39
|
-
* @param quad Sprite quad instance to add
|
|
40
|
-
*/
|
|
41
|
-
addQuad(quad) {
|
|
42
|
-
if (this.quads.length >= this.maxQuads) {
|
|
43
|
-
console.warn(`Sprite batch renderer at max capacity (${this.maxQuads})`);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
this.quads.push(quad);
|
|
47
|
-
this.isDirty = true;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Clear all quads from the batch
|
|
51
|
-
*/
|
|
52
|
-
clear() {
|
|
53
|
-
this.quads = [];
|
|
54
|
-
this.isDirty = true;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Get number of quads currently in batch
|
|
58
|
-
*/
|
|
59
|
-
getQuadCount() {
|
|
60
|
-
return this.quads.length;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Update the batch - rebuilds vertex buffer if quads changed
|
|
64
|
-
*/
|
|
65
|
-
update() {
|
|
66
|
-
if (!this.isDirty || this.quads.length === 0) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
let vertexIndex = 0;
|
|
70
|
-
for (const quad of this.quads) {
|
|
71
|
-
const {
|
|
72
|
-
x,
|
|
73
|
-
y,
|
|
74
|
-
z = 0,
|
|
75
|
-
width,
|
|
76
|
-
height,
|
|
77
|
-
rotation,
|
|
78
|
-
color = { r: 1, g: 1, b: 1, a: 1 },
|
|
79
|
-
uvRect = { uMin: 0, vMin: 0, uMax: 1, vMax: 1 },
|
|
80
|
-
texIndex = 0
|
|
81
|
-
} = quad;
|
|
82
|
-
const vertices = this.generateQuadVertices({
|
|
83
|
-
x,
|
|
84
|
-
y,
|
|
85
|
-
z,
|
|
86
|
-
width,
|
|
87
|
-
height,
|
|
88
|
-
rotation,
|
|
89
|
-
color,
|
|
90
|
-
uvRect,
|
|
91
|
-
texIndex
|
|
92
|
-
});
|
|
93
|
-
for (const vertex of vertices) {
|
|
94
|
-
this.vertexData[vertexIndex++] = vertex.x;
|
|
95
|
-
this.vertexData[vertexIndex++] = vertex.y;
|
|
96
|
-
this.vertexData[vertexIndex++] = vertex.z;
|
|
97
|
-
this.vertexData[vertexIndex++] = vertex.u;
|
|
98
|
-
this.vertexData[vertexIndex++] = vertex.v;
|
|
99
|
-
this.vertexData[vertexIndex++] = vertex.r;
|
|
100
|
-
this.vertexData[vertexIndex++] = vertex.g;
|
|
101
|
-
this.vertexData[vertexIndex++] = vertex.b;
|
|
102
|
-
this.vertexData[vertexIndex++] = vertex.a;
|
|
103
|
-
this.vertexData[vertexIndex++] = vertex.texIndex;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
if (this.vertexBuffer) {
|
|
107
|
-
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
108
|
-
this.gl.bufferSubData(
|
|
109
|
-
this.gl.ARRAY_BUFFER,
|
|
110
|
-
0,
|
|
111
|
-
this.vertexData.subarray(0, vertexIndex)
|
|
112
|
-
);
|
|
113
|
-
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
|
|
114
|
-
}
|
|
115
|
-
this.isDirty = false;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Set whether depth testing is enabled
|
|
119
|
-
* When enabled, sprites with lower Z values appear behind sprites with higher Z values
|
|
120
|
-
* @param enabled Whether to enable depth testing (default true)
|
|
121
|
-
*/
|
|
122
|
-
setDepthTestEnabled(enabled) {
|
|
123
|
-
this.depthTestEnabled = enabled;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Render the batch
|
|
127
|
-
* @param camera Optional camera for view transform (defaults to identity matrix)
|
|
128
|
-
*/
|
|
129
|
-
render(camera) {
|
|
130
|
-
if (this.quads.length === 0) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
this.update();
|
|
134
|
-
this.shader.use();
|
|
135
|
-
if (this.depthTestEnabled) {
|
|
136
|
-
this.gl.enable(this.gl.DEPTH_TEST);
|
|
137
|
-
this.gl.depthFunc(this.gl.LEQUAL);
|
|
138
|
-
} else {
|
|
139
|
-
this.gl.disable(this.gl.DEPTH_TEST);
|
|
140
|
-
}
|
|
141
|
-
if (this.vertexBuffer) {
|
|
142
|
-
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
143
|
-
const posAttr = this.shader.getAttributeLocation("aPosition");
|
|
144
|
-
const texCoordAttr = this.shader.getAttributeLocation("aTexCoord");
|
|
145
|
-
const colorAttr = this.shader.getAttributeLocation("aColor");
|
|
146
|
-
const texIndexAttr = this.shader.getAttributeLocation("aTexIndex");
|
|
147
|
-
const stride = this.floatsPerVertex * 4;
|
|
148
|
-
if (posAttr !== -1) {
|
|
149
|
-
this.gl.enableVertexAttribArray(posAttr);
|
|
150
|
-
this.gl.vertexAttribPointer(
|
|
151
|
-
posAttr,
|
|
152
|
-
3,
|
|
153
|
-
// 3 floats (x, y, z)
|
|
154
|
-
this.gl.FLOAT,
|
|
155
|
-
false,
|
|
156
|
-
stride,
|
|
157
|
-
0
|
|
158
|
-
// offset
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
if (texCoordAttr !== -1) {
|
|
162
|
-
this.gl.enableVertexAttribArray(texCoordAttr);
|
|
163
|
-
this.gl.vertexAttribPointer(
|
|
164
|
-
texCoordAttr,
|
|
165
|
-
2,
|
|
166
|
-
// 2 floats (u, v)
|
|
167
|
-
this.gl.FLOAT,
|
|
168
|
-
false,
|
|
169
|
-
stride,
|
|
170
|
-
3 * 4
|
|
171
|
-
// offset after position
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
if (colorAttr !== -1) {
|
|
175
|
-
this.gl.enableVertexAttribArray(colorAttr);
|
|
176
|
-
this.gl.vertexAttribPointer(
|
|
177
|
-
colorAttr,
|
|
178
|
-
4,
|
|
179
|
-
// 4 floats (r, g, b, a)
|
|
180
|
-
this.gl.FLOAT,
|
|
181
|
-
false,
|
|
182
|
-
stride,
|
|
183
|
-
5 * 4
|
|
184
|
-
// offset after texCoord
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
if (texIndexAttr !== -1) {
|
|
188
|
-
this.gl.enableVertexAttribArray(texIndexAttr);
|
|
189
|
-
this.gl.vertexAttribPointer(
|
|
190
|
-
texIndexAttr,
|
|
191
|
-
1,
|
|
192
|
-
// 1 float (texIndex)
|
|
193
|
-
this.gl.FLOAT,
|
|
194
|
-
false,
|
|
195
|
-
stride,
|
|
196
|
-
9 * 4
|
|
197
|
-
// offset after color
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
if (this.texture) {
|
|
201
|
-
this.texture.bind(0);
|
|
202
|
-
const textureUniform = this.shader.getUniformLocation("uTexture");
|
|
203
|
-
if (textureUniform !== null) {
|
|
204
|
-
this.gl.uniform1i(textureUniform, 0);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
const matrixUniform = this.shader.getUniformLocation("uMatrix");
|
|
208
|
-
if (matrixUniform !== null) {
|
|
209
|
-
const matrix = camera ? camera.getViewMatrix() : new Float32Array([
|
|
210
|
-
1,
|
|
211
|
-
0,
|
|
212
|
-
0,
|
|
213
|
-
0,
|
|
214
|
-
0,
|
|
215
|
-
1,
|
|
216
|
-
0,
|
|
217
|
-
0,
|
|
218
|
-
0,
|
|
219
|
-
0,
|
|
220
|
-
1,
|
|
221
|
-
0,
|
|
222
|
-
0,
|
|
223
|
-
0,
|
|
224
|
-
0,
|
|
225
|
-
1
|
|
226
|
-
]);
|
|
227
|
-
this.gl.uniformMatrix4fv(matrixUniform, false, matrix);
|
|
228
|
-
}
|
|
229
|
-
const vertexCount = this.quads.length * this.verticesPerQuad;
|
|
230
|
-
this.gl.drawArrays(this.gl.TRIANGLES, 0, vertexCount);
|
|
231
|
-
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Generate vertices for a quad with rotation applied
|
|
236
|
-
* Returns 6 vertices (2 triangles)
|
|
237
|
-
* @private
|
|
238
|
-
*/
|
|
239
|
-
generateQuadVertices(instance) {
|
|
240
|
-
const { x, y, z, width, height, rotation, color, uvRect, texIndex } = instance;
|
|
241
|
-
const halfW = width / 2;
|
|
242
|
-
const halfH = height / 2;
|
|
243
|
-
const cos = Math.cos(rotation);
|
|
244
|
-
const sin = Math.sin(rotation);
|
|
245
|
-
const rotatePoint = (px, py) => {
|
|
246
|
-
return [px * cos - py * sin, px * sin + py * cos];
|
|
247
|
-
};
|
|
248
|
-
const corners = [
|
|
249
|
-
[-halfW, -halfH],
|
|
250
|
-
// bottom-left
|
|
251
|
-
[halfW, -halfH],
|
|
252
|
-
// bottom-right
|
|
253
|
-
[halfW, halfH],
|
|
254
|
-
// top-right
|
|
255
|
-
[halfW, halfH],
|
|
256
|
-
// top-right (duplicate)
|
|
257
|
-
[-halfW, halfH],
|
|
258
|
-
// top-left
|
|
259
|
-
[-halfW, -halfH]
|
|
260
|
-
// bottom-left (duplicate)
|
|
261
|
-
];
|
|
262
|
-
const texCoords = [
|
|
263
|
-
[uvRect.uMin, uvRect.vMin],
|
|
264
|
-
// bottom-left
|
|
265
|
-
[uvRect.uMax, uvRect.vMin],
|
|
266
|
-
// bottom-right
|
|
267
|
-
[uvRect.uMax, uvRect.vMax],
|
|
268
|
-
// top-right
|
|
269
|
-
[uvRect.uMax, uvRect.vMax],
|
|
270
|
-
// top-right
|
|
271
|
-
[uvRect.uMin, uvRect.vMax],
|
|
272
|
-
// top-left
|
|
273
|
-
[uvRect.uMin, uvRect.vMin]
|
|
274
|
-
// bottom-left
|
|
275
|
-
];
|
|
276
|
-
const vertices = [];
|
|
277
|
-
for (let i = 0; i < corners.length; i++) {
|
|
278
|
-
const [localX, localY] = corners[i];
|
|
279
|
-
const [rotX, rotY] = rotatePoint(localX, localY);
|
|
280
|
-
const [u, v] = texCoords[i];
|
|
281
|
-
vertices.push({
|
|
282
|
-
x: x + rotX,
|
|
283
|
-
y: y + rotY,
|
|
284
|
-
z,
|
|
285
|
-
u,
|
|
286
|
-
v,
|
|
287
|
-
r: color.r,
|
|
288
|
-
g: color.g,
|
|
289
|
-
b: color.b,
|
|
290
|
-
a: color.a,
|
|
291
|
-
texIndex
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
return vertices;
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Dispose resources
|
|
298
|
-
*/
|
|
299
|
-
dispose() {
|
|
300
|
-
if (this.vertexBuffer) {
|
|
301
|
-
this.gl.deleteBuffer(this.vertexBuffer);
|
|
302
|
-
this.vertexBuffer = null;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
export {
|
|
307
|
-
SpriteBatchRenderer
|
|
308
|
-
};
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
class BrowserResourceLoader {
|
|
2
|
-
/**
|
|
3
|
-
* Create a new browser resource loader
|
|
4
|
-
* @param baseUrl Optional base URL for resolving relative paths (defaults to current origin)
|
|
5
|
-
* @param timeout Default timeout for requests in milliseconds (default: 10000)
|
|
6
|
-
*/
|
|
7
|
-
constructor(baseUrl = "", timeout = 1e4) {
|
|
8
|
-
this.baseUrl = baseUrl || this.getCurrentOrigin();
|
|
9
|
-
this.defaultTimeout = timeout;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Get the current origin (protocol + host + port)
|
|
13
|
-
*/
|
|
14
|
-
getCurrentOrigin() {
|
|
15
|
-
return typeof window !== "undefined" ? window.location.origin : "http://localhost";
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Resolve a relative path against the base URL
|
|
19
|
-
* @param path Relative or absolute path
|
|
20
|
-
* @returns Resolved absolute URL
|
|
21
|
-
*/
|
|
22
|
-
resolvePath(path) {
|
|
23
|
-
try {
|
|
24
|
-
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
25
|
-
return path;
|
|
26
|
-
}
|
|
27
|
-
if (path.startsWith("//")) {
|
|
28
|
-
return window.location.protocol + path;
|
|
29
|
-
}
|
|
30
|
-
if (path.startsWith("/")) {
|
|
31
|
-
return this.baseUrl + path;
|
|
32
|
-
}
|
|
33
|
-
return `${this.baseUrl}/${path}`;
|
|
34
|
-
} catch {
|
|
35
|
-
return path;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Load a single resource from a URL
|
|
40
|
-
* @param path URL or relative path to the resource
|
|
41
|
-
* @param options Optional loading configuration
|
|
42
|
-
* @returns Promise resolving to the resource content
|
|
43
|
-
*/
|
|
44
|
-
async load(path, options) {
|
|
45
|
-
const url = this.resolvePath(path);
|
|
46
|
-
try {
|
|
47
|
-
const fetchOptions = {
|
|
48
|
-
credentials: options?.credentials || "same-origin"
|
|
49
|
-
};
|
|
50
|
-
if (options?.headers) {
|
|
51
|
-
fetchOptions.headers = options.headers;
|
|
52
|
-
}
|
|
53
|
-
const controller = new AbortController();
|
|
54
|
-
const timeoutId = setTimeout(() => controller.abort(), this.defaultTimeout);
|
|
55
|
-
fetchOptions.signal = controller.signal;
|
|
56
|
-
const response = await fetch(url, fetchOptions);
|
|
57
|
-
clearTimeout(timeoutId);
|
|
58
|
-
if (!response.ok) {
|
|
59
|
-
throw new Error(
|
|
60
|
-
`HTTP ${response.status}: ${response.statusText} for URL: ${url}`
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
const text = await response.text();
|
|
64
|
-
return text;
|
|
65
|
-
} catch (error) {
|
|
66
|
-
if (error instanceof Error) {
|
|
67
|
-
if (error.name === "AbortError") {
|
|
68
|
-
throw new Error(
|
|
69
|
-
`Request timeout after ${this.defaultTimeout}ms for URL: ${url}`
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
throw new Error(`Failed to load resource from ${url}: ${error.message}`);
|
|
73
|
-
}
|
|
74
|
-
throw new Error(`Failed to load resource from ${url}: Unknown error`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Load multiple resources in parallel
|
|
79
|
-
* @param paths Array of URLs or paths
|
|
80
|
-
* @param options Optional loading configuration
|
|
81
|
-
* @returns Promise resolving to array of load results
|
|
82
|
-
*/
|
|
83
|
-
async loadMultiple(paths, options) {
|
|
84
|
-
const promises = paths.map(async (path) => {
|
|
85
|
-
try {
|
|
86
|
-
const data = await this.load(path, options);
|
|
87
|
-
return {
|
|
88
|
-
data,
|
|
89
|
-
path,
|
|
90
|
-
success: true
|
|
91
|
-
};
|
|
92
|
-
} catch (error) {
|
|
93
|
-
return {
|
|
94
|
-
data: "",
|
|
95
|
-
path,
|
|
96
|
-
success: false,
|
|
97
|
-
error: error instanceof Error ? error.message : String(error)
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
return Promise.all(promises);
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Check if the path is valid for loading in the browser
|
|
105
|
-
* @param path URL or path to check
|
|
106
|
-
* @returns true if the path can be loaded
|
|
107
|
-
*/
|
|
108
|
-
canLoad(path) {
|
|
109
|
-
const validPatterns = [
|
|
110
|
-
/^https?:\/\//i,
|
|
111
|
-
// Absolute HTTP(S) URLs
|
|
112
|
-
/^\/\//,
|
|
113
|
-
// Protocol-relative URLs
|
|
114
|
-
/^\//,
|
|
115
|
-
// Absolute paths
|
|
116
|
-
/^\.\.?\//
|
|
117
|
-
// Relative paths starting with ./ or ../
|
|
118
|
-
];
|
|
119
|
-
const hasFileExtension = /\.[a-z0-9]+$/i.test(path);
|
|
120
|
-
return validPatterns.some((pattern) => pattern.test(path)) || hasFileExtension;
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Set a new base URL for resolving relative paths
|
|
124
|
-
* @param baseUrl New base URL
|
|
125
|
-
*/
|
|
126
|
-
setBaseUrl(baseUrl) {
|
|
127
|
-
this.baseUrl = baseUrl;
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Get the current base URL
|
|
131
|
-
* @returns Current base URL
|
|
132
|
-
*/
|
|
133
|
-
getBaseUrl() {
|
|
134
|
-
return this.baseUrl;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Set the default request timeout
|
|
138
|
-
* @param timeout Timeout in milliseconds
|
|
139
|
-
*/
|
|
140
|
-
setTimeout(timeout) {
|
|
141
|
-
this.defaultTimeout = timeout;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
export {
|
|
145
|
-
BrowserResourceLoader
|
|
146
|
-
};
|