@bloopjs/toodle 0.1.4 → 0.1.5
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/mod.js +124 -18
- package/dist/mod.js.map +4 -4
- package/dist/textures/AssetManager.d.ts.map +1 -1
- package/dist/textures/util.d.ts +9 -0
- package/dist/textures/util.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/textures/AssetManager.ts +50 -29
- package/src/textures/util.ts +140 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssetManager.d.ts","sourceRoot":"","sources":["../../src/textures/AssetManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAQ3D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EACV,eAAe,EACf,WAAW,EACX,eAAe,EACf,iBAAiB,EAElB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"AssetManager.d.ts","sourceRoot":"","sources":["../../src/textures/AssetManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAQ3D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EACV,eAAe,EACf,WAAW,EACX,eAAe,EACf,iBAAiB,EAElB,MAAM,SAAS,CAAC;AAOjB,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAC/B,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAC9B,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAE5B,MAAM,MAAM,mBAAmB,GAAG;IAChC,oGAAoG;IACpG,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,qBAAa,YAAY;;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;gBAOd,OAAO,EAAE,cAAc,EAAE,OAAO,GAAE,mBAAwB;IAsBtE;;OAEG;IACH,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED;;;OAGG;IACH,IAAI,YAAY,IAAI,UAAU,CAE7B;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,IAAI,CAGpB;IAED;;;;;OAKG;IACH,OAAO,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAU5B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAYnC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO;IAUjC;;OAEG;IACH,IAAI,QAAQ,uCAEX;IAED;;OAEG;IACH,IAAI,UAAU,aAEb;IAED;;;;;;;;;;;;;;;;OAgBG;IACG,YAAY,CAAC,IAAI,EAAE,iBAAiB,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAMtE;;;;;;;;;;;OAWG;IACG,WAAW,CACf,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,GAAG,GAAG,WAAW,EACtB,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC;;;;IA+CtC;;;;;;;;OAQG;IACG,cAAc,CAClB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,iBAAiB,GAAG,eAAe,GACxC,OAAO,CAAC,QAAQ,CAAC;IAcpB;;;;OAIG;IACG,UAAU,CAAC,QAAQ,EAAE,QAAQ;IAsBnC;;;;;OAKG;IACG,YAAY,CAAC,QAAQ,EAAE,QAAQ;IAkBrC;;;;;;;;;;;OAWG;IACG,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB,SAAM;IAwC5D,OAAO,CAAC,EAAE,EAAE,MAAM;IASlB,wBAAwB,CAAC,IAAI,EAAE,SAAS,GAAG,QAAQ;IAqHnD;;OAEG;IACH,KAAK;sCAEyB,MAAM,EAAE;kCAKZ,MAAM,EAAE;QAIhC;;;;;;;WAOG;6BACkB,SAAS,UAAU,WAAW;QAInD;;;;;WAKG;6BACkB,SAAS,KAAG,WAAW,EAAE;QAI9C;;;;;WAKG;+BACoB,SAAS,KAAG,IAAI;QAIvC;;;;WAIG;;YAKC;;;eAGG;;YAEH;;eAEG;;YAEH;;eAEG;;;QAKP;;;WAGG;;QAaH;;;;;WAKG;2BACsB,eAAe;QAYxC;;;;WAIG;kCAC6B,MAAM;MAItC;IAqCF;;OAEG;IACH,OAAO;CAGR"}
|
package/dist/textures/util.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { CpuTextureAtlas, TextureWithMetadata } from "./types";
|
|
2
2
|
export declare function getBitmapFromUrl(url: URL): Promise<ImageBitmap>;
|
|
3
3
|
export declare function packBitmapsToAtlas(images: Map<string, TextureWithMetadata>, textureSize: number, device: GPUDevice): Promise<CpuTextureAtlas[]>;
|
|
4
|
+
/**
|
|
5
|
+
* CPU-only version of packBitmapsToAtlas for WebGL2 backend.
|
|
6
|
+
* Uses OffscreenCanvas to composite packed bitmaps instead of GPU textures.
|
|
7
|
+
* Does not support transparent pixel cropping.
|
|
8
|
+
*/
|
|
9
|
+
export declare function packBitmapsToAtlasCPU(images: Map<string, {
|
|
10
|
+
bitmap: ImageBitmap;
|
|
11
|
+
id: string;
|
|
12
|
+
}>, textureSize: number): Promise<CpuTextureAtlas[]>;
|
|
4
13
|
//# sourceMappingURL=util.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/textures/util.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAEf,mBAAmB,EACpB,MAAM,SAAS,CAAC;AAEjB,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,CASrE;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACxC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,eAAe,EAAE,CAAC,CAqH5B"}
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/textures/util.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAEf,mBAAmB,EACpB,MAAM,SAAS,CAAC;AAEjB,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,CASrE;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACxC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,eAAe,EAAE,CAAC,CAqH5B;AA2HD;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,EACxD,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,eAAe,EAAE,CAAC,CA8G5B"}
|
package/package.json
CHANGED
|
@@ -21,7 +21,11 @@ import type {
|
|
|
21
21
|
TextureBundleOpts,
|
|
22
22
|
TextureWithMetadata,
|
|
23
23
|
} from "./types";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
getBitmapFromUrl,
|
|
26
|
+
packBitmapsToAtlas,
|
|
27
|
+
packBitmapsToAtlasCPU,
|
|
28
|
+
} from "./util";
|
|
25
29
|
|
|
26
30
|
export type TextureId = string;
|
|
27
31
|
export type BundleId = string;
|
|
@@ -432,41 +436,58 @@ export class AssetManager {
|
|
|
432
436
|
bundleId: BundleId,
|
|
433
437
|
opts: TextureBundleOpts,
|
|
434
438
|
) {
|
|
435
|
-
if (this.#backend.type
|
|
436
|
-
|
|
437
|
-
|
|
439
|
+
if (this.#backend.type === "webgpu") {
|
|
440
|
+
// WebGPU path: supports optional crop compute shader
|
|
441
|
+
const device = (this.#backend as WebGPUBackend).device;
|
|
442
|
+
const images = new Map<string, TextureWithMetadata>();
|
|
443
|
+
|
|
444
|
+
await Promise.all(
|
|
445
|
+
Object.entries(opts.textures).map(async ([id, url]) => {
|
|
446
|
+
const bitmap = await getBitmapFromUrl(url);
|
|
447
|
+
let textureWrapper: TextureWithMetadata = this.#wrapBitmapToTexture(
|
|
448
|
+
bitmap,
|
|
449
|
+
id,
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
if (opts.cropTransparentPixels && this.#cropComputeShader) {
|
|
453
|
+
textureWrapper =
|
|
454
|
+
await this.#cropComputeShader.processTexture(textureWrapper);
|
|
455
|
+
}
|
|
456
|
+
images.set(id, textureWrapper);
|
|
457
|
+
}),
|
|
438
458
|
);
|
|
439
|
-
}
|
|
440
459
|
|
|
441
|
-
|
|
442
|
-
|
|
460
|
+
const atlases = await packBitmapsToAtlas(
|
|
461
|
+
images,
|
|
462
|
+
this.#backend.limits.textureSize,
|
|
463
|
+
device,
|
|
464
|
+
);
|
|
443
465
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
let textureWrapper: TextureWithMetadata = this.#wrapBitmapToTexture(
|
|
451
|
-
bitmap,
|
|
452
|
-
id,
|
|
466
|
+
this.bundles.registerDynamicBundle(bundleId, atlases);
|
|
467
|
+
} else {
|
|
468
|
+
// WebGL2 path: CPU-only packing without cropping support
|
|
469
|
+
if (opts.cropTransparentPixels) {
|
|
470
|
+
console.warn(
|
|
471
|
+
"cropTransparentPixels is not supported on WebGL2 backend and will be ignored.",
|
|
453
472
|
);
|
|
473
|
+
}
|
|
454
474
|
|
|
455
|
-
|
|
456
|
-
textureWrapper =
|
|
457
|
-
await this.#cropComputeShader.processTexture(textureWrapper);
|
|
458
|
-
}
|
|
459
|
-
images.set(id, textureWrapper);
|
|
460
|
-
}),
|
|
461
|
-
);
|
|
475
|
+
const images = new Map<string, { bitmap: ImageBitmap; id: string }>();
|
|
462
476
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
477
|
+
await Promise.all(
|
|
478
|
+
Object.entries(opts.textures).map(async ([id, url]) => {
|
|
479
|
+
const bitmap = await getBitmapFromUrl(url);
|
|
480
|
+
images.set(id, { bitmap, id });
|
|
481
|
+
}),
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
const atlases = await packBitmapsToAtlasCPU(
|
|
485
|
+
images,
|
|
486
|
+
this.#backend.limits.textureSize,
|
|
487
|
+
);
|
|
468
488
|
|
|
469
|
-
|
|
489
|
+
this.bundles.registerDynamicBundle(bundleId, atlases);
|
|
490
|
+
}
|
|
470
491
|
}
|
|
471
492
|
|
|
472
493
|
async #registerBundleFromAtlases(bundleId: BundleId, opts: AtlasBundleOpts) {
|
package/src/textures/util.ts
CHANGED
|
@@ -258,3 +258,143 @@ type Rectangle = {
|
|
|
258
258
|
width: number;
|
|
259
259
|
height: number;
|
|
260
260
|
};
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* CPU-only version of packBitmapsToAtlas for WebGL2 backend.
|
|
264
|
+
* Uses OffscreenCanvas to composite packed bitmaps instead of GPU textures.
|
|
265
|
+
* Does not support transparent pixel cropping.
|
|
266
|
+
*/
|
|
267
|
+
export async function packBitmapsToAtlasCPU(
|
|
268
|
+
images: Map<string, { bitmap: ImageBitmap; id: string }>,
|
|
269
|
+
textureSize: number,
|
|
270
|
+
): Promise<CpuTextureAtlas[]> {
|
|
271
|
+
const cpuTextureAtlases: CpuTextureAtlas[] = [];
|
|
272
|
+
const packed: PackedTexture[] = [];
|
|
273
|
+
const spaces: Rectangle[] = [
|
|
274
|
+
{ x: 0, y: 0, width: textureSize, height: textureSize },
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
let atlasRegionMap = new Map<string, TextureRegion>();
|
|
278
|
+
|
|
279
|
+
for (const [id, { bitmap }] of images) {
|
|
280
|
+
// Find best fitting space using guillotine method
|
|
281
|
+
let bestSpace = -1;
|
|
282
|
+
let bestScore = Number.POSITIVE_INFINITY;
|
|
283
|
+
|
|
284
|
+
for (let i = 0; i < spaces.length; i++) {
|
|
285
|
+
const space = spaces[i];
|
|
286
|
+
if (bitmap.width <= space.width && bitmap.height <= space.height) {
|
|
287
|
+
// Score based on how well it fits (smaller score is better)
|
|
288
|
+
const score = Math.abs(
|
|
289
|
+
space.width * space.height - bitmap.width * bitmap.height,
|
|
290
|
+
);
|
|
291
|
+
if (score < bestScore) {
|
|
292
|
+
bestScore = score;
|
|
293
|
+
bestSpace = i;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (bestSpace === -1) {
|
|
299
|
+
// Current atlas is full, finalize it and start a new one
|
|
300
|
+
const tex = createAtlasBitmapFromPacked(packed, textureSize);
|
|
301
|
+
cpuTextureAtlases.push({
|
|
302
|
+
texture: tex,
|
|
303
|
+
textureRegions: atlasRegionMap,
|
|
304
|
+
width: tex.width,
|
|
305
|
+
height: tex.height,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
atlasRegionMap = new Map<string, TextureRegion>();
|
|
309
|
+
packed.length = 0;
|
|
310
|
+
|
|
311
|
+
spaces.length = 0;
|
|
312
|
+
spaces.push({
|
|
313
|
+
x: 0,
|
|
314
|
+
y: 0,
|
|
315
|
+
width: textureSize,
|
|
316
|
+
height: textureSize,
|
|
317
|
+
});
|
|
318
|
+
bestSpace = 0;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const space = spaces[bestSpace];
|
|
322
|
+
|
|
323
|
+
// Pack the image
|
|
324
|
+
packed.push({
|
|
325
|
+
texture: bitmap,
|
|
326
|
+
x: space.x,
|
|
327
|
+
y: space.y,
|
|
328
|
+
width: bitmap.width,
|
|
329
|
+
height: bitmap.height,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Split remaining space into two new spaces
|
|
333
|
+
spaces.splice(bestSpace, 1);
|
|
334
|
+
|
|
335
|
+
if (space.width - bitmap.width > 0) {
|
|
336
|
+
spaces.push({
|
|
337
|
+
x: space.x + bitmap.width,
|
|
338
|
+
y: space.y,
|
|
339
|
+
width: space.width - bitmap.width,
|
|
340
|
+
height: bitmap.height,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (space.height - bitmap.height > 0) {
|
|
345
|
+
spaces.push({
|
|
346
|
+
x: space.x,
|
|
347
|
+
y: space.y + bitmap.height,
|
|
348
|
+
width: space.width,
|
|
349
|
+
height: space.height - bitmap.height,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Create atlas coords (no cropping for CPU path)
|
|
354
|
+
const uvScale = {
|
|
355
|
+
width: bitmap.width / textureSize,
|
|
356
|
+
height: bitmap.height / textureSize,
|
|
357
|
+
};
|
|
358
|
+
atlasRegionMap.set(id, {
|
|
359
|
+
uvOffset: {
|
|
360
|
+
x: space.x / textureSize,
|
|
361
|
+
y: space.y / textureSize,
|
|
362
|
+
},
|
|
363
|
+
uvScale,
|
|
364
|
+
uvScaleCropped: uvScale,
|
|
365
|
+
cropOffset: { x: 0, y: 0 },
|
|
366
|
+
originalSize: { width: bitmap.width, height: bitmap.height },
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Finalize the last atlas
|
|
371
|
+
const tex = createAtlasBitmapFromPacked(packed, textureSize);
|
|
372
|
+
cpuTextureAtlases.push({
|
|
373
|
+
texture: tex,
|
|
374
|
+
textureRegions: atlasRegionMap,
|
|
375
|
+
width: tex.width,
|
|
376
|
+
height: tex.height,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
return cpuTextureAtlases;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Composites packed textures onto an OffscreenCanvas and returns an ImageBitmap.
|
|
384
|
+
*/
|
|
385
|
+
function createAtlasBitmapFromPacked(
|
|
386
|
+
packed: PackedTexture[],
|
|
387
|
+
atlasSize: number,
|
|
388
|
+
): ImageBitmap {
|
|
389
|
+
const canvas = new OffscreenCanvas(atlasSize, atlasSize);
|
|
390
|
+
const ctx = canvas.getContext("2d");
|
|
391
|
+
if (!ctx) {
|
|
392
|
+
throw new Error("Failed to get 2d context from OffscreenCanvas");
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
for (const texture of packed) {
|
|
396
|
+
ctx.drawImage(texture.texture, texture.x, texture.y);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return canvas.transferToImageBitmap();
|
|
400
|
+
}
|