@nodecraft/rendermite 1.0.5 → 1.0.6

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 (110) hide show
  1. package/README.md +5 -0
  2. package/dist/atlas/registry.d.ts +3 -3
  3. package/dist/atlas/sources.d.ts +1 -1
  4. package/dist/{blend-Qvrn6YgK.d.ts → blend-CyDIxCne.d.ts} +1 -1
  5. package/dist/cache-Cr2ON3RQ.js +66 -0
  6. package/dist/cli.js +12 -6
  7. package/dist/compose-CQCb2LVv.js +104 -0
  8. package/dist/descriptor-DWebtkI1.js +26 -0
  9. package/dist/{discovery-D89pVVJA.d.ts → discovery-wiJBGryo.d.ts} +1 -1
  10. package/dist/dynamic/generators.d.ts +1 -1
  11. package/dist/dynamic/generators.js +1 -1
  12. package/dist/{encode-3u49i-eD.js → encode-D6dQu2dJ.js} +16 -4
  13. package/dist/{encode-C5kdAm45.d.ts → encode-DaNQF28M.d.ts} +1 -1
  14. package/dist/{entity-fallback-DfL0_eyf.js → entity-fallback-BRCeGKBT.js} +1 -1
  15. package/dist/{face-CpVdnf-w.d.ts → face-BIYg14TE.d.ts} +10 -4
  16. package/dist/{face-D-N1amV4.js → face-DmCiHUd5.js} +23 -10
  17. package/dist/{generators-CcVRG30y.js → generators-BlG4KmtP.js} +1 -1
  18. package/dist/{generators-CCB8h0mP.d.ts → generators-DhRov4RB.d.ts} +1 -1
  19. package/dist/{geo-Dq2zve09.js → geo-Diky41Tw.js} +6 -3
  20. package/dist/index.d.ts +14 -14
  21. package/dist/index.js +11 -11
  22. package/dist/{item-styles-DJ3LWZS2.js → item-styles-upgEO1yP.js} +1 -1
  23. package/dist/items/discovery.d.ts +1 -1
  24. package/dist/{jar-6T3xdgaL.d.ts → jar-CFkA-VgT.d.ts} +2 -1
  25. package/dist/{jar-Bt3i0ls4.js → jar-D9z-X7tA.js} +12 -12
  26. package/dist/{legacy-reshape-CewShfF3.js → legacy-reshape-C05SfAPR.js} +1 -1
  27. package/dist/{loader-A-BZa8J4.js → loader-B8NJO7pR.js} +12 -3
  28. package/dist/{loader-CBzn3Koc.js → loader-CfXMxcEV.js} +37 -7
  29. package/dist/{loader-C-6QN3xv.d.ts → loader-D22zZME7.d.ts} +2 -2
  30. package/dist/{loader-TFFNV4Jl.d.ts → loader-Myy22TDL.d.ts} +8 -2
  31. package/dist/{manager-dfI7b9N4.js → manager-CkletR5K.js} +29 -2
  32. package/dist/{manager-CoZaeQTu.d.ts → manager-Cv1NeFyF.d.ts} +28 -1
  33. package/dist/models/loader.d.ts +1 -1
  34. package/dist/models/loader.js +1 -1
  35. package/dist/mods/prepare.d.ts +1 -1
  36. package/dist/mods/prepare.js +1 -1
  37. package/dist/mods/synthesize.d.ts +2 -2
  38. package/dist/mods/synthesize.js +1 -1
  39. package/dist/mods/tints.d.ts +2 -2
  40. package/dist/mods/tints.js +1 -1
  41. package/dist/output/encode.d.ts +1 -1
  42. package/dist/output/encode.js +1 -1
  43. package/dist/output/item-styles.js +1 -1
  44. package/dist/pipeline-BIk1RRIp.js +221 -0
  45. package/dist/pipeline-zq-yU3Te.d.ts +58 -0
  46. package/dist/pool-CKGp1qup.js +131 -0
  47. package/dist/{prepare-BeUpXwaP.js → prepare-BESRrh6K.js} +4 -4
  48. package/dist/{prepare-gfR1aIjt.d.ts → prepare-CPxO0kKz.d.ts} +1 -1
  49. package/dist/{prepare-C8wRjAXP.js → prepare-Cc8QZ3j0.js} +4 -4
  50. package/dist/{prepare-wfWtaz_X.d.ts → prepare-Cs6QDqie.d.ts} +1 -1
  51. package/dist/{rasterizer-BHWDrmGz.js → rasterizer-76qO8TDh.js} +42 -4
  52. package/dist/{rasterizer-Dh35qXbn.d.ts → rasterizer-yqJkicm1.d.ts} +21 -2
  53. package/dist/render/blend.d.ts +1 -1
  54. package/dist/render/compose.d.ts +2 -2
  55. package/dist/render/compose.js +1 -1
  56. package/dist/render/entity-fallback.d.ts +6 -6
  57. package/dist/render/entity-fallback.js +1 -1
  58. package/dist/render/face.d.ts +2 -2
  59. package/dist/render/face.js +1 -1
  60. package/dist/render/fluid-bucket.d.ts +2 -2
  61. package/dist/render/geo.d.ts +4 -4
  62. package/dist/render/geo.js +1 -1
  63. package/dist/render/legacy-tints.d.ts +2 -2
  64. package/dist/render/obj.d.ts +5 -5
  65. package/dist/render/obj.js +1 -1
  66. package/dist/render/pipeline.d.ts +2 -0
  67. package/dist/render/pipeline.js +2 -0
  68. package/dist/render/rasterizer.d.ts +2 -2
  69. package/dist/render/rasterizer.js +2 -2
  70. package/dist/render/renderer.d.ts +2 -2
  71. package/dist/render/renderer.js +1 -1
  72. package/dist/render/tints.d.ts +1 -1
  73. package/dist/{renderer-LDhBNHcl.js → renderer-CjT19tHd.js} +130 -53
  74. package/dist/{renderer-nbMCw1Pl.d.ts → renderer-DVsZHAvf.d.ts} +23 -6
  75. package/dist/resources/cache.d.ts +30 -0
  76. package/dist/resources/cache.js +2 -0
  77. package/dist/resources/descriptor.d.ts +2 -0
  78. package/dist/resources/descriptor.js +2 -0
  79. package/dist/resources/jar.d.ts +1 -1
  80. package/dist/resources/jar.js +1 -1
  81. package/dist/resources/manager.d.ts +1 -1
  82. package/dist/resources/manager.js +1 -1
  83. package/dist/resources/synthetic.d.ts +1 -1
  84. package/dist/resources/synthetic.js +1 -1
  85. package/dist/resources/zip-reader.d.ts +5 -0
  86. package/dist/resources/zip-reader.js +2 -0
  87. package/dist/run-BnICOF4I.js +209 -0
  88. package/dist/{run-BlSZw9Qk.d.ts → run-CKUtynTy.d.ts} +12 -2
  89. package/dist/run.d.ts +1 -1
  90. package/dist/run.js +1 -1
  91. package/dist/{sources-DH9dk3KF.d.ts → sources-B9X9w3qP.d.ts} +1 -1
  92. package/dist/{synthesize-CRgSEmEp.js → synthesize-3Q0aaOnK.js} +1 -1
  93. package/dist/{synthetic-CDsj--li.d.ts → synthetic-Qk-BSUXO.d.ts} +5 -1
  94. package/dist/{synthetic-DC2bSnGe.js → synthetic-ZAUjXT7E.js} +0 -0
  95. package/dist/textures/loader.d.ts +1 -1
  96. package/dist/textures/loader.js +1 -1
  97. package/dist/{tints-CuG6GwQJ.js → tints-B8UJmZMu.js} +2 -2
  98. package/dist/{tints-txt-8xI5.d.ts → tints-DLBExVWN.d.ts} +2 -2
  99. package/dist/versions/legacy-reshape.d.ts +1 -1
  100. package/dist/versions/legacy-reshape.js +1 -1
  101. package/dist/versions/prepare.d.ts +1 -1
  102. package/dist/versions/prepare.js +1 -1
  103. package/dist/workers/pool.d.ts +36 -5
  104. package/dist/workers/pool.js +2 -67
  105. package/dist/workers/render-worker.d.ts +15 -0
  106. package/dist/workers/render-worker.js +40 -0
  107. package/dist/zip-reader-BSbJKUT1.js +156 -0
  108. package/package.json +4 -2
  109. package/dist/compose-qjhyBvnS.js +0 -41
  110. package/dist/run-CfnWIRY4.js +0 -316
@@ -1,13 +1,13 @@
1
1
  import { c as normalize, g as transformVector, m as sub, n as displayMatrix, o as identity, r as dot, s as multiply, t as cross } from "./vector-CCxLae_T.js";
2
2
  import { n as ResourceLocator } from "./locator-ycZzh_6z.js";
3
- import { i as resolveTexture } from "./loader-A-BZa8J4.js";
4
3
  import { a as tintTexture, n as cloneFloatTexture, r as darkenTexture, s as toFloatTexture, t as blendPixel } from "./blend-CHP2-j9U.js";
5
- import { t as combineFaceBuffers } from "./compose-qjhyBvnS.js";
6
- import { a as phpRound, i as drawTexturedTriangle, r as createFaceBuffer, t as RowCursor } from "./rasterizer-BHWDrmGz.js";
7
- import { n as renderFace, t as faceMaxZ } from "./face-D-N1amV4.js";
4
+ import { t as combineFaceBuffers } from "./compose-CQCb2LVv.js";
5
+ import { a as drawTexturedTriangle, i as createFaceBuffer, n as clearFaceBuffer, o as phpRound, t as RowCursor } from "./rasterizer-76qO8TDh.js";
6
+ import { n as renderFace, t as faceMaxZ } from "./face-DmCiHUd5.js";
8
7
  import { t as paintBucketFluid } from "./fluid-bucket-D-GpUQjg.js";
9
8
  import { r as buildElementFaces } from "./geometry-uIDha8S4.js";
10
9
  import { c as lightsForProfile } from "./lighting--5myudVu.js";
10
+ import { i as resolveTexture } from "./loader-B8NJO7pR.js";
11
11
  //#region src/render/obj.ts
12
12
  /**
13
13
  * Forge/NeoForge OBJ model rendering — the class the old pipeline left
@@ -140,6 +140,7 @@ async function renderObjModel(model, resources, textures, options) {
140
140
  const transformation = multiply(guiMatrix, options.itemTransform ?? identity());
141
141
  const renderSize = options.size * options.quality;
142
142
  const out = createFaceBuffer(renderSize, renderSize);
143
+ const scratch = createFaceBuffer(renderSize, renderSize);
143
144
  const shaded = /* @__PURE__ */ new Map();
144
145
  for (const triangle of parsed.triangles) {
145
146
  const transformed = triangle.positions.map((position) => transformVector(transformation, position));
@@ -164,7 +165,7 @@ async function renderObjModel(model, resources, textures, options) {
164
165
  darkenTexture(texture, 1 - bucket / 64, 0, 0, texture.width, texture.height);
165
166
  shaded.set(key, texture);
166
167
  }
167
- const scratch = createFaceBuffer(renderSize, renderSize);
168
+ clearFaceBuffer(scratch);
168
169
  const cursor = new RowCursor();
169
170
  const point = (index) => {
170
171
  return {
@@ -176,7 +177,8 @@ async function renderObjModel(model, resources, textures, options) {
176
177
  };
177
178
  };
178
179
  drawTexturedTriangle(point(0), point(1), point(2), scratch, texture, cursor);
179
- for (let pixel = 0; pixel < renderSize * renderSize; pixel++) {
180
+ for (let y = Math.max(scratch.dirty.minY, 0); y <= Math.min(scratch.dirty.maxY, renderSize - 1); y++) for (let x = Math.max(scratch.dirty.minX, 0); x <= Math.min(scratch.dirty.maxX, renderSize - 1); x++) {
181
+ const pixel = y * renderSize + x;
180
182
  const offset = pixel * 4;
181
183
  const alpha = scratch.colors[offset + 3];
182
184
  if (alpha <= 0) continue;
@@ -241,16 +243,19 @@ async function renderModelFrames(model, textures, options) {
241
243
  ticks = ticks / gcd(ticks, texture.tickLength) * texture.tickLength;
242
244
  interpolated ||= texture.interpolated;
243
245
  }
244
- const maxSkippable = interpolated ? Math.ceil(ticks / MAX_ANIMATION_FRAMES) : null;
246
+ const maxFrames = Math.max(1, options.animation?.maxFrames ?? MAX_ANIMATION_FRAMES);
247
+ const exact = interpolated ? null : changeTicks(animated.values().toArray(), ticks, maxFrames);
248
+ const candidates = exact ?? evenlySpacedTicks(ticks, maxFrames);
245
249
  const frames = [];
246
250
  const emitTicks = [];
247
251
  let lastKey = null;
248
- let sinceLast = 0;
249
- for (let tick = 0; tick < ticks; tick++) {
250
- sinceLast++;
251
- const key = animated.values().map((texture) => texture.resolveFrame(tick).frame).toArray().join(",");
252
- if (lastKey !== null && key === lastKey && !interpolated && tick !== ticks - 1) continue;
253
- if (lastKey !== null && interpolated && maxSkippable !== null && sinceLast < maxSkippable && key === lastKey) continue;
252
+ const session = createSession();
253
+ for (const tick of candidates) {
254
+ const key = animated.values().map((texture) => {
255
+ const resolved = texture.resolveFrame(tick);
256
+ return texture.interpolated ? `${resolved.frame}+${resolved.interpolation}` : String(resolved.frame);
257
+ }).toArray().join(",");
258
+ if (key === lastKey) continue;
254
259
  const supplier = async (reference) => {
255
260
  if (skipped.has(reference)) return null;
256
261
  const texture = animated.get(reference);
@@ -258,20 +263,59 @@ async function renderModelFrames(model, textures, options) {
258
263
  return texture.at(tick);
259
264
  };
260
265
  const renderSize = options.size * options.quality;
261
- const full = model.builtinParent === "builtin/generated" ? await renderGeneratedItemWith(model, supplier, renderSize, options.tints) : await renderElementModelWith(model, supplier, renderSize, options);
266
+ const full = model.builtinParent === "builtin/generated" ? await renderGeneratedItemWith(model, supplier, renderSize, options.tints) : await renderElementModelWith(model, supplier, renderSize, options, session);
262
267
  frames.push(options.quality === 1 ? full : boxDownscale(full, options.quality));
263
268
  emitTicks.push(tick);
264
269
  lastKey = key;
265
- sinceLast = 0;
266
270
  }
267
271
  return {
268
272
  frames,
269
273
  delaysMs: emitTicks.map((tick, index) => {
270
274
  const next = index + 1 < emitTicks.length ? emitTicks[index + 1] : ticks;
271
275
  return Math.max(TICK_MS, (next - tick) * TICK_MS);
272
- })
276
+ }),
277
+ animation: {
278
+ ticks,
279
+ candidates: candidates.length,
280
+ sampled: exact === null && ticks > frames.length,
281
+ interpolated
282
+ }
273
283
  };
274
284
  }
285
+ /**
286
+ * Every tick in the combined timeline where some texture switches frame, or
287
+ * null once that exceeds `limit` — past that the caller samples instead, and
288
+ * enumerating a multi-minute timeline in full would be the very cost this
289
+ * bound exists to avoid.
290
+ */
291
+ function changeTicks(textures, ticks, limit) {
292
+ const found = /* @__PURE__ */ new Set([0]);
293
+ for (const texture of textures) {
294
+ if (texture.frameCount <= 1) continue;
295
+ const period = Math.max(1, texture.tickLength);
296
+ const starts = texture.frameStartTicks();
297
+ for (let base = 0; base < ticks; base += period) for (const start of starts) {
298
+ const tick = base + start;
299
+ if (tick < ticks) found.add(tick);
300
+ if (found.size > limit) return null;
301
+ }
302
+ }
303
+ return [...found].toSorted((left, right) => left - right);
304
+ }
305
+ /** `count` tick positions spread evenly across the cycle, always starting at 0. */
306
+ function evenlySpacedTicks(ticks, maxFrames) {
307
+ const count = Math.min(ticks, maxFrames);
308
+ const sampled = [];
309
+ let previous = -1;
310
+ for (let index = 0; index < count; index++) {
311
+ const tick = Math.floor(index * ticks / count);
312
+ if (tick !== previous) {
313
+ sampled.push(tick);
314
+ previous = tick;
315
+ }
316
+ }
317
+ return sampled;
318
+ }
275
319
  async function loadAnimatedTexture(model, textures, reference) {
276
320
  const locator = resolveTexture(model, reference);
277
321
  if (locator) {
@@ -391,11 +435,23 @@ async function renderElementModel(model, textures, renderSize, options) {
391
435
  };
392
436
  return renderElementModelWith(model, supplier, renderSize, options);
393
437
  }
394
- async function renderElementModelWith(model, supplier, renderSize, options) {
438
+ function createSession() {
439
+ return {
440
+ geometry: null,
441
+ order: null,
442
+ transformation: null,
443
+ scratch: null,
444
+ shadeCache: /* @__PURE__ */ new WeakMap(),
445
+ floatTextures: /* @__PURE__ */ new Map(),
446
+ faceBuffers: []
447
+ };
448
+ }
449
+ /** Interpolated frames are new objects every tick; don't grow without bound. */
450
+ const MAX_CACHED_FLOAT_TEXTURES = 32;
451
+ async function renderElementModelWith(model, supplier, renderSize, options, session = createSession()) {
395
452
  const lights = lightsForProfile(options.lightingProfile);
396
453
  const light = model.elementsGuiLight === "side" ? lights.side : lights.front;
397
454
  const loadedTextures = /* @__PURE__ */ new Map();
398
- const floatTextures = /* @__PURE__ */ new Map();
399
455
  const load = (reference) => {
400
456
  let pending = loadedTextures.get(reference);
401
457
  if (!pending) {
@@ -404,47 +460,68 @@ async function renderElementModelWith(model, supplier, renderSize, options) {
404
460
  }
405
461
  return pending;
406
462
  };
407
- const faces = [];
463
+ if (!session.geometry) {
464
+ const geometry = [];
465
+ for (const element of model.elements) for (const face of buildElementFaces(element)) geometry.push({
466
+ geometry: face,
467
+ maxZ: faceMaxZ(face)
468
+ });
469
+ session.geometry = geometry;
470
+ session.order = geometry.map((_, index) => index).toSorted((left, right) => geometry[left].maxZ - geometry[right].maxZ);
471
+ }
472
+ const specs = session.geometry;
473
+ const order = session.order ?? [];
408
474
  let skippedFaces = 0;
409
- for (const element of model.elements) for (const geometry of buildElementFaces(element)) {
410
- const texture = await load(geometry.textureRef);
411
- if (!texture) {
412
- skippedFaces++;
413
- continue;
414
- }
415
- let float = floatTextures.get(texture);
475
+ let drawable = 0;
476
+ for (const spec of specs) if (await load(spec.geometry.textureRef)) drawable++;
477
+ else skippedFaces++;
478
+ if (drawable === 0 && skippedFaces > 0) throw new ItemRenderError(`Every face texture unresolved in ${model.locator} (would render invisible)`);
479
+ if (!session.transformation) {
480
+ const gui = model.display.gui;
481
+ const guiMatrix = displayMatrix(gui?.rotation ?? [
482
+ 0,
483
+ 0,
484
+ 0
485
+ ], gui?.translation ?? [
486
+ 0,
487
+ 0,
488
+ 0
489
+ ], gui?.scale ?? [
490
+ 1,
491
+ 1,
492
+ 1
493
+ ]);
494
+ session.transformation = multiply(guiMatrix, options.itemTransform ?? identity());
495
+ }
496
+ session.scratch ??= createFaceBuffer(renderSize, renderSize);
497
+ if (session.floatTextures.size > MAX_CACHED_FLOAT_TEXTURES) session.floatTextures.clear();
498
+ const buffers = [];
499
+ for (const index of order) {
500
+ const spec = specs[index];
501
+ const texture = await load(spec.geometry.textureRef);
502
+ if (!texture) continue;
503
+ let float = session.floatTextures.get(texture);
416
504
  if (!float) {
417
505
  float = toFloatTexture(texture);
418
- floatTextures.set(texture, float);
506
+ session.floatTextures.set(texture, float);
419
507
  }
420
- faces.push({
421
- geometry,
508
+ const cached = session.faceBuffers[index];
509
+ if (cached?.texture === float) {
510
+ if (cached.buffer) buffers.push(cached.buffer);
511
+ continue;
512
+ }
513
+ const face = {
514
+ geometry: spec.geometry,
422
515
  texture: float,
423
516
  light,
424
- tint: geometry.tintIndex !== null ? options.tints?.[geometry.tintIndex] ?? null : null,
425
- maxZ: faceMaxZ(geometry)
426
- });
427
- }
428
- if (faces.length === 0 && skippedFaces > 0) throw new ItemRenderError(`Every face texture unresolved in ${model.locator} (would render invisible)`);
429
- faces.sort((a, b) => a.maxZ - b.maxZ);
430
- const gui = model.display.gui;
431
- const guiMatrix = displayMatrix(gui?.rotation ?? [
432
- 0,
433
- 0,
434
- 0
435
- ], gui?.translation ?? [
436
- 0,
437
- 0,
438
- 0
439
- ], gui?.scale ?? [
440
- 1,
441
- 1,
442
- 1
443
- ]);
444
- const transformation = multiply(guiMatrix, options.itemTransform ?? identity());
445
- const buffers = [];
446
- for (const face of faces) {
447
- const buffer = renderFace(face, renderSize, renderSize, transformation);
517
+ tint: spec.geometry.tintIndex !== null ? options.tints?.[spec.geometry.tintIndex] ?? null : null,
518
+ maxZ: spec.maxZ
519
+ };
520
+ const buffer = renderFace(face, renderSize, renderSize, session.transformation, session.scratch, session.shadeCache);
521
+ session.faceBuffers[index] = {
522
+ texture: float,
523
+ buffer
524
+ };
448
525
  if (buffer) buffers.push(buffer);
449
526
  }
450
527
  return combineFaceBuffers(renderSize, renderSize, buffers);
@@ -1,15 +1,21 @@
1
- import { a as ResourceManager } from "./manager-CoZaeQTu.js";
2
- import { i as TextureLoader } from "./loader-TFFNV4Jl.js";
3
- import { r as ResolvedModel } from "./loader-C-6QN3xv.js";
4
- import { t as FloatTexture } from "./blend-Qvrn6YgK.js";
1
+ import { a as ResourceManager } from "./manager-Cv1NeFyF.js";
2
+ import { i as TextureLoader } from "./loader-Myy22TDL.js";
3
+ import { r as ResolvedModel } from "./loader-D22zZME7.js";
4
+ import { t as FloatTexture } from "./blend-CyDIxCne.js";
5
5
  import { t as Mat4 } from "./vector-DRKRi4--.js";
6
6
  import { a as LightingProfileName } from "./lighting-3FGRC3Af.js";
7
- import { n as TintColor } from "./face-CpVdnf-w.js";
7
+ import { r as TintColor } from "./face-BIYg14TE.js";
8
8
  //#region src/render/renderer.d.ts
9
+ interface AnimationLimits {
10
+ /** Emitted frames per icon (default 128). */
11
+ maxFrames?: number;
12
+ }
9
13
  interface RenderOptions {
10
14
  size: number;
11
15
  quality: number;
12
16
  lightingProfile: LightingProfileName;
17
+ /** Bounds on animated timelines; defaults apply when omitted. */
18
+ animation?: AnimationLimits;
13
19
  /** Accumulated item-definition transformation (inner matrix). */
14
20
  itemTransform?: Mat4;
15
21
  /** Tint colors by index (face tintindex / generated layer number). */
@@ -22,6 +28,17 @@ interface RenderedFrames {
22
28
  frames: FloatTexture[];
23
29
  /** Display duration per frame in milliseconds (50ms per game tick). */
24
30
  delaysMs: number[];
31
+ /** What the timeline did, so a bounded animation is never a silent one. */
32
+ animation?: AnimationDiagnostics;
33
+ }
34
+ interface AnimationDiagnostics {
35
+ /** Combined cycle length in game ticks (the lcm of the textures' periods). */
36
+ ticks: number;
37
+ /** Ticks considered before identical neighbours were coalesced. */
38
+ candidates: number;
39
+ /** Whether the frame cap forced even sampling instead of exact frames. */
40
+ sampled: boolean;
41
+ interpolated: boolean;
25
42
  }
26
43
  /**
27
44
  * Render every animation frame of a model (a single frame for unanimated
@@ -41,4 +58,4 @@ declare function pointResize(texture: FloatTexture, width: number, height: numbe
41
58
  */
42
59
  declare function boxDownscale(texture: FloatTexture, factor: number): FloatTexture;
43
60
  //#endregion
44
- export { pointResize as a, boxDownscale as i, RenderOptions as n, renderModel as o, RenderedFrames as r, renderModelFrames as s, ItemRenderError as t };
61
+ export { RenderedFrames as a, renderModel as c, RenderOptions as i, renderModelFrames as l, AnimationLimits as n, boxDownscale as o, ItemRenderError as r, pointResize as s, AnimationDiagnostics as t };
@@ -0,0 +1,30 @@
1
+ //#region src/resources/cache.d.ts
2
+ /**
3
+ * Least-recently-used map used by the model and texture loaders. Their caches
4
+ * were unbounded, which is fine for one namespace and not fine for a run that
5
+ * walks thousands of them in a single process — resident memory only ever grew.
6
+ */
7
+ declare class LruCache<K, V> {
8
+ private readonly entries;
9
+ private readonly maxEntries;
10
+ private readonly weigh;
11
+ private readonly maxWeight;
12
+ private readonly weights;
13
+ private weight;
14
+ constructor(maxEntries: number, options?: {
15
+ maxWeight?: number;
16
+ weigh?: (value: V) => number;
17
+ });
18
+ private evict;
19
+ get size(): number;
20
+ get(key: K): V | undefined;
21
+ set(key: K, value: V): void;
22
+ /**
23
+ * Record a value's real cost once it is known. Loaders cache promises, so the
24
+ * weight of a decoded texture only exists after it resolves.
25
+ */
26
+ settle(key: K, value: V): void;
27
+ clear(): void;
28
+ }
29
+ //#endregion
30
+ export { LruCache };
@@ -0,0 +1,2 @@
1
+ import { t as LruCache } from "../cache-Cr2ON3RQ.js";
2
+ export { LruCache };
@@ -0,0 +1,2 @@
1
+ import { c as describeResources, l as restoreResources, o as DescribableResourceManager, s as ResourceDescriptor } from "../manager-Cv1NeFyF.js";
2
+ export { DescribableResourceManager, ResourceDescriptor, describeResources, restoreResources };
@@ -0,0 +1,2 @@
1
+ import { n as restoreResources, t as describeResources } from "../descriptor-DWebtkI1.js";
2
+ export { describeResources, restoreResources };
@@ -1,2 +1,2 @@
1
- import { n as UnsafeJarError, r as isUnsafeEntryPath, t as JarResourceManager } from "../jar-6T3xdgaL.js";
1
+ import { n as UnsafeJarError, r as isUnsafeEntryPath, t as JarResourceManager } from "../jar-CFkA-VgT.js";
2
2
  export { JarResourceManager, UnsafeJarError, isUnsafeEntryPath };
@@ -1,2 +1,2 @@
1
- import { n as UnsafeJarError, r as isUnsafeEntryPath, t as JarResourceManager } from "../jar-Bt3i0ls4.js";
1
+ import { n as UnsafeJarError, r as isUnsafeEntryPath, t as JarResourceManager } from "../jar-D9z-X7tA.js";
2
2
  export { JarResourceManager, UnsafeJarError, isUnsafeEntryPath };
@@ -1,2 +1,2 @@
1
- import { a as ResourceManager, i as ResourceKindName, n as LayeredResourceManager, r as ResourceKind, t as FolderResourceManager } from "../manager-CoZaeQTu.js";
1
+ import { a as ResourceManager, i as ResourceKindName, n as LayeredResourceManager, r as ResourceKind, t as FolderResourceManager } from "../manager-Cv1NeFyF.js";
2
2
  export { FolderResourceManager, LayeredResourceManager, ResourceKind, ResourceKindName, ResourceManager };
@@ -1,2 +1,2 @@
1
- import { n as LayeredResourceManager, r as ResourceKind, t as FolderResourceManager } from "../manager-dfI7b9N4.js";
1
+ import { n as LayeredResourceManager, r as ResourceKind, t as FolderResourceManager } from "../manager-CkletR5K.js";
2
2
  export { FolderResourceManager, LayeredResourceManager, ResourceKind };
@@ -1,2 +1,2 @@
1
- import { n as MapResourceManager, t as KindFilteredResourceManager } from "../synthetic-CDsj--li.js";
1
+ import { n as MapResourceManager, t as KindFilteredResourceManager } from "../synthetic-Qk-BSUXO.js";
2
2
  export { KindFilteredResourceManager, MapResourceManager };
@@ -1,2 +1,2 @@
1
- import { n as MapResourceManager, t as KindFilteredResourceManager } from "../synthetic-DC2bSnGe.js";
1
+ import { n as MapResourceManager, t as KindFilteredResourceManager } from "../synthetic-ZAUjXT7E.js";
2
2
  export { KindFilteredResourceManager, MapResourceManager };
@@ -0,0 +1,5 @@
1
+ import { ZipFile } from "yauzl";
2
+ //#region src/resources/zip-reader.d.ts
3
+ declare function openZip(zipPath: string): Promise<ZipFile>;
4
+ //#endregion
5
+ export { openZip };
@@ -0,0 +1,2 @@
1
+ import { t as openZip } from "../zip-reader-BSbJKUT1.js";
2
+ export { openZip };
@@ -0,0 +1,209 @@
1
+ import { n as ResourceLocator } from "./locator-ycZzh_6z.js";
2
+ import { n as discoverItems } from "./discovery-0SM0cTLw.js";
3
+ import { r as encodeRasterAsset } from "./encode-D6dQu2dJ.js";
4
+ import { t as classifyItemStyle } from "./item-styles-upgEO1yP.js";
5
+ import { t as minifyStylesheet } from "./minify-bjMRmr5J.js";
6
+ import { t as generateStylesheet } from "./stylesheet-CovT3B2S.js";
7
+ import { t as ItemPipeline } from "./pipeline-BIk1RRIp.js";
8
+ import { t as describeResources } from "./descriptor-DWebtkI1.js";
9
+ import { t as WorkerRenderPool } from "./pool-CKGp1qup.js";
10
+ import path from "node:path";
11
+ import os from "node:os";
12
+ import { mkdir, writeFile } from "node:fs/promises";
13
+ import { fileURLToPath } from "node:url";
14
+ //#region src/run.ts
15
+ /**
16
+ * Render-run orchestration: discover items, evaluate definitions, render,
17
+ * encode, write images + stylesheet + item list. Unlike upstream, failures are
18
+ * per-item, collected, and reported — a run summary counts files, never log lines.
19
+ *
20
+ * Items render in worker threads when the resource tree can describe itself
21
+ * (see resources/descriptor.ts); otherwise the same pipeline runs in-process.
22
+ */
23
+ async function run(options) {
24
+ const namespaces = options.namespaces ?? ["minecraft"];
25
+ const size = options.size ?? 64;
26
+ const quality = options.quality ?? 2;
27
+ const prefix = options.prefix ?? "rc-";
28
+ const format = options.format ?? "webp";
29
+ const fallback = options.fallback ?? true;
30
+ const lightingProfile = options.lightingProfile ?? "modern";
31
+ const settings = {
32
+ builtin: options.builtin,
33
+ output: options.output,
34
+ size,
35
+ quality,
36
+ format,
37
+ gif: options.gif,
38
+ avif: options.avif,
39
+ lightingProfile,
40
+ animation: options.animation
41
+ };
42
+ const concurrency = Math.max(1, options.concurrency ?? 8);
43
+ const requestedWorkers = resolveWorkerCount(options.workers);
44
+ const descriptor = requestedWorkers > 1 ? describeResources(options.resources) : null;
45
+ const pool = descriptor ? new WorkerRenderPool({
46
+ workerUrl: workerUrl(),
47
+ workerData: {
48
+ resources: descriptor,
49
+ settings,
50
+ sharpConcurrency: Math.max(1, Math.round(os.availableParallelism() / requestedWorkers))
51
+ },
52
+ workers: requestedWorkers,
53
+ inFlight: concurrency
54
+ }) : null;
55
+ const usedWorkers = pool ? requestedWorkers : 1;
56
+ const pipeline = await ItemPipeline.create(options.resources, settings).catch(async (err) => {
57
+ await pool?.close();
58
+ throw err;
59
+ });
60
+ const { resources } = pipeline;
61
+ const glint = await resolveEnchantmentGlint(resources);
62
+ if (!glint) throw new Error("Missing enchantment glint texture (minecraft:misc/enchanted_glint_item or enchanted_item_glint)");
63
+ const encodedGlint = await encodeRasterAsset(glint, fallback ? ["png", format] : [format]);
64
+ const ids = [];
65
+ const seen = /* @__PURE__ */ new Set();
66
+ if (options.builtin) for (const placeholder of ["renderchest:unknown", "renderchest:empty"]) {
67
+ seen.add(placeholder);
68
+ ids.push(placeholder);
69
+ }
70
+ for (const namespace of namespaces) for (const item of await discoverItems(resources, namespace, { filterUntranslatedModels: options.filterUntranslatedModels })) {
71
+ if (seen.has(item.id)) continue;
72
+ if (options.builtin && !await itemExistsIn(options.resources, item.id)) continue;
73
+ seen.add(item.id);
74
+ ids.push(item.id);
75
+ }
76
+ if (pipeline.dynamicItems && namespaces.includes("minecraft")) {
77
+ const hasTrims = await resources.hasResource("texture", ResourceLocator.parse("minecraft:trims/color_palettes/trim_palette"));
78
+ const groupAvailable = {
79
+ _armor_trims: hasTrims,
80
+ _leather_armor: hasTrims,
81
+ _decorated_pot: await resources.hasResource("texture", ResourceLocator.parse("minecraft:entity/decorated_pot/decorated_pot_base")),
82
+ _crossbow: await resources.hasResource("model", ResourceLocator.parse("minecraft:item/crossbow_arrow"))
83
+ };
84
+ for (const id of pipeline.dynamicItems) if ((groupAvailable[id.split(":", 1)[0]] ?? true) && !seen.has(id)) {
85
+ seen.add(id);
86
+ ids.push(id);
87
+ }
88
+ }
89
+ const rendered = [];
90
+ const failed = [];
91
+ const spriteFallbacks = [];
92
+ const skippedTemplates = [];
93
+ const styles = [];
94
+ let writtenFiles = 0;
95
+ await mkdir(options.output, { recursive: true });
96
+ for (const [extension, buffer] of encodedGlint) {
97
+ await writeFile(path.join(options.output, `${prefix}enchantment.${extension}`), buffer);
98
+ writtenFiles++;
99
+ }
100
+ let templateParents = null;
101
+ const loadTemplateParents = async () => {
102
+ const parents = /* @__PURE__ */ new Set();
103
+ for (const namespace of namespaces) for (const modelLocator of await resources.listResources("model", namespace)) {
104
+ if (!modelLocator.path.startsWith("item/")) continue;
105
+ const raw = await resources.getResource("model", modelLocator);
106
+ if (!raw) continue;
107
+ try {
108
+ const parent = JSON.parse(raw.toString("utf8")).parent;
109
+ if (typeof parent === "string") parents.add(ResourceLocator.parse(parent).toString());
110
+ } catch {}
111
+ }
112
+ return parents;
113
+ };
114
+ const isTemplateModel = async (id) => {
115
+ const locator = ResourceLocator.parse(id);
116
+ if (await resources.hasResource("itemDefinition", locator)) return false;
117
+ templateParents ??= loadTemplateParents();
118
+ return (await templateParents).has(new ResourceLocator(locator.namespace, `item/${locator.path}`).toString());
119
+ };
120
+ let completed = 0;
121
+ const record = async (id, outcome, error) => {
122
+ if (outcome) {
123
+ writtenFiles += outcome.written;
124
+ rendered.push(id);
125
+ if (outcome.usedFallback) spriteFallbacks.push(id);
126
+ } else if (await isTemplateModel(id).catch(() => false)) skippedTemplates.push(id);
127
+ else failed.push({
128
+ id,
129
+ error: error ?? "Unknown render failure"
130
+ });
131
+ completed++;
132
+ options.onProgress?.(completed, ids.length, id, outcome ? void 0 : error);
133
+ };
134
+ if (pool) {
135
+ const pending = [];
136
+ await pool.run(ids.map((id) => {
137
+ return { id };
138
+ }), (message) => {
139
+ pending.push(record(message.id, message.result ?? null, message.error));
140
+ });
141
+ await Promise.all(pending);
142
+ } else {
143
+ let nextIndex = 0;
144
+ await Promise.all(Array.from({ length: Math.min(concurrency, ids.length) }, async () => {
145
+ while (nextIndex < ids.length) {
146
+ const id = ids[nextIndex++];
147
+ try {
148
+ await record(id, await pipeline.renderItem(id));
149
+ } catch (err) {
150
+ await record(id, null, err.message);
151
+ }
152
+ }
153
+ }));
154
+ }
155
+ rendered.sort((left, right) => left.localeCompare(right));
156
+ const renderedSet = new Set(rendered);
157
+ for (const id of rendered) {
158
+ const style = classifyItemStyle(id, renderedSet);
159
+ if (style) styles.push(style);
160
+ }
161
+ const stylesheet = generateStylesheet(styles, {
162
+ prefix,
163
+ format,
164
+ fallback
165
+ });
166
+ const finalStylesheet = options.minify === false ? stylesheet : minifyStylesheet(stylesheet, (message) => options.onProgress?.(0, 0, "stylesheet", message));
167
+ await writeFile(path.join(options.output, `${prefix}${namespaces[0]}-${size}.css`), finalStylesheet);
168
+ if (options.itemList !== false) {
169
+ await writeFile(path.join(options.output, `${prefix}${namespaces[0]}.json`), JSON.stringify(rendered));
170
+ await writeFile(path.join(options.output, `${prefix}${namespaces[0]}-failures.json`), JSON.stringify(failed, null, " "));
171
+ }
172
+ return {
173
+ rendered,
174
+ failed,
175
+ spriteFallbacks,
176
+ skippedTemplates,
177
+ writtenFiles,
178
+ workers: usedWorkers
179
+ };
180
+ }
181
+ /**
182
+ * One thread per core less the orchestrator, capped at 8: measured throughput
183
+ * on a 16-thread host peaks around 4-8 workers and falls off above that, where
184
+ * per-worker asset caches and libvips threads start competing for the same cores.
185
+ */
186
+ function resolveWorkerCount(requested) {
187
+ if (requested !== void 0) return Math.max(1, Math.floor(requested));
188
+ return Math.min(8, Math.max(1, os.availableParallelism() - 1));
189
+ }
190
+ /** The worker's own file, in whichever form this module was loaded from. */
191
+ function workerUrl() {
192
+ const extension = path.extname(fileURLToPath(import.meta.url)) || ".js";
193
+ return new URL(`./workers/render-worker${extension}`, import.meta.url);
194
+ }
195
+ async function itemExistsIn(resources, id) {
196
+ const locator = ResourceLocator.parse(id);
197
+ if (await resources.hasResource("itemDefinition", locator)) return true;
198
+ return resources.hasResource("model", new ResourceLocator(locator.namespace, `item/${locator.path}`));
199
+ }
200
+ /** Resolve both names used on either side of Minecraft 1.19.3. */
201
+ async function resolveEnchantmentGlint(resources) {
202
+ for (const path of ["misc/enchanted_glint_item", "misc/enchanted_item_glint"]) {
203
+ const texture = await resources.getResource("texture", new ResourceLocator("minecraft", path));
204
+ if (texture) return texture;
205
+ }
206
+ return null;
207
+ }
208
+ //#endregion
209
+ export { run as t };
@@ -1,5 +1,6 @@
1
- import { a as ResourceManager } from "./manager-CoZaeQTu.js";
1
+ import { a as ResourceManager } from "./manager-Cv1NeFyF.js";
2
2
  import { a as LightingProfileName } from "./lighting-3FGRC3Af.js";
3
+ import { n as AnimationLimits } from "./renderer-DVsZHAvf.js";
3
4
  //#region src/run.d.ts
4
5
  interface RunOptions {
5
6
  resources: ResourceManager;
@@ -22,8 +23,15 @@ interface RunOptions {
22
23
  avif?: boolean;
23
24
  /** Minify the stylesheet via lightningcss (default true). */
24
25
  minify?: boolean;
25
- /** In-flight items (encode overlaps on libvips' threadpool; default 8). */
26
+ /** In-flight items per thread (encode overlaps on libvips' threadpool; default 8). */
26
27
  concurrency?: number;
28
+ /**
29
+ * Render threads. Defaults to one per core (minus the main thread). 1 renders
30
+ * in-process; a resource tree that cannot be described falls back to that too.
31
+ */
32
+ workers?: number;
33
+ /** Bounds on animated timelines (frame count). */
34
+ animation?: AnimationLimits;
27
35
  /**
28
36
  * Drop classic item models with no matching translation (default true).
29
37
  * Disable for a prepared asset tree whose own preprocessing has already
@@ -54,6 +62,8 @@ interface RunResult {
54
62
  skippedTemplates: string[];
55
63
  /** Image files actually written, the only trustworthy success signal. */
56
64
  writtenFiles: number;
65
+ /** Worker threads used; 1 means everything rendered in-process. */
66
+ workers: number;
57
67
  }
58
68
  declare function run(options: RunOptions): Promise<RunResult>;
59
69
  //#endregion
package/dist/run.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { l as evaluateItemModel } from "./definition-BwX6Xxv0.js";
2
- import { i as run, n as RunOptions, r as RunResult, t as ItemFailure } from "./run-BlSZw9Qk.js";
2
+ import { i as run, n as RunOptions, r as RunResult, t as ItemFailure } from "./run-CKUtynTy.js";
3
3
  export { ItemFailure, RunOptions, RunResult, evaluateItemModel, run };
package/dist/run.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import { o as evaluateItemModel } from "./definition-3MBm8ar_.js";
2
- import { t as run } from "./run-CfnWIRY4.js";
2
+ import { t as run } from "./run-BnICOF4I.js";
3
3
  export { evaluateItemModel, run };
@@ -1,5 +1,5 @@
1
1
  import { n as ResourceLocator } from "./locator-jDbdUeTd.js";
2
- import { r as Texture } from "./loader-TFFNV4Jl.js";
2
+ import { r as Texture } from "./loader-Myy22TDL.js";
3
3
  //#region src/atlas/sources.d.ts
4
4
  declare class AtlasSourceError extends Error {}
5
5
  interface AtlasSource {