@bloopjs/toodle 0.0.102 → 0.0.104
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/Toodle.d.ts +9 -1
- package/dist/Toodle.d.ts.map +1 -1
- package/dist/mod.d.ts +1 -0
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +340 -159
- package/dist/mod.js.map +9 -8
- package/dist/scene/QuadNode.d.ts.map +1 -1
- package/dist/scene/SceneNode.d.ts +2 -2
- package/dist/scene/SceneNode.d.ts.map +1 -1
- package/dist/textures/AssetManager.d.ts +10 -2
- package/dist/textures/AssetManager.d.ts.map +1 -1
- package/dist/textures/Bundles.d.ts +183 -0
- package/dist/textures/Bundles.d.ts.map +1 -0
- package/dist/textures/mod.d.ts +2 -0
- package/dist/textures/mod.d.ts.map +1 -1
- package/dist/textures/types.d.ts +4 -2
- package/dist/textures/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Toodle.ts +31 -15
- package/src/mod.ts +1 -0
- package/src/scene/QuadNode.ts +1 -4
- package/src/scene/SceneNode.ts +11 -12
- package/src/text/TextNode.ts +2 -2
- package/src/textures/AssetManager.ts +44 -195
- package/src/textures/Bundles.ts +541 -0
- package/src/textures/mod.ts +2 -0
- package/src/textures/types.ts +4 -2
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundles - A renderer-agnostic class for managing texture bundles and atlas coordinates.
|
|
3
|
+
*
|
|
4
|
+
* This class can be used standalone (without WebGPU) for:
|
|
5
|
+
* - Registering pre-baked texture atlases (Pixi/AssetPack format)
|
|
6
|
+
* - Looking up texture regions and UV coordinates
|
|
7
|
+
* - Managing bundle state
|
|
8
|
+
*
|
|
9
|
+
* For WebGPU rendering, use AssetManager which wraps this class and handles GPU operations.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Size } from "../coreTypes/Size";
|
|
13
|
+
import type { Vec2 } from "../coreTypes/Vec2";
|
|
14
|
+
import type {
|
|
15
|
+
AtlasBundleOpts,
|
|
16
|
+
AtlasCoords,
|
|
17
|
+
CpuTextureAtlas,
|
|
18
|
+
PixiRegion,
|
|
19
|
+
TextureRegion,
|
|
20
|
+
} from "./types";
|
|
21
|
+
|
|
22
|
+
export type TextureId = string;
|
|
23
|
+
export type BundleId = string;
|
|
24
|
+
|
|
25
|
+
type CpuBundle = {
|
|
26
|
+
atlases: CpuTextureAtlas[];
|
|
27
|
+
isLoaded: boolean;
|
|
28
|
+
atlasIndices: number[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Options for creating a Bundles instance
|
|
33
|
+
*/
|
|
34
|
+
export type BundlesOptions = {
|
|
35
|
+
/** The size of the texture atlas (default: 4096) */
|
|
36
|
+
atlasSize?: number;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Bundles manages texture bundle registration and atlas coordinate lookups.
|
|
41
|
+
*
|
|
42
|
+
* This is a pure TypeScript class with no WebGPU dependencies, suitable for
|
|
43
|
+
* use with custom renderers (e.g., WebGL fallbacks).
|
|
44
|
+
*/
|
|
45
|
+
export class Bundles {
|
|
46
|
+
#bundles: Map<BundleId, CpuBundle> = new Map();
|
|
47
|
+
#textures: Map<TextureId, AtlasCoords[]> = new Map();
|
|
48
|
+
#atlasSize: number;
|
|
49
|
+
|
|
50
|
+
constructor(options: BundlesOptions = {}) {
|
|
51
|
+
this.#atlasSize = options.atlasSize ?? 4096;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Register a bundle of pre-baked texture atlases.
|
|
56
|
+
*
|
|
57
|
+
* @param bundleId - Unique identifier for this bundle
|
|
58
|
+
* @param opts - Atlas bundle options containing atlas definitions
|
|
59
|
+
* @returns The bundle ID
|
|
60
|
+
*/
|
|
61
|
+
async registerAtlasBundle(
|
|
62
|
+
bundleId: BundleId,
|
|
63
|
+
opts: AtlasBundleOpts,
|
|
64
|
+
): Promise<BundleId> {
|
|
65
|
+
const atlases: CpuTextureAtlas[] = [];
|
|
66
|
+
|
|
67
|
+
for (const atlas of opts.atlases) {
|
|
68
|
+
const jsonUrl =
|
|
69
|
+
atlas.json ??
|
|
70
|
+
new URL(
|
|
71
|
+
atlas.png!.toString().replace(".png", ".json"),
|
|
72
|
+
atlas.png!.origin,
|
|
73
|
+
);
|
|
74
|
+
const pngUrl =
|
|
75
|
+
atlas.png ??
|
|
76
|
+
new URL(
|
|
77
|
+
atlas.json!.toString().replace(".json", ".png"),
|
|
78
|
+
atlas.json!.origin,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const atlasDef = await (await fetch(jsonUrl)).json();
|
|
82
|
+
|
|
83
|
+
// For CPU-only usage, we may not need the actual bitmap
|
|
84
|
+
// but we fetch it for compatibility and to get dimensions
|
|
85
|
+
const bitmap = !opts.rg8
|
|
86
|
+
? await this.#getBitmapFromUrl(pngUrl)
|
|
87
|
+
: await createImageBitmap(new ImageData(1, 1)); // placeholder if using rg8
|
|
88
|
+
|
|
89
|
+
let rg8Bytes: Uint8Array<ArrayBuffer> | undefined;
|
|
90
|
+
if (opts.rg8) {
|
|
91
|
+
const rg8url = new URL(
|
|
92
|
+
pngUrl.toString().replace(".png", ".rg8.gz"),
|
|
93
|
+
pngUrl.origin,
|
|
94
|
+
);
|
|
95
|
+
rg8Bytes = await this.#fetchRg8Bytes(rg8url);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const cpuTextureAtlas: CpuTextureAtlas = {
|
|
99
|
+
texture: bitmap,
|
|
100
|
+
rg8Bytes,
|
|
101
|
+
textureRegions: new Map(),
|
|
102
|
+
width: opts.rg8 ? this.#atlasSize : bitmap.width,
|
|
103
|
+
height: opts.rg8 ? this.#atlasSize : bitmap.height,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Parse Pixi JSON format into TextureRegions
|
|
107
|
+
for (const [assetId, frame] of Object.entries(atlasDef.frames) as [
|
|
108
|
+
string,
|
|
109
|
+
PixiRegion,
|
|
110
|
+
][]) {
|
|
111
|
+
const textureRegion = this.#parsePixiFrame(
|
|
112
|
+
frame,
|
|
113
|
+
cpuTextureAtlas.width,
|
|
114
|
+
cpuTextureAtlas.height,
|
|
115
|
+
);
|
|
116
|
+
cpuTextureAtlas.textureRegions.set(assetId, textureRegion);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
atlases.push(cpuTextureAtlas);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.#bundles.set(bundleId, {
|
|
123
|
+
atlases,
|
|
124
|
+
atlasIndices: [],
|
|
125
|
+
isLoaded: false,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return bundleId;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Register a bundle with pre-built CPU texture atlases.
|
|
133
|
+
* Used internally by AssetManager for texture bundles that require GPU packing.
|
|
134
|
+
*
|
|
135
|
+
* @param bundleId - Unique identifier for this bundle
|
|
136
|
+
* @param atlases - Pre-built CPU texture atlases
|
|
137
|
+
*/
|
|
138
|
+
registerDynamicBundle(bundleId: BundleId, atlases: CpuTextureAtlas[]): void {
|
|
139
|
+
this.#bundles.set(bundleId, {
|
|
140
|
+
atlases,
|
|
141
|
+
atlasIndices: [],
|
|
142
|
+
isLoaded: false,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if a bundle is registered.
|
|
148
|
+
*
|
|
149
|
+
* @param bundleId - The bundle ID to check
|
|
150
|
+
*/
|
|
151
|
+
hasBundle(bundleId: BundleId): boolean {
|
|
152
|
+
return this.#bundles.has(bundleId);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Check if a bundle is loaded.
|
|
157
|
+
*
|
|
158
|
+
* @param bundleId - The bundle ID to check
|
|
159
|
+
*/
|
|
160
|
+
isBundleLoaded(bundleId: BundleId): boolean {
|
|
161
|
+
const bundle = this.#bundles.get(bundleId);
|
|
162
|
+
return bundle?.isLoaded ?? false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get the atlas indices for a loaded bundle.
|
|
167
|
+
*
|
|
168
|
+
* @param bundleId - The bundle ID
|
|
169
|
+
* @returns Array of atlas indices, or empty array if not loaded
|
|
170
|
+
*/
|
|
171
|
+
getBundleAtlasIndices(bundleId: BundleId): number[] {
|
|
172
|
+
const bundle = this.#bundles.get(bundleId);
|
|
173
|
+
return bundle?.atlasIndices ?? [];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Mark a bundle as loaded without populating texture lookups.
|
|
178
|
+
* Used when texture lookups are already populated via loadAtlas.
|
|
179
|
+
*
|
|
180
|
+
* @param bundleId - The bundle to mark as loaded
|
|
181
|
+
* @param atlasIndices - Array of atlas indices, one per atlas
|
|
182
|
+
*/
|
|
183
|
+
setBundleLoaded(bundleId: BundleId, atlasIndices: number[]): void {
|
|
184
|
+
const bundle = this.#bundles.get(bundleId);
|
|
185
|
+
if (!bundle) {
|
|
186
|
+
throw new Error(`Bundle ${bundleId} not found`);
|
|
187
|
+
}
|
|
188
|
+
bundle.atlasIndices = atlasIndices;
|
|
189
|
+
bundle.isLoaded = true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Mark a bundle as loaded and populate texture lookups.
|
|
194
|
+
* For standalone usage (without AssetManager).
|
|
195
|
+
*
|
|
196
|
+
* @param bundleId - The bundle to mark as loaded
|
|
197
|
+
* @param atlasIndices - Array of atlas indices, one per atlas. If not provided, indices are auto-assigned sequentially.
|
|
198
|
+
*/
|
|
199
|
+
markBundleLoaded(bundleId: BundleId, atlasIndices?: number[]): void {
|
|
200
|
+
const bundle = this.#bundles.get(bundleId);
|
|
201
|
+
if (!bundle) {
|
|
202
|
+
throw new Error(`Bundle ${bundleId} not found`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (bundle.isLoaded) {
|
|
206
|
+
console.warn(`Bundle ${bundleId} is already loaded.`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Use provided indices or auto-assign sequential ones
|
|
211
|
+
const indices =
|
|
212
|
+
atlasIndices ?? bundle.atlases.map(() => this.#getNextAtlasIndex());
|
|
213
|
+
|
|
214
|
+
if (indices.length !== bundle.atlases.length) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`Expected ${bundle.atlases.length} atlas indices, got ${indices.length}`,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
for (let i = 0; i < bundle.atlases.length; i++) {
|
|
221
|
+
const atlas = bundle.atlases[i];
|
|
222
|
+
const atlasIndex = indices[i];
|
|
223
|
+
bundle.atlasIndices.push(atlasIndex);
|
|
224
|
+
|
|
225
|
+
for (const [id, region] of atlas.textureRegions) {
|
|
226
|
+
const coords: AtlasCoords = { ...region, atlasIndex };
|
|
227
|
+
const existing = this.#textures.get(id);
|
|
228
|
+
if (existing) {
|
|
229
|
+
existing.push(coords);
|
|
230
|
+
} else {
|
|
231
|
+
this.#textures.set(id, [coords]);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
bundle.isLoaded = true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Unmark a bundle as loaded and remove texture lookups.
|
|
241
|
+
*
|
|
242
|
+
* @param bundleId - The bundle to unload
|
|
243
|
+
*/
|
|
244
|
+
unloadBundle(bundleId: BundleId): void {
|
|
245
|
+
const bundle = this.#bundles.get(bundleId);
|
|
246
|
+
if (!bundle) {
|
|
247
|
+
throw new Error(`Bundle ${bundleId} not found`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!bundle.isLoaded) {
|
|
251
|
+
console.warn(`Bundle ${bundleId} is not loaded.`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Remove texture entries for this bundle's atlas indices
|
|
256
|
+
for (const atlasIndex of bundle.atlasIndices) {
|
|
257
|
+
for (const [id, coords] of this.#textures.entries()) {
|
|
258
|
+
const indexToRemove = coords.findIndex(
|
|
259
|
+
(coord) => coord.atlasIndex === atlasIndex,
|
|
260
|
+
);
|
|
261
|
+
if (indexToRemove !== -1) {
|
|
262
|
+
coords.splice(indexToRemove, 1);
|
|
263
|
+
}
|
|
264
|
+
if (!coords.length) {
|
|
265
|
+
this.#textures.delete(id);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
bundle.isLoaded = false;
|
|
271
|
+
bundle.atlasIndices = [];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* A read-only map of all currently loaded textures.
|
|
276
|
+
*/
|
|
277
|
+
get textures(): ReadonlyMap<TextureId, AtlasCoords[]> {
|
|
278
|
+
return this.#textures;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* A read-only array of all currently loaded texture ids.
|
|
283
|
+
*/
|
|
284
|
+
get textureIds(): TextureId[] {
|
|
285
|
+
return Array.from(this.#textures.keys());
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get the atlas coordinates for a texture.
|
|
290
|
+
*
|
|
291
|
+
* @param id - The texture ID
|
|
292
|
+
* @returns Array of atlas coordinates (may have multiple if texture exists in multiple atlases)
|
|
293
|
+
*/
|
|
294
|
+
getAtlasCoords(id: TextureId): AtlasCoords[] {
|
|
295
|
+
const coords = this.#textures.get(id);
|
|
296
|
+
if (!coords) {
|
|
297
|
+
throw new Error(
|
|
298
|
+
`Texture ${id} not found. Have you registered and loaded a bundle containing this texture?`,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
return coords;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Set the atlas coordinates for a texture.
|
|
306
|
+
* This allows for UV precision adjustments.
|
|
307
|
+
*
|
|
308
|
+
* @param id - The texture ID
|
|
309
|
+
* @param coords - The atlas coordinates to set
|
|
310
|
+
*/
|
|
311
|
+
setAtlasCoords(id: TextureId, coords: AtlasCoords): void {
|
|
312
|
+
const oldCoords = this.#textures.get(id);
|
|
313
|
+
if (!oldCoords) return;
|
|
314
|
+
const indexToModify = oldCoords.findIndex(
|
|
315
|
+
(coord) => coord.atlasIndex === coords.atlasIndex,
|
|
316
|
+
);
|
|
317
|
+
if (indexToModify === -1) return;
|
|
318
|
+
oldCoords[indexToModify] = coords;
|
|
319
|
+
this.#textures.set(id, oldCoords);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Add atlas coordinates for a texture entry.
|
|
324
|
+
* Used by AssetManager.loadAtlas for textures loaded outside of bundles.
|
|
325
|
+
*
|
|
326
|
+
* @param id - The texture ID
|
|
327
|
+
* @param coords - The atlas coordinates to add
|
|
328
|
+
*/
|
|
329
|
+
addTextureEntry(id: TextureId, coords: AtlasCoords): void {
|
|
330
|
+
const existing = this.#textures.get(id);
|
|
331
|
+
if (existing) {
|
|
332
|
+
existing.push(coords);
|
|
333
|
+
} else {
|
|
334
|
+
this.#textures.set(id, [coords]);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Remove texture entries for a specific atlas index.
|
|
340
|
+
* Used by AssetManager.unloadAtlas.
|
|
341
|
+
*
|
|
342
|
+
* @param atlasIndex - The atlas index to remove entries for
|
|
343
|
+
*/
|
|
344
|
+
removeTextureEntriesForAtlas(atlasIndex: number): void {
|
|
345
|
+
for (const [id, coords] of this.#textures.entries()) {
|
|
346
|
+
const indexToRemove = coords.findIndex(
|
|
347
|
+
(coord) => coord.atlasIndex === atlasIndex,
|
|
348
|
+
);
|
|
349
|
+
if (indexToRemove !== -1) {
|
|
350
|
+
coords.splice(indexToRemove, 1);
|
|
351
|
+
}
|
|
352
|
+
if (!coords.length) {
|
|
353
|
+
this.#textures.delete(id);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Get the texture region (without atlas index) for a texture.
|
|
360
|
+
*
|
|
361
|
+
* @param id - The texture ID
|
|
362
|
+
* @returns The texture region, or undefined if not found
|
|
363
|
+
*/
|
|
364
|
+
getTextureRegion(id: TextureId): TextureRegion | undefined {
|
|
365
|
+
const coords = this.#textures.get(id);
|
|
366
|
+
if (!coords || coords.length === 0) return undefined;
|
|
367
|
+
|
|
368
|
+
const { atlasIndex: _, ...region } = coords[0];
|
|
369
|
+
return region;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Get the crop offset for a texture.
|
|
374
|
+
*
|
|
375
|
+
* @param id - The texture ID
|
|
376
|
+
* @returns The crop offset vector
|
|
377
|
+
*/
|
|
378
|
+
getTextureOffset(id: TextureId): Vec2 {
|
|
379
|
+
const coords = this.#textures.get(id);
|
|
380
|
+
if (!coords) {
|
|
381
|
+
throw new Error(
|
|
382
|
+
`Texture ${id} not found. Have you registered and loaded a bundle containing this texture?`,
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
return coords[0].cropOffset;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Get the original (uncropped) size of a texture.
|
|
390
|
+
*
|
|
391
|
+
* @param id - The texture ID
|
|
392
|
+
* @returns The original size in pixels
|
|
393
|
+
*/
|
|
394
|
+
getSize(id: TextureId): Size {
|
|
395
|
+
const coords = this.getAtlasCoords(id);
|
|
396
|
+
const uvScale = coords[0].uvScale;
|
|
397
|
+
return {
|
|
398
|
+
width: uvScale.width * this.#atlasSize,
|
|
399
|
+
height: uvScale.height * this.#atlasSize,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Get the cropped size of a texture.
|
|
405
|
+
*
|
|
406
|
+
* @param id - The texture ID
|
|
407
|
+
* @returns The cropped size in pixels
|
|
408
|
+
*/
|
|
409
|
+
getCroppedSize(id: TextureId): Size {
|
|
410
|
+
const coords = this.getAtlasCoords(id);
|
|
411
|
+
const uvScaleCropped = coords[0].uvScaleCropped;
|
|
412
|
+
if (uvScaleCropped) {
|
|
413
|
+
return {
|
|
414
|
+
width: uvScaleCropped.width * this.#atlasSize,
|
|
415
|
+
height: uvScaleCropped.height * this.#atlasSize,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
return this.getSize(id);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Check if a texture exists.
|
|
423
|
+
*
|
|
424
|
+
* @param id - The texture ID
|
|
425
|
+
* @returns True if the texture is registered
|
|
426
|
+
*/
|
|
427
|
+
hasTexture(id: TextureId): boolean {
|
|
428
|
+
return this.#textures.has(id);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Get all registered bundle IDs.
|
|
433
|
+
*/
|
|
434
|
+
getRegisteredBundleIds(): BundleId[] {
|
|
435
|
+
return Array.from(this.#bundles.keys());
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Get all loaded bundle IDs.
|
|
440
|
+
*/
|
|
441
|
+
getLoadedBundleIds(): BundleId[] {
|
|
442
|
+
return Array.from(this.#bundles.entries())
|
|
443
|
+
.filter(([, bundle]) => bundle.isLoaded)
|
|
444
|
+
.map(([id]) => id);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Get the CPU-side atlas data for a bundle.
|
|
449
|
+
* Useful for custom renderers that need access to the raw atlas data.
|
|
450
|
+
*
|
|
451
|
+
* @param bundleId - The bundle ID
|
|
452
|
+
* @returns Array of CPU texture atlases
|
|
453
|
+
*/
|
|
454
|
+
getBundleAtlases(bundleId: BundleId): CpuTextureAtlas[] {
|
|
455
|
+
const bundle = this.#bundles.get(bundleId);
|
|
456
|
+
if (!bundle) {
|
|
457
|
+
throw new Error(`Bundle ${bundleId} not found`);
|
|
458
|
+
}
|
|
459
|
+
return bundle.atlases;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* The atlas size used for coordinate calculations.
|
|
464
|
+
*/
|
|
465
|
+
get atlasSize(): number {
|
|
466
|
+
return this.#atlasSize;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// --- Private helpers ---
|
|
470
|
+
|
|
471
|
+
#parsePixiFrame(
|
|
472
|
+
frame: PixiRegion,
|
|
473
|
+
atlasWidth: number,
|
|
474
|
+
atlasHeight: number,
|
|
475
|
+
): TextureRegion {
|
|
476
|
+
const leftCrop = frame.spriteSourceSize.x;
|
|
477
|
+
const rightCrop =
|
|
478
|
+
frame.sourceSize.w - frame.spriteSourceSize.x - frame.spriteSourceSize.w;
|
|
479
|
+
const topCrop = frame.spriteSourceSize.y;
|
|
480
|
+
const bottomCrop =
|
|
481
|
+
frame.sourceSize.h - frame.spriteSourceSize.y - frame.spriteSourceSize.h;
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
cropOffset: {
|
|
485
|
+
x: leftCrop - rightCrop,
|
|
486
|
+
y: bottomCrop - topCrop,
|
|
487
|
+
},
|
|
488
|
+
originalSize: {
|
|
489
|
+
width: frame.sourceSize.w,
|
|
490
|
+
height: frame.sourceSize.h,
|
|
491
|
+
},
|
|
492
|
+
uvOffset: {
|
|
493
|
+
x: frame.frame.x / atlasWidth,
|
|
494
|
+
y: frame.frame.y / atlasHeight,
|
|
495
|
+
},
|
|
496
|
+
uvScale: {
|
|
497
|
+
width: frame.sourceSize.w / atlasWidth,
|
|
498
|
+
height: frame.sourceSize.h / atlasHeight,
|
|
499
|
+
},
|
|
500
|
+
uvScaleCropped: {
|
|
501
|
+
width: frame.frame.w / atlasWidth,
|
|
502
|
+
height: frame.frame.h / atlasHeight,
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async #getBitmapFromUrl(url: URL): Promise<ImageBitmap> {
|
|
508
|
+
const response = await fetch(url);
|
|
509
|
+
const blob = await response.blob();
|
|
510
|
+
return createImageBitmap(blob);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async #fetchRg8Bytes(url: URL): Promise<Uint8Array<ArrayBuffer>> {
|
|
514
|
+
const response = await fetch(url);
|
|
515
|
+
const enc = (response.headers.get("content-encoding") || "").toLowerCase();
|
|
516
|
+
|
|
517
|
+
// If server/CDN already set Content-Encoding, Fetch returns decompressed bytes
|
|
518
|
+
if (enc.includes("gzip") || enc.includes("br") || enc.includes("deflate")) {
|
|
519
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (!response.body) {
|
|
523
|
+
throw new Error("Response body of rg8 file is null");
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const ds = new DecompressionStream("gzip");
|
|
527
|
+
const ab = await new Response(response.body.pipeThrough(ds)).arrayBuffer();
|
|
528
|
+
return new Uint8Array(ab);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
#getNextAtlasIndex(): number {
|
|
532
|
+
// Find the highest used atlas index and return the next one
|
|
533
|
+
let maxIndex = -1;
|
|
534
|
+
for (const bundle of this.#bundles.values()) {
|
|
535
|
+
for (const idx of bundle.atlasIndices) {
|
|
536
|
+
if (idx > maxIndex) maxIndex = idx;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return maxIndex + 1;
|
|
540
|
+
}
|
|
541
|
+
}
|
package/src/textures/mod.ts
CHANGED
package/src/textures/types.ts
CHANGED
|
@@ -107,7 +107,8 @@ export type TextureBundleOpts = {
|
|
|
107
107
|
*/
|
|
108
108
|
cropTransparentPixels?: boolean;
|
|
109
109
|
/**
|
|
110
|
-
* Whether the bundle should be loaded automatically on registration
|
|
110
|
+
* Whether the bundle should be loaded automatically on registration.
|
|
111
|
+
* @default true
|
|
111
112
|
*/
|
|
112
113
|
autoLoad?: boolean;
|
|
113
114
|
};
|
|
@@ -135,7 +136,8 @@ export type AtlasBundleOpts = {
|
|
|
135
136
|
atlases: AtlasDef[];
|
|
136
137
|
|
|
137
138
|
/**
|
|
138
|
-
* Whether the bundle should be loaded automatically on registration
|
|
139
|
+
* Whether the bundle should be loaded automatically on registration.
|
|
140
|
+
* @default true
|
|
139
141
|
*/
|
|
140
142
|
autoLoad?: boolean;
|
|
141
143
|
|