@nodecraft/rendermite 1.1.3 → 1.2.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 (127) hide show
  1. package/README.md +27 -2
  2. package/assets/builtin/renderchest/models/block/banner_standing.json +225 -0
  3. package/assets/builtin/renderchest/models/block/banner_wall.json +155 -0
  4. package/assets/builtin/renderchest/models/block/bed_foot.json +44 -0
  5. package/assets/builtin/renderchest/models/block/bed_head.json +44 -0
  6. package/assets/builtin/renderchest/models/block/chest_left.json +182 -0
  7. package/assets/builtin/renderchest/models/block/chest_right.json +182 -0
  8. package/assets/builtin/renderchest/models/block/copper_golem_statue.json +709 -0
  9. package/assets/builtin/renderchest/models/block/fluid.json +83 -0
  10. package/assets/builtin/renderchest/models/block/fluid_tinted.json +89 -0
  11. package/assets/builtin/renderchest/models/block/hanging_sign.json +54 -0
  12. package/assets/builtin/renderchest/models/block/standing_sign.json +32 -0
  13. package/assets/builtin/renderchest/models/block/wall_sign.json +19 -0
  14. package/dist/atlas/registry.js +1 -1
  15. package/dist/atlas/sources.js +1 -1
  16. package/dist/atlas-pack-CoOih7By.js +130 -0
  17. package/dist/atlas-pack-DRTrWcSU.d.ts +41 -0
  18. package/dist/block-entities-BbS1kv0m.js +481 -0
  19. package/dist/blocks/atlas-pack.d.ts +2 -0
  20. package/dist/blocks/atlas-pack.js +2 -0
  21. package/dist/blocks/block-entities.d.ts +93 -0
  22. package/dist/blocks/block-entities.js +2 -0
  23. package/dist/blocks/blockstate-eval.d.ts +2 -0
  24. package/dist/blocks/blockstate-eval.js +2 -0
  25. package/dist/blocks/cli.d.ts +9 -0
  26. package/dist/blocks/cli.js +2 -0
  27. package/dist/blocks/export.d.ts +2 -0
  28. package/dist/blocks/export.js +2 -0
  29. package/dist/blocks/obj-mesh.d.ts +2 -0
  30. package/dist/blocks/obj-mesh.js +2 -0
  31. package/dist/blocks/tints.d.ts +2 -0
  32. package/dist/blocks/tints.js +2 -0
  33. package/dist/{blockstate-CdstnW2-.js → blockstate-DVb8Mhxc.js} +1 -1
  34. package/dist/blockstate-eval-Ckjlyxn4.js +231 -0
  35. package/dist/blockstate-eval-DDArZs1P.d.ts +107 -0
  36. package/dist/cli-D7_oUuPT.js +161 -0
  37. package/dist/cli.js +9 -7
  38. package/dist/{descriptor-DWebtkI1.js → descriptor-BVIoEpPO.js} +3 -3
  39. package/dist/{discovery-B9aqV_Rz.js → discovery-CsZu08re.js} +1 -1
  40. package/dist/dynamic/generators.js +1 -1
  41. package/dist/{entity-fallback-Cu22uEIK.js → entity-fallback-DNO0Ta9C.js} +3 -155
  42. package/dist/entity-textures-DFRIVdF1.js +209 -0
  43. package/dist/export-BIpqNU3H.d.ts +44 -0
  44. package/dist/export-DxREy3qA.js +701 -0
  45. package/dist/{face-cQ-pVHoe.d.ts → face-BKwkeTps.d.ts} +1 -1
  46. package/dist/{fluid-bucket-BzoLofB_.js → fluid-bucket-B-I4ZYHl.js} +1 -1
  47. package/dist/{generators-BlG4KmtP.js → generators-BCZVlWig.js} +1 -1
  48. package/dist/{geo-w9HHIrb5.js → geo-BguldlU-.js} +5 -5
  49. package/dist/{geometry-w0yQbzDZ.d.ts → geometry-Bkur58Vi.d.ts} +1 -1
  50. package/dist/index.d.ts +10 -5
  51. package/dist/index.js +21 -16
  52. package/dist/{item-styles-upgEO1yP.js → item-styles-DjYRdyHi.js} +1 -1
  53. package/dist/items/blockstate.js +1 -1
  54. package/dist/items/discovery.js +1 -1
  55. package/dist/{jar-D9z-X7tA.js → jar-5aLDI0nT.js} +2 -2
  56. package/dist/{legacy-reshape-C05SfAPR.js → legacy-reshape-CcdstKyI.js} +2 -2
  57. package/dist/{legacy-tints-B09J0Rxh.js → legacy-tints-l-541wgI.js} +1 -1
  58. package/dist/{loader-CWsTZgrk.js → loader-BvLcevlN.js} +100 -9
  59. package/dist/{loader-Lb0ZeILz.js → loader-C18GpurU.js} +1 -1
  60. package/dist/{loader-DD83d80u.d.ts → loader-C_1_amsq.d.ts} +18 -1
  61. package/dist/{locator-ycZzh_6z.js → locator-COMEtEn9.js} +23 -1
  62. package/dist/{manager-CkletR5K.js → manager-CYbAkWuT.js} +2 -2
  63. package/dist/models/loader.d.ts +1 -1
  64. package/dist/models/loader.js +1 -1
  65. package/dist/models/types.d.ts +1 -1
  66. package/dist/{models-BKxGkPS7.js → models-CQwcTZcg.js} +1 -1
  67. package/dist/mods/models.js +1 -1
  68. package/dist/mods/prepare.js +1 -1
  69. package/dist/mods/synthesize.js +1 -1
  70. package/dist/mods/tints.js +1 -1
  71. package/dist/{renderer-DB1Oe7xs.js → obj-BEjq3_Ao.js} +234 -234
  72. package/dist/obj-mesh-C6Sp4Ysc.d.ts +41 -0
  73. package/dist/obj-mesh-CiHiqrlm.js +97 -0
  74. package/dist/output/item-styles.js +1 -1
  75. package/dist/{pipeline-Bp6amYz_.d.ts → pipeline-BDvXcw20.d.ts} +1 -1
  76. package/dist/{pipeline-CgJaVpoL.js → pipeline-DRMsBiod.js} +14 -14
  77. package/dist/{prepare-D56Z-9cg.js → prepare-B8oPdGYz.js} +5 -5
  78. package/dist/{prepare-Cc8QZ3j0.js → prepare-C95vjt_o.js} +5 -5
  79. package/dist/{registry-DS5PZ57l.js → registry-DlduyjtM.js} +2 -2
  80. package/dist/render/entity-fallback.d.ts +3 -3
  81. package/dist/render/entity-fallback.js +1 -1
  82. package/dist/render/face.d.ts +1 -1
  83. package/dist/render/face.js +1 -1
  84. package/dist/render/fluid-bucket.js +1 -1
  85. package/dist/render/geo.d.ts +2 -2
  86. package/dist/render/geo.js +1 -1
  87. package/dist/render/geometry.d.ts +1 -1
  88. package/dist/render/legacy-tints.d.ts +1 -1
  89. package/dist/render/legacy-tints.js +1 -1
  90. package/dist/render/obj.d.ts +10 -3
  91. package/dist/render/obj.js +2 -2
  92. package/dist/render/pipeline.d.ts +1 -1
  93. package/dist/render/pipeline.js +1 -1
  94. package/dist/render/renderer.d.ts +1 -1
  95. package/dist/render/renderer.js +1 -1
  96. package/dist/render/tints.d.ts +1 -1
  97. package/dist/render/tints.js +1 -1
  98. package/dist/{renderer-C79EQDSn.d.ts → renderer-U20hnhbi.d.ts} +2 -2
  99. package/dist/resources/descriptor.js +1 -1
  100. package/dist/resources/entity-textures.d.ts +51 -0
  101. package/dist/resources/entity-textures.js +2 -0
  102. package/dist/resources/jar.js +1 -1
  103. package/dist/resources/locator.js +1 -1
  104. package/dist/resources/manager.js +1 -1
  105. package/dist/resources/synthetic.js +1 -1
  106. package/dist/{run-uq8_hUx9.d.ts → run-CvIqiZ6T.d.ts} +1 -1
  107. package/dist/{run-BPfpBZyZ.js → run-D2S_W5As.js} +10 -7
  108. package/dist/run.d.ts +1 -1
  109. package/dist/run.js +1 -1
  110. package/dist/{sources-Bxk-WOzH.js → sources-BlUriDCs.js} +1 -1
  111. package/dist/{synthesize-3Q0aaOnK.js → synthesize-jTjVjPAt.js} +2 -2
  112. package/dist/{synthetic-ZAUjXT7E.js → synthetic-rc9BPT4P.js} +0 -0
  113. package/dist/textures/loader.js +1 -1
  114. package/dist/tints-2k3ZhkY3.d.ts +41 -0
  115. package/dist/{tints-3VI_2SNy.js → tints-B3WQcPtx.js} +1 -1
  116. package/dist/{tints-Do9Sh3dg.d.ts → tints-DqhgFi8a.d.ts} +1 -1
  117. package/dist/tints-a9FWZLma.js +176 -0
  118. package/dist/{tints-CebmvjmP.js → tints-nxtGrqv5.js} +3 -3
  119. package/dist/{types-DpRSF9Vj.d.ts → types-qlp3I8_-.d.ts} +11 -0
  120. package/dist/versions/download.js +1 -1
  121. package/dist/versions/legacy-reshape.js +1 -1
  122. package/dist/versions/prepare.js +1 -1
  123. package/dist/workers/render-worker.d.ts +1 -1
  124. package/dist/workers/render-worker.js +2 -2
  125. package/package.json +1 -1
  126. package/dist/{download-D9aARBFp.js → download-1wAfrGKx.js} +1 -1
  127. package/dist/{face-CMJ3b3Ae.js → face-CKHboPfh.js} +1 -1
@@ -0,0 +1,701 @@
1
+ import { t as packAtlas } from "./atlas-pack-CoOih7By.js";
2
+ import { n as ResourceLocator } from "./locator-COMEtEn9.js";
3
+ import { n as blockEntityGeometry, r as synthesizeVariants, t as INVISIBLE_BLOCKS } from "./block-entities-BbS1kv0m.js";
4
+ import { a as parseBlockState, n as collectModels } from "./blockstate-eval-Ckjlyxn4.js";
5
+ import { a as salvageJson, i as resolveTexture, t as ModelLoader } from "./loader-BvLcevlN.js";
6
+ import { n as buildObjMesh } from "./obj-mesh-CiHiqrlm.js";
7
+ import { t as buildBlockTints } from "./tints-a9FWZLma.js";
8
+ import { t as loadAtlasResolver } from "./registry-DlduyjtM.js";
9
+ import { n as LayeredResourceManager, t as FolderResourceManager } from "./manager-CYbAkWuT.js";
10
+ import { n as TextureLoader } from "./loader-C18GpurU.js";
11
+ import path from "node:path";
12
+ import { createHash } from "node:crypto";
13
+ import { mkdir, writeFile } from "node:fs/promises";
14
+ import sharp from "sharp";
15
+ //#region src/blocks/export.ts
16
+ /**
17
+ * Block pack export: block geometry and textures as data, for a renderer that
18
+ * draws blocks itself rather than consuming finished icons.
19
+ *
20
+ * Everything above the rasterizer is reused unchanged — jar layering, parent
21
+ * chain flattening, `#texture` resolution, atlas sources — and the output is
22
+ * flat: a stitched atlas plus JSON that names sprites, baked elements and the
23
+ * blockstate → model mapping with nothing left to resolve at runtime.
24
+ *
25
+ * Scope is Java Edition, 1.13 and up (see docs/block-pack-export.md). Nothing
26
+ * here is version-gated: 1.13+ sits entirely inside the `models` and `modern`
27
+ * eras, so `prepareVersionResources` layers the jar and little else.
28
+ */
29
+ /** `render_type` values collapsed onto the pack's three layers. */
30
+ const RENDER_TYPES = {
31
+ solid: "solid",
32
+ cutout: "cutout",
33
+ cutout_mipped: "cutout",
34
+ cutout_mipped_all: "cutout",
35
+ tripwire: "cutout",
36
+ translucent: "translucent",
37
+ translucent_moving_block: "translucent"
38
+ };
39
+ /**
40
+ * A sprite whose strip is longer than this exports as its first frame only.
41
+ * A broken `.mcmeta` can claim thousands of frames, and packing them would
42
+ * blow the atlas out for one texture.
43
+ */
44
+ const MAX_FRAMES_PER_SPRITE = 256;
45
+ const FACE_DIRECTIONS = /* @__PURE__ */ new Set([
46
+ "down",
47
+ "up",
48
+ "north",
49
+ "south",
50
+ "west",
51
+ "east"
52
+ ]);
53
+ /** Old models spell the vertical faces `bottom`/`top` in `cullface`. */
54
+ const CULLFACE_ALIASES = {
55
+ bottom: "down",
56
+ top: "up"
57
+ };
58
+ /**
59
+ * Vanilla's own id for the missing-texture sprite. A face whose texture does
60
+ * not resolve is drawn by the game in black and magenta rather than skipped —
61
+ * Blockbench leaves `"texture": "#missing"` on unassigned faces and mods ship
62
+ * models naming textures they forgot to include. Dropping those faces would
63
+ * put holes in geometry the player can see, and would read as our bug rather
64
+ * than the mod's.
65
+ */
66
+ const MISSING_SPRITE = "minecraft:missingno";
67
+ /**
68
+ * Fluids are blocks in `blockstates.json` but the game meshes them in code, so
69
+ * their models state nothing and their textures would otherwise never reach the
70
+ * atlas — leaving a consumer that has to mesh fluids itself with no pixels to do
71
+ * it from.
72
+ *
73
+ * Which textures those are is read off the files rather than listed: a fluid's
74
+ * two frames are `<name>_still` and `<name>_flow` (or `_flowing`) by a
75
+ * convention every loader follows, and mods put them wherever they like —
76
+ * Create under `fluid/`, Immersive Engineering under `block/fluid/`, Mekanism
77
+ * under `liquid/`. A vanilla-only list would have missed all of them.
78
+ */
79
+ const FLUID_SUFFIX = /_(?:still|flow|flowing)$/;
80
+ /** Sampled where water meets a non-opaque neighbour; matches no suffix. */
81
+ const EXTRA_FLUID_SPRITES = ["minecraft:block/water_overlay"];
82
+ /** `MissingTextureAtlasSprite`: 16x16, black and magenta in 8px quarters. */
83
+ function missingTexture() {
84
+ const size = 16;
85
+ const data = /* @__PURE__ */ new Uint8ClampedArray(1024);
86
+ for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) {
87
+ const magenta = x < 8 !== y < 8;
88
+ const offset = (y * size + x) * 4;
89
+ data[offset] = magenta ? 248 : 0;
90
+ data[offset + 1] = 0;
91
+ data[offset + 2] = magenta ? 248 : 0;
92
+ data[offset + 3] = 255;
93
+ }
94
+ return {
95
+ width: size,
96
+ height: size,
97
+ data
98
+ };
99
+ }
100
+ /**
101
+ * Vanilla's `BlockElement.uvsByFace`: the UV a face gets when the model states
102
+ * none. Deliberately not `render/geometry.ts`'s auto-UVs — those are in the
103
+ * rasterizer's own unflipped convention, and the pack has to speak the same
104
+ * language a model file's explicit `uv` does.
105
+ */
106
+ function defaultFaceUv(direction, from, to) {
107
+ switch (direction) {
108
+ case "down": return [
109
+ from[0],
110
+ 16 - to[2],
111
+ to[0],
112
+ 16 - from[2]
113
+ ];
114
+ case "up": return [
115
+ from[0],
116
+ from[2],
117
+ to[0],
118
+ to[2]
119
+ ];
120
+ case "north": return [
121
+ 16 - to[0],
122
+ 16 - to[1],
123
+ 16 - from[0],
124
+ 16 - from[1]
125
+ ];
126
+ case "south": return [
127
+ from[0],
128
+ 16 - to[1],
129
+ to[0],
130
+ 16 - from[1]
131
+ ];
132
+ case "west": return [
133
+ from[2],
134
+ 16 - to[1],
135
+ to[2],
136
+ 16 - from[1]
137
+ ];
138
+ case "east": return [
139
+ 16 - to[2],
140
+ 16 - to[1],
141
+ 16 - from[2],
142
+ 16 - from[1]
143
+ ];
144
+ }
145
+ }
146
+ /**
147
+ * Alpha decides the render layer: vanilla assets do not state cutout versus
148
+ * translucent (`render_type` is a loader extension adopted only recently), and
149
+ * inferring it covers mods for free.
150
+ */
151
+ function inferRenderLayer(frames) {
152
+ let sawHole = false;
153
+ for (const frame of frames) for (let offset = 3; offset < frame.data.length; offset += 4) {
154
+ const alpha = frame.data[offset];
155
+ if (alpha === 0) sawHole = true;
156
+ else if (alpha !== 255) return "translucent";
157
+ }
158
+ return sawHole ? "cutout" : "solid";
159
+ }
160
+ function sortedByKey(entries) {
161
+ return Object.fromEntries(Object.entries(entries).toSorted(([left], [right]) => left.localeCompare(right)));
162
+ }
163
+ /**
164
+ * Serialize a definition, keeping only the variants whose model was exported.
165
+ * Returns null when that leaves nothing — the consumer must never be handed a
166
+ * model id the pack does not carry.
167
+ */
168
+ /**
169
+ * Shifts a vendored model's boxes without touching anything else. The array is
170
+ * returned as-is when there is nothing to shift, because models loaded from a
171
+ * shared parent hand out the same element array and callers rely on that.
172
+ */
173
+ function translateElements(elements, offset) {
174
+ if (offset === void 0) return elements;
175
+ const move = (point) => [
176
+ point[0] + offset[0],
177
+ point[1] + offset[1],
178
+ point[2] + offset[2]
179
+ ];
180
+ return elements.map((element) => {
181
+ const rotation = element.rotation;
182
+ return {
183
+ ...element,
184
+ from: move(element.from),
185
+ to: move(element.to),
186
+ ...rotation && { rotation: {
187
+ ...rotation,
188
+ origin: move(rotation.origin)
189
+ } }
190
+ };
191
+ });
192
+ }
193
+ function serializeBlockState(definition, exported) {
194
+ const cases = [];
195
+ for (const entry of definition.cases) {
196
+ const apply = entry.apply.filter((variant) => exported(variant.model));
197
+ if (apply.length === 0) continue;
198
+ cases.push({
199
+ when: entry.when,
200
+ apply: apply.map((variant) => {
201
+ return {
202
+ model: variant.model,
203
+ ...variant.x !== 0 && { x: variant.x },
204
+ ...variant.y !== 0 && { y: variant.y },
205
+ ...variant.z !== 0 && { z: variant.z },
206
+ ...variant.uvlock && { uvlock: true },
207
+ ...variant.weight !== 1 && { weight: variant.weight }
208
+ };
209
+ })
210
+ });
211
+ }
212
+ if (cases.length === 0) return null;
213
+ return definition.kind === "variants" ? { variants: cases } : { multipart: cases };
214
+ }
215
+ /** Bake one flattened model into pack form, reporting every face it loses. */
216
+ function bakeModel(id, model, failures) {
217
+ const sprites = /* @__PURE__ */ new Set();
218
+ const tintIndices = /* @__PURE__ */ new Set();
219
+ const elements = [];
220
+ for (const element of model.elements) {
221
+ const faces = {};
222
+ for (const [name, face] of Object.entries(element.faces)) {
223
+ if (!face || !FACE_DIRECTIONS.has(name)) continue;
224
+ const direction = name;
225
+ const texture = resolveTexture(model, face.texture);
226
+ if (texture === null) failures.push({
227
+ id,
228
+ kind: "texture",
229
+ reason: `unresolved texture ${face.texture} on ${direction}; using the missing-texture sprite`
230
+ });
231
+ const sprite = texture === null ? MISSING_SPRITE : texture.toString();
232
+ sprites.add(sprite);
233
+ const cullRaw = typeof face.cullface === "string" ? face.cullface : null;
234
+ const cullface = cullRaw === null ? void 0 : CULLFACE_ALIASES[cullRaw] ?? (FACE_DIRECTIONS.has(cullRaw) ? cullRaw : void 0);
235
+ if (typeof face.tintindex === "number" && face.tintindex >= 0) tintIndices.add(face.tintindex);
236
+ faces[direction] = {
237
+ sprite,
238
+ uv: face.uv ?? defaultFaceUv(direction, element.from, element.to),
239
+ ...face.rotation && { rotation: face.rotation },
240
+ ...typeof face.tintindex === "number" && face.tintindex >= 0 && { tintindex: face.tintindex },
241
+ ...cullface !== void 0 && { cullface }
242
+ };
243
+ }
244
+ if (Object.keys(faces).length === 0) continue;
245
+ elements.push({
246
+ from: element.from,
247
+ to: element.to,
248
+ ...element.rotation && { rotation: element.rotation },
249
+ ...element.shade === false && { shade: false },
250
+ faces
251
+ });
252
+ }
253
+ const renderType = model.renderType === null ? void 0 : RENDER_TYPES[model.renderType];
254
+ const particle = resolveTexture(model, "#particle");
255
+ if (particle !== null) sprites.add(particle.toString());
256
+ return {
257
+ emitted: {
258
+ ao: model.ambientOcclusion,
259
+ elements,
260
+ ...particle !== null && { particle: particle.toString() },
261
+ ...renderType !== void 0 && { renderType },
262
+ ...model.splitParts && { multiblock: model.splitParts }
263
+ },
264
+ sprites,
265
+ tintIndices
266
+ };
267
+ }
268
+ async function exportBlockPack(options) {
269
+ const { output, version } = options;
270
+ const resources = options.builtin ? new LayeredResourceManager([options.resources, new FolderResourceManager(options.builtin)]) : options.resources;
271
+ const namespaces = options.namespaces ?? ["minecraft"];
272
+ const failures = [];
273
+ const report = (stage, done, total) => options.onProgress?.(stage, done, total);
274
+ const textures = new TextureLoader(resources);
275
+ textures.setAtlasResolver(await loadAtlasResolver(resources, (locator) => textures.load(locator)));
276
+ const models = new ModelLoader(resources);
277
+ const definitions = /* @__PURE__ */ new Map();
278
+ const blockModels = /* @__PURE__ */ new Map();
279
+ const modelRefs = /* @__PURE__ */ new Set();
280
+ const blockLocators = [];
281
+ for (const namespace of namespaces) blockLocators.push(...await resources.listResources("blockstate", namespace));
282
+ let done = 0;
283
+ for (const locator of blockLocators) {
284
+ const id = locator.toString();
285
+ report("blockstates", ++done, blockLocators.length);
286
+ const raw = await resources.getResource("blockstate", locator);
287
+ if (raw === null) {
288
+ failures.push({
289
+ id,
290
+ kind: "blockstate",
291
+ reason: "listed but unreadable"
292
+ });
293
+ continue;
294
+ }
295
+ try {
296
+ const text = raw.toString("utf8");
297
+ let json;
298
+ try {
299
+ json = JSON.parse(text);
300
+ } catch {
301
+ json = salvageJson(text);
302
+ }
303
+ const definition = parseBlockState(locator.namespace, json, id, {
304
+ onCaseError: (reason) => {
305
+ failures.push({
306
+ id,
307
+ kind: "blockstate",
308
+ reason: `${reason}; case dropped`
309
+ });
310
+ },
311
+ onNote: (reason) => {
312
+ failures.push({
313
+ id,
314
+ kind: "blockstate",
315
+ reason
316
+ });
317
+ }
318
+ });
319
+ const references = collectModels(definition);
320
+ definitions.set(id, definition);
321
+ blockModels.set(id, references);
322
+ for (const model of references) modelRefs.add(model);
323
+ } catch (err) {
324
+ failures.push({
325
+ id,
326
+ kind: "blockstate",
327
+ reason: err.message
328
+ });
329
+ }
330
+ }
331
+ const emittedModels = {};
332
+ const modelTintIndices = /* @__PURE__ */ new Map();
333
+ const spriteIds = /* @__PURE__ */ new Set();
334
+ done = 0;
335
+ for (const reference of [...modelRefs].toSorted((left, right) => left.localeCompare(right))) {
336
+ report("models", ++done, modelRefs.size);
337
+ let resolved;
338
+ try {
339
+ resolved = await models.load(reference);
340
+ } catch (err) {
341
+ failures.push({
342
+ id: reference,
343
+ kind: "model",
344
+ reason: err.message
345
+ });
346
+ continue;
347
+ }
348
+ const { emitted, sprites, tintIndices } = bakeModel(reference, resolved, failures);
349
+ if (resolved.splitParts) failures.push({
350
+ id: reference,
351
+ kind: "geometry",
352
+ reason: `split multiblock spanning ${resolved.splitParts.length} block positions; the pack carries the whole mesh, since which slice a position draws lives in the block entity`
353
+ });
354
+ if (resolved.obj) try {
355
+ const mesh = await buildObjMesh(resolved, resources, MISSING_SPRITE);
356
+ if (mesh) {
357
+ emitted.mesh = mesh.groups;
358
+ for (const group of mesh.groups) sprites.add(group.sprite);
359
+ for (const material of mesh.unresolved) failures.push({
360
+ id: reference,
361
+ kind: "texture",
362
+ reason: `OBJ material ${material} resolves to no texture; using the missing-texture sprite`
363
+ });
364
+ if (mesh.conditional && mesh.available.length > 1) failures.push({
365
+ id: reference,
366
+ kind: "geometry",
367
+ reason: `the loader picks between this OBJ's ${mesh.available.length} named objects at runtime (${mesh.available.slice(0, 8).join(", ")}${mesh.available.length > 8 ? ", …" : ""}) and nothing in the assets says which; the pack draws all of them, tagged with their object names`
368
+ });
369
+ }
370
+ } catch (err) {
371
+ failures.push({
372
+ id: reference,
373
+ kind: "geometry",
374
+ reason: err.message
375
+ });
376
+ }
377
+ emittedModels[reference] = emitted;
378
+ modelTintIndices.set(reference, tintIndices);
379
+ for (const sprite of sprites) spriteIds.add(sprite);
380
+ }
381
+ for (const [id, references] of blockModels) {
382
+ if (references.some((reference) => {
383
+ const model = emittedModels[reference];
384
+ return model !== void 0 && (model.elements.length > 0 || model.mesh?.length);
385
+ })) continue;
386
+ const invisible = INVISIBLE_BLOCKS[id];
387
+ if (invisible !== void 0) {
388
+ failures.push({
389
+ id,
390
+ kind: "intentional",
391
+ reason: `empty on purpose: ${invisible}`
392
+ });
393
+ continue;
394
+ }
395
+ const geometry = await blockEntityGeometry(id, {
396
+ resources,
397
+ textures,
398
+ models
399
+ });
400
+ if (geometry === null) {
401
+ failures.push({
402
+ id,
403
+ kind: "geometry",
404
+ reason: "the game draws this block in code and no geometry is vendored for it"
405
+ });
406
+ continue;
407
+ }
408
+ const cases = [];
409
+ const substituted = [];
410
+ for (const shape of geometry.shapes) {
411
+ const textures = {
412
+ ...geometry.textures,
413
+ ...shape.textures
414
+ };
415
+ if ((await Promise.all(Object.values(textures).map(async (texture) => resources.hasResource("texture", ResourceLocator.parse(texture))))).includes(false)) continue;
416
+ let resolved;
417
+ try {
418
+ resolved = await models.load(shape.model);
419
+ } catch (err) {
420
+ failures.push({
421
+ id,
422
+ kind: "geometry",
423
+ reason: `substitute ${shape.model} did not load: ${err.message}`
424
+ });
425
+ continue;
426
+ }
427
+ const bound = {
428
+ ...resolved,
429
+ textures: {
430
+ ...resolved.textures,
431
+ ...textures
432
+ },
433
+ elements: translateElements(resolved.elements, shape.translate)
434
+ };
435
+ const suffix = Object.values(shape.when ?? {}).join("_");
436
+ const substituteId = `rendermite:block/${ResourceLocator.parse(id).path}${suffix && `_${suffix}`}`;
437
+ const { emitted, sprites, tintIndices } = bakeModel(substituteId, bound, failures);
438
+ if (emitted.elements.length === 0) continue;
439
+ if ((await Promise.all([...sprites].map(async (sprite) => sprite !== MISSING_SPRITE && await resources.hasResource("texture", ResourceLocator.parse(sprite))))).includes(false)) {
440
+ failures.push({
441
+ id,
442
+ kind: "geometry",
443
+ reason: `substitute ${shape.model} left textures unresolved, so the block is left empty rather than part magenta`
444
+ });
445
+ continue;
446
+ }
447
+ const still = emitted.elements[0].faces.up?.sprite;
448
+ const flow = emitted.elements[0].faces.north?.sprite;
449
+ if (geometry.fluid !== void 0 && still !== void 0 && flow !== void 0) emitted.fluid = {
450
+ still,
451
+ flow,
452
+ ...geometry.fluid
453
+ };
454
+ emittedModels[substituteId] = emitted;
455
+ modelTintIndices.set(substituteId, tintIndices);
456
+ substituted.push(substituteId);
457
+ for (const sprite of sprites) spriteIds.add(sprite);
458
+ for (const variant of synthesizeVariants(geometry.orient)) cases.push({
459
+ when: {
460
+ ...shape.when,
461
+ ...variant.when
462
+ },
463
+ apply: [{
464
+ model: substituteId,
465
+ x: variant.x,
466
+ y: variant.y,
467
+ z: 0,
468
+ uvlock: false,
469
+ weight: 1
470
+ }]
471
+ });
472
+ }
473
+ if (cases.length === 0) {
474
+ failures.push({
475
+ id,
476
+ kind: "geometry",
477
+ reason: `the game draws this block in code; vendored ${geometry.shapes.map((shape) => shape.model).join(", ")} could not be bound to it`
478
+ });
479
+ continue;
480
+ }
481
+ definitions.set(id, {
482
+ kind: "variants",
483
+ cases
484
+ });
485
+ blockModels.set(id, substituted);
486
+ failures.push({
487
+ id,
488
+ kind: "geometry",
489
+ reason: `the game draws this block in code; the pack substitutes vendored geometry from ${geometry.shapes.map((shape) => shape.model).join(", ")} and synthesises the orientation variants the blockstate does not carry`
490
+ });
491
+ }
492
+ const blockStates = {};
493
+ for (const [id, definition] of definitions) {
494
+ const serialized = serializeBlockState(definition, (model) => emittedModels[model] !== void 0);
495
+ if (serialized === null) {
496
+ failures.push({
497
+ id,
498
+ kind: "blockstate",
499
+ reason: "every case named a model that could not be exported"
500
+ });
501
+ blockModels.delete(id);
502
+ continue;
503
+ }
504
+ blockStates[id] = serialized;
505
+ }
506
+ for (const namespace of namespaces) for (const locator of await resources.listResources("texture", namespace)) if (FLUID_SUFFIX.test(locator.path)) spriteIds.add(locator.toString());
507
+ for (const extra of EXTRA_FLUID_SPRITES) if (await resources.hasResource("texture", ResourceLocator.parse(extra))) spriteIds.add(extra);
508
+ const loaded = [];
509
+ const missingSprites = /* @__PURE__ */ new Set();
510
+ done = 0;
511
+ for (const id of [...spriteIds].toSorted((left, right) => left.localeCompare(right))) {
512
+ report("sprites", ++done, spriteIds.size);
513
+ if (id === MISSING_SPRITE) continue;
514
+ let animated;
515
+ try {
516
+ animated = await textures.loadAnimated(ResourceLocator.parse(id));
517
+ } catch (err) {
518
+ failures.push({
519
+ id,
520
+ kind: "sprite",
521
+ reason: err.message
522
+ });
523
+ missingSprites.add(id);
524
+ continue;
525
+ }
526
+ if (!animated) {
527
+ failures.push({
528
+ id,
529
+ kind: "sprite",
530
+ reason: "texture not found"
531
+ });
532
+ missingSprites.add(id);
533
+ continue;
534
+ }
535
+ const overlong = animated.frameCount > MAX_FRAMES_PER_SPRITE;
536
+ if (overlong) failures.push({
537
+ id,
538
+ kind: "sprite",
539
+ reason: `${animated.frameCount} frames exceeds the ${MAX_FRAMES_PER_SPRITE} cap; exported static`
540
+ });
541
+ const starts = overlong ? [0] : animated.frameStartTicks();
542
+ const total = overlong ? 1 : animated.tickLength;
543
+ const sequence = starts.map((start, index) => {
544
+ return {
545
+ frame: animated.resolveFrame(start).frame,
546
+ time: (index + 1 < starts.length ? starts[index + 1] : total) - start
547
+ };
548
+ });
549
+ const frames = /* @__PURE__ */ new Map();
550
+ for (const entry of sequence) if (!frames.has(entry.frame)) frames.set(entry.frame, animated.frame(entry.frame));
551
+ loaded.push({
552
+ id,
553
+ frames,
554
+ sequence,
555
+ interpolate: animated.interpolated
556
+ });
557
+ }
558
+ spriteIds.add(MISSING_SPRITE);
559
+ loaded.push({
560
+ id: MISSING_SPRITE,
561
+ frames: /* @__PURE__ */ new Map([[0, missingTexture()]]),
562
+ sequence: [{
563
+ frame: 0,
564
+ time: 1
565
+ }],
566
+ interpolate: false
567
+ });
568
+ const packed = packAtlas(loaded.flatMap((sprite) => [...sprite.frames].map(([index, texture]) => {
569
+ return {
570
+ key: `${sprite.id}#${index}`,
571
+ texture
572
+ };
573
+ })), {
574
+ padding: options.padding,
575
+ maxPageSize: options.maxPageSize
576
+ });
577
+ for (const key of packed.oversized) {
578
+ const id = key.split("#", 1)[0];
579
+ if (!missingSprites.has(id)) {
580
+ failures.push({
581
+ id,
582
+ kind: "sprite",
583
+ reason: "sprite is larger than one atlas page; drawn as the missing texture"
584
+ });
585
+ missingSprites.add(id);
586
+ }
587
+ }
588
+ if (missingSprites.has(MISSING_SPRITE)) throw new Error("the missing-texture sprite does not fit an atlas page; maxPageSize is too small");
589
+ if (missingSprites.size > 0) for (const model of Object.values(emittedModels)) {
590
+ for (const element of model.elements) for (const face of Object.values(element.faces)) if (missingSprites.has(face.sprite)) face.sprite = MISSING_SPRITE;
591
+ for (const group of model.mesh ?? []) if (missingSprites.has(group.sprite)) group.sprite = MISSING_SPRITE;
592
+ if (model.particle !== void 0 && missingSprites.has(model.particle)) model.particle = MISSING_SPRITE;
593
+ }
594
+ for (const [id, model] of Object.entries(emittedModels)) {
595
+ const sprites = [...model.elements.flatMap((element) => Object.values(element.faces).map((face) => face.sprite)), ...(model.mesh ?? []).map((group) => group.sprite)];
596
+ if (sprites.length > 0 && sprites.every((sprite) => sprite === MISSING_SPRITE)) failures.push({
597
+ id,
598
+ kind: "texture",
599
+ reason: "every face uses the missing-texture sprite; the model binds none of the textures it names"
600
+ });
601
+ if (model.elements.length === 0 && !model.mesh?.length) failures.push({
602
+ id,
603
+ kind: "geometry",
604
+ reason: "model has no drawable elements"
605
+ });
606
+ }
607
+ const rects = new Map(packed.rects.map((rect) => [rect.key, rect]));
608
+ const emittedSprites = {};
609
+ for (const sprite of loaded) {
610
+ const first = rects.get(`${sprite.id}#${sprite.sequence[0].frame}`);
611
+ if (!first) continue;
612
+ const frames = [];
613
+ for (const entry of sprite.sequence) {
614
+ const rect = rects.get(`${sprite.id}#${entry.frame}`);
615
+ if (rect) frames.push({
616
+ page: rect.page,
617
+ x: rect.x,
618
+ y: rect.y,
619
+ time: entry.time
620
+ });
621
+ }
622
+ emittedSprites[sprite.id] = {
623
+ page: first.page,
624
+ x: first.x,
625
+ y: first.y,
626
+ w: first.width,
627
+ h: first.height,
628
+ layer: inferRenderLayer(sprite.frames.values().toArray()),
629
+ ...frames.length === sprite.sequence.length && frames.length > 1 && { frames },
630
+ ...sprite.interpolate && { interpolate: true }
631
+ };
632
+ }
633
+ const tints = await buildBlockTints(resources, textures);
634
+ for (const [block, references] of blockModels) {
635
+ const used = /* @__PURE__ */ new Set();
636
+ for (const reference of references) for (const index of modelTintIndices.get(reference) ?? []) used.add(index);
637
+ for (const index of used) if (tints.pack.blocks[block]?.[String(index)] === void 0) failures.push({
638
+ id: block,
639
+ kind: "tint",
640
+ reason: `tintindex ${index} is not in the tint table; exported untinted`
641
+ });
642
+ }
643
+ await mkdir(output, { recursive: true });
644
+ const hashes = {};
645
+ let writtenFiles = 0;
646
+ const emit = async (file, data) => {
647
+ const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
648
+ const target = path.join(output, file);
649
+ await mkdir(path.dirname(target), { recursive: true });
650
+ await writeFile(target, buffer);
651
+ hashes[file] = createHash("sha256").update(buffer).digest("hex");
652
+ writtenFiles++;
653
+ };
654
+ const pageFiles = [];
655
+ for (const [index, page] of packed.pages.entries()) {
656
+ const file = `atlas-${index}.png`;
657
+ const raw = Buffer.from(page.data.buffer, page.data.byteOffset, page.data.byteLength);
658
+ await emit(file, await sharp(raw, { raw: {
659
+ width: page.width,
660
+ height: page.height,
661
+ channels: 4
662
+ } }).png({ compressionLevel: 9 }).toBuffer());
663
+ pageFiles.push({
664
+ file,
665
+ width: page.width,
666
+ height: page.height
667
+ });
668
+ }
669
+ for (const file of tints.files) await emit(file.path, file.data);
670
+ await emit("atlas.json", JSON.stringify({
671
+ padding: packed.padding,
672
+ pages: pageFiles,
673
+ sprites: sortedByKey(emittedSprites)
674
+ }));
675
+ await emit("models.json", JSON.stringify(sortedByKey(emittedModels)));
676
+ await emit("blockstates.json", JSON.stringify(sortedByKey(blockStates)));
677
+ await emit("tints.json", JSON.stringify(tints.pack));
678
+ failures.sort((left, right) => left.id.localeCompare(right.id) || left.kind.localeCompare(right.kind));
679
+ await emit("failures.json", JSON.stringify(failures, null, " "));
680
+ const counts = {
681
+ blocks: Object.keys(blockStates).length,
682
+ models: Object.keys(emittedModels).length,
683
+ sprites: Object.keys(emittedSprites).length,
684
+ pages: packed.pages.length,
685
+ failures: failures.length
686
+ };
687
+ await emit("meta.json", JSON.stringify({
688
+ generator: "rendermite",
689
+ version,
690
+ namespaces,
691
+ counts,
692
+ hashes: sortedByKey(hashes)
693
+ }, null, " "));
694
+ return {
695
+ ...counts,
696
+ writtenFiles,
697
+ failures
698
+ };
699
+ }
700
+ //#endregion
701
+ export { exportBlockPack as t };
@@ -1,6 +1,6 @@
1
1
  import { n as FloatTexture } from "./blend-8lnfxtLe.js";
2
2
  import { t as Mat4 } from "./vector-DRKRi4--.js";
3
- import { n as FaceGeometry } from "./geometry-w0yQbzDZ.js";
3
+ import { n as FaceGeometry } from "./geometry-Bkur58Vi.js";
4
4
  import { r as LightSource } from "./lighting-3FGRC3Af.js";
5
5
  import { n as FaceBuffer } from "./rasterizer-BSQHEhJ-.js";
6
6
  //#region src/render/face.d.ts