bloody-engine 1.0.1 → 1.0.3

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.
@@ -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
- };
@@ -1,271 +0,0 @@
1
- class Matrix4 {
2
- /**
3
- * Create an identity matrix
4
- * @returns 4x4 identity matrix in column-major order
5
- */
6
- static identity() {
7
- return new Float32Array([
8
- 1,
9
- 0,
10
- 0,
11
- 0,
12
- // column 0
13
- 0,
14
- 1,
15
- 0,
16
- 0,
17
- // column 1
18
- 0,
19
- 0,
20
- 1,
21
- 0,
22
- // column 2
23
- 0,
24
- 0,
25
- 0,
26
- 1
27
- // column 3
28
- ]);
29
- }
30
- /**
31
- * Create a translation matrix
32
- * @param x Translation along X axis
33
- * @param y Translation along Y axis
34
- * @param z Translation along Z axis (default 0)
35
- * @returns 4x4 translation matrix in column-major order
36
- */
37
- static translation(x, y, z = 0) {
38
- return new Float32Array([
39
- 1,
40
- 0,
41
- 0,
42
- 0,
43
- // column 0
44
- 0,
45
- 1,
46
- 0,
47
- 0,
48
- // column 1
49
- 0,
50
- 0,
51
- 1,
52
- 0,
53
- // column 2
54
- x,
55
- y,
56
- z,
57
- 1
58
- // column 3
59
- ]);
60
- }
61
- /**
62
- * Create a scale matrix
63
- * @param x Scale factor along X axis
64
- * @param y Scale factor along Y axis
65
- * @param z Scale factor along Z axis (default 1)
66
- * @returns 4x4 scale matrix in column-major order
67
- */
68
- static scale(x, y, z = 1) {
69
- return new Float32Array([
70
- x,
71
- 0,
72
- 0,
73
- 0,
74
- // column 0
75
- 0,
76
- y,
77
- 0,
78
- 0,
79
- // column 1
80
- 0,
81
- 0,
82
- z,
83
- 0,
84
- // column 2
85
- 0,
86
- 0,
87
- 0,
88
- 1
89
- // column 3
90
- ]);
91
- }
92
- /**
93
- * Multiply two matrices (result = a * b)
94
- * @param a First matrix (left operand)
95
- * @param b Second matrix (right operand)
96
- * @returns Result of matrix multiplication in column-major order
97
- */
98
- static multiply(a, b) {
99
- const result = new Float32Array(16);
100
- for (let col = 0; col < 4; col++) {
101
- for (let row = 0; row < 4; row++) {
102
- let sum = 0;
103
- for (let k = 0; k < 4; k++) {
104
- sum += a[k * 4 + row] * b[col * 4 + k];
105
- }
106
- result[col * 4 + row] = sum;
107
- }
108
- }
109
- return result;
110
- }
111
- /**
112
- * Create a view matrix from camera position and zoom
113
- * The view matrix transforms world coordinates to camera/eye coordinates
114
- *
115
- * View = Translation(-cameraX, -cameraY, 0) * Scale(zoom, zoom, 1)
116
- *
117
- * @param x Camera X position (translation will be negative)
118
- * @param y Camera Y position (translation will be negative)
119
- * @param zoom Camera zoom level (1.0 = no zoom, >1 = zoom in, <1 = zoom out)
120
- * @returns 4x4 view matrix in column-major order
121
- */
122
- static createViewMatrix(x, y, zoom) {
123
- const translation = Matrix4.translation(-x, -y, 0);
124
- const scale = Matrix4.scale(zoom, zoom, 1);
125
- return Matrix4.multiply(translation, scale);
126
- }
127
- }
128
- class Camera {
129
- /**
130
- * Create a new camera
131
- * @param x Initial X position (default 0)
132
- * @param y Initial Y position (default 0)
133
- * @param zoom Initial zoom level (default 1.0)
134
- */
135
- constructor(x = 0, y = 0, zoom = 1) {
136
- this._viewMatrix = null;
137
- this._viewMatrixDirty = true;
138
- this._x = x;
139
- this._y = y;
140
- this._zoom = zoom;
141
- }
142
- /**
143
- * Get the camera X position
144
- */
145
- get x() {
146
- return this._x;
147
- }
148
- /**
149
- * Set the camera X position
150
- */
151
- set x(value) {
152
- this._x = value;
153
- this._viewMatrixDirty = true;
154
- }
155
- /**
156
- * Get the camera Y position
157
- */
158
- get y() {
159
- return this._y;
160
- }
161
- /**
162
- * Set the camera Y position
163
- */
164
- set y(value) {
165
- this._y = value;
166
- this._viewMatrixDirty = true;
167
- }
168
- /**
169
- * Get the camera zoom level
170
- */
171
- get zoom() {
172
- return this._zoom;
173
- }
174
- /**
175
- * Set the camera zoom level
176
- * Values: 1.0 = no zoom, >1 = zoom in, <1 = zoom out
177
- */
178
- set zoom(value) {
179
- this._zoom = Math.max(1e-3, value);
180
- this._viewMatrixDirty = true;
181
- }
182
- /**
183
- * Set both X and Y position at once
184
- * @param x New X position
185
- * @param y New Y position
186
- */
187
- setPosition(x, y) {
188
- this._x = x;
189
- this._y = y;
190
- this._viewMatrixDirty = true;
191
- }
192
- /**
193
- * Move the camera by a relative offset
194
- * @param dx X offset to add to current position
195
- * @param dy Y offset to add to current position
196
- */
197
- move(dx, dy) {
198
- this._x += dx;
199
- this._y += dy;
200
- this._viewMatrixDirty = true;
201
- }
202
- /**
203
- * Scale the zoom by a factor
204
- * @param factor Multiplier for current zoom (e.g., 1.1 to zoom in 10%)
205
- */
206
- zoomBy(factor) {
207
- this._zoom = Math.max(1e-3, this._zoom * factor);
208
- this._viewMatrixDirty = true;
209
- }
210
- /**
211
- * Reset camera to default position and zoom
212
- */
213
- reset() {
214
- this._x = 0;
215
- this._y = 0;
216
- this._zoom = 1;
217
- this._viewMatrixDirty = true;
218
- }
219
- /**
220
- * Get the view matrix for this camera
221
- * The view matrix transforms world coordinates to camera space
222
- * Caches the result until camera properties change
223
- *
224
- * @returns 4x4 view matrix in column-major order
225
- */
226
- getViewMatrix() {
227
- if (this._viewMatrixDirty || this._viewMatrix === null) {
228
- this._viewMatrix = Matrix4.createViewMatrix(this._x, this._y, this._zoom);
229
- this._viewMatrixDirty = false;
230
- }
231
- return this._viewMatrix;
232
- }
233
- /**
234
- * Convert screen coordinates to world coordinates
235
- * Useful for mouse picking and interaction
236
- *
237
- * @param screenX Screen X coordinate (pixels)
238
- * @param screenY Screen Y coordinate (pixels)
239
- * @param viewportWidth Viewport width in pixels
240
- * @param viewportHeight Viewport height in pixels
241
- * @returns World coordinates {x, y}
242
- */
243
- screenToWorld(screenX, screenY, viewportWidth, viewportHeight) {
244
- const centeredX = screenX - viewportWidth / 2;
245
- const centeredY = screenY - viewportHeight / 2;
246
- const worldX = centeredX / this._zoom + this._x;
247
- const worldY = centeredY / this._zoom + this._y;
248
- return { x: worldX, y: worldY };
249
- }
250
- /**
251
- * Convert world coordinates to screen coordinates
252
- * Useful for UI positioning and debug rendering
253
- *
254
- * @param worldX World X coordinate
255
- * @param worldY World Y coordinate
256
- * @param viewportWidth Viewport width in pixels
257
- * @param viewportHeight Viewport height in pixels
258
- * @returns Screen coordinates {x, y} in pixels
259
- */
260
- worldToScreen(worldX, worldY, viewportWidth, viewportHeight) {
261
- const centeredX = (worldX - this._x) * this._zoom;
262
- const centeredY = (worldY - this._y) * this._zoom;
263
- const screenX = centeredX + viewportWidth / 2;
264
- const screenY = centeredY + viewportHeight / 2;
265
- return { x: screenX, y: screenY };
266
- }
267
- }
268
- export {
269
- Camera,
270
- Matrix4
271
- };