pixospritz-core 0.10.1 → 1.0.1
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/README.md +36 -286
- package/dist/bundle.js +13 -3
- package/dist/bundle.js.map +1 -1
- package/dist/style.css +1 -0
- package/package.json +43 -44
- package/src/components/WebGLView.jsx +318 -0
- package/src/css/pixos.css +372 -0
- package/src/engine/actions/animate.js +41 -0
- package/src/engine/actions/changezone.js +135 -0
- package/src/engine/actions/chat.js +109 -0
- package/src/engine/actions/dialogue.js +90 -0
- package/src/engine/actions/face.js +22 -0
- package/src/engine/actions/greeting.js +28 -0
- package/src/engine/actions/interact.js +86 -0
- package/src/engine/actions/move.js +67 -0
- package/src/engine/actions/patrol.js +109 -0
- package/src/engine/actions/prompt.js +185 -0
- package/src/engine/actions/script.js +42 -0
- package/src/engine/core/audio/AudioSystem.js +543 -0
- package/src/engine/core/cutscene/PxcPlayer.js +956 -0
- package/src/engine/core/cutscene/manager.js +243 -0
- package/src/engine/core/database/index.js +75 -0
- package/src/engine/core/debug/index.js +371 -0
- package/src/engine/core/hud/index.js +765 -0
- package/src/engine/core/index.js +540 -0
- package/src/engine/core/input/gamepad/Controller.js +71 -0
- package/src/engine/core/input/gamepad/ControllerButtons.js +231 -0
- package/src/engine/core/input/gamepad/ControllerStick.js +173 -0
- package/src/engine/core/input/gamepad/index.js +592 -0
- package/src/engine/core/input/keyboard.js +196 -0
- package/src/engine/core/input/manager.js +485 -0
- package/src/engine/core/input/mouse.js +203 -0
- package/src/engine/core/input/touch.js +175 -0
- package/src/engine/core/mode/manager.js +199 -0
- package/src/engine/core/net/manager.js +535 -0
- package/src/engine/core/queue/action.js +83 -0
- package/src/engine/core/queue/event.js +82 -0
- package/src/engine/core/queue/index.js +44 -0
- package/src/engine/core/queue/loadable.js +33 -0
- package/src/engine/core/render/CameraEffects.js +494 -0
- package/src/engine/core/render/FrustumCuller.js +417 -0
- package/src/engine/core/render/LODManager.js +285 -0
- package/src/engine/core/render/ParticleManager.js +529 -0
- package/src/engine/core/render/TextureAtlas.js +465 -0
- package/src/engine/core/render/camera.js +338 -0
- package/src/engine/core/render/light.js +197 -0
- package/src/engine/core/render/manager.js +1079 -0
- package/src/engine/core/render/shaders.js +110 -0
- package/src/engine/core/render/skybox.js +342 -0
- package/src/engine/core/resource/manager.js +133 -0
- package/src/engine/core/resource/object.js +611 -0
- package/src/engine/core/resource/texture.js +103 -0
- package/src/engine/core/resource/tileset.js +177 -0
- package/src/engine/core/scene/avatar.js +215 -0
- package/src/engine/core/scene/speech.js +138 -0
- package/src/engine/core/scene/sprite.js +702 -0
- package/src/engine/core/scene/spritz.js +189 -0
- package/src/engine/core/scene/world.js +681 -0
- package/src/engine/core/scene/zone.js +1167 -0
- package/src/engine/core/store/index.js +110 -0
- package/src/engine/dynamic/animatedSprite.js +64 -0
- package/src/engine/dynamic/animatedTile.js +98 -0
- package/src/engine/dynamic/avatar.js +110 -0
- package/src/engine/dynamic/map.js +174 -0
- package/src/engine/dynamic/sprite.js +255 -0
- package/src/engine/dynamic/spritz.js +119 -0
- package/src/engine/events/EventSystem.js +609 -0
- package/src/engine/events/camera.js +142 -0
- package/src/engine/events/chat.js +75 -0
- package/src/engine/events/menu.js +186 -0
- package/src/engine/scripting/CallbackManager.js +514 -0
- package/src/engine/scripting/PixoScriptInterpreter.js +81 -0
- package/src/engine/scripting/PixoScriptLibrary.js +704 -0
- package/src/engine/shaders/effects/index.js +450 -0
- package/src/engine/shaders/fs.js +222 -0
- package/src/engine/shaders/particles/fs.js +41 -0
- package/src/engine/shaders/particles/vs.js +61 -0
- package/src/engine/shaders/picker/fs.js +34 -0
- package/src/engine/shaders/picker/init.js +62 -0
- package/src/engine/shaders/picker/vs.js +42 -0
- package/src/engine/shaders/pxsl/README.md +250 -0
- package/src/engine/shaders/pxsl/index.js +25 -0
- package/src/engine/shaders/pxsl/library.js +608 -0
- package/src/engine/shaders/pxsl/manager.js +338 -0
- package/src/engine/shaders/pxsl/specification.js +363 -0
- package/src/engine/shaders/pxsl/transpiler.js +753 -0
- package/src/engine/shaders/skybox/cosmic/fs.js +147 -0
- package/src/engine/shaders/skybox/cosmic/vs.js +23 -0
- package/src/engine/shaders/skybox/matrix/fs.js +127 -0
- package/src/engine/shaders/skybox/matrix/vs.js +23 -0
- package/src/engine/shaders/skybox/morning/fs.js +109 -0
- package/src/engine/shaders/skybox/morning/vs.js +23 -0
- package/src/engine/shaders/skybox/neon/fs.js +119 -0
- package/src/engine/shaders/skybox/neon/vs.js +23 -0
- package/src/engine/shaders/skybox/sky/fs.js +114 -0
- package/src/engine/shaders/skybox/sky/vs.js +23 -0
- package/src/engine/shaders/skybox/sunset/fs.js +101 -0
- package/src/engine/shaders/skybox/sunset/vs.js +23 -0
- package/src/engine/shaders/transition/blur/fs.js +42 -0
- package/src/engine/shaders/transition/blur/vs.js +26 -0
- package/src/engine/shaders/transition/cross/fs.js +36 -0
- package/src/engine/shaders/transition/cross/vs.js +26 -0
- package/src/engine/shaders/transition/crossBlur/fs.js +41 -0
- package/src/engine/shaders/transition/crossBlur/vs.js +25 -0
- package/src/engine/shaders/transition/dissolve/fs.js +78 -0
- package/src/engine/shaders/transition/dissolve/vs.js +24 -0
- package/src/engine/shaders/transition/fade/fs.js +31 -0
- package/src/engine/shaders/transition/fade/vs.js +27 -0
- package/src/engine/shaders/transition/iris/fs.js +52 -0
- package/src/engine/shaders/transition/iris/vs.js +24 -0
- package/src/engine/shaders/transition/pixelate/fs.js +44 -0
- package/src/engine/shaders/transition/pixelate/vs.js +24 -0
- package/src/engine/shaders/transition/slide/fs.js +53 -0
- package/src/engine/shaders/transition/slide/vs.js +24 -0
- package/src/engine/shaders/transition/swirl/fs.js +39 -0
- package/src/engine/shaders/transition/swirl/vs.js +26 -0
- package/src/engine/shaders/transition/wipe/fs.js +50 -0
- package/src/engine/shaders/transition/wipe/vs.js +24 -0
- package/src/engine/shaders/vs.js +60 -0
- package/src/engine/utils/CameraController.js +506 -0
- package/src/engine/utils/ObjHelper.js +551 -0
- package/src/engine/utils/debug-logger.js +110 -0
- package/src/engine/utils/enums.js +305 -0
- package/src/engine/utils/generator.js +156 -0
- package/src/engine/utils/index.js +21 -0
- package/src/engine/utils/loaders/ActionLoader.js +77 -0
- package/src/engine/utils/loaders/AudioLoader.js +157 -0
- package/src/engine/utils/loaders/EventLoader.js +66 -0
- package/src/engine/utils/loaders/ObjectLoader.js +67 -0
- package/src/engine/utils/loaders/SpriteLoader.js +77 -0
- package/src/engine/utils/loaders/TilesetLoader.js +103 -0
- package/src/engine/utils/loaders/index.js +21 -0
- package/src/engine/utils/math/matrix4.js +367 -0
- package/src/engine/utils/math/vector.js +458 -0
- package/src/engine/utils/obj/_old_js/index.js +46 -0
- package/src/engine/utils/obj/_old_js/layout.js +308 -0
- package/src/engine/utils/obj/_old_js/material.js +711 -0
- package/src/engine/utils/obj/_old_js/mesh.js +761 -0
- package/src/engine/utils/obj/_old_js/utils.js +647 -0
- package/src/engine/utils/obj/index.js +24 -0
- package/src/engine/utils/obj/js/index.js +277 -0
- package/src/engine/utils/obj/js/loader.js +232 -0
- package/src/engine/utils/obj/layout.js +246 -0
- package/src/engine/utils/obj/material.js +665 -0
- package/src/engine/utils/obj/mesh.js +657 -0
- package/src/engine/utils/obj/ts/index.ts +72 -0
- package/src/engine/utils/obj/ts/layout.ts +265 -0
- package/src/engine/utils/obj/ts/material.ts +760 -0
- package/src/engine/utils/obj/ts/mesh.ts +785 -0
- package/src/engine/utils/obj/ts/utils.ts +501 -0
- package/src/engine/utils/obj/utils.js +428 -0
- package/src/engine/utils/resources.js +18 -0
- package/src/index.jsx +55 -0
- package/src/spritz/player.js +18 -0
- package/src/spritz/readme.md +18 -0
- package/LICENSE +0 -437
- package/dist/bundle.js.LICENSE.txt +0 -31
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
/* *\
|
|
2
|
+
** ----------------------------------------------- **
|
|
3
|
+
** Calliope - Pixos Game Engine **
|
|
4
|
+
** ----------------------------------------------- **
|
|
5
|
+
** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
|
|
6
|
+
** **
|
|
7
|
+
** Any unauthorized distribution or transfer **
|
|
8
|
+
** of this work is strictly prohibited. **
|
|
9
|
+
** **
|
|
10
|
+
** All Rights Reserved. **
|
|
11
|
+
** ----------------------------------------------- **
|
|
12
|
+
\* */
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {object} AtlasEntry
|
|
16
|
+
* @property {string} id - Unique texture identifier.
|
|
17
|
+
* @property {number} x - X position in atlas (pixels).
|
|
18
|
+
* @property {number} y - Y position in atlas (pixels).
|
|
19
|
+
* @property {number} width - Width in atlas (pixels).
|
|
20
|
+
* @property {number} height - Height in atlas (pixels).
|
|
21
|
+
* @property {number} u0 - Left UV coordinate (0-1).
|
|
22
|
+
* @property {number} v0 - Top UV coordinate (0-1).
|
|
23
|
+
* @property {number} u1 - Right UV coordinate (0-1).
|
|
24
|
+
* @property {number} v1 - Bottom UV coordinate (0-1).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {object} BatchEntry
|
|
29
|
+
* @property {Float32Array} vertices - Vertex positions.
|
|
30
|
+
* @property {Float32Array} texCoords - Texture coordinates.
|
|
31
|
+
* @property {Uint16Array} indices - Element indices.
|
|
32
|
+
* @property {string} textureId - Atlas texture ID.
|
|
33
|
+
* @property {string} shaderId - Shader program ID.
|
|
34
|
+
* @property {number} priority - Render priority (z-order).
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {object} DrawBatch
|
|
39
|
+
* @property {WebGLTexture} texture - Atlas texture.
|
|
40
|
+
* @property {string} shaderId - Shader to use.
|
|
41
|
+
* @property {Float32Array} vertices - Combined vertex data.
|
|
42
|
+
* @property {Float32Array} texCoords - Combined texture coordinates.
|
|
43
|
+
* @property {Uint16Array} indices - Combined indices.
|
|
44
|
+
* @property {number} count - Number of indices to draw.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* TextureAtlas - Packs multiple textures into atlas textures for batch rendering.
|
|
49
|
+
* Reduces draw calls by combining sprites that share the same texture atlas.
|
|
50
|
+
*/
|
|
51
|
+
export default class TextureAtlas {
|
|
52
|
+
/**
|
|
53
|
+
* Creates an instance of TextureAtlas.
|
|
54
|
+
* @param {import('./manager.js').default} renderManager - The render manager instance.
|
|
55
|
+
*/
|
|
56
|
+
constructor(renderManager) {
|
|
57
|
+
/** @type {import('./manager.js').default} */
|
|
58
|
+
this.renderManager = renderManager;
|
|
59
|
+
|
|
60
|
+
/** @type {Map<string, WebGLTexture>} Atlas name -> GL texture */
|
|
61
|
+
this.atlasTextures = new Map();
|
|
62
|
+
|
|
63
|
+
/** @type {Map<string, Map<string, AtlasEntry>>} Atlas name -> (entry ID -> entry) */
|
|
64
|
+
this.atlasEntries = new Map();
|
|
65
|
+
|
|
66
|
+
/** @type {Map<string, {width: number, height: number}>} Atlas dimensions */
|
|
67
|
+
this.atlasDimensions = new Map();
|
|
68
|
+
|
|
69
|
+
/** @type {BatchEntry[]} Pending batch entries */
|
|
70
|
+
this.batchQueue = [];
|
|
71
|
+
|
|
72
|
+
/** @type {Map<string, DrawBatch>} Compiled batches by key */
|
|
73
|
+
this.compiledBatches = new Map();
|
|
74
|
+
|
|
75
|
+
/** @type {number} Maximum atlas size (WebGL2 typically supports 16384) */
|
|
76
|
+
this.maxAtlasSize = 4096;
|
|
77
|
+
|
|
78
|
+
/** @type {number} Padding between textures to prevent bleeding */
|
|
79
|
+
this.padding = 2;
|
|
80
|
+
|
|
81
|
+
/** @type {boolean} Whether batching is enabled */
|
|
82
|
+
this.enabled = true;
|
|
83
|
+
|
|
84
|
+
/** @type {number} Statistics: draw calls this frame */
|
|
85
|
+
this.drawCallsThisFrame = 0;
|
|
86
|
+
|
|
87
|
+
/** @type {number} Statistics: draw calls saved */
|
|
88
|
+
this.drawCallsSaved = 0;
|
|
89
|
+
|
|
90
|
+
// Batch buffers (reusable)
|
|
91
|
+
/** @type {WebGLBuffer|null} */
|
|
92
|
+
this.batchVertexBuffer = null;
|
|
93
|
+
/** @type {WebGLBuffer|null} */
|
|
94
|
+
this.batchTexCoordBuffer = null;
|
|
95
|
+
/** @type {WebGLBuffer|null} */
|
|
96
|
+
this.batchIndexBuffer = null;
|
|
97
|
+
|
|
98
|
+
/** @type {number} Maximum sprites per batch */
|
|
99
|
+
this.maxSpritesPerBatch = 10000;
|
|
100
|
+
|
|
101
|
+
/** @type {Float32Array} Shared vertex array */
|
|
102
|
+
this.sharedVertices = new Float32Array(this.maxSpritesPerBatch * 12); // 4 verts * 3 components
|
|
103
|
+
/** @type {Float32Array} Shared tex coord array */
|
|
104
|
+
this.sharedTexCoords = new Float32Array(this.maxSpritesPerBatch * 8); // 4 verts * 2 components
|
|
105
|
+
/** @type {Uint16Array} Shared index array */
|
|
106
|
+
this.sharedIndices = new Uint16Array(this.maxSpritesPerBatch * 6); // 2 triangles * 3 indices
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Initialize WebGL buffers.
|
|
111
|
+
*/
|
|
112
|
+
init() {
|
|
113
|
+
const gl = this.renderManager.engine.gl;
|
|
114
|
+
if (!gl) return;
|
|
115
|
+
|
|
116
|
+
this.batchVertexBuffer = gl.createBuffer();
|
|
117
|
+
this.batchTexCoordBuffer = gl.createBuffer();
|
|
118
|
+
this.batchIndexBuffer = gl.createBuffer();
|
|
119
|
+
|
|
120
|
+
// Pre-generate index pattern (0,1,2, 0,2,3 for each quad)
|
|
121
|
+
for (let i = 0; i < this.maxSpritesPerBatch; i++) {
|
|
122
|
+
const base = i * 6;
|
|
123
|
+
const vertBase = i * 4;
|
|
124
|
+
this.sharedIndices[base + 0] = vertBase + 0;
|
|
125
|
+
this.sharedIndices[base + 1] = vertBase + 1;
|
|
126
|
+
this.sharedIndices[base + 2] = vertBase + 2;
|
|
127
|
+
this.sharedIndices[base + 3] = vertBase + 0;
|
|
128
|
+
this.sharedIndices[base + 4] = vertBase + 2;
|
|
129
|
+
this.sharedIndices[base + 5] = vertBase + 3;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Creates a texture atlas from an array of image sources.
|
|
135
|
+
* Uses a shelf-based packing algorithm.
|
|
136
|
+
* @param {string} atlasId - Unique atlas identifier.
|
|
137
|
+
* @param {{id: string, image: HTMLImageElement|ImageData}[]} images - Images to pack.
|
|
138
|
+
* @returns {Map<string, AtlasEntry>} Map of entry IDs to atlas entries.
|
|
139
|
+
*/
|
|
140
|
+
createAtlas(atlasId, images) {
|
|
141
|
+
const gl = this.renderManager.engine.gl;
|
|
142
|
+
if (!gl) return new Map();
|
|
143
|
+
|
|
144
|
+
// Sort images by height (tallest first) for better packing
|
|
145
|
+
const sorted = [...images].sort((a, b) => {
|
|
146
|
+
const hA = a.image.height || a.image.naturalHeight;
|
|
147
|
+
const hB = b.image.height || b.image.naturalHeight;
|
|
148
|
+
return hB - hA;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Calculate total area needed
|
|
152
|
+
let totalArea = 0;
|
|
153
|
+
for (const img of sorted) {
|
|
154
|
+
const w = (img.image.width || img.image.naturalWidth) + this.padding;
|
|
155
|
+
const h = (img.image.height || img.image.naturalHeight) + this.padding;
|
|
156
|
+
totalArea += w * h;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Determine atlas size (power of 2, at least sqrt(totalArea))
|
|
160
|
+
let size = 64;
|
|
161
|
+
while (size * size < totalArea * 1.2 && size < this.maxAtlasSize) {
|
|
162
|
+
size *= 2;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Create canvas for atlas
|
|
166
|
+
const canvas = document.createElement('canvas');
|
|
167
|
+
canvas.width = size;
|
|
168
|
+
canvas.height = size;
|
|
169
|
+
const ctx = canvas.getContext('2d');
|
|
170
|
+
|
|
171
|
+
// Shelf packing
|
|
172
|
+
const entries = new Map();
|
|
173
|
+
let shelfY = 0;
|
|
174
|
+
let shelfHeight = 0;
|
|
175
|
+
let cursorX = 0;
|
|
176
|
+
|
|
177
|
+
for (const img of sorted) {
|
|
178
|
+
const w = img.image.width || img.image.naturalWidth;
|
|
179
|
+
const h = img.image.height || img.image.naturalHeight;
|
|
180
|
+
|
|
181
|
+
// Check if fits on current shelf
|
|
182
|
+
if (cursorX + w + this.padding > size) {
|
|
183
|
+
// Move to next shelf
|
|
184
|
+
shelfY += shelfHeight + this.padding;
|
|
185
|
+
shelfHeight = 0;
|
|
186
|
+
cursorX = 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Check if fits vertically
|
|
190
|
+
if (shelfY + h + this.padding > size) {
|
|
191
|
+
console.warn(`TextureAtlas: Image ${img.id} doesn't fit in atlas ${atlasId}`);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Draw image to atlas
|
|
196
|
+
ctx.drawImage(img.image, cursorX, shelfY);
|
|
197
|
+
|
|
198
|
+
// Create entry
|
|
199
|
+
const entry = {
|
|
200
|
+
id: img.id,
|
|
201
|
+
x: cursorX,
|
|
202
|
+
y: shelfY,
|
|
203
|
+
width: w,
|
|
204
|
+
height: h,
|
|
205
|
+
u0: cursorX / size,
|
|
206
|
+
v0: shelfY / size,
|
|
207
|
+
u1: (cursorX + w) / size,
|
|
208
|
+
v1: (shelfY + h) / size
|
|
209
|
+
};
|
|
210
|
+
entries.set(img.id, entry);
|
|
211
|
+
|
|
212
|
+
// Update shelf
|
|
213
|
+
cursorX += w + this.padding;
|
|
214
|
+
shelfHeight = Math.max(shelfHeight, h);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Create GL texture
|
|
218
|
+
const texture = gl.createTexture();
|
|
219
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
220
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
|
|
221
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
222
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
223
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
224
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
225
|
+
|
|
226
|
+
// Store atlas
|
|
227
|
+
this.atlasTextures.set(atlasId, texture);
|
|
228
|
+
this.atlasEntries.set(atlasId, entries);
|
|
229
|
+
this.atlasDimensions.set(atlasId, { width: size, height: size });
|
|
230
|
+
|
|
231
|
+
return entries;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get UV coordinates for a texture in an atlas.
|
|
236
|
+
* @param {string} atlasId - Atlas identifier.
|
|
237
|
+
* @param {string} entryId - Entry identifier.
|
|
238
|
+
* @returns {AtlasEntry|null} The atlas entry or null.
|
|
239
|
+
*/
|
|
240
|
+
getEntry(atlasId, entryId) {
|
|
241
|
+
const entries = this.atlasEntries.get(atlasId);
|
|
242
|
+
return entries ? entries.get(entryId) || null : null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Queue a sprite for batched rendering.
|
|
247
|
+
* @param {string} atlasId - Atlas containing the texture.
|
|
248
|
+
* @param {string} entryId - Entry in the atlas.
|
|
249
|
+
* @param {number} x - World X position.
|
|
250
|
+
* @param {number} y - World Y position.
|
|
251
|
+
* @param {number} z - World Z position.
|
|
252
|
+
* @param {number} width - Sprite width.
|
|
253
|
+
* @param {number} height - Sprite height.
|
|
254
|
+
* @param {string} [shaderId='sprite'] - Shader to use.
|
|
255
|
+
* @param {number} [priority=0] - Render priority.
|
|
256
|
+
*/
|
|
257
|
+
queueSprite(atlasId, entryId, x, y, z, width, height, shaderId = 'sprite', priority = 0) {
|
|
258
|
+
const entry = this.getEntry(atlasId, entryId);
|
|
259
|
+
if (!entry) return;
|
|
260
|
+
|
|
261
|
+
// Generate quad vertices
|
|
262
|
+
const halfW = width / 2;
|
|
263
|
+
const halfH = height / 2;
|
|
264
|
+
|
|
265
|
+
const vertices = new Float32Array([
|
|
266
|
+
x - halfW, y - halfH, z,
|
|
267
|
+
x + halfW, y - halfH, z,
|
|
268
|
+
x + halfW, y + halfH, z,
|
|
269
|
+
x - halfW, y + halfH, z
|
|
270
|
+
]);
|
|
271
|
+
|
|
272
|
+
const texCoords = new Float32Array([
|
|
273
|
+
entry.u0, entry.v1,
|
|
274
|
+
entry.u1, entry.v1,
|
|
275
|
+
entry.u1, entry.v0,
|
|
276
|
+
entry.u0, entry.v0
|
|
277
|
+
]);
|
|
278
|
+
|
|
279
|
+
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
|
|
280
|
+
|
|
281
|
+
this.batchQueue.push({
|
|
282
|
+
vertices,
|
|
283
|
+
texCoords,
|
|
284
|
+
indices,
|
|
285
|
+
textureId: atlasId,
|
|
286
|
+
shaderId,
|
|
287
|
+
priority
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Compile queued sprites into optimized batches.
|
|
293
|
+
* Groups by texture and shader, sorts by priority.
|
|
294
|
+
*/
|
|
295
|
+
compileBatches() {
|
|
296
|
+
if (this.batchQueue.length === 0) return;
|
|
297
|
+
|
|
298
|
+
// Sort by priority, then by texture+shader
|
|
299
|
+
this.batchQueue.sort((a, b) => {
|
|
300
|
+
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
301
|
+
if (a.textureId !== b.textureId) return a.textureId.localeCompare(b.textureId);
|
|
302
|
+
return a.shaderId.localeCompare(b.shaderId);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Group into batches
|
|
306
|
+
this.compiledBatches.clear();
|
|
307
|
+
let currentKey = '';
|
|
308
|
+
let currentBatch = null;
|
|
309
|
+
let vertexOffset = 0;
|
|
310
|
+
let indexOffset = 0;
|
|
311
|
+
let baseVertex = 0;
|
|
312
|
+
|
|
313
|
+
for (const entry of this.batchQueue) {
|
|
314
|
+
const key = `${entry.textureId}|${entry.shaderId}`;
|
|
315
|
+
|
|
316
|
+
if (key !== currentKey || baseVertex + 4 > 65535) {
|
|
317
|
+
// Start new batch
|
|
318
|
+
if (currentBatch) {
|
|
319
|
+
this.finalizeBatch(currentBatch, vertexOffset, indexOffset);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
currentKey = key;
|
|
323
|
+
currentBatch = {
|
|
324
|
+
texture: this.atlasTextures.get(entry.textureId),
|
|
325
|
+
shaderId: entry.shaderId,
|
|
326
|
+
vertices: this.sharedVertices,
|
|
327
|
+
texCoords: this.sharedTexCoords,
|
|
328
|
+
indices: this.sharedIndices,
|
|
329
|
+
count: 0
|
|
330
|
+
};
|
|
331
|
+
vertexOffset = 0;
|
|
332
|
+
indexOffset = 0;
|
|
333
|
+
baseVertex = 0;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Copy vertices
|
|
337
|
+
for (let i = 0; i < entry.vertices.length; i++) {
|
|
338
|
+
this.sharedVertices[vertexOffset * 3 + i] = entry.vertices[i];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Copy tex coords
|
|
342
|
+
for (let i = 0; i < entry.texCoords.length; i++) {
|
|
343
|
+
this.sharedTexCoords[vertexOffset * 2 + i] = entry.texCoords[i];
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Adjust and copy indices
|
|
347
|
+
for (let i = 0; i < entry.indices.length; i++) {
|
|
348
|
+
this.sharedIndices[indexOffset + i] = entry.indices[i] + baseVertex;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
vertexOffset += 4;
|
|
352
|
+
indexOffset += 6;
|
|
353
|
+
baseVertex += 4;
|
|
354
|
+
currentBatch.count += 6;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Finalize last batch
|
|
358
|
+
if (currentBatch) {
|
|
359
|
+
this.finalizeBatch(currentBatch, vertexOffset, indexOffset);
|
|
360
|
+
this.compiledBatches.set(currentKey, currentBatch);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
this.drawCallsSaved = this.batchQueue.length - this.compiledBatches.size;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Finalize a batch by trimming arrays.
|
|
368
|
+
* @param {DrawBatch} batch - Batch to finalize.
|
|
369
|
+
* @param {number} vertexCount - Number of vertices.
|
|
370
|
+
* @param {number} indexCount - Number of indices.
|
|
371
|
+
*/
|
|
372
|
+
finalizeBatch(batch, vertexCount, indexCount) {
|
|
373
|
+
batch.vertices = new Float32Array(this.sharedVertices.buffer, 0, vertexCount * 3);
|
|
374
|
+
batch.texCoords = new Float32Array(this.sharedTexCoords.buffer, 0, vertexCount * 2);
|
|
375
|
+
batch.indices = new Uint16Array(this.sharedIndices.buffer, 0, indexCount);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Render all compiled batches.
|
|
380
|
+
* @param {WebGLProgram} program - Shader program to use.
|
|
381
|
+
*/
|
|
382
|
+
renderBatches(program) {
|
|
383
|
+
const gl = this.renderManager.engine.gl;
|
|
384
|
+
if (!gl || this.compiledBatches.size === 0) return;
|
|
385
|
+
|
|
386
|
+
this.drawCallsThisFrame = 0;
|
|
387
|
+
|
|
388
|
+
for (const [key, batch] of this.compiledBatches) {
|
|
389
|
+
if (!batch.texture || batch.count === 0) continue;
|
|
390
|
+
|
|
391
|
+
// Bind texture
|
|
392
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
393
|
+
gl.bindTexture(gl.TEXTURE_2D, batch.texture);
|
|
394
|
+
|
|
395
|
+
// Upload vertex data
|
|
396
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.batchVertexBuffer);
|
|
397
|
+
gl.bufferData(gl.ARRAY_BUFFER, batch.vertices, gl.DYNAMIC_DRAW);
|
|
398
|
+
|
|
399
|
+
const posLoc = gl.getAttribLocation(program, 'aVertexPosition');
|
|
400
|
+
if (posLoc >= 0) {
|
|
401
|
+
gl.enableVertexAttribArray(posLoc);
|
|
402
|
+
gl.vertexAttribPointer(posLoc, 3, gl.FLOAT, false, 0, 0);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Upload tex coord data
|
|
406
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.batchTexCoordBuffer);
|
|
407
|
+
gl.bufferData(gl.ARRAY_BUFFER, batch.texCoords, gl.DYNAMIC_DRAW);
|
|
408
|
+
|
|
409
|
+
const texLoc = gl.getAttribLocation(program, 'aTextureCoord');
|
|
410
|
+
if (texLoc >= 0) {
|
|
411
|
+
gl.enableVertexAttribArray(texLoc);
|
|
412
|
+
gl.vertexAttribPointer(texLoc, 2, gl.FLOAT, false, 0, 0);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Upload indices and draw
|
|
416
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.batchIndexBuffer);
|
|
417
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, batch.indices, gl.DYNAMIC_DRAW);
|
|
418
|
+
|
|
419
|
+
gl.drawElements(gl.TRIANGLES, batch.count, gl.UNSIGNED_SHORT, 0);
|
|
420
|
+
this.drawCallsThisFrame++;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Clear the batch queue for next frame.
|
|
426
|
+
*/
|
|
427
|
+
clearQueue() {
|
|
428
|
+
this.batchQueue.length = 0;
|
|
429
|
+
this.compiledBatches.clear();
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Get rendering statistics.
|
|
434
|
+
* @returns {{drawCalls: number, saved: number, batches: number, sprites: number}}
|
|
435
|
+
*/
|
|
436
|
+
getStats() {
|
|
437
|
+
return {
|
|
438
|
+
drawCalls: this.drawCallsThisFrame,
|
|
439
|
+
saved: this.drawCallsSaved,
|
|
440
|
+
batches: this.compiledBatches.size,
|
|
441
|
+
sprites: this.batchQueue.length
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Dispose of all atlas textures.
|
|
447
|
+
*/
|
|
448
|
+
dispose() {
|
|
449
|
+
const gl = this.renderManager.engine.gl;
|
|
450
|
+
if (!gl) return;
|
|
451
|
+
|
|
452
|
+
for (const texture of this.atlasTextures.values()) {
|
|
453
|
+
gl.deleteTexture(texture);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
this.atlasTextures.clear();
|
|
457
|
+
this.atlasEntries.clear();
|
|
458
|
+
this.atlasDimensions.clear();
|
|
459
|
+
this.clearQueue();
|
|
460
|
+
|
|
461
|
+
if (this.batchVertexBuffer) gl.deleteBuffer(this.batchVertexBuffer);
|
|
462
|
+
if (this.batchTexCoordBuffer) gl.deleteBuffer(this.batchTexCoordBuffer);
|
|
463
|
+
if (this.batchIndexBuffer) gl.deleteBuffer(this.batchIndexBuffer);
|
|
464
|
+
}
|
|
465
|
+
}
|