@nodecraft/rendermite 1.4.1 → 1.6.0

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.
Files changed (94) hide show
  1. package/README.md +3 -0
  2. package/dist/blocks/cli.js +1 -1
  3. package/dist/blocks/entities.d.ts +30 -0
  4. package/dist/blocks/entities.js +2 -0
  5. package/dist/blocks/entity-geo.d.ts +18 -0
  6. package/dist/blocks/entity-geo.js +2 -0
  7. package/dist/blocks/entity-models.d.ts +2 -0
  8. package/dist/blocks/entity-models.js +2 -0
  9. package/dist/blocks/export.d.ts +1 -1
  10. package/dist/blocks/export.js +1 -1
  11. package/dist/blocks/item-sprites.d.ts +34 -0
  12. package/dist/blocks/item-sprites.js +2 -0
  13. package/dist/blocks/obj-mesh.js +1 -1
  14. package/dist/blocks/pack-format.d.ts +2 -2
  15. package/dist/blocks/sprite-sheet.d.ts +59 -0
  16. package/dist/blocks/sprite-sheet.js +2 -0
  17. package/dist/classfile-BwaQjnUM.d.ts +51 -0
  18. package/dist/classfile-CAhS5qNf.js +255 -0
  19. package/dist/{cli-DqJtT-Ms.js → cli-BSKnXBPr.js} +31 -4
  20. package/dist/cli.js +5 -5
  21. package/dist/{discovery-CsZu08re.js → discovery-CD0QqxSF.js} +1 -1
  22. package/dist/{discovery-C5ZR4aoG.d.ts → discovery-Cmir3QfC.d.ts} +8 -1
  23. package/dist/entities/classfile.d.ts +2 -0
  24. package/dist/entities/classfile.js +2 -0
  25. package/dist/entities/geo-model.d.ts +13 -0
  26. package/dist/entities/geo-model.js +2 -0
  27. package/dist/entities/inherit.d.ts +29 -0
  28. package/dist/entities/inherit.js +2 -0
  29. package/dist/entities/jar-classes.d.ts +26 -0
  30. package/dist/entities/jar-classes.js +2 -0
  31. package/dist/entities/link.d.ts +71 -0
  32. package/dist/entities/link.js +2 -0
  33. package/dist/entities/mappings.d.ts +2 -0
  34. package/dist/entities/mappings.js +2 -0
  35. package/dist/entities/merge.d.ts +18 -0
  36. package/dist/entities/merge.js +2 -0
  37. package/dist/entities/model-bytecode.d.ts +31 -0
  38. package/dist/entities/model-bytecode.js +2 -0
  39. package/dist/entities/model-code.d.ts +2 -0
  40. package/dist/entities/model-code.js +2 -0
  41. package/dist/entities/model.d.ts +2 -0
  42. package/dist/entities/model.js +1 -0
  43. package/dist/entities-DvmA9fEV.js +175 -0
  44. package/dist/{entity-fallback-BTkM8j0V.js → entity-fallback-Bth0fa7m.js} +1 -1
  45. package/dist/entity-geo-B2CkuMWx.js +100 -0
  46. package/dist/entity-models-B6-0r2gR.js +123 -0
  47. package/dist/entity-models-c2HpfdWV.d.ts +49 -0
  48. package/dist/{export-Bllg8Fw7.d.ts → export-D8fLC1wS.d.ts} +30 -1
  49. package/dist/{export-kMoMes_a.js → export-DT_drDoI.js} +182 -160
  50. package/dist/geo-CmjrXu0G.d.ts +59 -0
  51. package/dist/{geo-BguldlU-.js → geo-Desq-gRC.js} +5 -4
  52. package/dist/geo-model-Bo1gpEwX.js +163 -0
  53. package/dist/index.d.ts +2 -2
  54. package/dist/index.js +10 -10
  55. package/dist/inherit-KkDsM2N_.js +77 -0
  56. package/dist/item-sprites-G-yikdfV.js +126 -0
  57. package/dist/items/discovery.d.ts +2 -2
  58. package/dist/items/discovery.js +2 -2
  59. package/dist/jar-classes-Bj15hjdr.js +75 -0
  60. package/dist/link-Bm5oQYh6.js +331 -0
  61. package/dist/mappings-BEPVaiwE.js +139 -0
  62. package/dist/mappings-DxX4KoZl.d.ts +75 -0
  63. package/dist/merge-3HkeQ_6A.js +46 -0
  64. package/dist/model-PnQ4gV0-.d.ts +49 -0
  65. package/dist/model-bytecode-C1nw2G8O.js +732 -0
  66. package/dist/model-code-B7jSFQqd.d.ts +16 -0
  67. package/dist/model-code-BKMEMXxF.js +49 -0
  68. package/dist/mods/prepare.js +1 -1
  69. package/dist/mods/tints.js +1 -1
  70. package/dist/{obj-bvs3gwex.js → obj-CJHEHpki.js} +4 -4
  71. package/dist/{obj-mesh-rnYbSbpv.js → obj-mesh-JwPkZEWL.js} +1 -1
  72. package/dist/pack-format-QcVFxG_b.d.ts +280 -0
  73. package/dist/pack.d.ts +1 -1
  74. package/dist/{pipeline-B8e6x8Hh.js → pipeline-BUIMzMzg.js} +6 -6
  75. package/dist/{prepare-C9gjqQTt.js → prepare-Myd_ec4v.js} +2 -2
  76. package/dist/render/entity-fallback.js +1 -1
  77. package/dist/render/face.js +1 -1
  78. package/dist/render/geo.d.ts +2 -54
  79. package/dist/render/geo.js +2 -2
  80. package/dist/render/obj.js +1 -1
  81. package/dist/render/pipeline.js +1 -1
  82. package/dist/render/renderer.js +1 -1
  83. package/dist/resources/descriptor.js +1 -1
  84. package/dist/{run-B_JAUbXe.js → run-BQJkqc5f.js} +3 -3
  85. package/dist/run.js +1 -1
  86. package/dist/sprite-sheet-LQAEtGkp.js +185 -0
  87. package/dist/versions/prepare.js +1 -1
  88. package/dist/workers/render-worker.js +2 -2
  89. package/package.json +1 -1
  90. package/dist/pack-format-BVC_B-ZW.d.ts +0 -140
  91. package/dist/{descriptor-D7VKgeGT.js → descriptor-JyXhfd-c.js} +1 -1
  92. package/dist/{face-CKHboPfh.js → face-B2JjWT46.js} +1 -1
  93. package/dist/{prepare-8LswFoS-.js → prepare-D49Gu4Lv.js} +1 -1
  94. package/dist/{tints-C1tT6pKk.js → tints-BspVCz2G.js} +1 -1
@@ -0,0 +1,49 @@
1
+ import { a as ResourceManager } from "./manager-exOIcdyx.js";
2
+ import { S as EntityPackModel } from "./pack-format-QcVFxG_b.js";
3
+ //#region src/blocks/entity-models.d.ts
4
+ /** What the exporter needs to read code out of; a jar, in practice. */
5
+ interface ClassSource {
6
+ list: () => Promise<string[]>;
7
+ read: (internalName: string) => Promise<Uint8Array | null>;
8
+ }
9
+ interface EntityGeometryResult {
10
+ /** Model id (the class it came from) → geometry. */
11
+ models: Record<string, EntityPackModel>;
12
+ /** Entity id → model id, for the entity table to point with. */
13
+ modelOf: Map<string, string>;
14
+ /** Entity sheets the models address, to be stitched into the pack's own atlas. */
15
+ spriteIds: Set<string>;
16
+ failures: Array<{
17
+ id: string;
18
+ reason: string;
19
+ }>;
20
+ }
21
+ interface CollectEntityGeometryOptions {
22
+ version: string;
23
+ /** Where mappings cache; the same directory the jars do. */
24
+ cacheDir: string;
25
+ classes: ClassSource;
26
+ /** Entity ids to look for, as the registry named them. */
27
+ entityIds: readonly string[];
28
+ /** The sheet each entity resolved to, from the entity registry, keyed by entity id. */
29
+ textureOf: ReadonlyMap<string, string>;
30
+ /**
31
+ * The assets, for the mods whose geometry is not in code at all.
32
+ *
33
+ * A GeckoLib mob ships Blockbench's bedrock format and registers it by resource location, so there
34
+ * is nothing for the interpreter to read; `entity-geo.ts` finds and converts it instead.
35
+ */
36
+ resources?: ResourceManager;
37
+ onProgress?: (done: number, total: number) => void;
38
+ }
39
+ /**
40
+ * Recovers geometry for as many of `entityIds` as the code describes.
41
+ *
42
+ * Mappings are resolved once: downloaded for an obfuscated jar, and the identity for one that is not
43
+ * (from the 26 line the client ships in the clear, and a mod jar never was obfuscated). A version that
44
+ * is obfuscated *and* unmapped — 1.13.x — yields nothing, with the reason recorded once rather than
45
+ * per entity.
46
+ */
47
+ declare function collectEntityGeometry(options: CollectEntityGeometryOptions): Promise<EntityGeometryResult>;
48
+ //#endregion
49
+ export { collectEntityGeometry as i, CollectEntityGeometryOptions as n, EntityGeometryResult as r, ClassSource as t };
@@ -1,4 +1,5 @@
1
1
  import { a as ResourceManager } from "./manager-exOIcdyx.js";
2
+ import { t as ClassSource } from "./entity-models-c2HpfdWV.js";
2
3
  //#region src/blocks/export.d.ts
3
4
  interface ExportBlockPackOptions {
4
5
  resources: ResourceManager;
@@ -19,6 +20,28 @@ interface ExportBlockPackOptions {
19
20
  padding?: number;
20
21
  /** Ceiling on an atlas page's dimensions (default 4096). */
21
22
  maxPageSize?: number;
23
+ /**
24
+ * Export `items.json` and its own atlas: the flat sprite stacks for items that
25
+ * are not blocks, which is what a world viewer draws a dropped stack of a lead
26
+ * or a handful of charcoal from. On by default.
27
+ */
28
+ items?: boolean;
29
+ /**
30
+ * Export `entities.json`: every entity type the namespaces register, with a display name and the
31
+ * mean colour of its sheet. On by default. No geometry — no jar has any — but it is what lets a
32
+ * consumer name and tint the stand-in box it draws for a mob.
33
+ */
34
+ entities?: boolean;
35
+ /**
36
+ * Reads class files, for recovering entity geometry from the client's own code.
37
+ *
38
+ * Entity models are Java at every version, so without this a pack carries entity names and marker
39
+ * colours but no shapes. Absent for an `--assets` run, which has no code to read — the honest
40
+ * answer there is no geometry rather than a silent gap.
41
+ */
42
+ classes?: ClassSource;
43
+ /** Where mappings cache; the jar cache directory. */
44
+ cacheDir?: string;
22
45
  onProgress?: (stage: string, done: number, total: number) => void;
23
46
  }
24
47
  interface BlockPackFailure {
@@ -27,7 +50,7 @@ interface BlockPackFailure {
27
50
  * `intentional` is not a failure: it marks a block that is empty because the
28
51
  * game draws nothing for it. Filter it out to get the real gaps.
29
52
  */
30
- kind: 'blockstate' | 'model' | 'texture' | 'sprite' | 'geometry' | 'tint' | 'biome' | 'intentional';
53
+ kind: 'blockstate' | 'model' | 'texture' | 'sprite' | 'geometry' | 'tint' | 'biome' | 'item' | 'entity' | 'intentional';
31
54
  reason: string;
32
55
  }
33
56
  interface BlockPackResult {
@@ -37,6 +60,12 @@ interface BlockPackResult {
37
60
  pages: number;
38
61
  /** Biomes described in `biomes.json`; `0` means the file was not written. */
39
62
  biomes: number;
63
+ /** Flat items described in `items.json`; `0` means the file was not written. */
64
+ items: number;
65
+ /** Entity types described in `entities.json`; `0` means the file was not written. */
66
+ entities: number;
67
+ /** Distinct entity models in `entity-models.json`; `0` means the file was not written. */
68
+ entityModels: number;
40
69
  /** Files actually written — the only trustworthy success signal. */
41
70
  writtenFiles: number;
42
71
  failures: BlockPackFailure[];
@@ -1,10 +1,14 @@
1
- import { t as packAtlas } from "./atlas-pack-CoOih7By.js";
2
1
  import { r as buildBiomeClimates } from "./biomes-5ZwYxMYe.js";
3
2
  import { n as ResourceLocator } from "./locator-COMEtEn9.js";
4
3
  import { n as blockEntityGeometry, r as synthesizeVariants, t as INVISIBLE_BLOCKS } from "./block-entities-xv3bQpEX.js";
5
4
  import { a as parseBlockState, n as collectModels } from "./blockstate-eval-BRQ3NWY2.js";
5
+ import { t as collectEntities } from "./entities-DvmA9fEV.js";
6
+ import { t as collectEntityGeometry } from "./entity-models-B6-0r2gR.js";
7
+ import { n as discoverItems } from "./discovery-CD0QqxSF.js";
6
8
  import { a as salvageJson, i as resolveTexture, t as ModelLoader } from "./loader-C-NuJaEi.js";
7
- import { n as buildObjMesh } from "./obj-mesh-rnYbSbpv.js";
9
+ import { t as collectItemSprites } from "./item-sprites-G-yikdfV.js";
10
+ import { n as buildObjMesh } from "./obj-mesh-JwPkZEWL.js";
11
+ import { n as buildSpriteSheet, t as MISSING_SPRITE } from "./sprite-sheet-LQAEtGkp.js";
8
12
  import { n as buildBlockTints, t as UNREGISTERED_TINTS } from "./tints-Cl0r-Ol3.js";
9
13
  import { t as loadAtlasResolver } from "./registry-DlduyjtM.js";
10
14
  import { n as LayeredResourceManager, t as FolderResourceManager } from "./manager-BNibYkmI.js";
@@ -37,12 +41,6 @@ const RENDER_TYPES = {
37
41
  translucent: "translucent",
38
42
  translucent_moving_block: "translucent"
39
43
  };
40
- /**
41
- * A sprite whose strip is longer than this exports as its first frame only.
42
- * A broken `.mcmeta` can claim thousands of frames, and packing them would
43
- * blow the atlas out for one texture.
44
- */
45
- const MAX_FRAMES_PER_SPRITE = 256;
46
44
  const FACE_DIRECTIONS = /* @__PURE__ */ new Set([
47
45
  "down",
48
46
  "up",
@@ -57,15 +55,6 @@ const CULLFACE_ALIASES = {
57
55
  top: "up"
58
56
  };
59
57
  /**
60
- * Vanilla's own id for the missing-texture sprite. A face whose texture does
61
- * not resolve is drawn by the game in black and magenta rather than skipped —
62
- * Blockbench leaves `"texture": "#missing"` on unassigned faces and mods ship
63
- * models naming textures they forgot to include. Dropping those faces would
64
- * put holes in geometry the player can see, and would read as our bug rather
65
- * than the mod's.
66
- */
67
- const MISSING_SPRITE = "minecraft:missingno";
68
- /**
69
58
  * Fluids are blocks in `blockstates.json` but the game meshes them in code, so
70
59
  * their models state nothing and their textures would otherwise never reach the
71
60
  * atlas — leaving a consumer that has to mesh fluids itself with no pixels to do
@@ -80,24 +69,6 @@ const MISSING_SPRITE = "minecraft:missingno";
80
69
  const FLUID_SUFFIX = /_(?:still|flow|flowing)$/;
81
70
  /** Sampled where water meets a non-opaque neighbour; matches no suffix. */
82
71
  const EXTRA_FLUID_SPRITES = ["minecraft:block/water_overlay"];
83
- /** `MissingTextureAtlasSprite`: 16x16, black and magenta in 8px quarters. */
84
- function missingTexture() {
85
- const size = 16;
86
- const data = /* @__PURE__ */ new Uint8ClampedArray(1024);
87
- for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) {
88
- const magenta = x < 8 !== y < 8;
89
- const offset = (y * size + x) * 4;
90
- data[offset] = magenta ? 248 : 0;
91
- data[offset + 1] = 0;
92
- data[offset + 2] = magenta ? 248 : 0;
93
- data[offset + 3] = 255;
94
- }
95
- return {
96
- width: size,
97
- height: size,
98
- data
99
- };
100
- }
101
72
  /**
102
73
  * Vanilla's `BlockElement.uvsByFace`: the UV a face gets when the model states
103
74
  * none. Deliberately not `render/geometry.ts`'s auto-UVs — those are in the
@@ -144,20 +115,6 @@ function defaultFaceUv(direction, from, to) {
144
115
  ];
145
116
  }
146
117
  }
147
- /**
148
- * Alpha decides the render layer: vanilla assets do not state cutout versus
149
- * translucent (`render_type` is a loader extension adopted only recently), and
150
- * inferring it covers mods for free.
151
- */
152
- function inferRenderLayer(frames) {
153
- let sawHole = false;
154
- for (const frame of frames) for (let offset = 3; offset < frame.data.length; offset += 4) {
155
- const alpha = frame.data[offset];
156
- if (alpha === 0) sawHole = true;
157
- else if (alpha !== 255) return "translucent";
158
- }
159
- return sawHole ? "cutout" : "solid";
160
- }
161
118
  function sortedByKey(entries) {
162
119
  return Object.fromEntries(Object.entries(entries).toSorted(([left], [right]) => left.localeCompare(right)));
163
120
  }
@@ -261,7 +218,7 @@ async function itemModelSubstitute(blockId, resources, models) {
261
218
  }
262
219
  const { emitted, sprites, tintIndices } = bakeModel(item.toString(), resolved, []);
263
220
  if (emitted.elements.length === 0) return null;
264
- if ((await Promise.all([...sprites].map(async (sprite) => sprite !== MISSING_SPRITE && await resources.hasResource("texture", ResourceLocator.parse(sprite))))).includes(false)) return null;
221
+ if ((await Promise.all([...sprites].map(async (sprite) => sprite !== "minecraft:missingno" && await resources.hasResource("texture", ResourceLocator.parse(sprite))))).includes(false)) return null;
265
222
  return {
266
223
  id: item.toString(),
267
224
  emitted,
@@ -493,7 +450,7 @@ async function exportBlockPack(options) {
493
450
  const substituteId = `rendermite:block/${ResourceLocator.parse(id).path}${suffix && `_${suffix}`}`;
494
451
  const { emitted, sprites, tintIndices } = bakeModel(substituteId, bound, failures);
495
452
  if (emitted.elements.length === 0) continue;
496
- if ((await Promise.all([...sprites].map(async (sprite) => sprite !== MISSING_SPRITE && await resources.hasResource("texture", ResourceLocator.parse(sprite))))).includes(false)) {
453
+ if ((await Promise.all([...sprites].map(async (sprite) => sprite !== "minecraft:missingno" && await resources.hasResource("texture", ResourceLocator.parse(sprite))))).includes(false)) {
497
454
  failures.push({
498
455
  id,
499
456
  kind: "geometry",
@@ -618,87 +575,20 @@ async function exportBlockPack(options) {
618
575
  }
619
576
  for (const namespace of namespaces) for (const locator of await resources.listResources("texture", namespace)) if (FLUID_SUFFIX.test(locator.path)) spriteIds.add(locator.toString());
620
577
  for (const extra of EXTRA_FLUID_SPRITES) if (await resources.hasResource("texture", ResourceLocator.parse(extra))) spriteIds.add(extra);
621
- const loaded = [];
622
- const missingSprites = /* @__PURE__ */ new Set();
623
- done = 0;
624
- for (const id of [...spriteIds].toSorted((left, right) => left.localeCompare(right))) {
625
- report("sprites", ++done, spriteIds.size);
626
- if (id === MISSING_SPRITE) continue;
627
- let animated;
628
- try {
629
- animated = await textures.loadAnimated(ResourceLocator.parse(id));
630
- } catch (err) {
631
- failures.push({
632
- id,
633
- kind: "sprite",
634
- reason: err.message
635
- });
636
- missingSprites.add(id);
637
- continue;
638
- }
639
- if (!animated) {
640
- failures.push({
641
- id,
642
- kind: "sprite",
643
- reason: "texture not found"
644
- });
645
- missingSprites.add(id);
646
- continue;
647
- }
648
- const overlong = animated.frameCount > MAX_FRAMES_PER_SPRITE;
649
- if (overlong) failures.push({
650
- id,
651
- kind: "sprite",
652
- reason: `${animated.frameCount} frames exceeds the ${MAX_FRAMES_PER_SPRITE} cap; exported static`
653
- });
654
- const starts = overlong ? [0] : animated.frameStartTicks();
655
- const total = overlong ? 1 : animated.tickLength;
656
- const sequence = starts.map((start, index) => {
657
- return {
658
- frame: animated.resolveFrame(start).frame,
659
- time: (index + 1 < starts.length ? starts[index + 1] : total) - start
660
- };
661
- });
662
- const frames = /* @__PURE__ */ new Map();
663
- for (const entry of sequence) if (!frames.has(entry.frame)) frames.set(entry.frame, animated.frame(entry.frame));
664
- loaded.push({
665
- id,
666
- frames,
667
- sequence,
668
- interpolate: animated.interpolated
669
- });
670
- }
671
- spriteIds.add(MISSING_SPRITE);
672
- loaded.push({
673
- id: MISSING_SPRITE,
674
- frames: /* @__PURE__ */ new Map([[0, missingTexture()]]),
675
- sequence: [{
676
- frame: 0,
677
- time: 1
678
- }],
679
- interpolate: false
680
- });
681
- const packed = packAtlas(loaded.flatMap((sprite) => [...sprite.frames].map(([index, texture]) => {
682
- return {
683
- key: `${sprite.id}#${index}`,
684
- texture
685
- };
686
- })), {
578
+ const sheet = await buildSpriteSheet(spriteIds, textures, {
687
579
  padding: options.padding,
688
- maxPageSize: options.maxPageSize
580
+ maxPageSize: options.maxPageSize,
581
+ includeMissing: true,
582
+ onProgress: (count, total) => report("sprites", count, total)
689
583
  });
690
- for (const key of packed.oversized) {
691
- const id = key.split("#", 1)[0];
692
- if (!missingSprites.has(id)) {
693
- failures.push({
694
- id,
695
- kind: "sprite",
696
- reason: "sprite is larger than one atlas page; drawn as the missing texture"
697
- });
698
- missingSprites.add(id);
699
- }
700
- }
701
- if (missingSprites.has(MISSING_SPRITE)) throw new Error("the missing-texture sprite does not fit an atlas page; maxPageSize is too small");
584
+ for (const failure of sheet.failures) failures.push({
585
+ id: failure.id,
586
+ kind: "sprite",
587
+ reason: failure.reason
588
+ });
589
+ const emittedSprites = sheet.sprites;
590
+ const missingSprites = sheet.missing;
591
+ spriteIds.add(MISSING_SPRITE);
702
592
  if (missingSprites.size > 0) for (const model of Object.values(emittedModels)) {
703
593
  for (const element of model.elements) for (const face of Object.values(element.faces)) if (missingSprites.has(face.sprite)) face.sprite = MISSING_SPRITE;
704
594
  for (const group of model.mesh ?? []) if (missingSprites.has(group.sprite)) group.sprite = MISSING_SPRITE;
@@ -706,7 +596,7 @@ async function exportBlockPack(options) {
706
596
  }
707
597
  for (const [id, model] of Object.entries(emittedModels)) {
708
598
  const sprites = [...model.elements.flatMap((element) => Object.values(element.faces).map((face) => face.sprite)), ...(model.mesh ?? []).map((group) => group.sprite)];
709
- if (sprites.length > 0 && sprites.every((sprite) => sprite === MISSING_SPRITE)) failures.push({
599
+ if (sprites.length > 0 && sprites.every((sprite) => sprite === "minecraft:missingno")) failures.push({
710
600
  id,
711
601
  kind: "texture",
712
602
  reason: "every face uses the missing-texture sprite; the model binds none of the textures it names"
@@ -717,32 +607,6 @@ async function exportBlockPack(options) {
717
607
  reason: "model has no drawable elements"
718
608
  });
719
609
  }
720
- const rects = new Map(packed.rects.map((rect) => [rect.key, rect]));
721
- const emittedSprites = {};
722
- for (const sprite of loaded) {
723
- const first = rects.get(`${sprite.id}#${sprite.sequence[0].frame}`);
724
- if (!first) continue;
725
- const frames = [];
726
- for (const entry of sprite.sequence) {
727
- const rect = rects.get(`${sprite.id}#${entry.frame}`);
728
- if (rect) frames.push({
729
- page: rect.page,
730
- x: rect.x,
731
- y: rect.y,
732
- time: entry.time
733
- });
734
- }
735
- emittedSprites[sprite.id] = {
736
- page: first.page,
737
- x: first.x,
738
- y: first.y,
739
- w: first.width,
740
- h: first.height,
741
- layer: inferRenderLayer(sprite.frames.values().toArray()),
742
- ...frames.length === sprite.sequence.length && frames.length > 1 && { frames },
743
- ...sprite.interpolate && { interpolate: true }
744
- };
745
- }
746
610
  const tints = await buildBlockTints(resources, textures);
747
611
  for (const [block, references] of blockModels) {
748
612
  const used = /* @__PURE__ */ new Set();
@@ -762,6 +626,142 @@ async function exportBlockPack(options) {
762
626
  }
763
627
  }
764
628
  const biomes = await buildBiomeClimates(resources, namespaces);
629
+ let itemPack = null;
630
+ let itemPages = [];
631
+ if (options.items ?? true) {
632
+ const itemIds = [];
633
+ for (const namespace of namespaces) for (const item of await discoverItems(resources, namespace)) itemIds.push(item.id);
634
+ const collected = await collectItemSprites({
635
+ resources,
636
+ models,
637
+ textures,
638
+ namespaces,
639
+ blocks: new Set(Object.keys(blockStates)),
640
+ itemIds,
641
+ onProgress: (count, total) => report("items", count, total)
642
+ });
643
+ for (const failure of collected.failures) failures.push({
644
+ id: failure.id,
645
+ kind: "item",
646
+ reason: failure.reason
647
+ });
648
+ const itemSheet = await buildSpriteSheet(collected.spriteIds, textures, {
649
+ padding: options.padding,
650
+ maxPageSize: options.maxPageSize,
651
+ onProgress: (count, total) => report("item sprites", count, total)
652
+ });
653
+ for (const failure of itemSheet.failures) failures.push({
654
+ id: failure.id,
655
+ kind: "sprite",
656
+ reason: failure.reason
657
+ });
658
+ const items = {};
659
+ for (const [id, entry] of Object.entries(collected.items)) {
660
+ const lost = entry.layers.filter((layer) => itemSheet.missing.has(layer.sprite));
661
+ if (lost.length > 0) {
662
+ failures.push({
663
+ id,
664
+ kind: "item",
665
+ reason: `layer sprite ${lost.map((layer) => layer.sprite).join(", ")} has no atlas rect; item dropped`
666
+ });
667
+ continue;
668
+ }
669
+ items[id] = entry;
670
+ }
671
+ if (Object.keys(items).length > 0) {
672
+ itemPages = itemSheet.pages;
673
+ itemPack = {
674
+ padding: itemSheet.padding,
675
+ pages: itemPages.map((page, index) => {
676
+ return {
677
+ file: `item-atlas-${index}.png`,
678
+ width: page.width,
679
+ height: page.height
680
+ };
681
+ }),
682
+ sprites: sortedByKey(itemSheet.sprites),
683
+ items: sortedByKey(items)
684
+ };
685
+ }
686
+ }
687
+ let entityPack = null;
688
+ let entityModelPack = null;
689
+ let entityPages = [];
690
+ if (options.entities ?? true) {
691
+ const collected = await collectEntities({
692
+ resources,
693
+ textures,
694
+ namespaces,
695
+ onProgress: (count, total) => report("entities", count, total)
696
+ });
697
+ for (const failure of collected.failures) failures.push({
698
+ id: failure.id,
699
+ kind: "entity",
700
+ reason: failure.reason
701
+ });
702
+ if (options.classes) {
703
+ const textureOf = /* @__PURE__ */ new Map();
704
+ for (const [id, entry] of Object.entries(collected.entities)) if (entry.texture) textureOf.set(id, entry.texture);
705
+ const geometry = await collectEntityGeometry({
706
+ version,
707
+ cacheDir: options.cacheDir ?? output,
708
+ classes: options.classes,
709
+ entityIds: Object.keys(collected.entities),
710
+ textureOf,
711
+ resources,
712
+ onProgress: (count, total) => report("entity geometry", count, total)
713
+ });
714
+ for (const failure of geometry.failures) failures.push({
715
+ id: failure.id,
716
+ kind: "entity",
717
+ reason: failure.reason
718
+ });
719
+ for (const [id, model] of geometry.modelOf) collected.entities[id].model = model;
720
+ if (Object.keys(geometry.models).length > 0) {
721
+ const sheet = await buildSpriteSheet(geometry.spriteIds, textures, {
722
+ padding: options.padding,
723
+ maxPageSize: options.maxPageSize,
724
+ onProgress: (count, total) => report("entity sprites", count, total)
725
+ });
726
+ for (const failure of sheet.failures) failures.push({
727
+ id: failure.id,
728
+ kind: "sprite",
729
+ reason: failure.reason
730
+ });
731
+ const sheetSizes = /* @__PURE__ */ new Map();
732
+ for (const [id, model] of geometry.modelOf) {
733
+ const sprite = sheet.sprites[collected.entities[id].texture ?? ""];
734
+ if (sprite) {
735
+ const sizes = sheetSizes.get(model) ?? /* @__PURE__ */ new Set();
736
+ sizes.add(`${sprite.w}x${sprite.h}`);
737
+ sheetSizes.set(model, sizes);
738
+ }
739
+ }
740
+ for (const [model, sizes] of sheetSizes) {
741
+ const [only] = [...sizes];
742
+ if (sizes.size === 1) {
743
+ const [width, height] = only.split("x").map(Number);
744
+ geometry.models[model].textureSize = [width, height];
745
+ }
746
+ }
747
+ entityPages = sheet.pages;
748
+ entityModelPack = {
749
+ padding: sheet.padding,
750
+ pages: entityPages.map((page, index) => {
751
+ return {
752
+ file: `entity-atlas-${index}.png`,
753
+ width: page.width,
754
+ height: page.height
755
+ };
756
+ }),
757
+ sprites: sortedByKey(sheet.sprites),
758
+ models: sortedByKey(geometry.models)
759
+ };
760
+ for (const [id, entry] of Object.entries(collected.entities)) if (entry.texture && sheet.missing.has(entry.texture)) delete collected.entities[id].texture;
761
+ }
762
+ }
763
+ if (Object.keys(collected.entities).length > 0) entityPack = sortedByKey(collected.entities);
764
+ }
765
765
  await mkdir(output, { recursive: true });
766
766
  const hashes = {};
767
767
  let writtenFiles = 0;
@@ -774,7 +774,7 @@ async function exportBlockPack(options) {
774
774
  writtenFiles++;
775
775
  };
776
776
  const pageFiles = [];
777
- for (const [index, page] of packed.pages.entries()) {
777
+ for (const [index, page] of sheet.pages.entries()) {
778
778
  const file = `atlas-${index}.png`;
779
779
  const raw = Buffer.from(page.data.buffer, page.data.byteOffset, page.data.byteLength);
780
780
  await emit(file, await sharp(raw, { raw: {
@@ -788,9 +788,28 @@ async function exportBlockPack(options) {
788
788
  height: page.height
789
789
  });
790
790
  }
791
+ for (const [index, page] of itemPages.entries()) {
792
+ const raw = Buffer.from(page.data.buffer, page.data.byteOffset, page.data.byteLength);
793
+ await emit(`item-atlas-${index}.png`, await sharp(raw, { raw: {
794
+ width: page.width,
795
+ height: page.height,
796
+ channels: 4
797
+ } }).png({ compressionLevel: 9 }).toBuffer());
798
+ }
799
+ if (itemPack) await emit("items.json", JSON.stringify(itemPack));
800
+ if (entityPack) await emit("entities.json", JSON.stringify(entityPack));
801
+ for (const [index, page] of entityPages.entries()) {
802
+ const raw = Buffer.from(page.data.buffer, page.data.byteOffset, page.data.byteLength);
803
+ await emit(`entity-atlas-${index}.png`, await sharp(raw, { raw: {
804
+ width: page.width,
805
+ height: page.height,
806
+ channels: 4
807
+ } }).png({ compressionLevel: 9 }).toBuffer());
808
+ }
809
+ if (entityModelPack) await emit("entity-models.json", JSON.stringify(entityModelPack));
791
810
  for (const file of tints.files) await emit(file.path, file.data);
792
811
  await emit("atlas.json", JSON.stringify({
793
- padding: packed.padding,
812
+ padding: sheet.padding,
794
813
  pages: pageFiles,
795
814
  sprites: sortedByKey(emittedSprites)
796
815
  }));
@@ -809,8 +828,11 @@ async function exportBlockPack(options) {
809
828
  blocks: Object.keys(blockStates).length,
810
829
  models: Object.keys(emittedModels).length,
811
830
  sprites: Object.keys(emittedSprites).length,
812
- pages: packed.pages.length,
831
+ pages: sheet.pages.length,
813
832
  biomes: Object.keys(biomes.pack).length,
833
+ items: Object.keys(itemPack?.items ?? {}).length,
834
+ entities: Object.keys(entityPack ?? {}).length,
835
+ entityModels: Object.keys(entityModelPack?.models ?? {}).length,
814
836
  failures: failures.length
815
837
  };
816
838
  await emit("meta.json", JSON.stringify({
@@ -0,0 +1,59 @@
1
+ import { n as ResourceLocator } from "./locator-jDbdUeTd.js";
2
+ import { a as ResourceManager } from "./manager-exOIcdyx.js";
3
+ import { i as TextureLoader } from "./loader-DUlx29PC.js";
4
+ import { n as FloatTexture } from "./blend-eT64Z6qm.js";
5
+ import { n as FaceGeometry } from "./geometry-Bkur58Vi.js";
6
+ import { i as RenderOptions } from "./renderer-C_DFi8DF.js";
7
+ //#region src/render/geo.d.ts
8
+ interface GeoCube {
9
+ origin?: number[];
10
+ size?: number[];
11
+ uv?: number[] | Record<string, {
12
+ uv: number[];
13
+ uv_size?: number[];
14
+ }>;
15
+ inflate?: number;
16
+ /** Bedrock mirrors a box's texture rather than its geometry, so only a consumer's UVs care. */
17
+ mirror?: boolean;
18
+ pivot?: number[];
19
+ rotation?: number[];
20
+ }
21
+ interface GeoBone {
22
+ name?: string;
23
+ parent?: string;
24
+ pivot?: number[];
25
+ rotation?: number[];
26
+ cubes?: GeoCube[];
27
+ }
28
+ interface GeoDescription {
29
+ texture_width?: number;
30
+ texture_height?: number;
31
+ }
32
+ interface GeoFile {
33
+ 'minecraft:geometry'?: Array<{
34
+ description?: GeoDescription;
35
+ bones?: GeoBone[];
36
+ }>;
37
+ [key: string]: unknown;
38
+ }
39
+ interface ParsedGeo {
40
+ textureWidth: number;
41
+ textureHeight: number;
42
+ bones: GeoBone[];
43
+ }
44
+ /** Handles both the modern (1.12+) and legacy (`geometry.name` keyed) layouts. */
45
+ declare function parseGeo(json: GeoFile): ParsedGeo | null;
46
+ /** Convert a parsed geo into face geometry, unit-cube fitted with gui pose. */
47
+ declare function geoToFaces(geo: ParsedGeo): FaceGeometry[];
48
+ /** Conventional locations GeckoLib mods keep item geometry and textures. */
49
+ declare function findGeoForItem(resources: ResourceManager, id: string): Promise<{
50
+ geo: ParsedGeo;
51
+ texture: ResourceLocator;
52
+ } | null>;
53
+ /** Name tokens, dropping the numeric suffixes mods use for shape variants. */
54
+ /** Tokens of a name, for the matches that need to see through a mod's own spelling of it. */
55
+ declare function nameTokens(name: string): string[];
56
+ /** Render a parsed geo with the standard block gui pose. */
57
+ declare function renderGeo(geo: ParsedGeo, texture: ResourceLocator, textures: TextureLoader, options: RenderOptions): Promise<FloatTexture | null>;
58
+ //#endregion
59
+ export { parseGeo as a, nameTokens as i, findGeoForItem as n, renderGeo as o, geoToFaces as r, ParsedGeo as t };
@@ -1,10 +1,10 @@
1
1
  import { n as ResourceLocator } from "./locator-COMEtEn9.js";
2
2
  import { l as toFloatTexture } from "./blend-KXMclcHk.js";
3
+ import { t as combineFaceBuffers } from "./compose-BGsNFR12.js";
4
+ import { i as createFaceBuffer } from "./rasterizer-DcVJzkxz.js";
3
5
  import { n as displayMatrix } from "./vector-CCxLae_T.js";
6
+ import { n as faceMaxZ, r as renderFace, t as createShadeCache } from "./face-B2JjWT46.js";
4
7
  import { c as lightsForProfile } from "./lighting--5myudVu.js";
5
- import { i as createFaceBuffer } from "./rasterizer-DcVJzkxz.js";
6
- import { t as combineFaceBuffers } from "./compose-BGsNFR12.js";
7
- import { n as faceMaxZ, r as renderFace, t as createShadeCache } from "./face-CKHboPfh.js";
8
8
  //#region src/render/geo.ts
9
9
  /**
10
10
  * Bedrock-format geometry (`.geo.json`) rendering, best-effort. GeckoLib mods
@@ -317,6 +317,7 @@ async function findGeoForItem(resources, id) {
317
317
  return null;
318
318
  }
319
319
  /** Name tokens, dropping the numeric suffixes mods use for shape variants. */
320
+ /** Tokens of a name, for the matches that need to see through a mod's own spelling of it. */
320
321
  function nameTokens(name) {
321
322
  return name.split(/[/_]/).filter((token) => token.length > 0 && !/^\d+$/.test(token));
322
323
  }
@@ -407,4 +408,4 @@ async function renderGeo(geo, texture, textures, options) {
407
408
  return combineFaceBuffers(renderSize, renderSize, buffers);
408
409
  }
409
410
  //#endregion
410
- export { renderGeo as i, geoToFaces as n, parseGeo as r, findGeoForItem as t };
411
+ export { renderGeo as a, parseGeo as i, geoToFaces as n, nameTokens as r, findGeoForItem as t };