@nodecraft/rendermite 1.4.0 → 1.5.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 (44) hide show
  1. package/README.md +1 -0
  2. package/dist/blocks/cli.js +1 -1
  3. package/dist/blocks/export.d.ts +1 -1
  4. package/dist/blocks/export.js +1 -1
  5. package/dist/blocks/item-sprites.d.ts +34 -0
  6. package/dist/blocks/item-sprites.js +2 -0
  7. package/dist/blocks/obj-mesh.js +1 -1
  8. package/dist/blocks/pack-format.d.ts +2 -2
  9. package/dist/blocks/sprite-sheet.d.ts +59 -0
  10. package/dist/blocks/sprite-sheet.js +2 -0
  11. package/dist/{cli-DqJtT-Ms.js → cli-DMTaIbDI.js} +9 -2
  12. package/dist/cli.js +3 -3
  13. package/dist/{discovery-CsZu08re.js → discovery-CD0QqxSF.js} +1 -1
  14. package/dist/{discovery-C5ZR4aoG.d.ts → discovery-Cmir3QfC.d.ts} +8 -1
  15. package/dist/{entity-fallback-BTkM8j0V.js → entity-fallback-DutR9Hdz.js} +1 -1
  16. package/dist/{export-kMoMes_a.js → export-sadR0pWp.js} +90 -160
  17. package/dist/{export-Bllg8Fw7.d.ts → export-wvt0Fryo.d.ts} +9 -1
  18. package/dist/{geo-BguldlU-.js → geo-BC8jtG5Q.js} +2 -2
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.js +10 -10
  21. package/dist/item-sprites-G-yikdfV.js +126 -0
  22. package/dist/items/discovery.d.ts +2 -2
  23. package/dist/items/discovery.js +2 -2
  24. package/dist/mods/prepare.js +1 -1
  25. package/dist/mods/tints.js +1 -1
  26. package/dist/{obj-bvs3gwex.js → obj-BaGvPUbb.js} +3 -3
  27. package/dist/{obj-mesh-rnYbSbpv.js → obj-mesh-CazmV8ya.js} +1 -1
  28. package/dist/{pack-format-BVC_B-ZW.d.ts → pack-format-oRExIeD3.d.ts} +42 -1
  29. package/dist/pack.d.ts +1 -1
  30. package/dist/{pipeline-B8e6x8Hh.js → pipeline-AAfohoHU.js} +7 -7
  31. package/dist/{prepare-C9gjqQTt.js → prepare-B_tRy9lB.js} +1 -1
  32. package/dist/render/entity-fallback.js +1 -1
  33. package/dist/render/face.js +1 -1
  34. package/dist/render/geo.js +1 -1
  35. package/dist/render/obj.js +1 -1
  36. package/dist/render/pipeline.js +1 -1
  37. package/dist/render/renderer.js +1 -1
  38. package/dist/{run-B_JAUbXe.js → run-UKgVb-kW.js} +2 -2
  39. package/dist/run.js +1 -1
  40. package/dist/sprite-sheet-LQAEtGkp.js +185 -0
  41. package/dist/workers/render-worker.js +1 -1
  42. package/package.json +1 -1
  43. package/dist/{face-CKHboPfh.js → face-CMJ3b3Ae.js} +1 -1
  44. package/dist/{tints-C1tT6pKk.js → tints-BspVCz2G.js} +1 -1
package/README.md CHANGED
@@ -75,6 +75,7 @@ blocks/1.21.8/
75
75
  blockstates.json # block id -> variants/multipart, keys and predicates pre-parsed
76
76
  tints.json # plains tints, colormaps, ramps, per-block tint semantics
77
77
  colormap/*.png # the version's own grass/foliage/dry_foliage maps
78
+ items.json # flat item sprite stacks + their own atlas, for drawing dropped stacks
78
79
  failures.json # anything not fully handled, with reasons, never silent
79
80
  meta.json # version, counts, sha256 of every other file
80
81
  ```
@@ -1,2 +1,2 @@
1
- import { t as runBlocksCli } from "../cli-DqJtT-Ms.js";
1
+ import { t as runBlocksCli } from "../cli-DMTaIbDI.js";
2
2
  export { runBlocksCli };
@@ -1,2 +1,2 @@
1
- import { i as exportBlockPack, n as BlockPackResult, r as ExportBlockPackOptions, t as BlockPackFailure } from "../export-Bllg8Fw7.js";
1
+ import { i as exportBlockPack, n as BlockPackResult, r as ExportBlockPackOptions, t as BlockPackFailure } from "../export-wvt0Fryo.js";
2
2
  export { BlockPackFailure, BlockPackResult, ExportBlockPackOptions, exportBlockPack };
@@ -1,2 +1,2 @@
1
- import { t as exportBlockPack } from "../export-kMoMes_a.js";
1
+ import { t as exportBlockPack } from "../export-sadR0pWp.js";
2
2
  export { exportBlockPack };
@@ -0,0 +1,34 @@
1
+ import { a as ResourceManager } from "../manager-exOIcdyx.js";
2
+ import { i as TextureLoader } from "../loader-DUlx29PC.js";
3
+ import { t as ModelLoader } from "../loader-DSir1-P0.js";
4
+ import { _ as ItemPackEntry } from "../pack-format-oRExIeD3.js";
5
+ //#region src/blocks/item-sprites.d.ts
6
+ interface ItemSpriteFailure {
7
+ id: string;
8
+ reason: string;
9
+ }
10
+ interface ItemSpriteResult {
11
+ /** Flat items only, by item id; anything absent is the consumer's block-model job. */
12
+ items: Record<string, ItemPackEntry>;
13
+ /** Sprite ids to pack, which is every layer of every entry. */
14
+ spriteIds: Set<string>;
15
+ failures: ItemSpriteFailure[];
16
+ }
17
+ interface CollectItemSpritesOptions {
18
+ resources: ResourceManager;
19
+ models: ModelLoader;
20
+ textures: TextureLoader;
21
+ namespaces: readonly string[];
22
+ /**
23
+ * Ids the pack describes as blocks. Only used to decide whether a non-flat item
24
+ * is worth reporting: a stone block item drawing from `blockstates.json` is the
25
+ * design, while one nothing describes is a gap somebody should hear about.
26
+ */
27
+ blocks: ReadonlySet<string>;
28
+ /** Item ids to consider, as `discoverItems` yields them. */
29
+ itemIds: readonly string[];
30
+ onProgress?: (done: number, total: number) => void;
31
+ }
32
+ declare function collectItemSprites(options: CollectItemSpritesOptions): Promise<ItemSpriteResult>;
33
+ //#endregion
34
+ export { CollectItemSpritesOptions, ItemSpriteFailure, ItemSpriteResult, collectItemSprites };
@@ -0,0 +1,2 @@
1
+ import { t as collectItemSprites } from "../item-sprites-G-yikdfV.js";
2
+ export { collectItemSprites };
@@ -1,2 +1,2 @@
1
- import { n as buildObjMesh, t as ObjMeshError } from "../obj-mesh-rnYbSbpv.js";
1
+ import { n as buildObjMesh, t as ObjMeshError } from "../obj-mesh-CazmV8ya.js";
2
2
  export { ObjMeshError, buildObjMesh };
@@ -1,2 +1,2 @@
1
- import { a as BlockPackBlockStatesFile, c as BlockPackMetaFile, d as BlockPackRenderLayer, f as BlockPackSprite, h as BlockPackVariant, i as BlockPackBlockState, l as BlockPackModel, m as BlockPackTintsFile, n as BlockPackAtlasPage, o as BlockPackElement, p as BlockPackSpriteFrame, r as BlockPackBiomesFile, s as BlockPackFace, t as BlockPackAtlasFile, u as BlockPackModelsFile } from "../pack-format-BVC_B-ZW.js";
2
- export { BlockPackAtlasFile, BlockPackAtlasPage, BlockPackBiomesFile, BlockPackBlockState, BlockPackBlockStatesFile, BlockPackElement, BlockPackFace, BlockPackMetaFile, BlockPackModel, BlockPackModelsFile, BlockPackRenderLayer, BlockPackSprite, BlockPackSpriteFrame, BlockPackTintsFile, BlockPackVariant };
1
+ import { _ as ItemPackEntry, a as BlockPackBlockStatesFile, c as BlockPackItemsFile, d as BlockPackModelsFile, f as BlockPackRenderLayer, g as BlockPackVariant, h as BlockPackTintsFile, i as BlockPackBlockState, l as BlockPackMetaFile, m as BlockPackSpriteFrame, n as BlockPackAtlasPage, o as BlockPackElement, p as BlockPackSprite, r as BlockPackBiomesFile, s as BlockPackFace, t as BlockPackAtlasFile, u as BlockPackModel, v as ItemPackLayer } from "../pack-format-oRExIeD3.js";
2
+ export { BlockPackAtlasFile, BlockPackAtlasPage, BlockPackBiomesFile, BlockPackBlockState, BlockPackBlockStatesFile, BlockPackElement, BlockPackFace, BlockPackItemsFile, BlockPackMetaFile, BlockPackModel, BlockPackModelsFile, BlockPackRenderLayer, BlockPackSprite, BlockPackSpriteFrame, BlockPackTintsFile, BlockPackVariant, ItemPackEntry, ItemPackLayer };
@@ -0,0 +1,59 @@
1
+ import { i as TextureLoader, r as Texture } from "../loader-DUlx29PC.js";
2
+ import { i as PackedPage } from "../atlas-pack-D8745_wo.js";
3
+ import { f as BlockPackRenderLayer, p as BlockPackSprite } from "../pack-format-oRExIeD3.js";
4
+ //#region src/blocks/sprite-sheet.d.ts
5
+ /**
6
+ * Vanilla's own id for the missing-texture sprite. A face whose texture does
7
+ * not resolve is drawn by the game in black and magenta rather than skipped —
8
+ * Blockbench leaves `"texture": "#missing"` on unassigned faces and mods ship
9
+ * models naming textures they forgot to include. Dropping those faces would
10
+ * put holes in geometry the player can see, and would read as our bug rather
11
+ * than the mod's.
12
+ */
13
+ declare const MISSING_SPRITE = "minecraft:missingno";
14
+ /** `MissingTextureAtlasSprite`: 16x16, black and magenta in 8px quarters. */
15
+ declare function missingTexture(): Texture;
16
+ /**
17
+ * Alpha decides the render layer: vanilla assets do not state cutout versus
18
+ * translucent (`render_type` is a loader extension adopted only recently), and
19
+ * inferring it covers mods for free.
20
+ */
21
+ declare function inferRenderLayer(frames: Texture[]): BlockPackRenderLayer;
22
+ interface SpriteSheetFailure {
23
+ id: string;
24
+ reason: string;
25
+ }
26
+ interface BuildSpriteSheetOptions {
27
+ /** Edge-extended border per sprite side (default 4). */
28
+ padding?: number;
29
+ /** Ceiling on a page's dimensions (default 4096). */
30
+ maxPageSize?: number;
31
+ /**
32
+ * Pack the black-and-magenta missing-texture sprite whether or not anything
33
+ * named it, and throw if it does not fit. What a consumer draws for something
34
+ * this pack does not describe at all; a guarantee worth its 256 pixels.
35
+ */
36
+ includeMissing?: boolean;
37
+ onProgress?: (done: number, total: number) => void;
38
+ }
39
+ interface SpriteSheet {
40
+ pages: PackedPage[];
41
+ /** Ready for `atlas.json`, unsorted — the caller decides the key order. */
42
+ sprites: Record<string, BlockPackSprite>;
43
+ /**
44
+ * Ids that never loaded or never fit a page, so have no rect. A caller must
45
+ * not emit anything naming one of these; the block path rewrites those
46
+ * references to `MISSING_SPRITE`, and the item path drops the item.
47
+ */
48
+ missing: Set<string>;
49
+ failures: SpriteSheetFailure[];
50
+ padding: number;
51
+ }
52
+ /**
53
+ * Loads every id, packs the frames, and returns the sheet plus everything that
54
+ * did not make it. Ids are packed in sorted order so an unchanged input produces
55
+ * a byte-identical atlas.
56
+ */
57
+ declare function buildSpriteSheet(ids: Iterable<string>, textures: TextureLoader, options?: BuildSpriteSheetOptions): Promise<SpriteSheet>;
58
+ //#endregion
59
+ export { BuildSpriteSheetOptions, MISSING_SPRITE, SpriteSheet, SpriteSheetFailure, buildSpriteSheet, inferRenderLayer, missingTexture };
@@ -0,0 +1,2 @@
1
+ import { i as missingTexture, n as buildSpriteSheet, r as inferRenderLayer, t as MISSING_SPRITE } from "../sprite-sheet-LQAEtGkp.js";
2
+ export { MISSING_SPRITE, buildSpriteSheet, inferRenderLayer, missingTexture };
@@ -1,7 +1,7 @@
1
1
  import { n as builtinPackPath } from "./builtin-WkWjATeh.js";
2
2
  import { n as LayeredResourceManager, t as FolderResourceManager } from "./manager-BNibYkmI.js";
3
- import { t as exportBlockPack } from "./export-kMoMes_a.js";
4
- import { n as prepareModResources } from "./prepare-C9gjqQTt.js";
3
+ import { t as exportBlockPack } from "./export-sadR0pWp.js";
4
+ import { n as prepareModResources } from "./prepare-B_tRy9lB.js";
5
5
  import { t as downloadClientJar } from "./download-1wAfrGKx.js";
6
6
  import { a as prepareVersionResources, r as compareVersions } from "./prepare-8LswFoS-.js";
7
7
  import path from "node:path";
@@ -34,6 +34,7 @@ Options:
34
34
  --max-page-size <px> atlas page ceiling (default 4096)
35
35
  --builtin <dir> override the vendored geometry pack
36
36
  --no-builtin do not substitute geometry for code-drawn blocks
37
+ --no-items do not export items.json (flat sprites for non-block items)
37
38
  --cache-dir <dir> jar download cache (default ~/.rendermite)
38
39
  `;
39
40
  /**
@@ -93,6 +94,10 @@ async function runBlocksCli(argv) {
93
94
  type: "boolean",
94
95
  default: false
95
96
  },
97
+ "no-items": {
98
+ type: "boolean",
99
+ default: false
100
+ },
96
101
  "help": {
97
102
  type: "boolean",
98
103
  short: "h",
@@ -146,11 +151,13 @@ async function runBlocksCli(argv) {
146
151
  namespaces,
147
152
  padding: sizes.padding,
148
153
  maxPageSize: sizes.maxPageSize,
154
+ items: !values["no-items"],
149
155
  onProgress: (stage, done, total) => {
150
156
  if (done === total || done % 250 === 0) console.log(`${stage}: ${done}/${total}`);
151
157
  }
152
158
  });
153
159
  console.log(`\n${result.blocks} blocks, ${result.models} models, ${result.sprites} sprites across ${result.pages} atlas page(s); ${result.writtenFiles} files written.`);
160
+ console.log(result.items > 0 ? `${result.items} flat items described in items.json.` : "No flat item sprites exported.");
154
161
  console.log(result.biomes > 0 ? `${result.biomes} biomes described in biomes.json.` : "No biomes to describe; biomes.json not written.");
155
162
  console.log(`${result.failures.length} entries in failures.json.`);
156
163
  return 0;
package/dist/cli.js CHANGED
@@ -2,12 +2,12 @@
2
2
  import { n as builtinPackPath, t as bedCompatPath } from "./builtin-WkWjATeh.js";
3
3
  import { n as LayeredResourceManager, t as FolderResourceManager } from "./manager-BNibYkmI.js";
4
4
  import { t as curatedModelOverrides } from "./models-CZjCOjs4.js";
5
- import { n as harvestTintOverrides, t as curatedTintOverrides } from "./tints-C1tT6pKk.js";
5
+ import { n as harvestTintOverrides, t as curatedTintOverrides } from "./tints-BspVCz2G.js";
6
6
  import { t as JarResourceManager } from "./jar-W2cV2ZVO.js";
7
7
  import { t as downloadClientJar } from "./download-1wAfrGKx.js";
8
8
  import { a as prepareVersionResources, i as eraForVersion, n as GEOMETRY_DONOR_VERSIONS } from "./prepare-8LswFoS-.js";
9
- import { t as runBlocksCli } from "./cli-DqJtT-Ms.js";
10
- import { t as run } from "./run-B_JAUbXe.js";
9
+ import { t as runBlocksCli } from "./cli-DMTaIbDI.js";
10
+ import { t as run } from "./run-UKgVb-kW.js";
11
11
  import path from "node:path";
12
12
  import os from "node:os";
13
13
  import { parseArgs } from "node:util";
@@ -99,4 +99,4 @@ async function resolveItem(resources, id, properties) {
99
99
  throw new ItemResolutionError(`No item definition or item model for ${id}`);
100
100
  }
101
101
  //#endregion
102
- export { discoverItems as n, resolveItem as r, ItemResolutionError as t };
102
+ export { resolveItem as i, discoverItems as n, langRegistry as r, ItemResolutionError as t };
@@ -17,6 +17,13 @@ interface ItemDiscoveryOptions {
17
17
  filterUntranslatedModels?: boolean;
18
18
  }
19
19
  declare function discoverItems(resources: ResourceManager, namespace: string, options?: ItemDiscoveryOptions): Promise<DiscoveredItem[]>;
20
+ /**
21
+ * Item/block names with a translation key in the namespace's en_us.json — the
22
+ * closest thing to the game's item registry a jar contains (datagen writes a
23
+ * key per registered item). Null (no gating) when the namespace has no modern
24
+ * lang file or no item/block keys: 1.7.10-era mods use unkeyed .lang files.
25
+ */
26
+ declare function langRegistry(resources: ResourceManager, namespace: string): Promise<Set<string> | null>;
20
27
  /**
21
28
  * Resolve an item id to the models to render. Definitions are evaluated with
22
29
  * static-icon properties; plain model items render the model with no tints
@@ -24,4 +31,4 @@ declare function discoverItems(resources: ResourceManager, namespace: string, op
24
31
  */
25
32
  declare function resolveItem(resources: ResourceManager, id: string, properties?: ItemProperties): Promise<ItemRenderInstruction[]>;
26
33
  //#endregion
27
- export { resolveItem as a, discoverItems as i, ItemDiscoveryOptions as n, ItemResolutionError as r, DiscoveredItem as t };
34
+ export { langRegistry as a, discoverItems as i, ItemDiscoveryOptions as n, resolveItem as o, ItemResolutionError as r, DiscoveredItem as t };
@@ -1,6 +1,6 @@
1
1
  import { n as ResourceLocator } from "./locator-COMEtEn9.js";
2
2
  import { a as findChestTexture, i as findBedTexture, l as findMobTexture, n as HEAD_LAYOUTS, o as findEntityTexture, r as coversSheet, t as DYE_COLORS, u as tokens } from "./entity-textures-DFRIVdF1.js";
3
- import { c as renderModel } from "./obj-bvs3gwex.js";
3
+ import { c as renderModel } from "./obj-BaGvPUbb.js";
4
4
  //#region src/render/entity-fallback.ts
5
5
  /**
6
6
  * Heuristic rendering for mod items that parent `builtin/entity` — chests,
@@ -1,10 +1,12 @@
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 { n as discoverItems } from "./discovery-CD0QqxSF.js";
6
6
  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";
7
+ import { t as collectItemSprites } from "./item-sprites-G-yikdfV.js";
8
+ import { n as buildObjMesh } from "./obj-mesh-CazmV8ya.js";
9
+ import { n as buildSpriteSheet, t as MISSING_SPRITE } from "./sprite-sheet-LQAEtGkp.js";
8
10
  import { n as buildBlockTints, t as UNREGISTERED_TINTS } from "./tints-Cl0r-Ol3.js";
9
11
  import { t as loadAtlasResolver } from "./registry-DlduyjtM.js";
10
12
  import { n as LayeredResourceManager, t as FolderResourceManager } from "./manager-BNibYkmI.js";
@@ -37,12 +39,6 @@ const RENDER_TYPES = {
37
39
  translucent: "translucent",
38
40
  translucent_moving_block: "translucent"
39
41
  };
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
42
  const FACE_DIRECTIONS = /* @__PURE__ */ new Set([
47
43
  "down",
48
44
  "up",
@@ -57,15 +53,6 @@ const CULLFACE_ALIASES = {
57
53
  top: "up"
58
54
  };
59
55
  /**
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
56
  * Fluids are blocks in `blockstates.json` but the game meshes them in code, so
70
57
  * their models state nothing and their textures would otherwise never reach the
71
58
  * atlas — leaving a consumer that has to mesh fluids itself with no pixels to do
@@ -80,24 +67,6 @@ const MISSING_SPRITE = "minecraft:missingno";
80
67
  const FLUID_SUFFIX = /_(?:still|flow|flowing)$/;
81
68
  /** Sampled where water meets a non-opaque neighbour; matches no suffix. */
82
69
  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
70
  /**
102
71
  * Vanilla's `BlockElement.uvsByFace`: the UV a face gets when the model states
103
72
  * none. Deliberately not `render/geometry.ts`'s auto-UVs — those are in the
@@ -144,20 +113,6 @@ function defaultFaceUv(direction, from, to) {
144
113
  ];
145
114
  }
146
115
  }
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
116
  function sortedByKey(entries) {
162
117
  return Object.fromEntries(Object.entries(entries).toSorted(([left], [right]) => left.localeCompare(right)));
163
118
  }
@@ -261,7 +216,7 @@ async function itemModelSubstitute(blockId, resources, models) {
261
216
  }
262
217
  const { emitted, sprites, tintIndices } = bakeModel(item.toString(), resolved, []);
263
218
  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;
219
+ if ((await Promise.all([...sprites].map(async (sprite) => sprite !== "minecraft:missingno" && await resources.hasResource("texture", ResourceLocator.parse(sprite))))).includes(false)) return null;
265
220
  return {
266
221
  id: item.toString(),
267
222
  emitted,
@@ -493,7 +448,7 @@ async function exportBlockPack(options) {
493
448
  const substituteId = `rendermite:block/${ResourceLocator.parse(id).path}${suffix && `_${suffix}`}`;
494
449
  const { emitted, sprites, tintIndices } = bakeModel(substituteId, bound, failures);
495
450
  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)) {
451
+ if ((await Promise.all([...sprites].map(async (sprite) => sprite !== "minecraft:missingno" && await resources.hasResource("texture", ResourceLocator.parse(sprite))))).includes(false)) {
497
452
  failures.push({
498
453
  id,
499
454
  kind: "geometry",
@@ -618,87 +573,20 @@ async function exportBlockPack(options) {
618
573
  }
619
574
  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
575
  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
- })), {
576
+ const sheet = await buildSpriteSheet(spriteIds, textures, {
687
577
  padding: options.padding,
688
- maxPageSize: options.maxPageSize
578
+ maxPageSize: options.maxPageSize,
579
+ includeMissing: true,
580
+ onProgress: (count, total) => report("sprites", count, total)
689
581
  });
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");
582
+ for (const failure of sheet.failures) failures.push({
583
+ id: failure.id,
584
+ kind: "sprite",
585
+ reason: failure.reason
586
+ });
587
+ const emittedSprites = sheet.sprites;
588
+ const missingSprites = sheet.missing;
589
+ spriteIds.add(MISSING_SPRITE);
702
590
  if (missingSprites.size > 0) for (const model of Object.values(emittedModels)) {
703
591
  for (const element of model.elements) for (const face of Object.values(element.faces)) if (missingSprites.has(face.sprite)) face.sprite = MISSING_SPRITE;
704
592
  for (const group of model.mesh ?? []) if (missingSprites.has(group.sprite)) group.sprite = MISSING_SPRITE;
@@ -706,7 +594,7 @@ async function exportBlockPack(options) {
706
594
  }
707
595
  for (const [id, model] of Object.entries(emittedModels)) {
708
596
  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({
597
+ if (sprites.length > 0 && sprites.every((sprite) => sprite === "minecraft:missingno")) failures.push({
710
598
  id,
711
599
  kind: "texture",
712
600
  reason: "every face uses the missing-texture sprite; the model binds none of the textures it names"
@@ -717,32 +605,6 @@ async function exportBlockPack(options) {
717
605
  reason: "model has no drawable elements"
718
606
  });
719
607
  }
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
608
  const tints = await buildBlockTints(resources, textures);
747
609
  for (const [block, references] of blockModels) {
748
610
  const used = /* @__PURE__ */ new Set();
@@ -762,6 +624,64 @@ async function exportBlockPack(options) {
762
624
  }
763
625
  }
764
626
  const biomes = await buildBiomeClimates(resources, namespaces);
627
+ let itemPack = null;
628
+ let itemPages = [];
629
+ if (options.items ?? true) {
630
+ const itemIds = [];
631
+ for (const namespace of namespaces) for (const item of await discoverItems(resources, namespace)) itemIds.push(item.id);
632
+ const collected = await collectItemSprites({
633
+ resources,
634
+ models,
635
+ textures,
636
+ namespaces,
637
+ blocks: new Set(Object.keys(blockStates)),
638
+ itemIds,
639
+ onProgress: (count, total) => report("items", count, total)
640
+ });
641
+ for (const failure of collected.failures) failures.push({
642
+ id: failure.id,
643
+ kind: "item",
644
+ reason: failure.reason
645
+ });
646
+ const itemSheet = await buildSpriteSheet(collected.spriteIds, textures, {
647
+ padding: options.padding,
648
+ maxPageSize: options.maxPageSize,
649
+ onProgress: (count, total) => report("item sprites", count, total)
650
+ });
651
+ for (const failure of itemSheet.failures) failures.push({
652
+ id: failure.id,
653
+ kind: "sprite",
654
+ reason: failure.reason
655
+ });
656
+ const items = {};
657
+ for (const [id, entry] of Object.entries(collected.items)) {
658
+ const lost = entry.layers.filter((layer) => itemSheet.missing.has(layer.sprite));
659
+ if (lost.length > 0) {
660
+ failures.push({
661
+ id,
662
+ kind: "item",
663
+ reason: `layer sprite ${lost.map((layer) => layer.sprite).join(", ")} has no atlas rect; item dropped`
664
+ });
665
+ continue;
666
+ }
667
+ items[id] = entry;
668
+ }
669
+ if (Object.keys(items).length > 0) {
670
+ itemPages = itemSheet.pages;
671
+ itemPack = {
672
+ padding: itemSheet.padding,
673
+ pages: itemPages.map((page, index) => {
674
+ return {
675
+ file: `item-atlas-${index}.png`,
676
+ width: page.width,
677
+ height: page.height
678
+ };
679
+ }),
680
+ sprites: sortedByKey(itemSheet.sprites),
681
+ items: sortedByKey(items)
682
+ };
683
+ }
684
+ }
765
685
  await mkdir(output, { recursive: true });
766
686
  const hashes = {};
767
687
  let writtenFiles = 0;
@@ -774,7 +694,7 @@ async function exportBlockPack(options) {
774
694
  writtenFiles++;
775
695
  };
776
696
  const pageFiles = [];
777
- for (const [index, page] of packed.pages.entries()) {
697
+ for (const [index, page] of sheet.pages.entries()) {
778
698
  const file = `atlas-${index}.png`;
779
699
  const raw = Buffer.from(page.data.buffer, page.data.byteOffset, page.data.byteLength);
780
700
  await emit(file, await sharp(raw, { raw: {
@@ -788,9 +708,18 @@ async function exportBlockPack(options) {
788
708
  height: page.height
789
709
  });
790
710
  }
711
+ for (const [index, page] of itemPages.entries()) {
712
+ const raw = Buffer.from(page.data.buffer, page.data.byteOffset, page.data.byteLength);
713
+ await emit(`item-atlas-${index}.png`, await sharp(raw, { raw: {
714
+ width: page.width,
715
+ height: page.height,
716
+ channels: 4
717
+ } }).png({ compressionLevel: 9 }).toBuffer());
718
+ }
719
+ if (itemPack) await emit("items.json", JSON.stringify(itemPack));
791
720
  for (const file of tints.files) await emit(file.path, file.data);
792
721
  await emit("atlas.json", JSON.stringify({
793
- padding: packed.padding,
722
+ padding: sheet.padding,
794
723
  pages: pageFiles,
795
724
  sprites: sortedByKey(emittedSprites)
796
725
  }));
@@ -809,8 +738,9 @@ async function exportBlockPack(options) {
809
738
  blocks: Object.keys(blockStates).length,
810
739
  models: Object.keys(emittedModels).length,
811
740
  sprites: Object.keys(emittedSprites).length,
812
- pages: packed.pages.length,
741
+ pages: sheet.pages.length,
813
742
  biomes: Object.keys(biomes.pack).length,
743
+ items: Object.keys(itemPack?.items ?? {}).length,
814
744
  failures: failures.length
815
745
  };
816
746
  await emit("meta.json", JSON.stringify({
@@ -19,6 +19,12 @@ interface ExportBlockPackOptions {
19
19
  padding?: number;
20
20
  /** Ceiling on an atlas page's dimensions (default 4096). */
21
21
  maxPageSize?: number;
22
+ /**
23
+ * Export `items.json` and its own atlas: the flat sprite stacks for items that
24
+ * are not blocks, which is what a world viewer draws a dropped stack of a lead
25
+ * or a handful of charcoal from. On by default.
26
+ */
27
+ items?: boolean;
22
28
  onProgress?: (stage: string, done: number, total: number) => void;
23
29
  }
24
30
  interface BlockPackFailure {
@@ -27,7 +33,7 @@ interface BlockPackFailure {
27
33
  * `intentional` is not a failure: it marks a block that is empty because the
28
34
  * game draws nothing for it. Filter it out to get the real gaps.
29
35
  */
30
- kind: 'blockstate' | 'model' | 'texture' | 'sprite' | 'geometry' | 'tint' | 'biome' | 'intentional';
36
+ kind: 'blockstate' | 'model' | 'texture' | 'sprite' | 'geometry' | 'tint' | 'biome' | 'item' | 'intentional';
31
37
  reason: string;
32
38
  }
33
39
  interface BlockPackResult {
@@ -37,6 +43,8 @@ interface BlockPackResult {
37
43
  pages: number;
38
44
  /** Biomes described in `biomes.json`; `0` means the file was not written. */
39
45
  biomes: number;
46
+ /** Flat items described in `items.json`; `0` means the file was not written. */
47
+ items: number;
40
48
  /** Files actually written — the only trustworthy success signal. */
41
49
  writtenFiles: number;
42
50
  failures: BlockPackFailure[];
@@ -1,10 +1,10 @@
1
1
  import { n as ResourceLocator } from "./locator-COMEtEn9.js";
2
- import { l as toFloatTexture } from "./blend-KXMclcHk.js";
3
2
  import { n as displayMatrix } from "./vector-CCxLae_T.js";
3
+ import { l as toFloatTexture } from "./blend-KXMclcHk.js";
4
4
  import { c as lightsForProfile } from "./lighting--5myudVu.js";
5
5
  import { i as createFaceBuffer } from "./rasterizer-DcVJzkxz.js";
6
6
  import { t as combineFaceBuffers } from "./compose-BGsNFR12.js";
7
- import { n as faceMaxZ, r as renderFace, t as createShadeCache } from "./face-CKHboPfh.js";
7
+ import { n as faceMaxZ, r as renderFace, t as createShadeCache } from "./face-CMJ3b3Ae.js";
8
8
  //#region src/render/geo.ts
9
9
  /**
10
10
  * Bedrock-format geometry (`.geo.json`) rendering, best-effort. GeckoLib mods
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ import { a as PackedRect, i as PackedPage, n as PackOptions, o as packAtlas, r a
6
6
  import { a as buildBiomeClimates, i as VANILLA_BIOME_CLIMATES, n as BiomeClimatePack, o as parseBiomeClimate, r as DEFAULT_WATER_COLOR, t as BiomeClimate } from "./biomes-CJDJQqJb.js";
7
7
  import { a as resolveTexture, n as ModelResolutionError, o as salvageJson, r as ResolvedModel, t as ModelLoader } from "./loader-DSir1-P0.js";
8
8
  import { a as BlockStateMultipartCase, d as matchesCondition, f as parseBlockState, i as BlockStateError, l as collectModels, n as BlockProperties, o as BlockStateVariantCase, p as weightedVariantIndex, r as BlockStateDefinition, s as MultipartCondition, t as BlockModelVariant, u as evaluateBlockState } from "./blockstate-eval-BzznRcVY.js";
9
- import { i as exportBlockPack, n as BlockPackResult, r as ExportBlockPackOptions, t as BlockPackFailure } from "./export-Bllg8Fw7.js";
9
+ import { i as exportBlockPack, n as BlockPackResult, r as ExportBlockPackOptions, t as BlockPackFailure } from "./export-wvt0Fryo.js";
10
10
  import { i as buildObjMesh, n as ObjMesh, r as ObjMeshError, t as MeshGroup } from "./obj-mesh-ydhVfLKs.js";
11
11
  import { a as buildBlockTints, i as UNREGISTERED_TINTS, n as BlockTintPack, t as BlockTint } from "./tints-DoBv5S9v.js";
12
12
  import { s as builtinDynamicResources } from "./generators-BnIzAftW.js";
@@ -14,7 +14,7 @@ import { d as toFloatTexture, n as FloatTexture, u as toByteTexture } from "./bl
14
14
  import { a as LightingProfileName, i as LightingProfile, r as LightSource, u as lightsForProfile } from "./lighting-3FGRC3Af.js";
15
15
  import { a as RenderedFrames, c as renderModel, i as RenderOptions, l as renderModelFrames, o as boxDownscale, r as ItemRenderError, s as pointResize } from "./renderer-C_DFi8DF.js";
16
16
  import { c as defaultProperties, i as ItemRenderInstruction, l as evaluateItemModel, n as InvalidItemDefinitionError, o as TintDefinition, r as ItemProperties, s as UNKNOWN_MODEL } from "./definition-ricL7Jqf.js";
17
- import { a as resolveItem, i as discoverItems, n as ItemDiscoveryOptions, r as ItemResolutionError, t as DiscoveredItem } from "./discovery-C5ZR4aoG.js";
17
+ import { i as discoverItems, n as ItemDiscoveryOptions, o as resolveItem, r as ItemResolutionError, t as DiscoveredItem } from "./discovery-Cmir3QfC.js";
18
18
  import { a as resolveTint, i as parseTintValue, n as InvalidTinterDefinitionError, o as resolveTints, t as DYE_COLORS } from "./tints-6z6ysGUs.js";
19
19
  import { i as EncodedIcon, n as EncodeOptions, o as encodeAnimatedIcon, r as EncodedAnimatedIcon, s as encodeIcon, t as AnimatedEncodeOptions } from "./encode-Cf8hI5OY.js";
20
20
  import { t as minifyStylesheet } from "./minify-CIV6enbr.js";