minerender 2.1.0-beta.7 → 2.1.0-beta.8

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 (73) hide show
  1. package/README.md +10 -1
  2. package/dist/browser/esm/armor/Armor.mjs +84 -0
  3. package/dist/browser/esm/armor/Armor.mjs.map +1 -0
  4. package/dist/browser/esm/armor/index.mjs +3 -0
  5. package/dist/browser/esm/armor/index.mjs.map +1 -0
  6. package/dist/browser/esm/assets/BlockEntities.mjs +2 -0
  7. package/dist/browser/esm/assets/BlockEntities.mjs.map +1 -1
  8. package/dist/browser/esm/assets/Entities.mjs +39 -11
  9. package/dist/browser/esm/assets/Entities.mjs.map +1 -1
  10. package/dist/browser/esm/assets/MineRenderData.mjs +8 -17
  11. package/dist/browser/esm/assets/MineRenderData.mjs.map +1 -1
  12. package/dist/browser/esm/assets/ModelTextures.mjs +13 -7
  13. package/dist/browser/esm/assets/ModelTextures.mjs.map +1 -1
  14. package/dist/browser/esm/assets/_versions/DatasetVersions.mjs +22 -0
  15. package/dist/browser/esm/assets/_versions/DatasetVersions.mjs.map +1 -0
  16. package/dist/browser/esm/cache/Caching.mjs +8 -0
  17. package/dist/browser/esm/cache/Caching.mjs.map +1 -1
  18. package/dist/browser/esm/entity/scene/EntityObject.mjs +31 -5
  19. package/dist/browser/esm/entity/scene/EntityObject.mjs.map +1 -1
  20. package/dist/browser/esm/env/browser/BrowserEnv.mjs +11 -4
  21. package/dist/browser/esm/env/browser/BrowserEnv.mjs.map +1 -1
  22. package/dist/browser/esm/index.browser.d.mts +129 -13
  23. package/dist/browser/esm/index.browser.mjs.map +1 -1
  24. package/dist/browser/esm/index.mjs +1 -0
  25. package/dist/browser/esm/index.mjs.map +1 -1
  26. package/dist/browser/esm/model/ItemGlint.mjs +7 -6
  27. package/dist/browser/esm/model/ItemGlint.mjs.map +1 -1
  28. package/dist/browser/esm/model/block/scene/BlockObject.mjs +16 -5
  29. package/dist/browser/esm/model/block/scene/BlockObject.mjs.map +1 -1
  30. package/dist/browser/esm/scene/SceneDocument.mjs +35 -5
  31. package/dist/browser/esm/scene/SceneDocument.mjs.map +1 -1
  32. package/dist/browser/esm/scene/SceneDocumentLoader.mjs +14 -3
  33. package/dist/browser/esm/scene/SceneDocumentLoader.mjs.map +1 -1
  34. package/dist/browser/esm/skin/SkinGeometries.mjs +4 -1
  35. package/dist/browser/esm/skin/SkinGeometries.mjs.map +1 -1
  36. package/dist/browser/esm/skin/SkinPart.mjs +2 -0
  37. package/dist/browser/esm/skin/SkinPart.mjs.map +1 -1
  38. package/dist/browser/esm/skin/SkinTextureCoordinates.mjs +11 -1
  39. package/dist/browser/esm/skin/SkinTextureCoordinates.mjs.map +1 -1
  40. package/dist/browser/esm/skin/SkinTextures.mjs +1 -1
  41. package/dist/browser/esm/skin/SkinTextures.mjs.map +1 -1
  42. package/dist/browser/esm/skin/scene/SkinObject.mjs +119 -7
  43. package/dist/browser/esm/skin/scene/SkinObject.mjs.map +1 -1
  44. package/dist/browser/esm/texture/PalettedTextures.mjs +46 -0
  45. package/dist/browser/esm/texture/PalettedTextures.mjs.map +1 -0
  46. package/dist/browser/esm/texture/index.mjs +1 -0
  47. package/dist/browser/esm/texture/index.mjs.map +1 -1
  48. package/dist/browser/esm/util/util.mjs +0 -2
  49. package/dist/browser/esm/util/util.mjs.map +1 -1
  50. package/dist/browser/esm/world/Chunk.mjs +62 -33
  51. package/dist/browser/esm/world/Chunk.mjs.map +1 -1
  52. package/dist/browser/esm/world/MineRenderWorld.mjs +98 -13
  53. package/dist/browser/esm/world/MineRenderWorld.mjs.map +1 -1
  54. package/dist/browser/esm/world/SectionMesh.mjs +65 -9
  55. package/dist/browser/esm/world/SectionMesh.mjs.map +1 -1
  56. package/dist/browser/esm/world/SectionModels.mjs +21 -15
  57. package/dist/browser/esm/world/SectionModels.mjs.map +1 -1
  58. package/dist/browser/esm/world/SectionWorker.mjs +8 -2
  59. package/dist/browser/esm/world/SectionWorker.mjs.map +1 -1
  60. package/dist/browser/esm/world/WorldStreamer.mjs +109 -15
  61. package/dist/browser/esm/world/WorldStreamer.mjs.map +1 -1
  62. package/dist/browser/index.d.ts +129 -13
  63. package/dist/browser/index.js +1592 -894
  64. package/dist/browser/index.js.map +1 -1
  65. package/dist/bundle.js +1330 -619
  66. package/dist/bundle.js.map +1 -1
  67. package/dist/node/index.d.mts +117 -12
  68. package/dist/node/index.d.ts +117 -12
  69. package/dist/node/index.js +1505 -793
  70. package/dist/node/index.js.map +1 -1
  71. package/dist/node/index.mjs +1303 -593
  72. package/dist/node/index.mjs.map +1 -1
  73. package/package.json +2 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MineRender V2
2
2
 
3
- A TypeScript library for interactive Minecraft skins, models, blocks, entities, and worlds.
3
+ A TypeScript library for interactive Minecraft skins, models, blocks, entities, GUIs, and worlds.
4
4
  V2 is in beta. See the [roadmap](https://github.com/InventivetalentDev/MineRender/blob/main/ROADMAP.md) for feature parity and remaining work.
5
5
 
6
6
  Live examples and usage docs: [beta.minerender.org](https://beta.minerender.org/). API reference: [beta-docs.minerender.org](https://beta-docs.minerender.org/).
@@ -29,6 +29,11 @@ an injected WebGL 2 context or the optional native `gl` backend. See
29
29
 
30
30
  Requires Node.js 22.12+ or a browser with WebGL 2, Fetch, `AbortSignal.any()`, and `AbortSignal.timeout()`.
31
31
 
32
+ The library prints only warnings and errors. To see debug output, call
33
+ `Logging.configure({ level: "debug" })`, or set the `minerender:log` localStorage key
34
+ in a browser or the `MINERENDER_LOG` environment variable in Node. See
35
+ [Logging](https://beta-docs.minerender.org/concepts#logging).
36
+
32
37
  ## Resource-pack ZIPs
33
38
 
34
39
  Register a browser `File` or `Blob` before loading models or textures:
@@ -123,3 +128,7 @@ Supported object types are `skin`, `block`, `item`, `model`, `entity`, and `gui`
123
128
  Positions use model units (16 per block); JSON rotations use radians.
124
129
 
125
130
  Callers apply the saved camera settings and select the matching asset version.
131
+
132
+ `encodeSceneDocument` and `decodeSceneDocument` convert a document to and from the compact
133
+ link format used by the editor. To show a scene on another page without bundling the library,
134
+ use the [iframe embed page](https://beta-docs.minerender.org/embeds).
@@ -0,0 +1,84 @@
1
+ // src/armor/Armor.ts
2
+ import { AssetKey, BasicAssetKey, isResourceLocation } from "../assets/AssetKey.mjs";
3
+ import { AssetLoader } from "../assets/AssetLoader.mjs";
4
+ import { Entities } from "../assets/Entities.mjs";
5
+ import { AssetParser } from "../assets/source/parser/AssetParsers.mjs";
6
+ import { Caching } from "../cache/Caching.mjs";
7
+ import { MineRenderError } from "../error/MineRenderError.mjs";
8
+ var ARMOR_TRANSFORM = [
9
+ { rotate: [0, Math.PI, 0] },
10
+ { scale: [-1, -1, 1] },
11
+ { translate: [0, -24, 0] }
12
+ ];
13
+ var Armor = class {
14
+ /** Loads 1.21.4+ armor in SkinObject's frame, or returns undefined when no layer draws. */
15
+ static async getEntity(armor) {
16
+ const model = await Entities.getModelFile(new BasicAssetKey("minecraft", "player"));
17
+ const layers = {};
18
+ const slots = { chest: "chestplate", legs: "leggings", feet: "boots", head: "helmet" };
19
+ for (const slot of Object.keys(slots)) {
20
+ const value = armor[slot];
21
+ if (value === void 0) continue;
22
+ const piece = typeof value === "string" ? { asset: value } : value;
23
+ const name = slots[slot], geometry = model?.layers[name];
24
+ if (!geometry) throw new MineRenderError(`Missing armor geometry ${name}; armor needs Minecraft 1.21.4 or later`);
25
+ const id = this.identifier(piece.asset);
26
+ const key = new AssetKey(id.namespace, id.path, "equipment", void 0, "assets", ".json");
27
+ const equipment = await Caching.equipmentCache.get(key.serialize(), () => AssetLoader.get(key, AssetParser.JSON));
28
+ if (!equipment) throw new MineRenderError(`Missing equipment asset ${piece.asset}; armor takes equipment asset IDs such as minecraft:diamond and needs Minecraft 1.21.4 or later`);
29
+ const layerType = slot === "legs" ? "humanoid_leggings" : "humanoid";
30
+ const equipmentLayers = equipment.layers[layerType];
31
+ if (!equipmentLayers?.length) continue;
32
+ let count = 0;
33
+ const add = (texture, settings) => {
34
+ layers[++count === 1 ? name : `${name}#${count}`] = { key: texture, texture, layer: geometry, ...settings };
35
+ };
36
+ let glint = piece.glint;
37
+ for (const layer of equipmentLayers) {
38
+ let color;
39
+ if (layer.dyeable) {
40
+ const undyed = layer.dyeable.color_when_undyed;
41
+ color = piece.color || (Array.isArray(undyed) ? Math.floor(undyed[0] * 255) << 16 | Math.floor(undyed[1] * 255) << 8 | Math.floor(undyed[2] * 255) : undyed === void 0 ? void 0 : undyed & 16777215);
42
+ if (color === void 0) continue;
43
+ }
44
+ const texture = this.identifier(layer.texture);
45
+ add(
46
+ new AssetKey(texture.namespace, `equipment/${layerType}/${texture.path}`, "textures", "entity", "assets", ".png"),
47
+ { ...color !== void 0 && { color }, ...glint && { glint: true } }
48
+ );
49
+ glint = false;
50
+ }
51
+ if (piece.trim) {
52
+ const patternId = this.identifier(piece.trim.pattern), materialId = this.identifier(piece.trim.material);
53
+ const patternKey = new AssetKey(patternId.namespace, patternId.path, "trim_pattern", void 0, "data", ".json");
54
+ const materialKey = new AssetKey(materialId.namespace, materialId.path, "trim_material", void 0, "data", ".json");
55
+ const pattern = await Caching.trimCache.get(patternKey.serialize(), () => AssetLoader.get(patternKey, AssetParser.JSON));
56
+ if (!pattern) throw new MineRenderError(`Missing trim pattern ${piece.trim.pattern}`);
57
+ const material = await Caching.trimCache.get(materialKey.serialize(), () => AssetLoader.get(materialKey, AssetParser.JSON));
58
+ if (!material) throw new MineRenderError(`Missing trim material ${piece.trim.material}`);
59
+ const patternAsset = this.identifier(pattern.asset_id);
60
+ const suffix = material.override_armor_assets?.[id.toNamespacedString()] ?? material.asset_name;
61
+ add(new AssetKey(patternAsset.namespace, `entity/${layerType}/${patternAsset.path}`, "textures", "trims", "assets", ".png"), {
62
+ palette: {
63
+ key: new AssetKey("minecraft", "color_palettes/trim_palette", "textures", "trims", "assets", ".png"),
64
+ permutation: new AssetKey("minecraft", `color_palettes/${suffix}`, "textures", "trims", "assets", ".png")
65
+ },
66
+ render: pattern.decal ? "cutout_z_offset" : "cutout"
67
+ });
68
+ }
69
+ }
70
+ const first = Object.values(layers)[0];
71
+ return first ? { ...first, id: "minecraft:player", layers, transform: ARMOR_TRANSFORM } : void 0;
72
+ }
73
+ static identifier(value) {
74
+ if (!isResourceLocation(value) || value.split(/[:/]/).some((part) => !part || part === "." || part === "..")) {
75
+ throw new MineRenderError(`Invalid armor identifier ${value}`);
76
+ }
77
+ const [namespace, path] = value.includes(":") ? value.split(":") : ["minecraft", value];
78
+ return new BasicAssetKey(namespace, path);
79
+ }
80
+ };
81
+ export {
82
+ Armor
83
+ };
84
+ //# sourceMappingURL=Armor.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/armor/Armor.ts"],"sourcesContent":["import { AssetKey, BasicAssetKey, isResourceLocation } from \"../assets/AssetKey\";\nimport { AssetLoader } from \"../assets/AssetLoader\";\nimport { Entities } from \"../assets/Entities\";\nimport { AssetParser } from \"../assets/source/parser/AssetParsers\";\nimport { Caching } from \"../cache/Caching\";\nimport type { EntityLayer, EntityModel, EntityTransformOp } from \"../entity/EntityModel\";\nimport { MineRenderError } from \"../error/MineRenderError\";\nimport type { Maybe } from \"../util/util\";\n\nexport type ArmorSlot = \"head\" | \"chest\" | \"legs\" | \"feet\";\n\nexport interface ArmorTrim {\n /** `trim_pattern` registry ID, such as `minecraft:sentry`. */\n pattern: string;\n /** `trim_material` registry ID, such as `minecraft:iron`. */\n material: string;\n}\n\n/** An equipment asset ID, such as `minecraft:diamond`, or the asset with the worn item's state. */\nexport type ArmorPiece = string | {\n asset: string;\n /** `dyed_color` as an RGB integer. */\n color?: number;\n trim?: ArmorTrim;\n /** Draws the enchantment glint, as an enchanted item does. */\n glint?: boolean;\n};\n\nexport type ArmorSet = Partial<Record<ArmorSlot, ArmorPiece>>;\n\nconst ARMOR_TRANSFORM: EntityTransformOp[] = [\n { rotate: [0, Math.PI, 0] }, { scale: [-1, -1, 1] }, { translate: [0, -24, 0] }\n];\n\n/** Resolves humanoid equipment assets into armor draws. */\nexport class Armor {\n /** Loads 1.21.4+ armor in SkinObject's frame, or returns undefined when no layer draws. */\n public static async getEntity(armor: ArmorSet): Promise<Maybe<EntityModel>> {\n const model = await Entities.getModelFile(new BasicAssetKey(\"minecraft\", \"player\"));\n const layers: Record<string, EntityLayer> = {};\n // HumanoidArmorLayer.submit draws the helmet last.\n const slots = { chest: \"chestplate\", legs: \"leggings\", feet: \"boots\", head: \"helmet\" } as const;\n for (const slot of Object.keys(slots) as ArmorSlot[]) {\n const value = armor[slot];\n if (value === undefined) continue;\n const piece = typeof value === \"string\" ? { asset: value } : value;\n const name = slots[slot], geometry = model?.layers[name];\n if (!geometry) throw new MineRenderError(`Missing armor geometry ${name}; armor needs Minecraft 1.21.4 or later`);\n const id = this.identifier(piece.asset);\n const key = new AssetKey(id.namespace, id.path, \"equipment\", undefined, \"assets\", \".json\");\n const equipment = await Caching.equipmentCache.get(key.serialize(), () => AssetLoader.get(key, AssetParser.JSON)) as Maybe<{\n layers: Record<string, { texture: string; dyeable?: { color_when_undyed?: number | [number, number, number] } }[]>;\n }>;\n if (!equipment) throw new MineRenderError(`Missing equipment asset ${piece.asset}; armor takes equipment asset IDs such as minecraft:diamond and needs Minecraft 1.21.4 or later`);\n // HumanoidArmorLayer.usesInnerModel selects the inner texture only for leggings.\n const layerType = slot === \"legs\" ? \"humanoid_leggings\" : \"humanoid\";\n const equipmentLayers = equipment.layers[layerType];\n if (!equipmentLayers?.length) continue;\n let count = 0;\n const add = (texture: AssetKey, settings: Partial<EntityLayer>) => {\n layers[++count === 1 ? name : `${name}#${count}`] = { key: texture, texture, layer: geometry, ...settings };\n };\n let glint = piece.glint;\n for (const layer of equipmentLayers) {\n let color: number | undefined;\n if (layer.dyeable) {\n // EquipmentLayerRenderer.getColorForLayer treats dyed colour 0 as undyed.\n const undyed = layer.dyeable.color_when_undyed;\n color = piece.color || (Array.isArray(undyed)\n ? (Math.floor(undyed[0] * 255) << 16) | (Math.floor(undyed[1] * 255) << 8) | Math.floor(undyed[2] * 255)\n : undyed === undefined ? undefined : undyed & 0xffffff);\n if (color === undefined) continue;\n }\n const texture = this.identifier(layer.texture);\n add(new AssetKey(texture.namespace, `equipment/${layerType}/${texture.path}`, \"textures\", \"entity\", \"assets\", \".png\"),\n { ...(color !== undefined && { color }), ...(glint && { glint: true }) });\n // EquipmentLayerRenderer.renderLayers consumes foil on the first drawn layer.\n glint = false;\n }\n if (piece.trim) {\n const patternId = this.identifier(piece.trim.pattern), materialId = this.identifier(piece.trim.material);\n const patternKey = new AssetKey(patternId.namespace, patternId.path, \"trim_pattern\", undefined, \"data\", \".json\");\n const materialKey = new AssetKey(materialId.namespace, materialId.path, \"trim_material\", undefined, \"data\", \".json\");\n const pattern = await Caching.trimCache.get(patternKey.serialize(), () => AssetLoader.get(patternKey, AssetParser.JSON)) as Maybe<{ asset_id: string; decal?: boolean }>;\n if (!pattern) throw new MineRenderError(`Missing trim pattern ${piece.trim.pattern}`);\n const material = await Caching.trimCache.get(materialKey.serialize(), () => AssetLoader.get(materialKey, AssetParser.JSON)) as Maybe<{ asset_name: string; override_armor_assets?: Record<string, string> }>;\n if (!material) throw new MineRenderError(`Missing trim material ${piece.trim.material}`);\n const patternAsset = this.identifier(pattern.asset_id);\n // MaterialAssetGroup.assetId keys overrides by equipment asset, not by item ID.\n const suffix = material.override_armor_assets?.[id.toNamespacedString()] ?? material.asset_name;\n add(new AssetKey(patternAsset.namespace, `entity/${layerType}/${patternAsset.path}`, \"textures\", \"trims\", \"assets\", \".png\"), {\n palette: {\n key: new AssetKey(\"minecraft\", \"color_palettes/trim_palette\", \"textures\", \"trims\", \"assets\", \".png\"),\n permutation: new AssetKey(\"minecraft\", `color_palettes/${suffix}`, \"textures\", \"trims\", \"assets\", \".png\")\n },\n render: pattern.decal ? \"cutout_z_offset\" : \"cutout\"\n });\n }\n }\n const first = Object.values(layers)[0];\n return first ? { ...first, id: \"minecraft:player\", layers, transform: ARMOR_TRANSFORM } : undefined;\n }\n\n private static identifier(value: string): BasicAssetKey {\n if (!isResourceLocation(value) || value.split(/[:/]/).some(part => !part || part === \".\" || part === \"..\")) {\n throw new MineRenderError(`Invalid armor identifier ${value}`);\n }\n const [namespace, path] = value.includes(\":\") ? value.split(\":\") : [\"minecraft\", value];\n return new BasicAssetKey(namespace, path);\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,3 @@
1
+ // src/armor/index.ts
2
+ export * from "./Armor.mjs";
3
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/armor/index.ts"],"sourcesContent":["// autogenerated\nexport * from './Armor';\n"],"mappings":";AACA,cAAc;","names":[]}
@@ -1,6 +1,7 @@
1
1
  // src/assets/BlockEntities.ts
2
2
  import { Matrix4 } from "three";
3
3
  import { AssetKey } from "./AssetKey.mjs";
4
+ import { Entities } from "./Entities.mjs";
4
5
  import { AssetLoader } from "./AssetLoader.mjs";
5
6
  import { AssetParser } from "./source/index.mjs";
6
7
  import { Caching } from "../cache/Caching.mjs";
@@ -17,6 +18,7 @@ var BlockEntities = class {
17
18
  key.rootType = void 0;
18
19
  const index = await Caching.blockEntityIndexCache.get(key.serialize(), async () => {
19
20
  try {
21
+ key.root = await Entities.getDatasetRoot(root);
20
22
  return await AssetLoader.get(key, AssetParser.JSON) ?? {};
21
23
  } catch (error) {
22
24
  log.warn("Could not load the block entity index; block entities are drawn as plain block models", error);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/assets/BlockEntities.ts"],"sourcesContent":["import { Matrix4 } from \"three\";\nimport { AssetKey } from \"./AssetKey\";\nimport { AssetLoader } from \"./AssetLoader\";\nimport { AssetParser } from \"./source\";\nimport { Caching } from \"../cache/Caching\";\nimport type { MinecraftAsset } from \"../MinecraftAsset\";\nimport type { BlockStateProperties } from \"../model/block/BlockStateProperties\";\nimport type { TripleArray } from \"../model/Model\";\nimport type { Maybe } from \"../util\";\nimport { logger } from \"../util/log\";\n\nconst log = logger(\"BlockEntities\");\n\n/** One model drawn by a block-entity renderer. */\nexport interface BlockEntityPart {\n model: string;\n /** Model layer; defaults to \"main\". */\n layer?: string;\n /** Set only when the texture differs from the layer's default. */\n textureLocation?: string;\n /** Blockstate multipart syntax: every property must match, and \"a|b\" matches either value. */\n when?: Record<string, string>;\n}\n\n/** Degrees per property value, or degrees per unit of a numeric property. */\nexport type BlockEntityRotation = { property: string; degrees: Record<string, number> } | { property: string; step: number };\n\n/** Models and state-dependent placement for one block ID in the block-entity index. */\nexport interface BlockEntityEntry {\n parts: BlockEntityPart[];\n rotation?: BlockEntityRotation;\n /** Offset in model units, applied before the rotation. */\n translation?: TripleArray;\n}\n\n/** The dataset's `blocks.json`: block ID to the models its block-entity renderer draws. */\nexport type BlockEntityIndex = MinecraftAsset & { [block: string]: BlockEntityEntry };\n\n/** Parts and placement selected by {@link BlockEntities.resolve} for a block's properties. */\nexport interface ResolvedBlockEntity {\n parts: BlockEntityPart[];\n /** Degrees counter-clockwise seen from above, about the block's vertical centre axis. */\n rotation: number;\n translation: TripleArray;\n}\n\n/** Resolves blockstate properties into the entity models used to draw chests, beds, and other block entities. */\nexport class BlockEntities {\n\n /**\n * Loads the block index of a version. Versions and sources without one yield an empty index, and so does\n * a failed load: the index is optional, and blocks must not retry or fail with it one by one. The empty\n * result is cached like any other; `Caching.clear()` or `AssetLoader.setVersion()` forces a new attempt.\n */\n public static async getIndex(root?: string): Promise<BlockEntityIndex> {\n // The index sits next to the namespace directories, at <root>/entity-models/blocks.json.\n const key = new AssetKey(\"entity-models\", \"blocks\", undefined, undefined, undefined, \".json\", root);\n key.rootType = undefined!;\n const index = await Caching.blockEntityIndexCache.get(key.serialize(), async () => {\n try {\n return await AssetLoader.get<BlockEntityIndex>(key, AssetParser.JSON) ?? {};\n } catch (error) {\n log.warn(\"Could not load the block entity index; block entities are drawn as plain block models\", error);\n return {};\n }\n });\n return index ?? {};\n }\n\n /** The index entry of a block ID such as `minecraft:chest`. */\n public static entry(index: BlockEntityIndex, block: string): Maybe<BlockEntityEntry> {\n const entry = Object.prototype.hasOwnProperty.call(index, block) ? index[block] : undefined;\n return Array.isArray(entry?.parts) ? entry : undefined;\n }\n\n /** Selects the parts and placement for a block ID such as `minecraft:chest`; undefined when nothing is drawn. */\n public static resolve(index: BlockEntityIndex, block: string, state: BlockStateProperties = {}): Maybe<ResolvedBlockEntity> {\n const entry = this.entry(index, block);\n if (!entry) return undefined;\n // Default block states may hold booleans, numbers and upper-case enum names.\n const value = (property: string) => String(state[property]).toLowerCase();\n const parts = entry.parts.filter(part => Object.entries(part.when ?? {})\n .every(([property, values]) => String(values).split(\"|\").includes(value(property))));\n if (!parts.length) return undefined;\n let rotation = 0;\n if (entry.rotation) {\n const selected = value(entry.rotation.property);\n rotation = \"degrees\" in entry.rotation ? entry.rotation.degrees[selected] : Number(selected) * entry.rotation.step;\n if (!Number.isFinite(rotation)) rotation = 0;\n }\n return { parts, rotation, translation: entry.translation ?? [0, 0, 0] };\n }\n\n /** Placement in block space (0..16 per axis), applied on top of the model's own transform. */\n public static matrix(resolved: ResolvedBlockEntity, matrix: Matrix4 = new Matrix4()): Matrix4 {\n return matrix.makeTranslation(8, 0, 8)\n .multiply(new Matrix4().makeRotationY(resolved.rotation * Math.PI / 180))\n .multiply(new Matrix4().makeTranslation(resolved.translation[0] - 8, resolved.translation[1], resolved.translation[2] - 8));\n }\n\n}\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AAC5B,SAAS,eAAe;AAKxB,SAAS,cAAc;AAEvB,IAAM,MAAM,OAAO,eAAe;AAoC3B,IAAM,gBAAN,MAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOvB,aAAoB,SAAS,MAA0C;AAEnE,UAAM,MAAM,IAAI,SAAS,iBAAiB,UAAU,QAAW,QAAW,QAAW,SAAS,IAAI;AAClG,QAAI,WAAW;AACf,UAAM,QAAQ,MAAM,QAAQ,sBAAsB,IAAI,IAAI,UAAU,GAAG,YAAY;AAC/E,UAAI;AACA,eAAO,MAAM,YAAY,IAAsB,KAAK,YAAY,IAAI,KAAK,CAAC;AAAA,MAC9E,SAAS,OAAO;AACZ,YAAI,KAAK,yFAAyF,KAAK;AACvG,eAAO,CAAC;AAAA,MACZ;AAAA,IACJ,CAAC;AACD,WAAO,SAAS,CAAC;AAAA,EACrB;AAAA;AAAA,EAGA,OAAc,MAAM,OAAyB,OAAwC;AACjF,UAAM,QAAQ,OAAO,UAAU,eAAe,KAAK,OAAO,KAAK,IAAI,MAAM,KAAK,IAAI;AAClF,WAAO,MAAM,QAAQ,OAAO,KAAK,IAAI,QAAQ;AAAA,EACjD;AAAA;AAAA,EAGA,OAAc,QAAQ,OAAyB,OAAe,QAA8B,CAAC,GAA+B;AACxH,UAAM,QAAQ,KAAK,MAAM,OAAO,KAAK;AACrC,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,QAAQ,CAAC,aAAqB,OAAO,MAAM,QAAQ,CAAC,EAAE,YAAY;AACxE,UAAM,QAAQ,MAAM,MAAM,OAAO,UAAQ,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,EAClE,MAAM,CAAC,CAAC,UAAU,MAAM,MAAM,OAAO,MAAM,EAAE,MAAM,GAAG,EAAE,SAAS,MAAM,QAAQ,CAAC,CAAC,CAAC;AACvF,QAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,QAAI,WAAW;AACf,QAAI,MAAM,UAAU;AAChB,YAAM,WAAW,MAAM,MAAM,SAAS,QAAQ;AAC9C,iBAAW,aAAa,MAAM,WAAW,MAAM,SAAS,QAAQ,QAAQ,IAAI,OAAO,QAAQ,IAAI,MAAM,SAAS;AAC9G,UAAI,CAAC,OAAO,SAAS,QAAQ,EAAG,YAAW;AAAA,IAC/C;AACA,WAAO,EAAE,OAAO,UAAU,aAAa,MAAM,eAAe,CAAC,GAAG,GAAG,CAAC,EAAE;AAAA,EAC1E;AAAA;AAAA,EAGA,OAAc,OAAO,UAA+B,SAAkB,IAAI,QAAQ,GAAY;AAC1F,WAAO,OAAO,gBAAgB,GAAG,GAAG,CAAC,EAChC,SAAS,IAAI,QAAQ,EAAE,cAAc,SAAS,WAAW,KAAK,KAAK,GAAG,CAAC,EACvE,SAAS,IAAI,QAAQ,EAAE,gBAAgB,SAAS,YAAY,CAAC,IAAI,GAAG,SAAS,YAAY,CAAC,GAAG,SAAS,YAAY,CAAC,IAAI,CAAC,CAAC;AAAA,EAClI;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/assets/BlockEntities.ts"],"sourcesContent":["import { Matrix4 } from \"three\";\nimport { AssetKey } from \"./AssetKey\";\nimport { Entities } from \"./Entities\";\nimport { AssetLoader } from \"./AssetLoader\";\nimport { AssetParser } from \"./source\";\nimport { Caching } from \"../cache/Caching\";\nimport type { MinecraftAsset } from \"../MinecraftAsset\";\nimport type { BlockStateProperties } from \"../model/block/BlockStateProperties\";\nimport type { TripleArray } from \"../model/Model\";\nimport type { Maybe } from \"../util\";\nimport { logger } from \"../util/log\";\n\nconst log = logger(\"BlockEntities\");\n\n/** One model drawn by a block-entity renderer. */\nexport interface BlockEntityPart {\n model: string;\n /** Model layer; defaults to \"main\". */\n layer?: string;\n /** Set only when the texture differs from the layer's default. */\n textureLocation?: string;\n /** Blockstate multipart syntax: every property must match, and \"a|b\" matches either value. */\n when?: Record<string, string>;\n}\n\n/** Degrees per property value, or degrees per unit of a numeric property. */\nexport type BlockEntityRotation = { property: string; degrees: Record<string, number> } | { property: string; step: number };\n\n/** Models and state-dependent placement for one block ID in the block-entity index. */\nexport interface BlockEntityEntry {\n parts: BlockEntityPart[];\n rotation?: BlockEntityRotation;\n /** Offset in model units, applied before the rotation. */\n translation?: TripleArray;\n}\n\n/** The dataset's `blocks.json`: block ID to the models its block-entity renderer draws. */\nexport type BlockEntityIndex = MinecraftAsset & { [block: string]: BlockEntityEntry };\n\n/** Parts and placement selected by {@link BlockEntities.resolve} for a block's properties. */\nexport interface ResolvedBlockEntity {\n parts: BlockEntityPart[];\n /** Degrees counter-clockwise seen from above, about the block's vertical centre axis. */\n rotation: number;\n translation: TripleArray;\n}\n\n/** Resolves blockstate properties into the entity models used to draw chests, beds, and other block entities. */\nexport class BlockEntities {\n\n /**\n * Loads the block index of a version. Versions and sources without one yield an empty index, and so does\n * a failed load: the index is optional, and blocks must not retry or fail with it one by one. The empty\n * result is cached like any other; `Caching.clear()` or `AssetLoader.setVersion()` forces a new attempt.\n */\n public static async getIndex(root?: string): Promise<BlockEntityIndex> {\n // The index sits next to the namespace directories, at <root>/entity-models/blocks.json.\n const key = new AssetKey(\"entity-models\", \"blocks\", undefined, undefined, undefined, \".json\", root);\n key.rootType = undefined!;\n const index = await Caching.blockEntityIndexCache.get(key.serialize(), async () => {\n try {\n key.root = await Entities.getDatasetRoot(root);\n return await AssetLoader.get<BlockEntityIndex>(key, AssetParser.JSON) ?? {};\n } catch (error) {\n log.warn(\"Could not load the block entity index; block entities are drawn as plain block models\", error);\n return {};\n }\n });\n return index ?? {};\n }\n\n /** The index entry of a block ID such as `minecraft:chest`. */\n public static entry(index: BlockEntityIndex, block: string): Maybe<BlockEntityEntry> {\n const entry = Object.prototype.hasOwnProperty.call(index, block) ? index[block] : undefined;\n return Array.isArray(entry?.parts) ? entry : undefined;\n }\n\n /** Selects the parts and placement for a block ID such as `minecraft:chest`; undefined when nothing is drawn. */\n public static resolve(index: BlockEntityIndex, block: string, state: BlockStateProperties = {}): Maybe<ResolvedBlockEntity> {\n const entry = this.entry(index, block);\n if (!entry) return undefined;\n // Default block states may hold booleans, numbers and upper-case enum names.\n const value = (property: string) => String(state[property]).toLowerCase();\n const parts = entry.parts.filter(part => Object.entries(part.when ?? {})\n .every(([property, values]) => String(values).split(\"|\").includes(value(property))));\n if (!parts.length) return undefined;\n let rotation = 0;\n if (entry.rotation) {\n const selected = value(entry.rotation.property);\n rotation = \"degrees\" in entry.rotation ? entry.rotation.degrees[selected] : Number(selected) * entry.rotation.step;\n if (!Number.isFinite(rotation)) rotation = 0;\n }\n return { parts, rotation, translation: entry.translation ?? [0, 0, 0] };\n }\n\n /** Placement in block space (0..16 per axis), applied on top of the model's own transform. */\n public static matrix(resolved: ResolvedBlockEntity, matrix: Matrix4 = new Matrix4()): Matrix4 {\n return matrix.makeTranslation(8, 0, 8)\n .multiply(new Matrix4().makeRotationY(resolved.rotation * Math.PI / 180))\n .multiply(new Matrix4().makeTranslation(resolved.translation[0] - 8, resolved.translation[1], resolved.translation[2] - 8));\n }\n\n}\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AAC5B,SAAS,eAAe;AAKxB,SAAS,cAAc;AAEvB,IAAM,MAAM,OAAO,eAAe;AAoC3B,IAAM,gBAAN,MAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOvB,aAAoB,SAAS,MAA0C;AAEnE,UAAM,MAAM,IAAI,SAAS,iBAAiB,UAAU,QAAW,QAAW,QAAW,SAAS,IAAI;AAClG,QAAI,WAAW;AACf,UAAM,QAAQ,MAAM,QAAQ,sBAAsB,IAAI,IAAI,UAAU,GAAG,YAAY;AAC/E,UAAI;AACA,YAAI,OAAO,MAAM,SAAS,eAAe,IAAI;AAC7C,eAAO,MAAM,YAAY,IAAsB,KAAK,YAAY,IAAI,KAAK,CAAC;AAAA,MAC9E,SAAS,OAAO;AACZ,YAAI,KAAK,yFAAyF,KAAK;AACvG,eAAO,CAAC;AAAA,MACZ;AAAA,IACJ,CAAC;AACD,WAAO,SAAS,CAAC;AAAA,EACrB;AAAA;AAAA,EAGA,OAAc,MAAM,OAAyB,OAAwC;AACjF,UAAM,QAAQ,OAAO,UAAU,eAAe,KAAK,OAAO,KAAK,IAAI,MAAM,KAAK,IAAI;AAClF,WAAO,MAAM,QAAQ,OAAO,KAAK,IAAI,QAAQ;AAAA,EACjD;AAAA;AAAA,EAGA,OAAc,QAAQ,OAAyB,OAAe,QAA8B,CAAC,GAA+B;AACxH,UAAM,QAAQ,KAAK,MAAM,OAAO,KAAK;AACrC,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,QAAQ,CAAC,aAAqB,OAAO,MAAM,QAAQ,CAAC,EAAE,YAAY;AACxE,UAAM,QAAQ,MAAM,MAAM,OAAO,UAAQ,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,EAClE,MAAM,CAAC,CAAC,UAAU,MAAM,MAAM,OAAO,MAAM,EAAE,MAAM,GAAG,EAAE,SAAS,MAAM,QAAQ,CAAC,CAAC,CAAC;AACvF,QAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,QAAI,WAAW;AACf,QAAI,MAAM,UAAU;AAChB,YAAM,WAAW,MAAM,MAAM,SAAS,QAAQ;AAC9C,iBAAW,aAAa,MAAM,WAAW,MAAM,SAAS,QAAQ,QAAQ,IAAI,OAAO,QAAQ,IAAI,MAAM,SAAS;AAC9G,UAAI,CAAC,OAAO,SAAS,QAAQ,EAAG,YAAW;AAAA,IAC/C;AACA,WAAO,EAAE,OAAO,UAAU,aAAa,MAAM,eAAe,CAAC,GAAG,GAAG,CAAC,EAAE;AAAA,EAC1E;AAAA;AAAA,EAGA,OAAc,OAAO,UAA+B,SAAkB,IAAI,QAAQ,GAAY;AAC1F,WAAO,OAAO,gBAAgB,GAAG,GAAG,CAAC,EAChC,SAAS,IAAI,QAAQ,EAAE,cAAc,SAAS,WAAW,KAAK,KAAK,GAAG,CAAC,EACvE,SAAS,IAAI,QAAQ,EAAE,gBAAgB,SAAS,YAAY,CAAC,IAAI,GAAG,SAAS,YAAY,CAAC,GAAG,SAAS,YAAY,CAAC,IAAI,CAAC,CAAC;AAAA,EAClI;AAEJ;","names":[]}
@@ -1,5 +1,6 @@
1
1
  // src/assets/Entities.ts
2
2
  import { AssetKey, isAssetKey } from "./AssetKey.mjs";
3
+ import { parseRelease, nearestRelease } from "./_versions/DatasetVersions.mjs";
3
4
  import { AssetLoader } from "./AssetLoader.mjs";
4
5
  import { AssetParser } from "./source/index.mjs";
5
6
  import { Caching } from "../cache/Caching.mjs";
@@ -7,11 +8,36 @@ import { DEFAULT_NAMESPACE } from "./AssetDefaults.mjs";
7
8
  import { MineRenderError } from "../error/MineRenderError.mjs";
8
9
  import { ModelTextures } from "./ModelTextures.mjs";
9
10
  var Entities = class {
11
+ /**
12
+ * Resolves `root` (default: the active version). Uses the exact dataset when published or when the
13
+ * version is not a release; otherwise selects the nearest published release, like {@link MineRenderData.resolve}.
14
+ * Textures keep the requested root.
15
+ */
16
+ static async getDatasetRoot(root) {
17
+ const requestRoot = (root ?? AssetLoader.ROOT).replace(/\/+$/, "");
18
+ const requestedVersion = requestRoot.slice(requestRoot.lastIndexOf("/") + 1);
19
+ const base = requestRoot.slice(0, requestRoot.lastIndexOf("/"));
20
+ const selected = await Caching.entityDatasetVersionCache.get(root ?? `sources:${AssetLoader.ROOT}`, async () => {
21
+ const key = new AssetKey("entity-models", "version", void 0, void 0, void 0, ".json", root);
22
+ key.rootType = void 0;
23
+ if (!parseRelease(requestedVersion) || await AssetLoader.get(key, AssetParser.JSON) !== void 0) return requestedVersion;
24
+ const indexKey = new AssetKey("entity-models", "versions", void 0, void 0, void 0, ".json", base);
25
+ indexKey.rootType = void 0;
26
+ const index = await AssetLoader.get(indexKey, AssetParser.JSON);
27
+ if (index === void 0) return requestedVersion;
28
+ if (!index || typeof index !== "object" || Array.isArray(index) || !Array.isArray(index.versions) || !index.versions.every((entry) => entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.name === "string")) {
29
+ throw new MineRenderError(`Invalid entity model version index at ${base}/entity-models/versions.json`);
30
+ }
31
+ return nearestRelease(requestedVersion, index.versions.map((entry) => entry.name)) ?? requestedVersion;
32
+ });
33
+ return selected === requestedVersion ? root : `${base}/${selected}`;
34
+ }
10
35
  /** Lists model paths in the `minecraft` namespace, including subdirectories and excluding `.json`. */
11
36
  static async getEntityList() {
37
+ const root = await this.getDatasetRoot();
12
38
  const collect = async (path) => {
13
39
  const prefix = path ? `${path}/` : "";
14
- const key = new AssetKey(DEFAULT_NAMESPACE, `${prefix}_list`, void 0, void 0, "entity-models", ".json");
40
+ const key = new AssetKey(DEFAULT_NAMESPACE, `${prefix}_list`, void 0, void 0, "entity-models", ".json", root);
15
41
  const list = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get(key, AssetParser.LIST));
16
42
  if (!list) return [];
17
43
  const files = list.files.filter((file) => file.endsWith(".json") && file !== "_list.json").map((file) => `${prefix}${file.slice(0, -5)}`);
@@ -20,9 +46,10 @@ var Entities = class {
20
46
  };
21
47
  return collect("");
22
48
  }
49
+ /** @internal */
23
50
  static async getModelFile(modelKey) {
24
51
  const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
25
- const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models", ".json", isAssetKey(modelKey) ? modelKey.root : void 0);
52
+ const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models", ".json", await this.getDatasetRoot(isAssetKey(modelKey) ? modelKey.root : void 0));
26
53
  return Caching.entityModelCache.get(key.serialize(), () => AssetLoader.get(key, AssetParser.JSON));
27
54
  }
28
55
  /** Returns selectable geometry-layer names, or an empty list when the model is missing. */
@@ -83,7 +110,7 @@ var Entities = class {
83
110
  */
84
111
  static async getAnimations(modelKey) {
85
112
  const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
86
- const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models/animations", ".json", isAssetKey(modelKey) ? modelKey.root : void 0);
113
+ const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models/animations", ".json", await this.getDatasetRoot(isAssetKey(modelKey) ? modelKey.root : void 0));
87
114
  const file = await Caching.entityAnimationCache.get(key.serialize(), () => AssetLoader.get(key, AssetParser.JSON));
88
115
  return file?.animations;
89
116
  }
@@ -92,26 +119,27 @@ var Entities = class {
92
119
  const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
93
120
  const root = isAssetKey(modelKey) ? modelKey.root : void 0;
94
121
  const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models", ".json", root);
95
- return Caching.entityTextureCache.get(key.serialize(), async () => {
122
+ const texture = await Caching.entityTextureCache.get(key.serialize(), async () => {
96
123
  const name = path.split("/").pop();
97
124
  for (const candidate of [path, `${path}/${name}`]) {
98
- const texture2 = new AssetKey(modelKey.namespace, candidate, "textures", "entity", "assets", ".png", root);
99
- if (await ModelTextures.get(texture2)) return texture2;
125
+ const texture3 = new AssetKey(modelKey.namespace, candidate, "textures", "entity", "assets", ".png", root);
126
+ if (await ModelTextures.get(texture3)) return texture3;
100
127
  }
101
128
  const listKey = new AssetKey(modelKey.namespace, "_list", `${name}_variant`, void 0, "data", ".json", root);
102
129
  const list = await AssetLoader.get(listKey, AssetParser.LIST);
103
130
  const files = list?.files.filter((file2) => file2.endsWith(".json") && file2 !== "_list.json");
104
131
  const file = files?.find((file2) => file2 === "temperate.json") ?? files?.[0];
105
- if (!file) return void 0;
132
+ if (!file) return Caching.MISSING;
106
133
  const variantKey = new AssetKey(modelKey.namespace, file.slice(0, -5), `${name}_variant`, void 0, "data", ".json", root);
107
134
  const variant = await AssetLoader.get(variantKey, AssetParser.JSON);
108
135
  const assets = variant?.assets;
109
136
  const textureId = variant?.asset_id ?? (typeof assets?.wild === "string" ? assets.wild : Object.values(assets ?? {}).find((value) => typeof value === "string"));
110
- if (typeof textureId !== "string") return void 0;
111
- const texture = AssetKey.parse("textures", textureId);
112
- texture.root = root;
113
- return texture;
137
+ if (typeof textureId !== "string") return Caching.MISSING;
138
+ const texture2 = AssetKey.parse("textures", textureId);
139
+ texture2.root = root;
140
+ return texture2;
114
141
  });
142
+ return texture === Caching.MISSING ? void 0 : texture;
115
143
  }
116
144
  /** @deprecated Use getEntityList(); block entities share the entity dataset. */
117
145
  static getBlockList() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/assets/Entities.ts"],"sourcesContent":["import { AssetKey, BasicAssetKey, isAssetKey } from \"./AssetKey\";\nimport type { EntityLayer, EntityModel, EntityModelFile, EntityModelPass } from \"../entity/EntityModel\";\nimport type { EntityAnimation, EntityAnimationFile } from \"../entity/EntityAnimation\";\nimport { AssetLoader } from \"./AssetLoader\";\nimport { AssetParser } from \"./source\";\nimport { Maybe } from \"../util\";\nimport { Caching } from \"../cache/Caching\";\nimport { DEFAULT_NAMESPACE } from \"./AssetDefaults\";\nimport type { ListAsset } from \"../ListAsset\";\nimport { MineRenderError } from \"../error/MineRenderError\";\nimport { ModelTextures } from \"./ModelTextures\";\nimport type { MinecraftAsset } from \"../MinecraftAsset\";\n\n/** Loads entity and block-entity geometry, texture selections, and animation clips from the dataset. */\nexport class Entities {\n\n /** Lists model paths in the `minecraft` namespace, including subdirectories and excluding `.json`. */\n public static async getEntityList(): Promise<string[]> {\n const collect = async (path: string): Promise<string[]> => {\n const prefix = path ? `${path}/` : \"\";\n const key = new AssetKey(DEFAULT_NAMESPACE, `${prefix}_list`, undefined, undefined, \"entity-models\", \".json\");\n const list = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get<ListAsset>(key, AssetParser.LIST));\n if (!list) return [];\n const files = list.files.filter(file => file.endsWith(\".json\") && file !== \"_list.json\")\n .map(file => `${prefix}${file.slice(0, -5)}`);\n const children = await Promise.all(list.directories.map(directory => collect(`${prefix}${directory}`)));\n return [...files, ...children.flat()];\n };\n return collect(\"\");\n }\n\n private static async getModelFile(modelKey: BasicAssetKey): Promise<Maybe<EntityModelFile>> {\n const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;\n const key = new AssetKey(modelKey.namespace, path, undefined, undefined, \"entity-models\", \".json\", isAssetKey(modelKey) ? modelKey.root : undefined);\n return Caching.entityModelCache.get(key.serialize(), () => AssetLoader.get<EntityModelFile>(key, AssetParser.JSON));\n }\n\n /** Returns selectable geometry-layer names, or an empty list when the model is missing. */\n public static async getLayerList(modelKey: BasicAssetKey): Promise<string[]> {\n return Object.keys((await this.getModelFile(modelKey))?.layers ?? {});\n }\n\n /** The dataset's extra draws on top of `main`; their `when` labels are the states `EntityModelOptions.when` can enable. */\n public static async getPassList(modelKey: BasicAssetKey): Promise<EntityModelPass[]> {\n return (await this.getModelFile(modelKey))?.passes ?? [];\n }\n\n /**\n * Loads the layers and textures to pass to {@link MineRenderScene.addEntity}.\n * By default, selects `main`, unconditional passes, and passes enabled by `options.when`.\n *\n * @param modelKey - Dataset model, such as `new BasicAssetKey(\"minecraft\", \"zombie\")`.\n * @param textureKey - Texture override for the first selected layer.\n * @param options - Layer selection, conditional passes, and per-layer texture overrides.\n * @returns The selected model, or `undefined` when its dataset file is missing.\n */\n public static async getEntity(modelKey: BasicAssetKey, textureKey?: BasicAssetKey, options?: EntityModelOptions): Promise<Maybe<EntityModel>> {\n const model = await this.getModelFile(modelKey);\n if (!model) return undefined;\n // Without an explicit selection, draw what vanilla draws: main, then the passes enabled for the requested state.\n const passes = options?.layers || options?.layer !== undefined ? [] :\n (model.passes ?? []).filter(pass => pass.when === undefined || options?.when?.includes(pass.when));\n const draws: EntityModelPass[] = [...new Set(options?.layers ?? [options?.layer ?? \"main\"])].map(layer => ({ layer }));\n if (draws.length === 0) throw new MineRenderError(`Entity ${model.id} requires at least one layer`);\n draws.push(...passes);\n const names: string[] = [];\n const selected = draws.map(pass => {\n const layer = model.layers[pass.layer];\n if (!layer) {\n throw new MineRenderError(`Entity ${model.id} has no layer \"${pass.layer}\". Available layers: ${Object.keys(model.layers).join(\", \")}`);\n }\n // A pass may draw geometry that is already selected with another texture.\n let name = pass.layer;\n for (let n = 2; names.includes(name); n++) name = `${pass.layer}#${n}`;\n names.push(name);\n return { name, layer, pass };\n });\n const layers: Record<string, EntityLayer> = Object.fromEntries(await Promise.all(selected.map(async ({ name, layer, pass }, index) => {\n const override = options?.textures?.[name] ?? (index === 0 ? textureKey : undefined);\n const textureLocation = pass.textureLocation ?? layer.textureLocation;\n let texture: Maybe<AssetKey>;\n if (override) {\n const path = isAssetKey(override) ? override.getFullPath() : override.path;\n texture = isAssetKey(override) && override.assetType === \"textures\" && path.startsWith(\"entity/\")\n ? override\n : new AssetKey(override.namespace, path, \"textures\", \"entity\", \"assets\", \".png\", isAssetKey(override) ? override.root : undefined);\n } else if (textureLocation !== undefined) {\n texture = AssetKey.parse(\"textures\", textureLocation.replace(/^([^:]+:)?textures\\//, \"$1\"));\n if (isAssetKey(modelKey)) texture.root = modelKey.root;\n } else {\n texture = await this.resolveTexture(modelKey);\n }\n const render = pass.render ?? layer.render;\n return [name, { key: override ?? modelKey, texture, layer, ...(render && { render }), ...(pass.tint && { tint: pass.tint }) }];\n })));\n return { ...layers[names[0]], id: model.id, layers, ...(model.transform && { transform: model.transform }) };\n }\n\n /**\n * Native and sampled vanilla clips by name, or undefined when the model or selected version has none.\n */\n public static async getAnimations(modelKey: BasicAssetKey): Promise<Maybe<Record<string, EntityAnimation>>> {\n const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;\n const key = new AssetKey(modelKey.namespace, path, undefined, undefined, \"entity-models/animations\", \".json\", isAssetKey(modelKey) ? modelKey.root : undefined);\n const file = await Caching.entityAnimationCache.get(key.serialize(), () => AssetLoader.get<EntityAnimationFile>(key, AssetParser.JSON));\n return file?.animations;\n }\n\n /** Finds a fallback texture using the model path, its directory, and vanilla variant data. */\n public static async resolveTexture(modelKey: BasicAssetKey): Promise<Maybe<AssetKey>> {\n const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;\n const root = isAssetKey(modelKey) ? modelKey.root : undefined;\n const key = new AssetKey(modelKey.namespace, path, undefined, undefined, \"entity-models\", \".json\", root);\n return Caching.entityTextureCache.get(key.serialize(), async () => {\n const name = path.split(\"/\").pop()!;\n for (const candidate of [path, `${path}/${name}`]) {\n const texture = new AssetKey(modelKey.namespace, candidate, \"textures\", \"entity\", \"assets\", \".png\", root);\n if (await ModelTextures.get(texture)) return texture;\n }\n const listKey = new AssetKey(modelKey.namespace, \"_list\", `${name}_variant`, undefined, \"data\", \".json\", root);\n const list = await AssetLoader.get<ListAsset>(listKey, AssetParser.LIST);\n const files = list?.files.filter(file => file.endsWith(\".json\") && file !== \"_list.json\");\n const file = files?.find(file => file === \"temperate.json\") ?? files?.[0];\n if (!file) return undefined;\n const variantKey = new AssetKey(modelKey.namespace, file.slice(0, -5), `${name}_variant`, undefined, \"data\", \".json\", root);\n const variant = await AssetLoader.get<EntityVariant>(variantKey, AssetParser.JSON);\n const assets = variant?.assets;\n const textureId = variant?.asset_id ?? (typeof assets?.wild === \"string\" ? assets.wild :\n Object.values(assets ?? {}).find(value => typeof value === \"string\"));\n if (typeof textureId !== \"string\") return undefined;\n const texture = AssetKey.parse(\"textures\", textureId);\n texture.root = root;\n return texture;\n });\n }\n\n /** @deprecated Use getEntityList(); block entities share the entity dataset. */\n public static getBlockList(): Promise<string[]> {\n return this.getEntityList();\n }\n\n /** @deprecated Use getEntity(); block entities share the entity dataset. */\n public static getBlock(modelKey: BasicAssetKey, textureKey?: BasicAssetKey, options?: EntityModelOptions): Promise<Maybe<EntityModel>> {\n return this.getEntity(modelKey, textureKey, options);\n }\n\n}\n\n/** Selection settings passed as the third argument to {@link Entities.getEntity}. */\nexport interface EntityModelOptions {\n /** Selects one geometry layer without dataset passes. `layers` takes precedence when supplied. */\n layer?: string;\n /** Layer names in draw order; an explicit selection draws exactly these layers and no dataset passes. */\n layers?: string[];\n /**\n * Entity states that enable the dataset's conditional passes, e.g. `[\"powered\"]` for a charged creeper.\n * Without `layer`/`layers`, `main` and every unconditional pass are always drawn.\n */\n when?: string[];\n /** Per-layer texture overrides, keyed like `EntityModel.layers`. The positional texture key applies to the first selected layer. */\n textures?: Record<string, BasicAssetKey>;\n}\n\ninterface EntityVariant extends MinecraftAsset {\n asset_id?: string;\n assets?: Record<string, unknown>;\n}\n"],"mappings":"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","names":["texture","file"]}
1
+ {"version":3,"sources":["../../../../src/assets/Entities.ts"],"sourcesContent":["import { AssetKey, BasicAssetKey, isAssetKey } from \"./AssetKey\";\nimport type { EntityLayer, EntityModel, EntityModelFile, EntityModelPass } from \"../entity/EntityModel\";\nimport type { EntityAnimation, EntityAnimationFile } from \"../entity/EntityAnimation\";\nimport { parseRelease, nearestRelease } from \"./_versions/DatasetVersions\";\nimport { AssetLoader } from \"./AssetLoader\";\nimport { AssetParser } from \"./source\";\nimport { Maybe } from \"../util\";\nimport { Caching } from \"../cache/Caching\";\nimport { DEFAULT_NAMESPACE } from \"./AssetDefaults\";\nimport type { ListAsset } from \"../ListAsset\";\nimport { MineRenderError } from \"../error/MineRenderError\";\nimport { ModelTextures } from \"./ModelTextures\";\nimport type { MinecraftAsset } from \"../MinecraftAsset\";\n\n/** Loads entity and block-entity geometry, texture selections, and animation clips from the dataset. */\nexport class Entities {\n\n /**\n * Resolves `root` (default: the active version). Uses the exact dataset when published or when the\n * version is not a release; otherwise selects the nearest published release, like {@link MineRenderData.resolve}.\n * Textures keep the requested root.\n */\n public static async getDatasetRoot(root?: string): Promise<Maybe<string>> {\n const requestRoot = (root ?? AssetLoader.ROOT).replace(/\\/+$/, \"\");\n const requestedVersion = requestRoot.slice(requestRoot.lastIndexOf(\"/\") + 1);\n const base = requestRoot.slice(0, requestRoot.lastIndexOf(\"/\"));\n const selected = await Caching.entityDatasetVersionCache.get(root ?? `sources:${AssetLoader.ROOT}`, async () => {\n const key = new AssetKey(\"entity-models\", \"version\", undefined, undefined, undefined, \".json\", root);\n key.rootType = undefined!;\n if (!parseRelease(requestedVersion) || await AssetLoader.get(key, AssetParser.JSON) !== undefined) return requestedVersion;\n const indexKey = new AssetKey(\"entity-models\", \"versions\", undefined, undefined, undefined, \".json\", base);\n indexKey.rootType = undefined!;\n const index = await AssetLoader.get<MinecraftAsset & { versions: { name: string }[] }>(indexKey, AssetParser.JSON);\n if (index === undefined) return requestedVersion;\n if (!index || typeof index !== \"object\" || Array.isArray(index) || !Array.isArray(index.versions)\n || !index.versions.every(entry => entry && typeof entry === \"object\" && !Array.isArray(entry) && typeof entry.name === \"string\")) {\n throw new MineRenderError(`Invalid entity model version index at ${base}/entity-models/versions.json`);\n }\n return nearestRelease(requestedVersion, index.versions.map(entry => entry.name)) ?? requestedVersion;\n });\n return selected === requestedVersion ? root : `${base}/${selected}`;\n }\n\n /** Lists model paths in the `minecraft` namespace, including subdirectories and excluding `.json`. */\n public static async getEntityList(): Promise<string[]> {\n const root = await this.getDatasetRoot();\n const collect = async (path: string): Promise<string[]> => {\n const prefix = path ? `${path}/` : \"\";\n const key = new AssetKey(DEFAULT_NAMESPACE, `${prefix}_list`, undefined, undefined, \"entity-models\", \".json\", root);\n const list = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get<ListAsset>(key, AssetParser.LIST));\n if (!list) return [];\n const files = list.files.filter(file => file.endsWith(\".json\") && file !== \"_list.json\")\n .map(file => `${prefix}${file.slice(0, -5)}`);\n const children = await Promise.all(list.directories.map(directory => collect(`${prefix}${directory}`)));\n return [...files, ...children.flat()];\n };\n return collect(\"\");\n }\n\n /** @internal */\n public static async getModelFile(modelKey: BasicAssetKey): Promise<Maybe<EntityModelFile>> {\n const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;\n const key = new AssetKey(modelKey.namespace, path, undefined, undefined, \"entity-models\", \".json\", await this.getDatasetRoot(isAssetKey(modelKey) ? modelKey.root : undefined));\n return Caching.entityModelCache.get(key.serialize(), () => AssetLoader.get<EntityModelFile>(key, AssetParser.JSON));\n }\n\n /** Returns selectable geometry-layer names, or an empty list when the model is missing. */\n public static async getLayerList(modelKey: BasicAssetKey): Promise<string[]> {\n return Object.keys((await this.getModelFile(modelKey))?.layers ?? {});\n }\n\n /** The dataset's extra draws on top of `main`; their `when` labels are the states `EntityModelOptions.when` can enable. */\n public static async getPassList(modelKey: BasicAssetKey): Promise<EntityModelPass[]> {\n return (await this.getModelFile(modelKey))?.passes ?? [];\n }\n\n /**\n * Loads the layers and textures to pass to {@link MineRenderScene.addEntity}.\n * By default, selects `main`, unconditional passes, and passes enabled by `options.when`.\n *\n * @param modelKey - Dataset model, such as `new BasicAssetKey(\"minecraft\", \"zombie\")`.\n * @param textureKey - Texture override for the first selected layer.\n * @param options - Layer selection, conditional passes, and per-layer texture overrides.\n * @returns The selected model, or `undefined` when its dataset file is missing.\n */\n public static async getEntity(modelKey: BasicAssetKey, textureKey?: BasicAssetKey, options?: EntityModelOptions): Promise<Maybe<EntityModel>> {\n const model = await this.getModelFile(modelKey);\n if (!model) return undefined;\n // Without an explicit selection, draw what vanilla draws: main, then the passes enabled for the requested state.\n const passes = options?.layers || options?.layer !== undefined ? [] :\n (model.passes ?? []).filter(pass => pass.when === undefined || options?.when?.includes(pass.when));\n const draws: EntityModelPass[] = [...new Set(options?.layers ?? [options?.layer ?? \"main\"])].map(layer => ({ layer }));\n if (draws.length === 0) throw new MineRenderError(`Entity ${model.id} requires at least one layer`);\n draws.push(...passes);\n const names: string[] = [];\n const selected = draws.map(pass => {\n const layer = model.layers[pass.layer];\n if (!layer) {\n throw new MineRenderError(`Entity ${model.id} has no layer \"${pass.layer}\". Available layers: ${Object.keys(model.layers).join(\", \")}`);\n }\n // A pass may draw geometry that is already selected with another texture.\n let name = pass.layer;\n for (let n = 2; names.includes(name); n++) name = `${pass.layer}#${n}`;\n names.push(name);\n return { name, layer, pass };\n });\n const layers: Record<string, EntityLayer> = Object.fromEntries(await Promise.all(selected.map(async ({ name, layer, pass }, index) => {\n const override = options?.textures?.[name] ?? (index === 0 ? textureKey : undefined);\n const textureLocation = pass.textureLocation ?? layer.textureLocation;\n let texture: Maybe<AssetKey>;\n if (override) {\n const path = isAssetKey(override) ? override.getFullPath() : override.path;\n texture = isAssetKey(override) && override.assetType === \"textures\" && path.startsWith(\"entity/\")\n ? override\n : new AssetKey(override.namespace, path, \"textures\", \"entity\", \"assets\", \".png\", isAssetKey(override) ? override.root : undefined);\n } else if (textureLocation !== undefined) {\n texture = AssetKey.parse(\"textures\", textureLocation.replace(/^([^:]+:)?textures\\//, \"$1\"));\n if (isAssetKey(modelKey)) texture.root = modelKey.root;\n } else {\n texture = await this.resolveTexture(modelKey);\n }\n const render = pass.render ?? layer.render;\n return [name, { key: override ?? modelKey, texture, layer, ...(render && { render }), ...(pass.tint && { tint: pass.tint }) }];\n })));\n return { ...layers[names[0]], id: model.id, layers, ...(model.transform && { transform: model.transform }) };\n }\n\n /**\n * Native and sampled vanilla clips by name, or undefined when the model or selected version has none.\n */\n public static async getAnimations(modelKey: BasicAssetKey): Promise<Maybe<Record<string, EntityAnimation>>> {\n const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;\n const key = new AssetKey(modelKey.namespace, path, undefined, undefined, \"entity-models/animations\", \".json\", await this.getDatasetRoot(isAssetKey(modelKey) ? modelKey.root : undefined));\n const file = await Caching.entityAnimationCache.get(key.serialize(), () => AssetLoader.get<EntityAnimationFile>(key, AssetParser.JSON));\n return file?.animations;\n }\n\n /** Finds a fallback texture using the model path, its directory, and vanilla variant data. */\n public static async resolveTexture(modelKey: BasicAssetKey): Promise<Maybe<AssetKey>> {\n const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;\n const root = isAssetKey(modelKey) ? modelKey.root : undefined;\n const key = new AssetKey(modelKey.namespace, path, undefined, undefined, \"entity-models\", \".json\", root);\n const texture = await Caching.entityTextureCache.get(key.serialize(), async () => {\n const name = path.split(\"/\").pop()!;\n for (const candidate of [path, `${path}/${name}`]) {\n const texture = new AssetKey(modelKey.namespace, candidate, \"textures\", \"entity\", \"assets\", \".png\", root);\n if (await ModelTextures.get(texture)) return texture;\n }\n const listKey = new AssetKey(modelKey.namespace, \"_list\", `${name}_variant`, undefined, \"data\", \".json\", root);\n const list = await AssetLoader.get<ListAsset>(listKey, AssetParser.LIST);\n const files = list?.files.filter(file => file.endsWith(\".json\") && file !== \"_list.json\");\n const file = files?.find(file => file === \"temperate.json\") ?? files?.[0];\n if (!file) return Caching.MISSING;\n const variantKey = new AssetKey(modelKey.namespace, file.slice(0, -5), `${name}_variant`, undefined, \"data\", \".json\", root);\n const variant = await AssetLoader.get<EntityVariant>(variantKey, AssetParser.JSON);\n const assets = variant?.assets;\n const textureId = variant?.asset_id ?? (typeof assets?.wild === \"string\" ? assets.wild :\n Object.values(assets ?? {}).find(value => typeof value === \"string\"));\n if (typeof textureId !== \"string\") return Caching.MISSING;\n const texture = AssetKey.parse(\"textures\", textureId);\n texture.root = root;\n return texture;\n });\n return texture === Caching.MISSING ? undefined : texture;\n }\n\n /** @deprecated Use getEntityList(); block entities share the entity dataset. */\n public static getBlockList(): Promise<string[]> {\n return this.getEntityList();\n }\n\n /** @deprecated Use getEntity(); block entities share the entity dataset. */\n public static getBlock(modelKey: BasicAssetKey, textureKey?: BasicAssetKey, options?: EntityModelOptions): Promise<Maybe<EntityModel>> {\n return this.getEntity(modelKey, textureKey, options);\n }\n\n}\n\n/** Selection settings passed as the third argument to {@link Entities.getEntity}. */\nexport interface EntityModelOptions {\n /** Selects one geometry layer without dataset passes. `layers` takes precedence when supplied. */\n layer?: string;\n /** Layer names in draw order; an explicit selection draws exactly these layers and no dataset passes. */\n layers?: string[];\n /**\n * Entity states that enable the dataset's conditional passes, e.g. `[\"powered\"]` for a charged creeper.\n * Without `layer`/`layers`, `main` and every unconditional pass are always drawn.\n */\n when?: string[];\n /** Per-layer texture overrides, keyed like `EntityModel.layers`. The positional texture key applies to the first selected layer. */\n textures?: Record<string, BasicAssetKey>;\n}\n\ninterface EntityVariant extends MinecraftAsset {\n asset_id?: string;\n assets?: Record<string, unknown>;\n}\n"],"mappings":"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","names":["texture","file"]}
@@ -1,17 +1,10 @@
1
1
  // src/assets/MineRenderData.ts
2
+ import { parseRelease, nearestRelease } from "./_versions/DatasetVersions.mjs";
2
3
  import { AssetKey } from "./AssetKey.mjs";
3
4
  import { AssetLoader } from "./AssetLoader.mjs";
4
5
  import { AssetParser } from "./source/parser/AssetParsers.mjs";
5
6
  import { Caching } from "../cache/Caching.mjs";
6
7
  var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
7
- var release = (value) => /^\d+\.\d+(?:\.\d+)?$/.test(value) ? value.split(".").map(Number) : void 0;
8
- var compare = (left, right) => {
9
- for (let i = 0; i < 3; i++) {
10
- const difference = (left[i] ?? 0) - (right[i] ?? 0);
11
- if (difference) return difference;
12
- }
13
- return 0;
14
- };
15
8
  var MineRenderData = class {
16
9
  /** Loads one registry. Missing files and invalid data reject the request. */
17
10
  static async get(dataset, root) {
@@ -41,9 +34,9 @@ var MineRenderData = class {
41
34
  const scope = AssetLoader.persistentScope;
42
35
  return this.cached(`resolve:${requestRoot === void 0 ? "sources" : "explicit"}:${root}`, scope, defaultRoot, async () => {
43
36
  const requestedVersion = root.slice(root.lastIndexOf("/") + 1);
44
- let manifest = await this.manifest(root, requestRoot !== void 0 && release(requestedVersion) ? requestedVersion : void 0, requestRoot);
37
+ let manifest = await this.manifest(root, requestRoot !== void 0 && parseRelease(requestedVersion) ? requestedVersion : void 0, requestRoot);
45
38
  if (manifest) return { requestedVersion, version: manifest.minecraftVersion, root, manifest };
46
- const requested = release(requestedVersion);
39
+ const requested = parseRelease(requestedVersion);
47
40
  if (!requested) {
48
41
  throw new Error(`No MineRender data is published for ${requestedVersion}; automatic fallback requires a release`);
49
42
  }
@@ -55,14 +48,12 @@ var MineRenderData = class {
55
48
  }
56
49
  return value.versions;
57
50
  });
58
- const versions = index.map((entry) => ({ name: entry.name, number: release(entry.name) })).filter((entry) => !!entry.number).sort((left, right) => compare(left.number, right.number));
59
- const sameLine = versions.filter((entry) => entry.number[0] === requested[0] && entry.number[1] === requested[1]);
60
- const selected = sameLine.sort((left, right) => Math.abs((left.number[2] ?? 0) - (requested[2] ?? 0)) - Math.abs((right.number[2] ?? 0) - (requested[2] ?? 0)) || compare(left.number, right.number))[0] ?? versions.filter((entry) => compare(entry.number, requested) <= 0).pop() ?? versions[0];
51
+ const selected = nearestRelease(requestedVersion, index.map((entry) => entry.name));
61
52
  if (!selected) throw new Error(`No compatible MineRender data is published for ${requestedVersion}`);
62
- const resolvedRoot = `${base}/${selected.name}`;
63
- manifest = await this.manifest(resolvedRoot, selected.name, resolvedRoot);
64
- if (!manifest) throw new Error(`Missing MineRender manifest for published version ${selected.name}`);
65
- return { requestedVersion, version: selected.name, root: resolvedRoot, manifest };
53
+ const resolvedRoot = `${base}/${selected}`;
54
+ manifest = await this.manifest(resolvedRoot, selected, resolvedRoot);
55
+ if (!manifest) throw new Error(`Missing MineRender manifest for published version ${selected}`);
56
+ return { requestedVersion, version: selected, root: resolvedRoot, manifest };
66
57
  });
67
58
  }
68
59
  static async manifest(root, expectedVersion, requestRoot) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/assets/MineRenderData.ts"],"sourcesContent":["import { AssetKey } from \"./AssetKey\";\nimport { AssetLoader } from \"./AssetLoader\";\nimport { AssetParser } from \"./source/parser/AssetParsers\";\nimport { Caching } from \"../cache/Caching\";\nimport type { BlockStatePropertyDefaults } from \"../model/block/BlockStateProperties\";\nimport type { BiomeColorKind } from \"./Biomes\";\nimport type { RenameKind } from \"./Renames\";\n\nexport interface MineRenderDatasets {\n itemDefaults: Record<string, Record<string, unknown>>;\n potionColors: {\n effects: Record<string, number>;\n potions: Record<string, { id: string; amplifier: number }[]>;\n };\n blockStates: Record<string, BlockStatePropertyDefaults>;\n blockTints: Record<string, ({ source: \"constant\"; color: number } | { source: \"grass\" | \"stem\" | \"redstone\" }) & {\n untinted?: number[];\n biome?: BiomeColorKind;\n }>;\n blockCulling: {\n profiles: { type: string; group?: string; kind?: string }[];\n blocks: Record<string, number>;\n groups?: Record<string, string[]>;\n };\n fluids: Record<string, { kind: \"water\" | \"lava\"; renderModel: boolean; levelProperty?: string }>;\n dyes: Record<string, { id: number; textureDiffuseColor: number; fireworkColor: number; textColor: number }>;\n legacyBlocks: { blocks: Record<string, string> };\n renames: { format: \"data-fixer-renames\"; baseDataVersion: number }\n & Record<RenameKind, Record<string, { registered: boolean; ranges: { before: number; to: string }[] }>>;\n}\n\nexport interface MineRenderDataManifest {\n schemaVersion: 1;\n minecraftVersion: string;\n dataVersion: number;\n datasets: Partial<Record<keyof MineRenderDatasets, { file: string; sha256: string; bytes: number; provenance: Record<string, unknown> }>>;\n unavailable: Record<string, string>;\n extractor: { repository: string; revision: string; sha256: string };\n}\n\n/** The requested asset version and the published data version used for its registries. */\nexport interface ResolvedMineRenderData {\n requestedVersion: string;\n version: string;\n /** Registry lookup root. Rootless requests still allow registered sources to override this URL. */\n root: string;\n manifest: MineRenderDataManifest;\n}\n\nconst object = (value: unknown): value is Record<string, any> => value !== null && typeof value === \"object\" && !Array.isArray(value);\nconst release = (value: string): number[] | undefined => /^\\d+\\.\\d+(?:\\.\\d+)?$/.test(value) ? value.split(\".\").map(Number) : undefined;\nconst compare = (left: number[], right: number[]): number => {\n for (let i = 0; i < 3; i++) {\n const difference = (left[i] ?? 0) - (right[i] ?? 0);\n if (difference) return difference;\n }\n return 0;\n};\n\n/** Loads extracted Minecraft registries from the active asset sources and version. */\nexport class MineRenderData {\n /** Loads one registry. Missing files and invalid data reject the request. */\n public static async get<K extends keyof MineRenderDatasets>(dataset: K, root?: string): Promise<MineRenderDatasets[K]> {\n root = root?.replace(/\\/+$/, \"\");\n const scope = AssetLoader.persistentScope;\n const defaultRoot = AssetLoader.ROOT;\n const resolved = await this.resolve(root);\n const entry = resolved.manifest.datasets[dataset];\n if (!entry) throw new Error(`MineRender ${dataset} data is unavailable for ${resolved.version}: ${resolved.manifest.unavailable[dataset] ?? \"not listed in the manifest\"}`);\n const file = entry.file;\n const requestRoot = resolved.root === (root ?? defaultRoot).replace(/\\/+$/, \"\") ? root : resolved.root;\n return await this.cached(`${requestRoot === undefined ? \"sources\" : \"explicit\"}:${resolved.root}/${file}:${entry.sha256}`, scope, defaultRoot, async () => {\n const data = await this.load(file.slice(0, -5), requestRoot);\n if (!this.validDataset(dataset, data)) throw new Error(`Invalid MineRender ${dataset} data at ${resolved.root}/minerender-data/${file}`);\n return data as MineRenderDatasets[K];\n });\n }\n\n /**\n * Uses the exact version when published. Otherwise, selects the nearest patch in the same\n * release line (older on ties), then the latest earlier release or the oldest published release.\n * Unpublished snapshots reject.\n */\n public static async resolve(root?: string): Promise<ResolvedMineRenderData> {\n const requestRoot = root?.replace(/\\/+$/, \"\");\n const defaultRoot = AssetLoader.ROOT;\n root = (root ?? defaultRoot).replace(/\\/+$/, \"\");\n const scope = AssetLoader.persistentScope;\n return this.cached(`resolve:${requestRoot === undefined ? \"sources\" : \"explicit\"}:${root}`, scope, defaultRoot, async () => {\n const requestedVersion = root.slice(root.lastIndexOf(\"/\") + 1);\n let manifest = await this.manifest(root, requestRoot !== undefined && release(requestedVersion) ? requestedVersion : undefined, requestRoot);\n if (manifest) return { requestedVersion, version: manifest.minecraftVersion, root, manifest };\n const requested = release(requestedVersion);\n if (!requested) {\n throw new Error(`No MineRender data is published for ${requestedVersion}; automatic fallback requires a release`);\n }\n const base = root.slice(0, root.lastIndexOf(\"/\"));\n const index = await this.cached(`versions:${base}`, scope, defaultRoot, async () => {\n const value = await this.load(\"versions\", base);\n if (!object(value) || !Array.isArray(value.versions)\n || !value.versions.every(entry => object(entry) && typeof entry.name === \"string\")) {\n throw new Error(`Invalid MineRender version index at ${base}/minerender-data/versions.json`);\n }\n return value.versions as { name: string }[];\n });\n const versions = index.map(entry => ({ name: entry.name, number: release(entry.name) }))\n .filter((entry): entry is { name: string; number: number[] } => !!entry.number)\n .sort((left, right) => compare(left.number, right.number));\n const sameLine = versions.filter(entry => entry.number[0] === requested[0] && entry.number[1] === requested[1]);\n const selected = sameLine.sort((left, right) => Math.abs((left.number[2] ?? 0) - (requested[2] ?? 0))\n - Math.abs((right.number[2] ?? 0) - (requested[2] ?? 0)) || compare(left.number, right.number))[0]\n ?? versions.filter(entry => compare(entry.number, requested) <= 0).pop()\n ?? versions[0];\n if (!selected) throw new Error(`No compatible MineRender data is published for ${requestedVersion}`);\n const resolvedRoot = `${base}/${selected.name}`;\n manifest = await this.manifest(resolvedRoot, selected.name, resolvedRoot);\n if (!manifest) throw new Error(`Missing MineRender manifest for published version ${selected.name}`);\n return { requestedVersion, version: selected.name, root: resolvedRoot, manifest };\n });\n }\n\n private static async manifest(root: string, expectedVersion: string | undefined, requestRoot?: string): Promise<MineRenderDataManifest | undefined> {\n const data = await this.load(\"manifest\", requestRoot);\n if (data === undefined) return undefined;\n if (!object(data) || data.schemaVersion !== 1 || typeof data.minecraftVersion !== \"string\" || !Number.isInteger(data.dataVersion)\n || !object(data.unavailable) || !Object.values(data.unavailable).every(value => typeof value === \"string\")\n || !object(data.datasets) || !Object.values(data.datasets).every(entry => {\n return object(entry) && typeof entry.file === \"string\" && /^[a-z0-9][a-z0-9._-]*\\.json$/.test(entry.file)\n && typeof entry.sha256 === \"string\" && /^[0-9a-f]{64}$/.test(entry.sha256);\n }) || (expectedVersion !== undefined && data.minecraftVersion !== expectedVersion)) {\n throw new Error(`Invalid MineRender manifest at ${root}/minerender-data/manifest.json`);\n }\n return data as unknown as MineRenderDataManifest;\n }\n\n private static load(file: string, root?: string): Promise<unknown> {\n const key = new AssetKey(\"minerender-data\", file, undefined, undefined, undefined, \".json\", root);\n key.rootType = undefined!;\n return AssetLoader.get(key, AssetParser.JSON);\n }\n\n private static async cached<T>(key: string, scope: string, defaultRoot: string, loader: () => Promise<T>): Promise<T> {\n const checkSources = () => {\n if (AssetLoader.persistentScope !== scope || AssetLoader.ROOT !== defaultRoot) throw new Error(\"Asset sources changed while loading MineRender data; retry the request\");\n };\n checkSources();\n return await Caching.mineRenderDataCache.get(scope ? `${scope}\\n${key}` : key, async () => {\n const value = await loader();\n checkSources();\n return value;\n }) as T;\n }\n\n private static validDataset(dataset: keyof MineRenderDatasets, data: unknown): boolean {\n if (!object(data)) return false;\n if (dataset === \"potionColors\") return object(data.effects) && Object.values(data.effects).every(Number.isInteger)\n && object(data.potions) && Object.values(data.potions).every(effects => Array.isArray(effects)\n && effects.every(effect => object(effect) && typeof effect.id === \"string\" && Number.isInteger(effect.amplifier)));\n if (dataset === \"renames\") return data.format === \"data-fixer-renames\" && Number.isInteger(data.baseDataVersion)\n && [\"blocks\", \"items\", \"entities\"].every(kind => object(data[kind]) && Object.values(data[kind]).every(entry =>\n object(entry) && typeof entry.registered === \"boolean\" && Array.isArray(entry.ranges) && entry.ranges.length > 0\n && entry.ranges.every(range => object(range) && Number.isInteger(range.before) && typeof range.to === \"string\")));\n if (dataset === \"legacyBlocks\") return object(data.blocks) && Object.values(data.blocks).every(value => typeof value === \"string\");\n if (dataset === \"blockCulling\") return Array.isArray(data.profiles)\n && data.profiles.every(profile => object(profile) && typeof profile.type === \"string\")\n && object(data.blocks) && Object.values(data.blocks).every(index => Number.isInteger(index) && index >= 0 && index < data.profiles.length)\n && (data.groups === undefined || object(data.groups) && Object.values(data.groups).every(group =>\n Array.isArray(group) && group.every(type => typeof type === \"string\")));\n return Object.values(data).every(value => {\n if (!object(value)) return false;\n switch (dataset) {\n case \"itemDefaults\": return true;\n case \"blockStates\": return Object.values(value).every(property => object(property) && \"default\" in property && Array.isArray(property.values));\n case \"blockTints\": return [\"constant\", \"grass\", \"stem\", \"redstone\"].includes(value.source)\n && (value.source !== \"constant\" || Number.isInteger(value.color))\n && (value.biome === undefined || [\"grass\", \"foliage\", \"dry_foliage\", \"water\"].includes(value.biome));\n case \"fluids\": return [\"water\", \"lava\"].includes(value.kind) && typeof value.renderModel === \"boolean\"\n && (value.levelProperty === undefined || typeof value.levelProperty === \"string\");\n case \"dyes\": return [value.id, value.textureDiffuseColor, value.fireworkColor, value.textColor].every(Number.isInteger);\n default: return false;\n }\n });\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/assets/MineRenderData.ts"],"sourcesContent":["import { parseRelease, nearestRelease } from \"./_versions/DatasetVersions\";\nimport { AssetKey } from \"./AssetKey\";\nimport { AssetLoader } from \"./AssetLoader\";\nimport { AssetParser } from \"./source/parser/AssetParsers\";\nimport { Caching } from \"../cache/Caching\";\nimport type { BlockStatePropertyDefaults } from \"../model/block/BlockStateProperties\";\nimport type { BiomeColorKind } from \"./Biomes\";\nimport type { RenameKind } from \"./Renames\";\n\nexport interface MineRenderDatasets {\n itemDefaults: Record<string, Record<string, unknown>>;\n potionColors: {\n effects: Record<string, number>;\n potions: Record<string, { id: string; amplifier: number }[]>;\n };\n blockStates: Record<string, BlockStatePropertyDefaults>;\n blockTints: Record<string, ({ source: \"constant\"; color: number } | { source: \"grass\" | \"stem\" | \"redstone\" }) & {\n untinted?: number[];\n biome?: BiomeColorKind;\n }>;\n blockCulling: {\n profiles: { type: string; group?: string; kind?: string }[];\n blocks: Record<string, number>;\n groups?: Record<string, string[]>;\n };\n fluids: Record<string, { kind: \"water\" | \"lava\"; renderModel: boolean; levelProperty?: string }>;\n dyes: Record<string, { id: number; textureDiffuseColor: number; fireworkColor: number; textColor: number }>;\n legacyBlocks: { blocks: Record<string, string> };\n renames: { format: \"data-fixer-renames\"; baseDataVersion: number }\n & Record<RenameKind, Record<string, { registered: boolean; ranges: { before: number; to: string }[] }>>;\n}\n\nexport interface MineRenderDataManifest {\n schemaVersion: 1;\n minecraftVersion: string;\n dataVersion: number;\n datasets: Partial<Record<keyof MineRenderDatasets, { file: string; sha256: string; bytes: number; provenance: Record<string, unknown> }>>;\n unavailable: Record<string, string>;\n extractor: { repository: string; revision: string; sha256: string };\n}\n\n/** The requested asset version and the published data version used for its registries. */\nexport interface ResolvedMineRenderData {\n requestedVersion: string;\n version: string;\n /** Registry lookup root. Rootless requests still allow registered sources to override this URL. */\n root: string;\n manifest: MineRenderDataManifest;\n}\n\nconst object = (value: unknown): value is Record<string, any> => value !== null && typeof value === \"object\" && !Array.isArray(value);\n\n/** Loads extracted Minecraft registries from the active asset sources and version. */\nexport class MineRenderData {\n /** Loads one registry. Missing files and invalid data reject the request. */\n public static async get<K extends keyof MineRenderDatasets>(dataset: K, root?: string): Promise<MineRenderDatasets[K]> {\n root = root?.replace(/\\/+$/, \"\");\n const scope = AssetLoader.persistentScope;\n const defaultRoot = AssetLoader.ROOT;\n const resolved = await this.resolve(root);\n const entry = resolved.manifest.datasets[dataset];\n if (!entry) throw new Error(`MineRender ${dataset} data is unavailable for ${resolved.version}: ${resolved.manifest.unavailable[dataset] ?? \"not listed in the manifest\"}`);\n const file = entry.file;\n const requestRoot = resolved.root === (root ?? defaultRoot).replace(/\\/+$/, \"\") ? root : resolved.root;\n return await this.cached(`${requestRoot === undefined ? \"sources\" : \"explicit\"}:${resolved.root}/${file}:${entry.sha256}`, scope, defaultRoot, async () => {\n const data = await this.load(file.slice(0, -5), requestRoot);\n if (!this.validDataset(dataset, data)) throw new Error(`Invalid MineRender ${dataset} data at ${resolved.root}/minerender-data/${file}`);\n return data as MineRenderDatasets[K];\n });\n }\n\n /**\n * Uses the exact version when published. Otherwise, selects the nearest patch in the same\n * release line (older on ties), then the latest earlier release or the oldest published release.\n * Unpublished snapshots reject.\n */\n public static async resolve(root?: string): Promise<ResolvedMineRenderData> {\n const requestRoot = root?.replace(/\\/+$/, \"\");\n const defaultRoot = AssetLoader.ROOT;\n root = (root ?? defaultRoot).replace(/\\/+$/, \"\");\n const scope = AssetLoader.persistentScope;\n return this.cached(`resolve:${requestRoot === undefined ? \"sources\" : \"explicit\"}:${root}`, scope, defaultRoot, async () => {\n const requestedVersion = root.slice(root.lastIndexOf(\"/\") + 1);\n let manifest = await this.manifest(root, requestRoot !== undefined && parseRelease(requestedVersion) ? requestedVersion : undefined, requestRoot);\n if (manifest) return { requestedVersion, version: manifest.minecraftVersion, root, manifest };\n const requested = parseRelease(requestedVersion);\n if (!requested) {\n throw new Error(`No MineRender data is published for ${requestedVersion}; automatic fallback requires a release`);\n }\n const base = root.slice(0, root.lastIndexOf(\"/\"));\n const index = await this.cached(`versions:${base}`, scope, defaultRoot, async () => {\n const value = await this.load(\"versions\", base);\n if (!object(value) || !Array.isArray(value.versions)\n || !value.versions.every(entry => object(entry) && typeof entry.name === \"string\")) {\n throw new Error(`Invalid MineRender version index at ${base}/minerender-data/versions.json`);\n }\n return value.versions as { name: string }[];\n });\n const selected = nearestRelease(requestedVersion, index.map(entry => entry.name));\n if (!selected) throw new Error(`No compatible MineRender data is published for ${requestedVersion}`);\n const resolvedRoot = `${base}/${selected}`;\n manifest = await this.manifest(resolvedRoot, selected, resolvedRoot);\n if (!manifest) throw new Error(`Missing MineRender manifest for published version ${selected}`);\n return { requestedVersion, version: selected, root: resolvedRoot, manifest };\n });\n }\n\n private static async manifest(root: string, expectedVersion: string | undefined, requestRoot?: string): Promise<MineRenderDataManifest | undefined> {\n const data = await this.load(\"manifest\", requestRoot);\n if (data === undefined) return undefined;\n if (!object(data) || data.schemaVersion !== 1 || typeof data.minecraftVersion !== \"string\" || !Number.isInteger(data.dataVersion)\n || !object(data.unavailable) || !Object.values(data.unavailable).every(value => typeof value === \"string\")\n || !object(data.datasets) || !Object.values(data.datasets).every(entry => {\n return object(entry) && typeof entry.file === \"string\" && /^[a-z0-9][a-z0-9._-]*\\.json$/.test(entry.file)\n && typeof entry.sha256 === \"string\" && /^[0-9a-f]{64}$/.test(entry.sha256);\n }) || (expectedVersion !== undefined && data.minecraftVersion !== expectedVersion)) {\n throw new Error(`Invalid MineRender manifest at ${root}/minerender-data/manifest.json`);\n }\n return data as unknown as MineRenderDataManifest;\n }\n\n private static load(file: string, root?: string): Promise<unknown> {\n const key = new AssetKey(\"minerender-data\", file, undefined, undefined, undefined, \".json\", root);\n key.rootType = undefined!;\n return AssetLoader.get(key, AssetParser.JSON);\n }\n\n private static async cached<T>(key: string, scope: string, defaultRoot: string, loader: () => Promise<T>): Promise<T> {\n const checkSources = () => {\n if (AssetLoader.persistentScope !== scope || AssetLoader.ROOT !== defaultRoot) throw new Error(\"Asset sources changed while loading MineRender data; retry the request\");\n };\n checkSources();\n return await Caching.mineRenderDataCache.get(scope ? `${scope}\\n${key}` : key, async () => {\n const value = await loader();\n checkSources();\n return value;\n }) as T;\n }\n\n private static validDataset(dataset: keyof MineRenderDatasets, data: unknown): boolean {\n if (!object(data)) return false;\n if (dataset === \"potionColors\") return object(data.effects) && Object.values(data.effects).every(Number.isInteger)\n && object(data.potions) && Object.values(data.potions).every(effects => Array.isArray(effects)\n && effects.every(effect => object(effect) && typeof effect.id === \"string\" && Number.isInteger(effect.amplifier)));\n if (dataset === \"renames\") return data.format === \"data-fixer-renames\" && Number.isInteger(data.baseDataVersion)\n && [\"blocks\", \"items\", \"entities\"].every(kind => object(data[kind]) && Object.values(data[kind]).every(entry =>\n object(entry) && typeof entry.registered === \"boolean\" && Array.isArray(entry.ranges) && entry.ranges.length > 0\n && entry.ranges.every(range => object(range) && Number.isInteger(range.before) && typeof range.to === \"string\")));\n if (dataset === \"legacyBlocks\") return object(data.blocks) && Object.values(data.blocks).every(value => typeof value === \"string\");\n if (dataset === \"blockCulling\") return Array.isArray(data.profiles)\n && data.profiles.every(profile => object(profile) && typeof profile.type === \"string\")\n && object(data.blocks) && Object.values(data.blocks).every(index => Number.isInteger(index) && index >= 0 && index < data.profiles.length)\n && (data.groups === undefined || object(data.groups) && Object.values(data.groups).every(group =>\n Array.isArray(group) && group.every(type => typeof type === \"string\")));\n return Object.values(data).every(value => {\n if (!object(value)) return false;\n switch (dataset) {\n case \"itemDefaults\": return true;\n case \"blockStates\": return Object.values(value).every(property => object(property) && \"default\" in property && Array.isArray(property.values));\n case \"blockTints\": return [\"constant\", \"grass\", \"stem\", \"redstone\"].includes(value.source)\n && (value.source !== \"constant\" || Number.isInteger(value.color))\n && (value.biome === undefined || [\"grass\", \"foliage\", \"dry_foliage\", \"water\"].includes(value.biome));\n case \"fluids\": return [\"water\", \"lava\"].includes(value.kind) && typeof value.renderModel === \"boolean\"\n && (value.levelProperty === undefined || typeof value.levelProperty === \"string\");\n case \"dyes\": return [value.id, value.textureDiffuseColor, value.fireworkColor, value.textColor].every(Number.isInteger);\n default: return false;\n }\n });\n }\n}\n"],"mappings":"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","names":[]}
@@ -45,15 +45,21 @@ var ModelTextures = class {
45
45
  key.extension += ".mcmeta";
46
46
  }
47
47
  const keyStr = key.serialize();
48
- return Caching.textureMetaCache.get(keyStr, (k) => {
49
- return this.PERSISTENT_META_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
50
- return AssetLoader.get(key, AssetParser.META).then((asset) => {
51
- if (asset)
52
- asset.key = key;
53
- return asset;
54
- });
48
+ const meta = await Caching.textureMetaCache.get(keyStr, (k) => {
49
+ return this.PERSISTENT_META_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), async (k1) => {
50
+ if (AssetLoader.persistentScope === "") {
51
+ const path = key.getFullPath();
52
+ const slash = path.lastIndexOf("/");
53
+ const listKey = new AssetKey(key.namespace, `${path.slice(0, slash + 1)}_list`, "textures", void 0, "assets", ".json", key.root);
54
+ const list = await Caching.listAssetCache.get(listKey.serialize(), () => AssetLoader.get(listKey, AssetParser.LIST)).catch(() => void 0);
55
+ if (list && !list.files.includes(`${path.slice(slash + 1)}${key.extension}`)) return Caching.MISSING;
56
+ }
57
+ const asset = await AssetLoader.get(key, AssetParser.META);
58
+ if (asset) asset.key = key;
59
+ return asset ?? Caching.MISSING;
55
60
  });
56
61
  });
62
+ return meta === Caching.MISSING ? void 0 : meta;
57
63
  }
58
64
  /** Clears persisted texture metadata. Use {@link Caching.clear} to discard in-memory textures and metadata. */
59
65
  static async clearCache() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/assets/ModelTextures.ts"],"sourcesContent":["import { Maybe } from \"../util/util\";\nimport { TextureAsset } from \"../model/Model\";\nimport { Caching } from \"../cache/Caching\";\nimport { AssetLoader } from \"./AssetLoader\";\nimport { CompatImage } from \"../canvas/CanvasCompat\";\nimport { ImageLoader } from \"../image/ImageLoader\";\nimport { ExtractableImageData } from \"../ExtractableImageData\";\nimport { MinecraftTextureMeta } from \"../MinecraftTextureMeta\";\nimport { PersistentCache } from \"../cache/PersistentCache\";\nimport { AssetKey } from \"./AssetKey\";\nimport { AssetParser } from \"./source/parser/AssetParsers\";\n\n/** Loads texture pixels and `.mcmeta` files through the active asset sources. */\nexport class ModelTextures {\n\n private static _persistentMetaCache: PersistentCache | undefined;\n\n private static get PERSISTENT_META_CACHE(): PersistentCache {\n return this._persistentMetaCache ??= PersistentCache.open(\"minerender-texturemeta\");\n }\n\n /** Loads and decodes a texture into a readable canvas, or returns `undefined` when missing. */\n public static async get(key: AssetKey): Promise<Maybe<ExtractableImageData>> {\n const keyStr = key.serialize();\n const pending = this.preload(key);\n const cached = Caching.textureAssetCache.getIfPresent(keyStr);\n try {\n const asset = await pending;\n if (asset) {\n return await ImageLoader.infoToCanvasData(asset);\n }\n return undefined;\n } catch (err) {\n // Discard corrupt encoded bytes, but preserve any newer load for this key.\n if (cached && Caching.textureAssetCache.getIfPresent(keyStr) === cached) {\n Caching.textureAssetCache.invalidate(keyStr);\n }\n throw err;\n }\n }\n\n /** Fetches and caches encoded texture bytes without decoding the pixels. */\n public static async preload(key: AssetKey): Promise<Maybe<TextureAsset>> {\n const keyStr = key.serialize();\n return Caching.textureAssetCache.get(keyStr, k => {\n return AssetLoader.get<TextureAsset>(key, AssetParser.IMAGE).then(asset => {\n if (asset)\n asset.key = key;\n return asset;\n })\n })\n }\n\n /** Loads a texture's `.mcmeta` file, or returns `undefined` when no metadata file exists. */\n public static async getMeta(key: AssetKey): Promise<Maybe<MinecraftTextureMeta>> {\n if (!key.extension || !key.extension.endsWith(\".mcmeta\")) {\n key = Object.assign(new AssetKey(\"\", \"\"), key);\n key.extension += \".mcmeta\";\n }\n const keyStr = key.serialize();\n\n return Caching.textureMetaCache.get(keyStr, k => {\n return this.PERSISTENT_META_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), k1 => {\n return AssetLoader.get<MinecraftTextureMeta>(key, AssetParser.META).then(asset => {\n if (asset)\n asset.key = key;\n return asset;\n })\n })\n })\n }\n\n /** Clears persisted texture metadata. Use {@link Caching.clear} to discard in-memory textures and metadata. */\n public static async clearCache() {\n await this.PERSISTENT_META_CACHE.clear();\n }\n\n}\n"],"mappings":";AAEA,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAE5B,SAAS,mBAAmB;AAG5B,SAAS,uBAAuB;AAChC,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAGrB,IAAM,gBAAN,MAAoB;AAAA,EAIvB,WAAmB,wBAAyC;AACxD,WAAO,KAAK,yBAAL,KAAK,uBAAyB,gBAAgB,KAAK,wBAAwB;AAAA,EACtF;AAAA;AAAA,EAGA,aAAoB,IAAI,KAAqD;AACzE,UAAM,SAAS,IAAI,UAAU;AAC7B,UAAM,UAAU,KAAK,QAAQ,GAAG;AAChC,UAAM,SAAS,QAAQ,kBAAkB,aAAa,MAAM;AAC5D,QAAI;AACA,YAAM,QAAQ,MAAM;AACpB,UAAI,OAAO;AACP,eAAO,MAAM,YAAY,iBAAiB,KAAK;AAAA,MACnD;AACA,aAAO;AAAA,IACX,SAAS,KAAK;AAEV,UAAI,UAAU,QAAQ,kBAAkB,aAAa,MAAM,MAAM,QAAQ;AACrE,gBAAQ,kBAAkB,WAAW,MAAM;AAAA,MAC/C;AACA,YAAM;AAAA,IACV;AAAA,EACJ;AAAA;AAAA,EAGA,aAAoB,QAAQ,KAA6C;AACrE,UAAM,SAAS,IAAI,UAAU;AAC7B,WAAO,QAAQ,kBAAkB,IAAI,QAAQ,OAAK;AAC9C,aAAO,YAAY,IAAkB,KAAK,YAAY,KAAK,EAAE,KAAK,WAAS;AACvE,YAAI;AACA,gBAAM,MAAM;AAChB,eAAO;AAAA,MACX,CAAC;AAAA,IACL,CAAC;AAAA,EACL;AAAA;AAAA,EAGA,aAAoB,QAAQ,KAAqD;AAC7E,QAAI,CAAC,IAAI,aAAa,CAAC,IAAI,UAAU,SAAS,SAAS,GAAG;AACtD,YAAM,OAAO,OAAO,IAAI,SAAS,IAAI,EAAE,GAAG,GAAG;AAC7C,UAAI,aAAa;AAAA,IACrB;AACA,UAAM,SAAS,IAAI,UAAU;AAE7B,WAAO,QAAQ,iBAAiB,IAAI,QAAQ,OAAK;AAC7C,aAAO,KAAK,sBAAsB,UAAU,YAAY,cAAc,MAAM,GAAG,QAAM;AACjF,eAAO,YAAY,IAA0B,KAAK,YAAY,IAAI,EAAE,KAAK,WAAS;AAC9E,cAAI;AACA,kBAAM,MAAM;AAChB,iBAAO;AAAA,QACX,CAAC;AAAA,MACL,CAAC;AAAA,IACL,CAAC;AAAA,EACL;AAAA;AAAA,EAGA,aAAoB,aAAa;AAC7B,UAAM,KAAK,sBAAsB,MAAM;AAAA,EAC3C;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/assets/ModelTextures.ts"],"sourcesContent":["import { Maybe } from \"../util/util\";\nimport { TextureAsset } from \"../model/Model\";\nimport { Caching } from \"../cache/Caching\";\nimport { AssetLoader } from \"./AssetLoader\";\nimport { CompatImage } from \"../canvas/CanvasCompat\";\nimport { ImageLoader } from \"../image/ImageLoader\";\nimport { ExtractableImageData } from \"../ExtractableImageData\";\nimport { MinecraftTextureMeta } from \"../MinecraftTextureMeta\";\nimport { PersistentCache } from \"../cache/PersistentCache\";\nimport { AssetKey } from \"./AssetKey\";\nimport { AssetParser } from \"./source/parser/AssetParsers\";\nimport type { ListAsset } from \"../ListAsset\";\n\n/** Loads texture pixels and `.mcmeta` files through the active asset sources. */\nexport class ModelTextures {\n\n private static _persistentMetaCache: PersistentCache | undefined;\n\n private static get PERSISTENT_META_CACHE(): PersistentCache {\n return this._persistentMetaCache ??= PersistentCache.open(\"minerender-texturemeta\");\n }\n\n /** Loads and decodes a texture into a readable canvas, or returns `undefined` when missing. */\n public static async get(key: AssetKey): Promise<Maybe<ExtractableImageData>> {\n const keyStr = key.serialize();\n const pending = this.preload(key);\n const cached = Caching.textureAssetCache.getIfPresent(keyStr);\n try {\n const asset = await pending;\n if (asset) {\n return await ImageLoader.infoToCanvasData(asset);\n }\n return undefined;\n } catch (err) {\n // Discard corrupt encoded bytes, but preserve any newer load for this key.\n if (cached && Caching.textureAssetCache.getIfPresent(keyStr) === cached) {\n Caching.textureAssetCache.invalidate(keyStr);\n }\n throw err;\n }\n }\n\n /** Fetches and caches encoded texture bytes without decoding the pixels. */\n public static async preload(key: AssetKey): Promise<Maybe<TextureAsset>> {\n const keyStr = key.serialize();\n return Caching.textureAssetCache.get(keyStr, k => {\n return AssetLoader.get<TextureAsset>(key, AssetParser.IMAGE).then(asset => {\n if (asset)\n asset.key = key;\n return asset;\n })\n })\n }\n\n /** Loads a texture's `.mcmeta` file, or returns `undefined` when no metadata file exists. */\n public static async getMeta(key: AssetKey): Promise<Maybe<MinecraftTextureMeta>> {\n if (!key.extension || !key.extension.endsWith(\".mcmeta\")) {\n key = Object.assign(new AssetKey(\"\", \"\"), key);\n key.extension += \".mcmeta\";\n }\n const keyStr = key.serialize();\n\n const meta = await Caching.textureMetaCache.get(keyStr, k => {\n return this.PERSISTENT_META_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), async k1 => {\n if (AssetLoader.persistentScope === \"\") {\n const path = key.getFullPath();\n const slash = path.lastIndexOf(\"/\");\n const listKey = new AssetKey(key.namespace, `${path.slice(0, slash + 1)}_list`, \"textures\", undefined, \"assets\", \".json\", key.root);\n const list = await Caching.listAssetCache.get(listKey.serialize(), () => AssetLoader.get<ListAsset>(listKey, AssetParser.LIST)).catch(() => undefined);\n if (list && !list.files.includes(`${path.slice(slash + 1)}${key.extension}`)) return Caching.MISSING;\n }\n const asset = await AssetLoader.get<MinecraftTextureMeta>(key, AssetParser.META);\n if (asset) asset.key = key;\n return asset ?? Caching.MISSING;\n })\n });\n return meta === Caching.MISSING ? undefined : meta;\n }\n\n /** Clears persisted texture metadata. Use {@link Caching.clear} to discard in-memory textures and metadata. */\n public static async clearCache() {\n await this.PERSISTENT_META_CACHE.clear();\n }\n\n}\n"],"mappings":";AAEA,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAE5B,SAAS,mBAAmB;AAG5B,SAAS,uBAAuB;AAChC,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAIrB,IAAM,gBAAN,MAAoB;AAAA,EAIvB,WAAmB,wBAAyC;AACxD,WAAO,KAAK,yBAAL,KAAK,uBAAyB,gBAAgB,KAAK,wBAAwB;AAAA,EACtF;AAAA;AAAA,EAGA,aAAoB,IAAI,KAAqD;AACzE,UAAM,SAAS,IAAI,UAAU;AAC7B,UAAM,UAAU,KAAK,QAAQ,GAAG;AAChC,UAAM,SAAS,QAAQ,kBAAkB,aAAa,MAAM;AAC5D,QAAI;AACA,YAAM,QAAQ,MAAM;AACpB,UAAI,OAAO;AACP,eAAO,MAAM,YAAY,iBAAiB,KAAK;AAAA,MACnD;AACA,aAAO;AAAA,IACX,SAAS,KAAK;AAEV,UAAI,UAAU,QAAQ,kBAAkB,aAAa,MAAM,MAAM,QAAQ;AACrE,gBAAQ,kBAAkB,WAAW,MAAM;AAAA,MAC/C;AACA,YAAM;AAAA,IACV;AAAA,EACJ;AAAA;AAAA,EAGA,aAAoB,QAAQ,KAA6C;AACrE,UAAM,SAAS,IAAI,UAAU;AAC7B,WAAO,QAAQ,kBAAkB,IAAI,QAAQ,OAAK;AAC9C,aAAO,YAAY,IAAkB,KAAK,YAAY,KAAK,EAAE,KAAK,WAAS;AACvE,YAAI;AACA,gBAAM,MAAM;AAChB,eAAO;AAAA,MACX,CAAC;AAAA,IACL,CAAC;AAAA,EACL;AAAA;AAAA,EAGA,aAAoB,QAAQ,KAAqD;AAC7E,QAAI,CAAC,IAAI,aAAa,CAAC,IAAI,UAAU,SAAS,SAAS,GAAG;AACtD,YAAM,OAAO,OAAO,IAAI,SAAS,IAAI,EAAE,GAAG,GAAG;AAC7C,UAAI,aAAa;AAAA,IACrB;AACA,UAAM,SAAS,IAAI,UAAU;AAE7B,UAAM,OAAO,MAAM,QAAQ,iBAAiB,IAAI,QAAQ,OAAK;AACzD,aAAO,KAAK,sBAAsB,UAAU,YAAY,cAAc,MAAM,GAAG,OAAM,OAAM;AACvF,YAAI,YAAY,oBAAoB,IAAI;AACpC,gBAAM,OAAO,IAAI,YAAY;AAC7B,gBAAM,QAAQ,KAAK,YAAY,GAAG;AAClC,gBAAM,UAAU,IAAI,SAAS,IAAI,WAAW,GAAG,KAAK,MAAM,GAAG,QAAQ,CAAC,CAAC,SAAS,YAAY,QAAW,UAAU,SAAS,IAAI,IAAI;AAClI,gBAAM,OAAO,MAAM,QAAQ,eAAe,IAAI,QAAQ,UAAU,GAAG,MAAM,YAAY,IAAe,SAAS,YAAY,IAAI,CAAC,EAAE,MAAM,MAAM,MAAS;AACrJ,cAAI,QAAQ,CAAC,KAAK,MAAM,SAAS,GAAG,KAAK,MAAM,QAAQ,CAAC,CAAC,GAAG,IAAI,SAAS,EAAE,EAAG,QAAO,QAAQ;AAAA,QACjG;AACA,cAAM,QAAQ,MAAM,YAAY,IAA0B,KAAK,YAAY,IAAI;AAC/E,YAAI,MAAO,OAAM,MAAM;AACvB,eAAO,SAAS,QAAQ;AAAA,MAC5B,CAAC;AAAA,IACL,CAAC;AACD,WAAO,SAAS,QAAQ,UAAU,SAAY;AAAA,EAClD;AAAA;AAAA,EAGA,aAAoB,aAAa;AAC7B,UAAM,KAAK,sBAAsB,MAAM;AAAA,EAC3C;AAEJ;","names":[]}