@nodecraft/rendermite 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/atlas/registry.d.ts +3 -3
- package/dist/atlas/sources.d.ts +1 -1
- package/dist/{blend-Qvrn6YgK.d.ts → blend-CEHoJ7tM.d.ts} +3 -2
- package/dist/{blend-CHP2-j9U.js → blend-D0XWgVQr.js} +20 -8
- package/dist/cache-Cr2ON3RQ.js +66 -0
- package/dist/cli.js +12 -6
- package/dist/compose-lWPrB7wh.js +104 -0
- package/dist/descriptor-DWebtkI1.js +26 -0
- package/dist/{discovery-D89pVVJA.d.ts → discovery-wiJBGryo.d.ts} +1 -1
- package/dist/dynamic/generators.d.ts +1 -1
- package/dist/dynamic/generators.js +1 -1
- package/dist/{encode-C5kdAm45.d.ts → encode-CaSK8-mi.d.ts} +1 -1
- package/dist/{encode-3u49i-eD.js → encode-Df_tWzkl.js} +44 -16
- package/dist/{entity-fallback-DfL0_eyf.js → entity-fallback-D9QTGU9X.js} +1 -1
- package/dist/{face-CpVdnf-w.d.ts → face-C49L66sQ.d.ts} +10 -4
- package/dist/{face-D-N1amV4.js → face-Cl1O4m2i.js} +24 -11
- package/dist/{generators-CcVRG30y.js → generators-BlG4KmtP.js} +1 -1
- package/dist/{generators-CCB8h0mP.d.ts → generators-DhRov4RB.d.ts} +1 -1
- package/dist/{geo-Dq2zve09.js → geo-DHiWBa-C.js} +7 -4
- package/dist/index.d.ts +14 -14
- package/dist/index.js +12 -12
- package/dist/{item-styles-DJ3LWZS2.js → item-styles-upgEO1yP.js} +1 -1
- package/dist/items/discovery.d.ts +1 -1
- package/dist/{jar-6T3xdgaL.d.ts → jar-CFkA-VgT.d.ts} +2 -1
- package/dist/{jar-Bt3i0ls4.js → jar-D9z-X7tA.js} +12 -12
- package/dist/{legacy-reshape-CewShfF3.js → legacy-reshape-C05SfAPR.js} +1 -1
- package/dist/{loader-A-BZa8J4.js → loader-B8NJO7pR.js} +12 -3
- package/dist/{loader-CBzn3Koc.js → loader-CfXMxcEV.js} +37 -7
- package/dist/{loader-C-6QN3xv.d.ts → loader-D22zZME7.d.ts} +2 -2
- package/dist/{loader-TFFNV4Jl.d.ts → loader-Myy22TDL.d.ts} +8 -2
- package/dist/{manager-dfI7b9N4.js → manager-CkletR5K.js} +29 -2
- package/dist/{manager-CoZaeQTu.d.ts → manager-Cv1NeFyF.d.ts} +28 -1
- package/dist/models/loader.d.ts +1 -1
- package/dist/models/loader.js +1 -1
- package/dist/mods/prepare.d.ts +1 -1
- package/dist/mods/prepare.js +1 -1
- package/dist/mods/synthesize.d.ts +2 -2
- package/dist/mods/synthesize.js +1 -1
- package/dist/mods/tints.d.ts +2 -2
- package/dist/mods/tints.js +1 -1
- package/dist/output/encode.d.ts +1 -1
- package/dist/output/encode.js +1 -1
- package/dist/output/item-styles.js +1 -1
- package/dist/pipeline-Da2Kavba.js +221 -0
- package/dist/pipeline-HiA3DC71.d.ts +58 -0
- package/dist/pool-CKGp1qup.js +131 -0
- package/dist/{prepare-BeUpXwaP.js → prepare-BESRrh6K.js} +4 -4
- package/dist/{prepare-gfR1aIjt.d.ts → prepare-CPxO0kKz.d.ts} +1 -1
- package/dist/{prepare-C8wRjAXP.js → prepare-Cc8QZ3j0.js} +4 -4
- package/dist/{prepare-wfWtaz_X.d.ts → prepare-Cs6QDqie.d.ts} +1 -1
- package/dist/{rasterizer-BHWDrmGz.js → rasterizer-76qO8TDh.js} +42 -4
- package/dist/{rasterizer-Dh35qXbn.d.ts → rasterizer-B9ZGJI3-.d.ts} +21 -2
- package/dist/render/blend.d.ts +1 -1
- package/dist/render/blend.js +1 -1
- package/dist/render/compose.d.ts +2 -2
- package/dist/render/compose.js +1 -1
- package/dist/render/entity-fallback.d.ts +6 -6
- package/dist/render/entity-fallback.js +1 -1
- package/dist/render/face.d.ts +2 -2
- package/dist/render/face.js +1 -1
- package/dist/render/fluid-bucket.d.ts +2 -2
- package/dist/render/geo.d.ts +4 -4
- package/dist/render/geo.js +1 -1
- package/dist/render/legacy-tints.d.ts +2 -2
- package/dist/render/obj.d.ts +5 -5
- package/dist/render/obj.js +1 -1
- package/dist/render/pipeline.d.ts +2 -0
- package/dist/render/pipeline.js +2 -0
- package/dist/render/rasterizer.d.ts +2 -2
- package/dist/render/rasterizer.js +2 -2
- package/dist/render/renderer.d.ts +2 -2
- package/dist/render/renderer.js +1 -1
- package/dist/render/tints.d.ts +1 -1
- package/dist/{renderer-nbMCw1Pl.d.ts → renderer-B20ssjRm.d.ts} +23 -6
- package/dist/{renderer-LDhBNHcl.js → renderer-gQW_I7YG.js} +146 -63
- package/dist/resources/cache.d.ts +30 -0
- package/dist/resources/cache.js +2 -0
- package/dist/resources/descriptor.d.ts +2 -0
- package/dist/resources/descriptor.js +2 -0
- package/dist/resources/jar.d.ts +1 -1
- package/dist/resources/jar.js +1 -1
- package/dist/resources/manager.d.ts +1 -1
- package/dist/resources/manager.js +1 -1
- package/dist/resources/synthetic.d.ts +1 -1
- package/dist/resources/synthetic.js +1 -1
- package/dist/resources/zip-reader.d.ts +5 -0
- package/dist/resources/zip-reader.js +2 -0
- package/dist/run-B0qPw3zf.js +209 -0
- package/dist/{run-BlSZw9Qk.d.ts → run-CT3VtFqg.d.ts} +12 -2
- package/dist/run.d.ts +1 -1
- package/dist/run.js +1 -1
- package/dist/{sources-DH9dk3KF.d.ts → sources-B9X9w3qP.d.ts} +1 -1
- package/dist/{synthesize-CRgSEmEp.js → synthesize-3Q0aaOnK.js} +1 -1
- package/dist/{synthetic-CDsj--li.d.ts → synthetic-Qk-BSUXO.d.ts} +5 -1
- package/dist/{synthetic-DC2bSnGe.js → synthetic-ZAUjXT7E.js} +0 -0
- package/dist/textures/loader.d.ts +1 -1
- package/dist/textures/loader.js +1 -1
- package/dist/{tints-CuG6GwQJ.js → tints-B8UJmZMu.js} +2 -2
- package/dist/{tints-txt-8xI5.d.ts → tints-BH9l0ND0.d.ts} +2 -2
- package/dist/versions/legacy-reshape.d.ts +1 -1
- package/dist/versions/legacy-reshape.js +1 -1
- package/dist/versions/prepare.d.ts +1 -1
- package/dist/versions/prepare.js +1 -1
- package/dist/workers/pool.d.ts +36 -5
- package/dist/workers/pool.js +2 -67
- package/dist/workers/render-worker.d.ts +15 -0
- package/dist/workers/render-worker.js +40 -0
- package/dist/zip-reader-BSbJKUT1.js +156 -0
- package/package.json +4 -2
- package/dist/compose-qjhyBvnS.js +0 -41
- package/dist/run-CfnWIRY4.js +0 -316
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { close, createReadStream, fstat, open, read } from "node:fs";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
import { RandomAccessReader, fromRandomAccessReader, open as open$1 } from "yauzl";
|
|
4
|
+
//#region src/resources/zip-reader.ts
|
|
5
|
+
/**
|
|
6
|
+
* Opens a zip with its central directory held in memory.
|
|
7
|
+
*
|
|
8
|
+
* yauzl reads each central-directory record with its own small read, so a
|
|
9
|
+
* 26,000-entry client jar costs ~26,000 syscalls and well over a second before
|
|
10
|
+
* the first icon renders. The directory is one contiguous region, so reading it
|
|
11
|
+
* whole and serving those reads from the buffer makes indexing ~6× faster while
|
|
12
|
+
* keeping entry data itself on disk — only the directory stays resident.
|
|
13
|
+
*
|
|
14
|
+
* Anything unexpected (zip64, a truncated archive, no end-of-directory record)
|
|
15
|
+
* falls back to yauzl's own open, which is correct for every archive.
|
|
16
|
+
*/
|
|
17
|
+
const EOCD_SIGNATURE = 101010256;
|
|
18
|
+
/** End-of-directory record plus the largest possible archive comment. */
|
|
19
|
+
const EOCD_SEARCH_BYTES = 65557;
|
|
20
|
+
const ZIP64_SENTINEL = 4294967295;
|
|
21
|
+
/** Above this, a range streams from its own descriptor instead of one buffer. */
|
|
22
|
+
const MAX_BUFFERED_READ_BYTES = 33554432;
|
|
23
|
+
/** Locate the central directory from the end-of-directory record. */
|
|
24
|
+
function findCentralDirectory(tail, tailStart) {
|
|
25
|
+
for (let index = tail.length - 22; index >= 0; index--) {
|
|
26
|
+
if (tail.readUInt32LE(index) !== EOCD_SIGNATURE) continue;
|
|
27
|
+
const size = tail.readUInt32LE(index + 12);
|
|
28
|
+
const offset = tail.readUInt32LE(index + 16);
|
|
29
|
+
if (size === ZIP64_SENTINEL || offset === ZIP64_SENTINEL) return null;
|
|
30
|
+
if (offset + size > tailStart + index) return null;
|
|
31
|
+
return {
|
|
32
|
+
offset,
|
|
33
|
+
size
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/** Promisified positional read; a raw descriptor avoids FileHandle's close-on-GC. */
|
|
39
|
+
async function readAt(fd, buffer, offset, length, position) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
read(fd, buffer, offset, length, position, (err, bytesRead) => {
|
|
42
|
+
if (err) {
|
|
43
|
+
reject(err);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
resolve(bytesRead);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
var BufferedDirectoryReader = class extends RandomAccessReader {
|
|
51
|
+
path;
|
|
52
|
+
fd;
|
|
53
|
+
directory;
|
|
54
|
+
directoryStart;
|
|
55
|
+
constructor(path, fd, directory, directoryStart) {
|
|
56
|
+
super();
|
|
57
|
+
this.path = path;
|
|
58
|
+
this.fd = fd;
|
|
59
|
+
this.directory = directory;
|
|
60
|
+
this.directoryStart = directoryStart;
|
|
61
|
+
}
|
|
62
|
+
_readStreamForRange(start, end) {
|
|
63
|
+
const directoryEnd = this.directoryStart + this.directory.length;
|
|
64
|
+
if (start >= this.directoryStart && end <= directoryEnd) return Readable.from([this.directory.subarray(start - this.directoryStart, end - this.directoryStart)]);
|
|
65
|
+
const length = end - start;
|
|
66
|
+
if (length > MAX_BUFFERED_READ_BYTES) return createReadStream(this.path, {
|
|
67
|
+
start,
|
|
68
|
+
end: end - 1
|
|
69
|
+
});
|
|
70
|
+
const { fd } = this;
|
|
71
|
+
return Readable.from((async function* read() {
|
|
72
|
+
const buffer = Buffer.alloc(length);
|
|
73
|
+
let filled = 0;
|
|
74
|
+
while (filled < length) {
|
|
75
|
+
const bytesRead = await readAt(fd, buffer, filled, length - filled, start + filled);
|
|
76
|
+
if (bytesRead === 0) break;
|
|
77
|
+
filled += bytesRead;
|
|
78
|
+
}
|
|
79
|
+
yield filled === length ? buffer : buffer.subarray(0, filled);
|
|
80
|
+
})());
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* yauzl reads each directory record through here. Copying from the buffer
|
|
84
|
+
* skips the per-read stream pipeline entirely, which is most of the cost of
|
|
85
|
+
* indexing tens of thousands of entries.
|
|
86
|
+
*/
|
|
87
|
+
read(buffer, offset, length, position, callback) {
|
|
88
|
+
const directoryEnd = this.directoryStart + this.directory.length;
|
|
89
|
+
if (position >= this.directoryStart && position + length <= directoryEnd) {
|
|
90
|
+
const from = position - this.directoryStart;
|
|
91
|
+
this.directory.copy(buffer, offset, from, from + length);
|
|
92
|
+
queueMicrotask(() => callback(null));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
readAt(this.fd, buffer, offset, length, position).then(() => callback(null)).catch(callback);
|
|
96
|
+
}
|
|
97
|
+
/** yauzl's close hook, reached when the ZipFile releases its last reference. */
|
|
98
|
+
close(callback) {
|
|
99
|
+
close(this.fd, (err) => callback(err ?? null));
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
async function openZip(zipPath) {
|
|
103
|
+
const fallback = async () => new Promise((resolve, reject) => {
|
|
104
|
+
open$1(zipPath, {
|
|
105
|
+
lazyEntries: true,
|
|
106
|
+
autoClose: false
|
|
107
|
+
}, (err, zip) => {
|
|
108
|
+
if (err) {
|
|
109
|
+
reject(err);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
resolve(zip);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
let fd;
|
|
116
|
+
try {
|
|
117
|
+
fd = await new Promise((resolve, reject) => {
|
|
118
|
+
open(zipPath, "r", (err, handle) => err ? reject(err) : resolve(handle));
|
|
119
|
+
});
|
|
120
|
+
} catch {
|
|
121
|
+
return fallback();
|
|
122
|
+
}
|
|
123
|
+
const abandon = async () => {
|
|
124
|
+
close(fd, () => {});
|
|
125
|
+
return fallback();
|
|
126
|
+
};
|
|
127
|
+
try {
|
|
128
|
+
const size = await new Promise((resolve, reject) => {
|
|
129
|
+
fstat(fd, (err, stats) => err ? reject(err) : resolve(stats.size));
|
|
130
|
+
});
|
|
131
|
+
const tailStart = Math.max(0, size - EOCD_SEARCH_BYTES);
|
|
132
|
+
const tail = Buffer.alloc(size - tailStart);
|
|
133
|
+
await readAt(fd, tail, 0, tail.length, tailStart);
|
|
134
|
+
const central = findCentralDirectory(tail, tailStart);
|
|
135
|
+
if (!central) return await abandon();
|
|
136
|
+
const directory = Buffer.alloc(central.size);
|
|
137
|
+
await readAt(fd, directory, 0, central.size, central.offset);
|
|
138
|
+
const reader = new BufferedDirectoryReader(zipPath, fd, directory, central.offset);
|
|
139
|
+
return await new Promise((resolve, reject) => {
|
|
140
|
+
fromRandomAccessReader(reader, size, {
|
|
141
|
+
lazyEntries: true,
|
|
142
|
+
autoClose: false
|
|
143
|
+
}, (err, zip) => {
|
|
144
|
+
if (err) {
|
|
145
|
+
reject(err);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
resolve(zip);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
} catch {
|
|
152
|
+
return abandon();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
export { openZip as t };
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodecraft/rendermite",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Minecraft item icon renderer: renders inventory-identical item icons from vanilla and modded assets across game versions, outputting optimized PNG/WebP/AVIF plus CSS and a JSON item library.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"author": "
|
|
7
|
+
"author": "Nodecraft",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/nodecraft/rendermite.git"
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
"lint": "eslint . --fix",
|
|
43
43
|
"lint:ci": "eslint .",
|
|
44
44
|
"test": "vitest run",
|
|
45
|
+
"bench": "vitest bench --config vitest.bench.config.ts --run",
|
|
46
|
+
"bench:report": "vitest bench --config vitest.bench.config.ts --run --outputJson bench-results.json && node scripts/bench-report.ts",
|
|
45
47
|
"test:watch": "vitest",
|
|
46
48
|
"typecheck": "tsc --noEmit",
|
|
47
49
|
"semantic-release": "semantic-release"
|
package/dist/compose-qjhyBvnS.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { t as blendPixel } from "./blend-CHP2-j9U.js";
|
|
2
|
-
//#region src/render/compose.ts
|
|
3
|
-
/**
|
|
4
|
-
* Per-pixel painter's compositing, mirroring upstream Model::combineFaceImages:
|
|
5
|
-
* at each pixel, faces sorted ascending by that pixel's depth value (0 where the
|
|
6
|
-
* rasterizer never wrote depth — the overdraw band relies on sorting beneath),
|
|
7
|
-
* then source-over blended in that order when alpha > 0.
|
|
8
|
-
*/
|
|
9
|
-
function combineFaceBuffers(width, height, faces) {
|
|
10
|
-
const out = new Float64Array(width * height * 4);
|
|
11
|
-
const order = [...faces];
|
|
12
|
-
for (let pixel = 0; pixel < width * height; pixel++) {
|
|
13
|
-
const x = pixel % width;
|
|
14
|
-
const y = Math.floor(pixel / width);
|
|
15
|
-
const localPixel = (face) => {
|
|
16
|
-
const localX = x - face.x;
|
|
17
|
-
const localY = y - face.y;
|
|
18
|
-
return localX >= 0 && localX < face.width && localY >= 0 && localY < face.height ? localY * face.width + localX : null;
|
|
19
|
-
};
|
|
20
|
-
order.sort((a, b) => {
|
|
21
|
-
const aPixel = localPixel(a);
|
|
22
|
-
const bPixel = localPixel(b);
|
|
23
|
-
return (aPixel === null ? 0 : a.depths[aPixel]) - (bPixel === null ? 0 : b.depths[bPixel]);
|
|
24
|
-
});
|
|
25
|
-
const offset = pixel * 4;
|
|
26
|
-
for (const face of order) {
|
|
27
|
-
const facePixel = localPixel(face);
|
|
28
|
-
if (facePixel === null) continue;
|
|
29
|
-
const faceOffset = facePixel * 4;
|
|
30
|
-
const alpha = face.colors[faceOffset + 3];
|
|
31
|
-
if (alpha > 0) blendPixel(out, offset, face.colors[faceOffset], face.colors[faceOffset + 1], face.colors[faceOffset + 2], alpha);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return {
|
|
35
|
-
width,
|
|
36
|
-
height,
|
|
37
|
-
data: out
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
//#endregion
|
|
41
|
-
export { combineFaceBuffers as t };
|
package/dist/run-CfnWIRY4.js
DELETED
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
import { a as defaultProperties } from "./definition-3MBm8ar_.js";
|
|
2
|
-
import { n as ResourceLocator } from "./locator-ycZzh_6z.js";
|
|
3
|
-
import { n as LayeredResourceManager, t as FolderResourceManager } from "./manager-dfI7b9N4.js";
|
|
4
|
-
import { t as loadAtlasResolver } from "./registry-B9TcjVbs.js";
|
|
5
|
-
import { a as builtinDynamicResources } from "./generators-CcVRG30y.js";
|
|
6
|
-
import { n as discoverItems, r as resolveItem } from "./discovery-0SM0cTLw.js";
|
|
7
|
-
import { t as ModelLoader } from "./loader-A-BZa8J4.js";
|
|
8
|
-
import { s as toFloatTexture, t as blendPixel } from "./blend-CHP2-j9U.js";
|
|
9
|
-
import { r as encodeRasterAsset, t as encodeAnimatedIcon } from "./encode-3u49i-eD.js";
|
|
10
|
-
import { t as classifyItemStyle } from "./item-styles-DJ3LWZS2.js";
|
|
11
|
-
import { t as minifyStylesheet } from "./minify-bjMRmr5J.js";
|
|
12
|
-
import { t as generateStylesheet } from "./stylesheet-CovT3B2S.js";
|
|
13
|
-
import { a as renderModelFrames, i as renderModel, n as boxDownscale, r as pointResize } from "./renderer-LDhBNHcl.js";
|
|
14
|
-
import { n as renderEntityFallback, r as spawnEggTints, t as renderDyedVariantFallback } from "./entity-fallback-DfL0_eyf.js";
|
|
15
|
-
import { i as renderGeo, t as findGeoForItem } from "./geo-Dq2zve09.js";
|
|
16
|
-
import { t as legacyTintsForChain } from "./legacy-tints-B09J0Rxh.js";
|
|
17
|
-
import { a as resolveTints } from "./tints-BtbHegpI.js";
|
|
18
|
-
import { n as TextureLoader } from "./loader-CBzn3Koc.js";
|
|
19
|
-
import path from "node:path";
|
|
20
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
21
|
-
//#region src/run.ts
|
|
22
|
-
/**
|
|
23
|
-
* Render-run orchestration: discover items, evaluate definitions, render,
|
|
24
|
-
* encode, write images + stylesheet + item list. Unlike upstream, failures are
|
|
25
|
-
* per-item, collected, and reported — a run summary counts files, never log lines.
|
|
26
|
-
*/
|
|
27
|
-
async function run(options) {
|
|
28
|
-
const namespaces = options.namespaces ?? ["minecraft"];
|
|
29
|
-
const size = options.size ?? 64;
|
|
30
|
-
const quality = options.quality ?? 2;
|
|
31
|
-
const prefix = options.prefix ?? "rc-";
|
|
32
|
-
const format = options.format ?? "webp";
|
|
33
|
-
const fallback = options.fallback ?? true;
|
|
34
|
-
const lightingProfile = options.lightingProfile ?? "modern";
|
|
35
|
-
const dynamic = options.builtin ? builtinDynamicResources() : null;
|
|
36
|
-
const resources = options.builtin && dynamic ? new LayeredResourceManager([
|
|
37
|
-
new FolderResourceManager(options.builtin),
|
|
38
|
-
dynamic.layer,
|
|
39
|
-
options.resources
|
|
40
|
-
]) : options.resources;
|
|
41
|
-
const models = new ModelLoader(resources);
|
|
42
|
-
const textures = new TextureLoader(resources);
|
|
43
|
-
const glint = await resolveEnchantmentGlint(resources);
|
|
44
|
-
if (!glint) throw new Error("Missing enchantment glint texture (minecraft:misc/enchanted_glint_item or enchanted_item_glint)");
|
|
45
|
-
const encodedGlint = await encodeRasterAsset(glint, fallback ? ["png", format] : [format]);
|
|
46
|
-
textures.setAtlasResolver(await loadAtlasResolver(resources, (locator) => textures.load(locator)));
|
|
47
|
-
const ids = [];
|
|
48
|
-
const seen = /* @__PURE__ */ new Set();
|
|
49
|
-
if (options.builtin) for (const placeholder of ["renderchest:unknown", "renderchest:empty"]) {
|
|
50
|
-
seen.add(placeholder);
|
|
51
|
-
ids.push(placeholder);
|
|
52
|
-
}
|
|
53
|
-
for (const namespace of namespaces) for (const item of await discoverItems(resources, namespace, { filterUntranslatedModels: options.filterUntranslatedModels })) {
|
|
54
|
-
if (seen.has(item.id)) continue;
|
|
55
|
-
if (options.builtin && !await itemExistsIn(options.resources, item.id)) continue;
|
|
56
|
-
seen.add(item.id);
|
|
57
|
-
ids.push(item.id);
|
|
58
|
-
}
|
|
59
|
-
if (dynamic && namespaces.includes("minecraft")) {
|
|
60
|
-
const hasTrims = await resources.hasResource("texture", ResourceLocator.parse("minecraft:trims/color_palettes/trim_palette"));
|
|
61
|
-
const groupAvailable = {
|
|
62
|
-
_armor_trims: hasTrims,
|
|
63
|
-
_leather_armor: hasTrims,
|
|
64
|
-
_decorated_pot: await resources.hasResource("texture", ResourceLocator.parse("minecraft:entity/decorated_pot/decorated_pot_base")),
|
|
65
|
-
_crossbow: await resources.hasResource("model", ResourceLocator.parse("minecraft:item/crossbow_arrow"))
|
|
66
|
-
};
|
|
67
|
-
for (const item of dynamic.items) if ((groupAvailable[item.id.split(":", 1)[0]] ?? true) && !seen.has(item.id)) {
|
|
68
|
-
seen.add(item.id);
|
|
69
|
-
ids.push(item.id);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
const rendered = [];
|
|
73
|
-
const failed = [];
|
|
74
|
-
const spriteFallbacks = [];
|
|
75
|
-
const skippedTemplates = [];
|
|
76
|
-
const styles = [];
|
|
77
|
-
let writtenFiles = 0;
|
|
78
|
-
await mkdir(options.output, { recursive: true });
|
|
79
|
-
for (const [extension, buffer] of encodedGlint) {
|
|
80
|
-
await writeFile(path.join(options.output, `${prefix}enchantment.${extension}`), buffer);
|
|
81
|
-
writtenFiles++;
|
|
82
|
-
}
|
|
83
|
-
let templateParents = null;
|
|
84
|
-
const loadTemplateParents = async () => {
|
|
85
|
-
const parents = /* @__PURE__ */ new Set();
|
|
86
|
-
for (const namespace of namespaces) for (const modelLocator of await resources.listResources("model", namespace)) {
|
|
87
|
-
if (!modelLocator.path.startsWith("item/")) continue;
|
|
88
|
-
const raw = await resources.getResource("model", modelLocator);
|
|
89
|
-
if (!raw) continue;
|
|
90
|
-
try {
|
|
91
|
-
const parent = JSON.parse(raw.toString("utf8")).parent;
|
|
92
|
-
if (typeof parent === "string") parents.add(ResourceLocator.parse(parent).toString());
|
|
93
|
-
} catch {}
|
|
94
|
-
}
|
|
95
|
-
return parents;
|
|
96
|
-
};
|
|
97
|
-
const isTemplateModel = async (id) => {
|
|
98
|
-
const locator = ResourceLocator.parse(id);
|
|
99
|
-
if (await resources.hasResource("itemDefinition", locator)) return false;
|
|
100
|
-
templateParents ??= loadTemplateParents();
|
|
101
|
-
return (await templateParents).has(new ResourceLocator(locator.namespace, `item/${locator.path}`).toString());
|
|
102
|
-
};
|
|
103
|
-
/**
|
|
104
|
-
* Code-rendered models often still ship a flat item sprite (the old
|
|
105
|
-
* pipeline's black-icon audit checked exactly this). A real sprite beats
|
|
106
|
-
* no icon; the fallback is recorded so it is never a silent substitution.
|
|
107
|
-
*/
|
|
108
|
-
const spriteFallback = async (id) => {
|
|
109
|
-
const locator = ResourceLocator.parse(id);
|
|
110
|
-
for (const directory of ["item", "items"]) {
|
|
111
|
-
const texture = await textures.load(new ResourceLocator(locator.namespace, `${directory}/${locator.path}`));
|
|
112
|
-
if (texture) {
|
|
113
|
-
const float = toFloatTexture(texture);
|
|
114
|
-
const upscaled = pointResize(float, size * quality, size * quality);
|
|
115
|
-
return quality === 1 ? upscaled : boxDownscale(upscaled, quality);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return null;
|
|
119
|
-
};
|
|
120
|
-
const concurrency = Math.max(1, options.concurrency ?? 8);
|
|
121
|
-
let nextIndex = 0;
|
|
122
|
-
let completed = 0;
|
|
123
|
-
const renderFrames = async (id) => {
|
|
124
|
-
const instructions = await resolveItem(resources, id, defaultProperties());
|
|
125
|
-
let frames;
|
|
126
|
-
let delaysMs;
|
|
127
|
-
if (instructions.length === 1) {
|
|
128
|
-
const model = await models.load(instructions[0].model);
|
|
129
|
-
let tints = instructions[0].tints.length > 0 ? await resolveTints(instructions[0].tints, { textures }) : await legacyTintsForChain(model.chain, textures);
|
|
130
|
-
if (!tints && model.chain.some((entry) => entry.endsWith("item/template_spawn_egg"))) tints = await spawnEggTints(id, resources, textures).catch(() => null);
|
|
131
|
-
tints ??= [];
|
|
132
|
-
const result = await renderModelFrames(model, textures, {
|
|
133
|
-
size,
|
|
134
|
-
quality,
|
|
135
|
-
lightingProfile,
|
|
136
|
-
itemTransform: instructions[0].transform,
|
|
137
|
-
tints,
|
|
138
|
-
resources
|
|
139
|
-
});
|
|
140
|
-
frames = result.frames;
|
|
141
|
-
delaysMs = result.delaysMs;
|
|
142
|
-
} else {
|
|
143
|
-
let combined = null;
|
|
144
|
-
for (const instruction of instructions) {
|
|
145
|
-
const model = await models.load(instruction.model);
|
|
146
|
-
const tints = instruction.tints.length > 0 ? await resolveTints(instruction.tints, { textures }) : await legacyTintsForChain(model.chain, textures) ?? [];
|
|
147
|
-
const renderedModel = await renderModel(model, textures, {
|
|
148
|
-
size,
|
|
149
|
-
quality,
|
|
150
|
-
lightingProfile,
|
|
151
|
-
itemTransform: instruction.transform,
|
|
152
|
-
tints,
|
|
153
|
-
resources
|
|
154
|
-
});
|
|
155
|
-
combined = combined === null ? renderedModel : blendOver(combined, renderedModel);
|
|
156
|
-
}
|
|
157
|
-
frames = [combined ?? {
|
|
158
|
-
width: size,
|
|
159
|
-
height: size,
|
|
160
|
-
data: new Float64Array(size * size * 4)
|
|
161
|
-
}];
|
|
162
|
-
delaysMs = [50];
|
|
163
|
-
}
|
|
164
|
-
if (frames.length === 0) {
|
|
165
|
-
frames = [{
|
|
166
|
-
width: size,
|
|
167
|
-
height: size,
|
|
168
|
-
data: new Float64Array(size * size * 4)
|
|
169
|
-
}];
|
|
170
|
-
delaysMs = [50];
|
|
171
|
-
}
|
|
172
|
-
return {
|
|
173
|
-
frames,
|
|
174
|
-
delaysMs
|
|
175
|
-
};
|
|
176
|
-
};
|
|
177
|
-
const renderOne = async (index) => {
|
|
178
|
-
const id = ids[index];
|
|
179
|
-
let frames;
|
|
180
|
-
let delaysMs;
|
|
181
|
-
let usedFallback = false;
|
|
182
|
-
try {
|
|
183
|
-
({frames, delaysMs} = await renderFrames(id));
|
|
184
|
-
} catch (err) {
|
|
185
|
-
const blockLocator = ResourceLocator.parse(id);
|
|
186
|
-
const blockRender = await models.load(new ResourceLocator(blockLocator.namespace, `block/${blockLocator.path}`)).then((model) => renderModel(model, textures, {
|
|
187
|
-
size,
|
|
188
|
-
quality,
|
|
189
|
-
lightingProfile,
|
|
190
|
-
resources
|
|
191
|
-
})).catch(() => null);
|
|
192
|
-
const geo = blockRender ? null : await findGeoForItem(resources, id).catch(() => null);
|
|
193
|
-
const geoRender = geo ? await renderGeo(geo.geo, geo.texture, textures, {
|
|
194
|
-
size,
|
|
195
|
-
quality,
|
|
196
|
-
lightingProfile
|
|
197
|
-
}).catch(() => null) : null;
|
|
198
|
-
let fallbackRender = blockRender ?? (geoRender && quality !== 1 ? boxDownscale(geoRender, quality) : geoRender);
|
|
199
|
-
fallbackRender ??= options.builtin ? await renderEntityFallback(id, models, textures, resources, {
|
|
200
|
-
size,
|
|
201
|
-
quality,
|
|
202
|
-
lightingProfile,
|
|
203
|
-
resources
|
|
204
|
-
}).catch(() => null) : null;
|
|
205
|
-
fallbackRender ??= await renderDyedVariantFallback(id, models, textures, {
|
|
206
|
-
size,
|
|
207
|
-
quality,
|
|
208
|
-
lightingProfile,
|
|
209
|
-
resources
|
|
210
|
-
}).catch(() => null);
|
|
211
|
-
if (fallbackRender) {
|
|
212
|
-
frames = [fallbackRender];
|
|
213
|
-
delaysMs = [50];
|
|
214
|
-
} else {
|
|
215
|
-
const fallback = await spriteFallback(id).catch(() => null);
|
|
216
|
-
if (!fallback) {
|
|
217
|
-
if (await isTemplateModel(id).catch(() => false)) skippedTemplates.push(id);
|
|
218
|
-
else failed.push({
|
|
219
|
-
id,
|
|
220
|
-
error: err.message
|
|
221
|
-
});
|
|
222
|
-
completed++;
|
|
223
|
-
options.onProgress?.(completed, ids.length, id, err.message);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
frames = [fallback];
|
|
227
|
-
delaysMs = [50];
|
|
228
|
-
usedFallback = true;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
try {
|
|
232
|
-
const encoded = await encodeAnimatedIcon(frames, delaysMs, {
|
|
233
|
-
webp: true,
|
|
234
|
-
gif: options.gif,
|
|
235
|
-
avif: options.avif || format === "avif"
|
|
236
|
-
});
|
|
237
|
-
const [namespace, itemPath] = id.split(":", 2);
|
|
238
|
-
const imageBase = path.join(options.output, "items", namespace, `${itemPath}`);
|
|
239
|
-
await mkdir(path.dirname(imageBase), { recursive: true });
|
|
240
|
-
await writeFile(`${imageBase}.png`, encoded.png);
|
|
241
|
-
writtenFiles++;
|
|
242
|
-
for (const [extension, buffer] of [
|
|
243
|
-
["webp", encoded.webp],
|
|
244
|
-
["gif", encoded.gif],
|
|
245
|
-
["avif", encoded.avif]
|
|
246
|
-
]) {
|
|
247
|
-
if (!buffer) continue;
|
|
248
|
-
await writeFile(`${imageBase}.${extension}`, buffer);
|
|
249
|
-
writtenFiles++;
|
|
250
|
-
}
|
|
251
|
-
rendered.push(id);
|
|
252
|
-
if (usedFallback) spriteFallbacks.push(id);
|
|
253
|
-
completed++;
|
|
254
|
-
options.onProgress?.(completed, ids.length, id);
|
|
255
|
-
} catch (err) {
|
|
256
|
-
failed.push({
|
|
257
|
-
id,
|
|
258
|
-
error: err.message
|
|
259
|
-
});
|
|
260
|
-
completed++;
|
|
261
|
-
options.onProgress?.(completed, ids.length, id, err.message);
|
|
262
|
-
}
|
|
263
|
-
};
|
|
264
|
-
await Promise.all(Array.from({ length: Math.min(concurrency, ids.length) }, async () => {
|
|
265
|
-
while (nextIndex < ids.length) {
|
|
266
|
-
const index = nextIndex++;
|
|
267
|
-
await renderOne(index);
|
|
268
|
-
}
|
|
269
|
-
}));
|
|
270
|
-
rendered.sort((left, right) => left.localeCompare(right));
|
|
271
|
-
const renderedSet = new Set(rendered);
|
|
272
|
-
for (const id of rendered) {
|
|
273
|
-
const style = classifyItemStyle(id, renderedSet);
|
|
274
|
-
if (style) styles.push(style);
|
|
275
|
-
}
|
|
276
|
-
const stylesheet = generateStylesheet(styles, {
|
|
277
|
-
prefix,
|
|
278
|
-
format,
|
|
279
|
-
fallback
|
|
280
|
-
});
|
|
281
|
-
const finalStylesheet = options.minify === false ? stylesheet : minifyStylesheet(stylesheet, (message) => options.onProgress?.(0, 0, "stylesheet", message));
|
|
282
|
-
await writeFile(path.join(options.output, `${prefix}${namespaces[0]}-${size}.css`), finalStylesheet);
|
|
283
|
-
if (options.itemList !== false) {
|
|
284
|
-
await writeFile(path.join(options.output, `${prefix}${namespaces[0]}.json`), JSON.stringify(rendered));
|
|
285
|
-
await writeFile(path.join(options.output, `${prefix}${namespaces[0]}-failures.json`), JSON.stringify(failed, null, " "));
|
|
286
|
-
}
|
|
287
|
-
return {
|
|
288
|
-
rendered,
|
|
289
|
-
failed,
|
|
290
|
-
spriteFallbacks,
|
|
291
|
-
skippedTemplates,
|
|
292
|
-
writtenFiles
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
async function itemExistsIn(resources, id) {
|
|
296
|
-
const locator = ResourceLocator.parse(id);
|
|
297
|
-
if (await resources.hasResource("itemDefinition", locator)) return true;
|
|
298
|
-
return resources.hasResource("model", new ResourceLocator(locator.namespace, `item/${locator.path}`));
|
|
299
|
-
}
|
|
300
|
-
/** Resolve both names used on either side of Minecraft 1.19.3. */
|
|
301
|
-
async function resolveEnchantmentGlint(resources) {
|
|
302
|
-
for (const path of ["misc/enchanted_glint_item", "misc/enchanted_item_glint"]) {
|
|
303
|
-
const texture = await resources.getResource("texture", new ResourceLocator("minecraft", path));
|
|
304
|
-
if (texture) return texture;
|
|
305
|
-
}
|
|
306
|
-
return null;
|
|
307
|
-
}
|
|
308
|
-
function blendOver(base, layer) {
|
|
309
|
-
for (let offset = 0; offset < base.data.length; offset += 4) {
|
|
310
|
-
const alpha = layer.data[offset + 3];
|
|
311
|
-
if (alpha > 0) blendPixel(base.data, offset, layer.data[offset], layer.data[offset + 1], layer.data[offset + 2], alpha);
|
|
312
|
-
}
|
|
313
|
-
return base;
|
|
314
|
-
}
|
|
315
|
-
//#endregion
|
|
316
|
-
export { run as t };
|