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