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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bloody-engine",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "A WebGL-based 2.5D graphics engine for isometric rendering",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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
- };