minerender 2.1.0-beta.1 → 2.1.0-beta.2

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.
@@ -68,6 +68,7 @@ var init_PersistentCache = __esm({
68
68
  /** Bump to invalidate every persisted entry. */
69
69
  this.VERSION = 2;
70
70
  }
71
+ /** Opens a named store in the active environment using the current cache schema version. */
71
72
  static open(name) {
72
73
  return Env.provider.openCache(name, _PersistentCache.VERSION);
73
74
  }
@@ -299,6 +300,9 @@ var init_Caching = __esm({
299
300
  static {
300
301
  this.fontCache = _Caching.createAssetCache();
301
302
  }
303
+ static {
304
+ this.bannerPatternCache = _Caching.createAssetCache();
305
+ }
302
306
  static createAssetCache() {
303
307
  return Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
304
308
  }
@@ -326,9 +330,11 @@ var init_Caching = __esm({
326
330
  this.entityTextureCache,
327
331
  this.entityAnimationCache,
328
332
  this.blockEntityIndexCache,
329
- this.fontCache
333
+ this.fontCache,
334
+ this.bannerPatternCache
330
335
  ];
331
336
  }
337
+ /** Invalidates in-memory entries so later lookups reload them. Persisted entries are retained. */
332
338
  static clear() {
333
339
  for (const cache of this.all) {
334
340
  cache.invalidateAll();
@@ -371,6 +377,7 @@ var init_Geometries = __esm({
371
377
  }
372
378
  return geometry;
373
379
  }
380
+ /** Returns shared geometry. Use {@link createBox} when the caller needs to modify or dispose it independently. */
374
381
  static getBox(key) {
375
382
  const keyStr = serializeBoxGeometryKey(key);
376
383
  return Caching.boxGeometryCache.get(keyStr, (k) => {
@@ -554,12 +561,15 @@ var init_Requests = __esm({
554
561
  static {
555
562
  this.mcAssetRequestQueue = new RequestQueue();
556
563
  }
564
+ /** Queues a general request. Set `request.signal` to cancel it and `request.timeout` to limit each attempt. */
557
565
  static genericRequest(request) {
558
566
  return this.genericQueue.request(request);
559
567
  }
568
+ /** Queues a request separately from general traffic, using the configured asset base URL for relative paths. */
560
569
  static mcAssetRequest(request) {
561
570
  return this.mcAssetRequestQueue.request(request);
562
571
  }
572
+ /** Sets the base URL for relative asset requests. Use {@link AssetLoader.setVersion} to change Minecraft versions. */
563
573
  static setMcAssetRoot(root) {
564
574
  this.mcAssetRequestQueue.baseURL = root;
565
575
  }
@@ -612,6 +622,7 @@ var init_WrappedImage = __esm({
612
622
  }
613
623
  return false;
614
624
  }
625
+ /** Whether the dimensions form a vertical strip of square frames, without consulting `.mcmeta`. */
615
626
  get animated() {
616
627
  return this.height > this.width && this.height % this.width === 0;
617
628
  }
@@ -658,6 +669,7 @@ var init_ImageLoader = __esm({
658
669
  static _createImage() {
659
670
  return createImage();
660
671
  }
672
+ /** Resolves when an image URL has decoded, or rejects if decoding fails. */
661
673
  static async loadAsync(src) {
662
674
  return new Promise((resolve, reject) => {
663
675
  const image = this._createImage();
@@ -666,6 +678,7 @@ var init_ImageLoader = __esm({
666
678
  image.src = src;
667
679
  });
668
680
  }
681
+ /** Returns an image immediately. Use the callbacks to observe loading, or await {@link loadAsync}. */
669
682
  static loadElement(src, onload, onerr) {
670
683
  const image = this._createImage();
671
684
  if (onload)
@@ -693,6 +706,7 @@ var init_ImageLoader = __esm({
693
706
  throw err;
694
707
  }
695
708
  }
709
+ /** Decodes the supplied bytes into a canvas without fetching the source URL again. */
696
710
  static async infoToCanvasData(info) {
697
711
  const type = info.type === "jpg" ? "jpeg" : info.type ?? "png";
698
712
  const image = await _ImageLoader.loadAsync(`data:image/${type};base64,${info.data.toString("base64")}`);
@@ -709,12 +723,14 @@ var init_ImageLoader = __esm({
709
723
  const image = await this.infoToCanvasData(info);
710
724
  return image.data.getImageData(0, 0, image.width, image.height);
711
725
  }
726
+ /** Returns cached RGBA pixels for an image URL or data URL. Failed loads can be retried. */
712
727
  static async getData(src) {
713
728
  const keyStr = serializeImageKey({ src });
714
729
  return await Caching.imageDataCache.get(keyStr, (k) => {
715
730
  return _ImageLoader.loadData(src);
716
731
  });
717
732
  }
733
+ /** Returns a cached canvas context and dimensions for an image URL or data URL. */
718
734
  static async getCanvasData(src) {
719
735
  const keyStr = serializeImageKey({ src });
720
736
  return await Caching.canvasImageDataCache.get(keyStr, (k) => {
@@ -800,7 +816,7 @@ var init_TextureLoader = __esm({
800
816
  static createTexture() {
801
817
  return new Texture();
802
818
  }
803
- static loadInBackground(src, format = RGBAFormat, rotation = 0) {
819
+ static loadInBackground(src, format2 = RGBAFormat, rotation = 0) {
804
820
  const texture = this.createTexture();
805
821
  texture.colorSpace = THREE.SRGBColorSpace;
806
822
  const image = ImageLoader.loadElement(src, () => {
@@ -810,11 +826,12 @@ var init_TextureLoader = __esm({
810
826
  console.warn("Failed to load texture", src, error);
811
827
  });
812
828
  texture.image = image;
813
- texture.format = format;
829
+ texture.format = format2;
814
830
  texture.rotation = rotation;
815
831
  return Textures.initTextureProps(texture);
816
832
  }
817
- static load(src, format = RGBAFormat, rotation = 0) {
833
+ /** Loads image pixels in the background. `rotation` is in radians. Check {@link hasFailed} for load failures. */
834
+ static load(src, format2 = RGBAFormat, rotation = 0) {
818
835
  const texture = new Texture();
819
836
  texture.colorSpace = THREE.SRGBColorSpace;
820
837
  ImageLoader.getData(src).then((image) => {
@@ -824,7 +841,7 @@ var init_TextureLoader = __esm({
824
841
  this.failedTextures.add(texture);
825
842
  console.warn("Failed to load texture", src, error);
826
843
  });
827
- texture.format = format;
844
+ texture.format = format2;
828
845
  texture.rotation = rotation;
829
846
  return Textures.initTextureProps(texture);
830
847
  }
@@ -850,8 +867,8 @@ var init_Textures = __esm({
850
867
  for (let y = 0; y < size; y++) {
851
868
  for (let x = 0; x < size; x++) {
852
869
  const offset = (y * size + x) * 4;
853
- const color = (x + y) % 2 === 0 ? 255 : 0;
854
- data[offset] = data[offset + 2] = color;
870
+ const color2 = (x + y) % 2 === 0 ? 255 : 0;
871
+ data[offset] = data[offset + 2] = color2;
855
872
  data[offset + 3] = 255;
856
873
  }
857
874
  }
@@ -862,6 +879,7 @@ var init_Textures = __esm({
862
879
  return this.initTextureProps(texture);
863
880
  });
864
881
  }
882
+ /** Sets nearest-neighbor filters without changing the texture's color space. Returns the same texture. */
865
883
  static initTextureProps(texture) {
866
884
  texture.magFilter = THREE2.NearestFilter;
867
885
  texture.minFilter = THREE2.NearestFilter;
@@ -874,11 +892,13 @@ var init_Textures = __esm({
874
892
  static createAsset(key) {
875
893
  return new Texture2();
876
894
  }
895
+ /** Creates an sRGB color texture from a canvas. */
877
896
  static createCanvasTexture(canvas) {
878
897
  const texture = new CanvasTexture(canvas);
879
898
  texture.colorSpace = THREE2.SRGBColorSpace;
880
899
  return this.initTextureProps(texture);
881
900
  }
901
+ /** Returns a shared texture immediately and loads its image asynchronously. Failed cached loads are retried. */
882
902
  static getImage(key) {
883
903
  const keyStr = serializeTextureKey(key);
884
904
  const cached2 = Caching.textureCache.peek(keyStr);
@@ -903,6 +923,7 @@ var init_Materials = __esm({
903
923
  init_TextureLoader();
904
924
  init_Caching();
905
925
  Materials = class _Materials {
926
+ /** Shared checkerboard material, created on first access. */
906
927
  static get MISSING_TEXTURE() {
907
928
  return Caching.materialCache.get("builtin:missing-texture", () => new MeshBasicMaterial({
908
929
  map: Textures.getMissing(),
@@ -974,6 +995,7 @@ var init_Materials = __esm({
974
995
  material.depthWrite = false;
975
996
  break;
976
997
  case "eyes":
998
+ case "no_outline":
977
999
  material.transparent = true;
978
1000
  material.alphaTest = 0;
979
1001
  material.depthWrite = false;
@@ -1006,6 +1028,7 @@ var init_Materials = __esm({
1006
1028
  if (tint !== void 0) material.color.multiply(new Color(tint));
1007
1029
  return material;
1008
1030
  }
1031
+ /** Creates an unlit, transparent GUI material without depth writes or tone mapping. */
1009
1032
  static createGuiCanvasMaterial(canvas) {
1010
1033
  const material = new MeshBasicMaterial({
1011
1034
  map: Textures.createCanvasTexture(canvas),
@@ -1031,7 +1054,7 @@ var init_Materials = __esm({
1031
1054
  };
1032
1055
  return material;
1033
1056
  }
1034
- static createShadedCanvasMaterial(canvas, transparent = false, shade = false) {
1057
+ static createShadedCanvasMaterial(canvas, transparent = false, shade = false, boundUvs = false) {
1035
1058
  const vertShader = `
1036
1059
  uniform bool SHADE;
1037
1060
 
@@ -1041,6 +1064,10 @@ var init_Materials = __esm({
1041
1064
  #else
1042
1065
  varying vec2 vUv;
1043
1066
  #endif
1067
+ #ifdef BOUND_UVS
1068
+ attribute vec4 uvBounds;
1069
+ flat out vec4 vUvBounds;
1070
+ #endif
1044
1071
  varying vec3 vTint;
1045
1072
  varying float light;
1046
1073
  varying float lift;
@@ -1076,13 +1103,17 @@ var init_Materials = __esm({
1076
1103
  }
1077
1104
 
1078
1105
  vUv = uv;
1106
+ #ifdef BOUND_UVS
1107
+ vUvBounds = uvBounds;
1108
+ #endif
1079
1109
  vTint = color;
1080
1110
 
1111
+ vec4 mvPosition = vec4(position, 1.0);
1081
1112
  #ifdef USE_INSTANCING
1082
- gl_Position = projectionMatrix * viewMatrix * modelMatrix * instanceMatrix * vec4(position, 1.0);
1083
- #else
1084
- gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
1113
+ mvPosition = instanceMatrix * mvPosition;
1085
1114
  #endif
1115
+ mvPosition = modelViewMatrix * mvPosition;
1116
+ gl_Position = projectionMatrix * mvPosition;
1086
1117
 
1087
1118
  }
1088
1119
  `;
@@ -1102,13 +1133,22 @@ var init_Materials = __esm({
1102
1133
  #else
1103
1134
  varying vec2 vUv;
1104
1135
  #endif
1136
+ #ifdef BOUND_UVS
1137
+ flat in vec4 vUvBounds;
1138
+ #endif
1105
1139
  varying vec3 vTint;
1106
1140
  varying float light;
1107
1141
  varying float lift;
1108
1142
 
1109
1143
  void main(void)
1110
1144
  {
1111
- vec4 color = texture2D(map, vUv);
1145
+ #ifdef BOUND_UVS
1146
+ // Keep edge samples inside their texture even without centroid interpolation.
1147
+ vec2 mapUv = clamp(vUv, vUvBounds.xy, vUvBounds.zw);
1148
+ #else
1149
+ vec2 mapUv = vUv;
1150
+ #endif
1151
+ vec4 color = texture2D(map, mapUv);
1112
1152
  color.rgb *= vTint;
1113
1153
 
1114
1154
  if (color.a < 0.01) discard;
@@ -1129,7 +1169,8 @@ var init_Materials = __esm({
1129
1169
  }
1130
1170
  `;
1131
1171
  try {
1132
- return new ShaderMaterial({
1172
+ const material = new ShaderMaterial({
1173
+ defines: boundUvs ? { BOUND_UVS: true } : {},
1133
1174
  uniforms: {
1134
1175
  SHADE: { value: true },
1135
1176
  BRIGHTNESS: { value: 1 },
@@ -1147,11 +1188,14 @@ var init_Materials = __esm({
1147
1188
  side: transparent ? DoubleSide : FrontSide,
1148
1189
  alphaTest: 0.5
1149
1190
  });
1191
+ Object.assign(material.defaultAttributeValues, { uvBounds: [0, 0, 1, 1] });
1192
+ return material;
1150
1193
  } catch (e) {
1151
1194
  console.warn(e);
1152
1195
  }
1153
1196
  return this.createBasicCanvasMaterial(canvas, transparent, shade);
1154
1197
  }
1198
+ /** Returns a shared image material. Use {@link createImage} for a separate material instance. */
1155
1199
  static getImage(key) {
1156
1200
  const keyStr = serializeMaterialKey(key);
1157
1201
  const map = Caching.materialCache.peek(keyStr)?.map;
@@ -1326,6 +1370,14 @@ var init_AssetKey = __esm({
1326
1370
  }
1327
1371
  };
1328
1372
  AssetKey = class _AssetKey extends BasicAssetKey {
1373
+ /**
1374
+ * Creates a key from separate path components. Use {@link parse} for a combined reference.
1375
+ *
1376
+ * @param assetType - Asset directory, such as `models`, `textures`, or `blockstates`.
1377
+ * @param type - Subdirectory, such as `block` or `item`.
1378
+ * @param rootType - Top-level directory, usually `assets` or `data`.
1379
+ * @param root - Base URL override for hosted asset sources.
1380
+ */
1329
1381
  constructor(namespace, path, assetType, type, rootType = "assets", extension = ".json", root) {
1330
1382
  super(namespace, path);
1331
1383
  this.namespace = namespace;
@@ -1337,6 +1389,16 @@ var init_AssetKey = __esm({
1337
1389
  this.root = root;
1338
1390
  this.isAssetKey = true;
1339
1391
  }
1392
+ /**
1393
+ * Parses an asset reference such as `minecraft:block/stone`.
1394
+ * A missing namespace defaults to the origin's namespace or `minecraft`.
1395
+ *
1396
+ * @param assetType - Asset category, such as `models` or `textures`.
1397
+ * @param str - Path with an optional namespace and file extension.
1398
+ * @param origin - Supplies the asset root, default namespace, and non-texture extension.
1399
+ * @returns A key with the matching extension removed from its path. Textures use `.png`;
1400
+ * other assets use the origin's extension or `.json`.
1401
+ */
1340
1402
  static parse(assetType, str, origin) {
1341
1403
  let namespace = origin?.namespace || DEFAULT_NAMESPACE;
1342
1404
  if (str.includes(":")) {
@@ -1368,6 +1430,7 @@ var init_AssetKey = __esm({
1368
1430
  ];
1369
1431
  return a.join("/");
1370
1432
  }
1433
+ /** Joins the type subdirectory and path without adding a namespace or file extension. */
1371
1434
  getFullPath() {
1372
1435
  let p10 = [];
1373
1436
  if (this.type) {
@@ -1379,6 +1442,7 @@ var init_AssetKey = __esm({
1379
1442
  toNamespacedString() {
1380
1443
  return this.namespace + ":" + this.getFullPath();
1381
1444
  }
1445
+ /** Returns a cache identifier that includes the explicit root or {@link AssetLoader.ROOT}. */
1382
1446
  serialize() {
1383
1447
  return this.toString();
1384
1448
  }
@@ -1390,17 +1454,17 @@ var init_AssetKey = __esm({
1390
1454
  var DisplayPosition, DISPLAY_POSITIONS;
1391
1455
  var init_DisplayPosition = __esm({
1392
1456
  "src/model/DisplayPosition.ts"() {
1393
- DisplayPosition = /* @__PURE__ */ ((DisplayPosition2) => {
1394
- DisplayPosition2["THIRDPERSON_RIGHTHAND"] = "thirdperson_righthand";
1395
- DisplayPosition2["THIRDPERSON_LEFTHAND"] = "thirdperson_lefthand";
1396
- DisplayPosition2["FIRSTPERSON_RIGHTHAND"] = "firstperson_righthand";
1397
- DisplayPosition2["FIRSTPERSON_LEFTHAND"] = "firstperson_lefthand";
1398
- DisplayPosition2["GUI"] = "gui";
1399
- DisplayPosition2["HEAD"] = "head";
1400
- DisplayPosition2["GROUND"] = "ground";
1401
- DisplayPosition2["FIXED"] = "fixed";
1402
- DisplayPosition2["ON_SHELF"] = "on_shelf";
1403
- return DisplayPosition2;
1457
+ DisplayPosition = /* @__PURE__ */ ((DisplayPosition3) => {
1458
+ DisplayPosition3["THIRDPERSON_RIGHTHAND"] = "thirdperson_righthand";
1459
+ DisplayPosition3["THIRDPERSON_LEFTHAND"] = "thirdperson_lefthand";
1460
+ DisplayPosition3["FIRSTPERSON_RIGHTHAND"] = "firstperson_righthand";
1461
+ DisplayPosition3["FIRSTPERSON_LEFTHAND"] = "firstperson_lefthand";
1462
+ DisplayPosition3["GUI"] = "gui";
1463
+ DisplayPosition3["HEAD"] = "head";
1464
+ DisplayPosition3["GROUND"] = "ground";
1465
+ DisplayPosition3["FIXED"] = "fixed";
1466
+ DisplayPosition3["ON_SHELF"] = "on_shelf";
1467
+ return DisplayPosition3;
1404
1468
  })(DisplayPosition || {});
1405
1469
  DISPLAY_POSITIONS = Object.values(DisplayPosition);
1406
1470
  }
@@ -1438,6 +1502,7 @@ var init_DisplayTransforms = __esm({
1438
1502
  const scale = new Vector32(...transform.scale ?? [1, 1, 1]).clampScalar(-4, 4);
1439
1503
  return new Matrix4().compose(translation, rotation, scale);
1440
1504
  }
1505
+ /** Transforms geometry in place and reverses triangle winding when the matrix reflects it. */
1441
1506
  static apply(geometry, matrix) {
1442
1507
  geometry.applyMatrix4(matrix);
1443
1508
  if (matrix.determinant() < 0) {
@@ -1465,7 +1530,10 @@ var init_ModelMerger = __esm({
1465
1530
  init_AssetKey();
1466
1531
  init_DisplayTransforms();
1467
1532
  ModelMerger = class {
1533
+ /** Loads the parent chain and returns a merged model without modifying the supplied definition. */
1468
1534
  static async mergeWithParents(model) {
1535
+ const parts = model.parts;
1536
+ if (parts) return { ...model, parts: await Promise.all(parts.map((part) => this.mergeWithParents(part))) };
1469
1537
  const models = await this.collectAllParents(model);
1470
1538
  let merged = {};
1471
1539
  for (const source of [...models, model]) {
@@ -1477,6 +1545,7 @@ var init_ModelMerger = __esm({
1477
1545
  }
1478
1546
  merged.hierarchy = models.map((m) => m.parent).filter((p10) => `${p10}`);
1479
1547
  merged.hierarchy.push(`${merged.parent}`);
1548
+ if (model.components) merged.components = model.components;
1480
1549
  return merged;
1481
1550
  }
1482
1551
  static async collectAllParents(model) {
@@ -1515,6 +1584,79 @@ var init_AssetParsers = __esm({
1515
1584
  }
1516
1585
  });
1517
1586
 
1587
+ // src/assets/BannerPatterns.ts
1588
+ var DYE_COLORS, BannerPatterns;
1589
+ var init_BannerPatterns = __esm({
1590
+ "src/assets/BannerPatterns.ts"() {
1591
+ init_AssetKey();
1592
+ init_AssetLoader();
1593
+ init_AssetParsers();
1594
+ init_Caching();
1595
+ DYE_COLORS = Object.freeze({
1596
+ white: 16383998,
1597
+ orange: 16351261,
1598
+ magenta: 13061821,
1599
+ light_blue: 3847130,
1600
+ yellow: 16701501,
1601
+ lime: 8439583,
1602
+ pink: 15961002,
1603
+ gray: 4673362,
1604
+ light_gray: 10329495,
1605
+ cyan: 1481884,
1606
+ purple: 8991416,
1607
+ blue: 3949738,
1608
+ brown: 8606770,
1609
+ green: 6192150,
1610
+ red: 11546150,
1611
+ black: 1908001
1612
+ });
1613
+ BannerPatterns = class {
1614
+ /** Lists namespaced pattern IDs from the selected namespace's data directory. */
1615
+ static async getList(namespace = "minecraft", root) {
1616
+ const collect = async (path) => {
1617
+ const prefix2 = path ? `${path}/` : "";
1618
+ const key = new AssetKey(namespace, `${prefix2}_list`, "banner_pattern", void 0, "data", ".json", root);
1619
+ const list2 = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get(key, "list" /* LIST */));
1620
+ if (!list2) return [];
1621
+ const files = list2.files.filter((file) => file.endsWith(".json") && file !== "_list.json").map((file) => `${namespace}:${prefix2}${file.slice(0, -5)}`);
1622
+ const children = await Promise.all(list2.directories.map((directory) => collect(`${prefix2}${directory}`)));
1623
+ return [...files, ...children.flat()];
1624
+ };
1625
+ return collect("");
1626
+ }
1627
+ /** Returns the named dye's sRGB value, rejecting unknown colors. */
1628
+ static getColor(color2) {
1629
+ if (typeof color2 !== "string" || !Object.prototype.hasOwnProperty.call(DYE_COLORS, color2)) throw new Error(`Unsupported dye color ${color2}`);
1630
+ return DYE_COLORS[color2];
1631
+ }
1632
+ /** Resolves the first 16 layers of a `banner_patterns` component, preserving their order. */
1633
+ static async getLayers(value, target, root) {
1634
+ if (value === void 0) return [];
1635
+ if (!Array.isArray(value)) throw new Error("banner_patterns must be an array");
1636
+ return Promise.all(value.slice(0, 16).map(async (layer) => {
1637
+ if (!layer || typeof layer !== "object" || Array.isArray(layer)) throw new Error("Each banner pattern requires a pattern and dye color");
1638
+ const color2 = this.getColor(layer.color);
1639
+ let pattern2 = layer.pattern;
1640
+ if (typeof pattern2 === "string") {
1641
+ const id = this.identifier(pattern2);
1642
+ const key = new AssetKey(id.namespace, id.getFullPath(), "banner_pattern", void 0, "data", ".json", root);
1643
+ pattern2 = await Caching.bannerPatternCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
1644
+ if (!pattern2) throw new Error(`Missing banner pattern ${layer.pattern}`);
1645
+ }
1646
+ if (!pattern2 || typeof pattern2 !== "object" || Array.isArray(pattern2) || typeof pattern2.translation_key !== "string") throw new Error("Banner patterns require asset_id and translation_key");
1647
+ const asset2 = this.identifier(pattern2.asset_id);
1648
+ return { color: color2, texture: new AssetKey(asset2.namespace, `${target}/${asset2.getFullPath()}`, "textures", "entity", "assets", ".png", root) };
1649
+ }));
1650
+ }
1651
+ static identifier(value) {
1652
+ if (typeof value !== "string" || !/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(value)) throw new Error(`Invalid banner-pattern identifier ${value}`);
1653
+ const [namespace, path] = value.includes(":") ? value.split(":") : ["minecraft", value];
1654
+ return new AssetKey(namespace, path);
1655
+ }
1656
+ };
1657
+ }
1658
+ });
1659
+
1518
1660
  // src/assets/Models.ts
1519
1661
  var Models;
1520
1662
  var init_Models = __esm({
@@ -1526,12 +1668,15 @@ var init_Models = __esm({
1526
1668
  init_PersistentCache();
1527
1669
  init_AssetKey();
1528
1670
  init_AssetParsers();
1671
+ init_DisplayPosition();
1672
+ init_BannerPatterns();
1529
1673
  Models = class _Models {
1530
1674
  // opened lazily: touching the store at import time would hit IndexedDB/disk just for loading
1531
1675
  // the library, and would require an EnvProvider before any entry has had a chance to register
1532
1676
  static get PERSISTENT_CACHE() {
1533
1677
  return this._persistentCache ??= PersistentCache.open("minerender-models");
1534
1678
  }
1679
+ /** Lists item filenames from the asset source's `_list.json`, with a legacy model-directory fallback. */
1535
1680
  static async getItemList() {
1536
1681
  const key = new AssetKey(DEFAULT_NAMESPACE, "_list", "items");
1537
1682
  const legacyKey = new AssetKey(DEFAULT_NAMESPACE, "_list", "models", "item");
@@ -1539,34 +1684,174 @@ var init_Models = __esm({
1539
1684
  return (await AssetLoader.getFirst([key, legacyKey], "list" /* LIST */))?.asset;
1540
1685
  }).then((r) => r?.files ?? []);
1541
1686
  }
1542
- static async loadAndMerge(key) {
1543
- const model = key.type === "item" ? await this.getItemModel(key) : await this.getRaw(key);
1687
+ static async loadAndMerge(key, context = {}) {
1688
+ const model = key.type === "item" ? await this.getItemModel(key, context) : await this.getRaw(key);
1544
1689
  if (!model) {
1545
1690
  return void 0;
1546
1691
  }
1547
1692
  return ModelMerger.mergeWithParents(model);
1548
1693
  }
1549
- static async getItemModel(key) {
1694
+ static async getItemModel(key, context = {}) {
1695
+ const preview = this.snapshotContext(key, context);
1550
1696
  const itemKey = new AssetKey(key.namespace, key.path, "items", void 0, key.rootType, ".json", key.root);
1551
- const model = await this.PERSISTENT_CACHE.getOrLoad(`item-v2:${AssetLoader.persistentKey(itemKey.serialize())}`, async () => {
1697
+ const cacheKey = itemKey.serialize() + this.contextKey(preview);
1698
+ const model = await this.PERSISTENT_CACHE.getOrLoad(`item-v4:${AssetLoader.persistentKey(cacheKey)}`, async () => {
1552
1699
  const result = await AssetLoader.getFirst([itemKey, key], "json" /* JSON */);
1553
1700
  if (!result) return void 0;
1554
- if (result.key.assetType !== "items") return { ...result.asset, key };
1555
- const selected = this.defaultItemModel(result.asset.model, key);
1556
- const modelKey = AssetKey.parse("models", selected.model);
1557
- modelKey.root = key.root;
1558
- const model2 = await this.getRaw(modelKey);
1559
- if (!model2) throw new Error(`Item ${key.toNamespacedString()} references missing model ${selected.model}`);
1560
- return { ...model2, ...selected.special && { special: selected.special }, ...selected.tints && { tints: selected.tints } };
1701
+ if (result.key.assetType !== "items") return { ...result.asset, key, components: preview.components };
1702
+ const load = async (selected) => {
1703
+ if ("parts" in selected) {
1704
+ return { key, parts: await Promise.all(selected.parts.map(load)) };
1705
+ }
1706
+ if ("item" in selected) {
1707
+ const model3 = await this.getItemModel(selected.item, { displayContext: preview.displayContext });
1708
+ if (!model3) throw new Error(`Item ${key.toNamespacedString()} references missing item ${selected.item.toNamespacedString()}`);
1709
+ return model3;
1710
+ }
1711
+ const modelKey = AssetKey.parse("models", selected.model);
1712
+ modelKey.root = key.root;
1713
+ const model2 = await this.getRaw(modelKey);
1714
+ if (!model2) throw new Error(`Item ${key.toNamespacedString()} references missing model ${selected.model}`);
1715
+ return { ...model2, components: preview.components, ...selected.special && { special: selected.special }, ...selected.tints && { tints: selected.tints } };
1716
+ };
1717
+ return load(this.selectItemModel(result.asset.model, key, preview));
1561
1718
  });
1562
- return model ? { ...model, key: Object.assign(new AssetKey("", ""), model.key) } : void 0;
1719
+ const restore = (model2) => ({
1720
+ ...model2,
1721
+ key: Object.assign(new AssetKey("", ""), model2.key),
1722
+ ...model2.parts && { parts: model2.parts.map(restore) }
1723
+ });
1724
+ return model ? restore(model) : void 0;
1725
+ }
1726
+ static contextIdentifier(id) {
1727
+ if (typeof id !== "string" || !/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(id)) {
1728
+ throw new Error(`Invalid item-preview identifier: ${id}`);
1729
+ }
1730
+ return id.includes(":") ? id : `minecraft:${id}`;
1731
+ }
1732
+ static snapshotContext(key, context) {
1733
+ const displayContext = context.displayContext ?? "gui" /* GUI */;
1734
+ if (displayContext !== "none" && !Object.values(DisplayPosition).includes(displayContext)) {
1735
+ throw new Error(`Unsupported item display context: ${displayContext}`);
1736
+ }
1737
+ for (const map of [context.properties, context.components, context.itemReferences]) {
1738
+ if (map !== void 0 && (!map || typeof map !== "object" || Array.isArray(map))) {
1739
+ throw new Error("Item-preview properties, components, and references must be objects keyed by identifier");
1740
+ }
1741
+ }
1742
+ const count = context.count === void 0 ? 1 : context.count;
1743
+ if (!Number.isSafeInteger(count) || count < 0) throw new Error("Item-preview count must be a nonnegative safe integer");
1744
+ const properties = {};
1745
+ for (const [id, value] of Object.entries(context.properties ?? {})) {
1746
+ const property = this.contextIdentifier(id);
1747
+ if (!["boolean", "string", "number"].includes(typeof value) || typeof value === "number" && !Number.isFinite(value)) {
1748
+ throw new Error(`Item-preview property ${property} must be a boolean, string, or finite number`);
1749
+ }
1750
+ if (Object.prototype.hasOwnProperty.call(properties, property)) throw new Error(`Duplicate item-preview property: ${property}`);
1751
+ properties[property] = value;
1752
+ }
1753
+ const components = {};
1754
+ for (const [id, value] of Object.entries(context.components ?? {})) {
1755
+ const component = this.contextIdentifier(id);
1756
+ if (Object.prototype.hasOwnProperty.call(components, component)) throw new Error(`Duplicate item-preview component: ${component}`);
1757
+ components[component] = this.snapshotJson(value);
1758
+ }
1759
+ const itemReferences = {};
1760
+ for (const [id, value] of Object.entries(context.itemReferences ?? {})) {
1761
+ const reference = this.contextIdentifier(id);
1762
+ if (!isAssetKey(value) || value.assetType !== "models" || value.type !== "item") {
1763
+ throw new Error(`Item-preview reference ${reference} must be an item-model AssetKey`);
1764
+ }
1765
+ this.contextIdentifier(`${value.namespace}:${value.path}`);
1766
+ if (Object.prototype.hasOwnProperty.call(itemReferences, reference)) throw new Error(`Duplicate item-preview reference: ${reference}`);
1767
+ itemReferences[reference] = Object.assign(new AssetKey("", ""), value, { root: value.root ?? key.root });
1768
+ }
1769
+ return { displayContext, properties, components, count, itemReferences };
1563
1770
  }
1564
- // Item previews use the GUI context, false conditions, and zero numeric properties.
1565
- static defaultItemModel(node, key) {
1771
+ static contextKey(context) {
1772
+ const itemReferences = Object.fromEntries(Object.entries(context.itemReferences).map(([id, key]) => [id, key.serialize()]));
1773
+ return `|item-v4:${JSON.stringify(this.snapshotJson({ ...context, itemReferences }))}`;
1774
+ }
1775
+ static snapshotJson(value, ancestors = /* @__PURE__ */ new Set()) {
1776
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) return value;
1777
+ if (typeof value !== "object" || ancestors.has(value) || !Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
1778
+ throw new Error("Item-preview components must contain finite JSON values without circular references");
1779
+ }
1780
+ ancestors.add(value);
1781
+ const copy = Array.isArray(value) ? Array.from(value, (entry) => this.snapshotJson(entry, ancestors)) : Object.fromEntries(Object.keys(value).sort().map((key) => [key, this.snapshotJson(value[key], ancestors)]));
1782
+ ancestors.delete(value);
1783
+ return copy;
1784
+ }
1785
+ static propertyValue(node, context) {
1786
+ const kind = node.type.replace(/^minecraft:/, "");
1787
+ const property = node.property ? this.contextIdentifier(node.property) : void 0;
1788
+ if (kind === "select" && property === "minecraft:display_context") return context.displayContext;
1789
+ if (property && Object.prototype.hasOwnProperty.call(context.properties, property)) return context.properties[property];
1790
+ const fallback = kind === "condition" ? false : kind === "range_dispatch" ? 0 : void 0;
1791
+ const component = (id) => context.components[this.contextIdentifier(id)];
1792
+ if (property === "minecraft:custom_model_data") {
1793
+ const index = node.index ?? 0;
1794
+ if (!Number.isSafeInteger(index) || index < 0) throw new Error("Item-model custom_model_data index must be a nonnegative integer");
1795
+ const data = component("custom_model_data");
1796
+ if (data === void 0) return fallback;
1797
+ if (!data || typeof data !== "object" || Array.isArray(data)) throw new Error("Item-preview custom_model_data must be an object");
1798
+ const field = kind === "condition" ? "flags" : kind === "select" ? "strings" : "floats";
1799
+ const values = data[field];
1800
+ if (values === void 0) return fallback;
1801
+ if (!Array.isArray(values)) throw new Error(`Item-preview custom_model_data.${field} must be an array`);
1802
+ return index < values.length ? values[index] : fallback;
1803
+ }
1804
+ if (kind === "condition" && property === "minecraft:has_component") {
1805
+ if (node.ignore_default) throw new Error("Item-preview has_component with ignore_default requires an explicit properties override; item defaults are unavailable");
1806
+ return Object.prototype.hasOwnProperty.call(context.components, this.contextIdentifier(node.component));
1807
+ }
1808
+ if (kind === "select") {
1809
+ switch (property) {
1810
+ case "minecraft:component":
1811
+ return component(node.component);
1812
+ case "minecraft:block_state": {
1813
+ const state = component("block_state");
1814
+ if (state === void 0) return void 0;
1815
+ if (!state || typeof state !== "object" || Array.isArray(state)) throw new Error("Item-preview block_state must be an object");
1816
+ if (typeof node.block_state_property !== "string") throw new Error("Item-model block_state requires block_state_property");
1817
+ return Object.prototype.hasOwnProperty.call(state, node.block_state_property) ? state[node.block_state_property] : void 0;
1818
+ }
1819
+ case "minecraft:charge_type": {
1820
+ const projectiles = component("charged_projectiles");
1821
+ if (projectiles === void 0) return "none";
1822
+ if (!Array.isArray(projectiles)) throw new Error("Item-preview charged_projectiles must be an array of item stacks");
1823
+ const ids = projectiles.map((stack) => {
1824
+ if (!stack || typeof stack !== "object" || Array.isArray(stack)) throw new Error("Item-preview charged_projectiles must contain item-stack objects");
1825
+ return this.contextIdentifier(stack.id);
1826
+ });
1827
+ return !ids.length ? "none" : ids.includes("minecraft:firework_rocket") ? "rocket" : "arrow";
1828
+ }
1829
+ }
1830
+ }
1831
+ if (kind === "range_dispatch" && (property === "minecraft:damage" || property === "minecraft:count")) {
1832
+ const damage = property === "minecraft:damage";
1833
+ const maximumComponent = component(damage ? "max_damage" : "max_stack_size");
1834
+ const maximum = maximumComponent === void 0 ? damage ? 0 : 1 : maximumComponent;
1835
+ const damageComponent = component("damage");
1836
+ const value = damage ? damageComponent === void 0 ? 0 : damageComponent : context.count;
1837
+ if (typeof value !== "number" || typeof maximum !== "number" || maximum < 0) throw new Error(`Item-preview ${property} requires numeric components and a nonnegative maximum`);
1838
+ const clamped = Math.max(0, Math.min(maximum, value));
1839
+ return node.normalize === false ? clamped : clamped / maximum;
1840
+ }
1841
+ return fallback;
1842
+ }
1843
+ static selectItemModel(node, key, context) {
1566
1844
  if (!node || typeof node.type !== "string") {
1567
1845
  throw new Error(`Unsupported item model definition for ${key.toNamespacedString()}`);
1568
1846
  }
1569
1847
  switch (node.type.replace(/^minecraft:/, "")) {
1848
+ case "composite":
1849
+ if (Array.isArray(node.models)) return { parts: node.models.map((child) => this.selectItemModel(child, key, context)) };
1850
+ break;
1851
+ case "bundle/selected_item": {
1852
+ const item = context.itemReferences["minecraft:bundle/selected_item"];
1853
+ return item ? { item } : { parts: [] };
1854
+ }
1570
1855
  case "model":
1571
1856
  if (typeof node.model === "string" && node.model) return { model: node.model, tints: node.tints };
1572
1857
  break;
@@ -1574,6 +1859,23 @@ var init_Models = __esm({
1574
1859
  if (!node.base || !node.model || typeof node.model !== "object") break;
1575
1860
  const special = node.model;
1576
1861
  switch (special.type) {
1862
+ case "banner":
1863
+ case "minecraft:banner":
1864
+ if (typeof special.color === "string" && Object.prototype.hasOwnProperty.call(DYE_COLORS, special.color)) return { model: node.base, special };
1865
+ break;
1866
+ case "shield":
1867
+ case "minecraft:shield":
1868
+ case "trident":
1869
+ case "minecraft:trident":
1870
+ case "conduit":
1871
+ case "minecraft:conduit":
1872
+ return { model: node.base, special };
1873
+ case "shulker_box":
1874
+ case "minecraft:shulker_box":
1875
+ if (typeof special.texture === "string" && special.texture && (special.openness === void 0 || typeof special.openness === "number" && Number.isFinite(special.openness)) && (special.orientation === void 0 || ["down", "up", "north", "south", "west", "east"].includes(special.orientation))) {
1876
+ return { model: node.base, special };
1877
+ }
1878
+ break;
1577
1879
  case "chest":
1578
1880
  case "minecraft:chest":
1579
1881
  case "bed":
@@ -1587,21 +1889,34 @@ var init_Models = __esm({
1587
1889
  }
1588
1890
  throw new Error(`Unsupported special item renderer ${special.type} for ${key.toNamespacedString()}`);
1589
1891
  }
1590
- case "condition":
1591
- return this.defaultItemModel(node.on_false, key);
1892
+ case "condition": {
1893
+ const value = this.propertyValue(node, context);
1894
+ if (typeof value !== "boolean") throw new Error(`Item-preview condition ${node.property} requires a boolean`);
1895
+ return this.selectItemModel(value ? node.on_true : node.on_false, key, context);
1896
+ }
1592
1897
  case "select": {
1593
- const selected = node.property?.replace(/^minecraft:/, "") === "display_context" ? node.cases?.find((entry) => Array.isArray(entry.when) ? entry.when.includes("gui") : entry.when === "gui")?.model : void 0;
1594
- return this.defaultItemModel(selected ?? node.fallback, key);
1898
+ const value = this.propertyValue(node, context);
1899
+ const component = node.property && this.contextIdentifier(node.property) === "minecraft:component";
1900
+ if (!component && value !== void 0 && typeof value !== "string") throw new Error(`Item-preview selector ${node.property} requires a string`);
1901
+ const matches = (candidate) => component ? JSON.stringify(this.snapshotJson(candidate)) === JSON.stringify(this.snapshotJson(value)) : candidate === value;
1902
+ const selected = value === void 0 ? void 0 : node.cases?.find((entry) => matches(entry.when) || Array.isArray(entry.when) && entry.when.some(matches))?.model;
1903
+ return this.selectItemModel(selected ?? node.fallback, key, context);
1595
1904
  }
1596
1905
  case "range_dispatch": {
1906
+ const value = this.propertyValue(node, context);
1907
+ if (typeof value !== "number") throw new Error(`Item-preview range ${node.property} requires a number`);
1908
+ if (Number.isNaN(value)) return this.selectItemModel(node.fallback, key, context);
1909
+ const scaled = value * (node.scale ?? 1);
1910
+ if (!Number.isFinite(scaled)) throw new Error(`Item-preview range ${node.property} requires a finite value and scale`);
1597
1911
  const selected = node.entries?.reduce((match, entry) => {
1598
- return entry.threshold <= 0 && (!match || entry.threshold >= match.threshold) ? entry : match;
1912
+ return entry.threshold <= scaled && (!match || entry.threshold >= match.threshold) ? entry : match;
1599
1913
  }, void 0);
1600
- return this.defaultItemModel(selected?.model ?? node.fallback, key);
1914
+ return this.selectItemModel(selected?.model ?? node.fallback, key, context);
1601
1915
  }
1602
1916
  }
1603
1917
  throw new Error(`Unsupported item model ${node?.type ?? "definition"} for ${key.toNamespacedString()}`);
1604
1918
  }
1919
+ /** Loads one model file without resolving its parents. Returns `undefined` when the file is missing. */
1605
1920
  static async getRaw(key) {
1606
1921
  if (!key.assetType) {
1607
1922
  key.assetType = "models";
@@ -1614,28 +1929,39 @@ var init_Models = __esm({
1614
1929
  return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
1615
1930
  return AssetLoader.get(key, "model" /* MODEL */);
1616
1931
  });
1617
- }).then((asset) => {
1618
- if (asset) {
1619
- asset.key = key;
1932
+ }).then((asset2) => {
1933
+ if (asset2) {
1934
+ asset2.key = key;
1620
1935
  }
1621
- return asset;
1936
+ return asset2;
1622
1937
  });
1623
1938
  }
1624
- static async getMerged(key) {
1939
+ /**
1940
+ * Loads a model and resolves its parent chain. Returns `undefined` when the model is missing.
1941
+ * Item keys default to GUI context and a stack count of 1. Unresolved conditions are false and numeric properties are zero.
1942
+ * Pass `context` to supply component values, property overrides, and item references for a preview.
1943
+ * Composite items retain independently merged children in `ItemModel.parts`.
1944
+ *
1945
+ * @param key - Model key, for example `AssetKey.parse("models", "minecraft:item/diamond_sword")`.
1946
+ */
1947
+ static async getMerged(key, context = {}) {
1625
1948
  if (!key.assetType) {
1626
1949
  key.assetType = "models";
1627
1950
  }
1628
1951
  if (!key.extension) {
1629
1952
  key.extension = ".json";
1630
1953
  }
1631
- const keyStr = key.serialize();
1954
+ const preview = this.snapshotContext(key, context);
1955
+ const keyStr = key.serialize() + (key.type === "item" ? this.contextKey(preview) : "");
1632
1956
  return Caching.mergedModelCache.get(keyStr, (k) => {
1633
- return _Models.loadAndMerge(key);
1957
+ return _Models.loadAndMerge(key, preview);
1634
1958
  });
1635
1959
  }
1636
- static async get(key) {
1637
- return this.getMerged(key);
1960
+ /** Alias for {@link getMerged}. */
1961
+ static async get(key, context = {}) {
1962
+ return this.getMerged(key, context);
1638
1963
  }
1964
+ /** Clears persisted models. Use {@link Caching.clear} to also discard in-memory assets. */
1639
1965
  static async clearCache() {
1640
1966
  return this.PERSISTENT_CACHE.clear();
1641
1967
  }
@@ -1658,6 +1984,7 @@ var init_BlockStates = __esm({
1658
1984
  return this._persistentCache ??= PersistentCache.open("minerender-blockstates");
1659
1985
  }
1660
1986
  // BlockState names are hardcoded
1987
+ /** Returns blockstate filenames from the selected version's `_list.json`, or an empty list. */
1661
1988
  static async getList() {
1662
1989
  const key = new AssetKey(
1663
1990
  DEFAULT_NAMESPACE,
@@ -1678,6 +2005,7 @@ var init_BlockStates = __esm({
1678
2005
  return AssetLoader.get(key, "json" /* JSON */);
1679
2006
  });
1680
2007
  }
2008
+ /** Looks up vanilla property definitions for the key's block path, or returns `undefined`. */
1681
2009
  static async getDefaultState(key) {
1682
2010
  const defaultStates = await this.getDefaultStates();
1683
2011
  if (!defaultStates) {
@@ -1685,6 +2013,7 @@ var init_BlockStates = __esm({
1685
2013
  }
1686
2014
  return defaultStates["minecraft:" + key.path];
1687
2015
  }
2016
+ /** Loads a cached blockstate definition for {@link MineRenderScene.addBlock}, or returns `undefined`. */
1688
2017
  static async get(key) {
1689
2018
  if (!key.assetType) {
1690
2019
  key.assetType = "blockstates";
@@ -1697,11 +2026,11 @@ var init_BlockStates = __esm({
1697
2026
  return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
1698
2027
  return AssetLoader.get(key, "blockstate" /* BLOCKSTATE */);
1699
2028
  });
1700
- }).then((asset) => {
1701
- if (asset) {
1702
- asset.key = key;
2029
+ }).then((asset2) => {
2030
+ if (asset2) {
2031
+ asset2.key = key;
1703
2032
  }
1704
- return asset;
2033
+ return asset2;
1705
2034
  });
1706
2035
  }
1707
2036
  static getAll(keys) {
@@ -1711,6 +2040,7 @@ var init_BlockStates = __esm({
1711
2040
  }
1712
2041
  return Promise.all(promises);
1713
2042
  }
2043
+ /** Clears persisted blockstates. Use {@link Caching.clear} to also discard in-memory assets. */
1714
2044
  static async clearCache() {
1715
2045
  await this.PERSISTENT_CACHE.clear();
1716
2046
  }
@@ -1736,9 +2066,11 @@ var init_Assets = __esm({
1736
2066
  static parseAssetKey(assetType, str, origin) {
1737
2067
  return AssetKey.parse(assetType, str, origin);
1738
2068
  }
2069
+ /** Loads a model with its parent chain resolved. See {@link Models.getMerged}. */
1739
2070
  static async getModel(key) {
1740
2071
  return Models.getMerged(key);
1741
2072
  }
2073
+ /** Loads a blockstate definition. See {@link BlockStates.get}. */
1742
2074
  static async getBlockState(key) {
1743
2075
  return BlockStates.get(key);
1744
2076
  }
@@ -1753,6 +2085,10 @@ var init_AssetSource = __esm({
1753
2085
  AssetSource = class {
1754
2086
  constructor() {
1755
2087
  }
2088
+ /** Whether this source prevents lower-priority sources from supplying the key. Its own assets remain available. */
2089
+ blocks(key) {
2090
+ return false;
2091
+ }
1756
2092
  /**
1757
2093
  * Identifies this source's content across sessions for persistent caching. Sources that
1758
2094
  * cannot tell whether their content changed (an unnamed archive, for example) return
@@ -1781,6 +2117,10 @@ var BatchedExecutor;
1781
2117
  var init_BatchedExecutor = __esm({
1782
2118
  "src/util/BatchedExecutor.ts"() {
1783
2119
  BatchedExecutor = class {
2120
+ /**
2121
+ * @param interval - Delay between queue runs, in milliseconds.
2122
+ * @param batch - Maximum tasks started per run and maximum tasks active at once.
2123
+ */
1784
2124
  constructor(interval = 1, batch = 30) {
1785
2125
  if (!Number.isFinite(interval) || interval < 0) throw new RangeError("Batch interval must be finite and nonnegative");
1786
2126
  if (!Number.isInteger(batch) || batch < 1) throw new RangeError("Batch size must be a positive integer");
@@ -1788,9 +2128,11 @@ var init_BatchedExecutor = __esm({
1788
2128
  this.batch = batch;
1789
2129
  this.queue = new JobQueue2(async (task) => task(), { interval, maxPerRun: batch, maxActive: batch });
1790
2130
  }
2131
+ /** Queues a task and resolves with its result, or rejects with its error. */
1791
2132
  submit(task) {
1792
2133
  return this.queue.add(() => task());
1793
2134
  }
2135
+ /** Rejects queued and future tasks. Tasks already running are allowed to finish. */
1794
2136
  stop() {
1795
2137
  this.queue.end();
1796
2138
  }
@@ -1838,29 +2180,29 @@ function applyGenericRotation(axis, rotation, obj) {
1838
2180
  break;
1839
2181
  }
1840
2182
  }
1841
- function addBox3WireframeToObject(box, obj, color = 16777215, w = 2) {
2183
+ function addBox3WireframeToObject(box, obj, color2 = 16777215, w = 2) {
1842
2184
  let size = new Vector33();
1843
2185
  box.getSize(size);
1844
2186
  let boxGeo = new BoxGeometry2(size.x, size.y, size.z, 1, 1, 1);
1845
2187
  let wireGeo = new EdgesGeometry(boxGeo);
1846
- let wireMat = new LineBasicMaterial({ color, linewidth: w });
2188
+ let wireMat = new LineBasicMaterial({ color: color2, linewidth: w });
1847
2189
  let wireframe = new LineSegments(wireGeo, wireMat);
1848
2190
  obj.add(wireframe);
1849
2191
  let axes = new AxesHelper(1);
1850
2192
  obj.add(axes);
1851
2193
  }
1852
- function addWireframeToObject(obj, color = 16777215, w = 2) {
2194
+ function addWireframeToObject(obj, color2 = 16777215, w = 2) {
1853
2195
  let boxGeo = new BoxGeometry2(16, 16, 16, 1, 1, 1);
1854
2196
  let wireGeo = new EdgesGeometry(boxGeo);
1855
- let wireMat = new LineBasicMaterial({ color, linewidth: w });
2197
+ let wireMat = new LineBasicMaterial({ color: color2, linewidth: w });
1856
2198
  let wireframe = new LineSegments(wireGeo, wireMat);
1857
2199
  obj.add(wireframe);
1858
2200
  let axes = new AxesHelper(1);
1859
2201
  obj.add(axes);
1860
2202
  }
1861
- function addWireframeToMesh(geo, mesh, color = 16777215, w = 2) {
2203
+ function addWireframeToMesh(geo, mesh, color2 = 16777215, w = 2) {
1862
2204
  let wireGeo = new EdgesGeometry(geo);
1863
- let wireMat = new LineBasicMaterial({ color, linewidth: w });
2205
+ let wireMat = new LineBasicMaterial({ color: color2, linewidth: w });
1864
2206
  let wireframe = new LineSegments(wireGeo, wireMat);
1865
2207
  mesh.add(wireframe);
1866
2208
  let axes = new AxesHelper(1);
@@ -1932,10 +2274,11 @@ var NBTHelper;
1932
2274
  var init_NBTHelper = __esm({
1933
2275
  "src/nbt/NBTHelper.ts"() {
1934
2276
  NBTHelper = class {
1935
- static async fromBuffer(data, format) {
2277
+ /** Parses NBT bytes and preserves format and compression metadata. Omit `format` for automatic detection. */
2278
+ static async fromBuffer(data, format2) {
1936
2279
  const buffer = Buffer3.from(data instanceof Uint8Array ? data : new Uint8Array(data));
1937
2280
  const prismarineNbt = await import("prismarine-nbt");
1938
- const { parsed, type, metadata } = await prismarineNbt.parse(buffer, format);
2281
+ const { parsed, type, metadata } = await prismarineNbt.parse(buffer, format2);
1939
2282
  return Object.assign(parsed, {
1940
2283
  format: type,
1941
2284
  metadata,
@@ -2076,6 +2419,7 @@ var init_HostedAssetSource = __esm({
2076
2419
  ["json" /* JSON */, _HostedAssetSource.JSON]
2077
2420
  ]);
2078
2421
  }
2422
+ /** Retries missing assets with the `minecraft` namespace and default root. Other failures reject. */
2079
2423
  async loadOrRetryWithDefaults(key, parser) {
2080
2424
  const direct = await this.load(key, parser);
2081
2425
  if (typeof direct !== "undefined") {
@@ -2133,6 +2477,10 @@ var init_HostedAssetSource = __esm({
2133
2477
  assetBasePath(key) {
2134
2478
  return `${key.root ?? this.root ?? AssetLoader.ROOT}/${key.rootType !== void 0 ? key.rootType + "/" : ""}${key.namespace !== void 0 ? key.namespace + "/" : ""}${key.assetType !== void 0 ? key.assetType + "/" : ""}`;
2135
2479
  }
2480
+ /**
2481
+ * Creates a hosted source for {@link AssetLoader.addSource}.
2482
+ * Set `options.retryDefaults` to `false` to restrict lookups to the requested namespace and root.
2483
+ */
2136
2484
  constructor(root, options) {
2137
2485
  super();
2138
2486
  this._root = root;
@@ -2164,6 +2512,11 @@ var BrowserArchiveProxy;
2164
2512
  var init_BrowserArchiveProxy = __esm({
2165
2513
  "src/assets/source/archive/BrowserArchiveProxy.ts"() {
2166
2514
  BrowserArchiveProxy = class {
2515
+ /**
2516
+ * Opens a ZIP for asset lookup.
2517
+ * @param id - Cache identity for this content. Change it when the pack changes.
2518
+ * A File supplies an identity from its name, size, and modification time when omitted.
2519
+ */
2167
2520
  constructor(blob, id) {
2168
2521
  this._blob = blob;
2169
2522
  this._reader = new ZipReader(new BlobReader(this._blob));
@@ -2184,6 +2537,164 @@ var init_BrowserArchiveProxy = __esm({
2184
2537
  }
2185
2538
  });
2186
2539
 
2540
+ // src/assets/source/archive/PackMetadata.ts
2541
+ function object(value, field) {
2542
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
2543
+ throw new Error(`Invalid ${field}: expected an object`);
2544
+ }
2545
+ return value;
2546
+ }
2547
+ function integer(value, field) {
2548
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
2549
+ throw new Error(`Invalid ${field}: expected a nonnegative integer`);
2550
+ }
2551
+ return value;
2552
+ }
2553
+ function format(value, field, upper = false) {
2554
+ const parts = Array.isArray(value) ? value : [value];
2555
+ if (parts.length < 1 || parts.length > 2) {
2556
+ throw new Error(`Invalid ${field}: expected a major version or [major, minor]`);
2557
+ }
2558
+ return [integer(parts[0], field), parts.length === 2 ? integer(parts[1], field) : upper ? Infinity : 0];
2559
+ }
2560
+ function compare(a, b) {
2561
+ return a[0] === b[0] ? a[1] - b[1] : a[0] - b[0];
2562
+ }
2563
+ function overlay(value, field) {
2564
+ const entry = object(value, field);
2565
+ const directory = entry.directory;
2566
+ if (typeof directory !== "string" || !directory || /[^-_a-zA-Z0-9.]/.test(directory) || directory === "." || directory === "..") {
2567
+ throw new Error(`Invalid ${field}.directory: expected a directory name without path separators`);
2568
+ }
2569
+ let min, max;
2570
+ if ("min_format" in entry || "max_format" in entry) {
2571
+ min = format(entry.min_format, `${field}.min_format`);
2572
+ max = format(entry.max_format, `${field}.max_format`, true);
2573
+ } else {
2574
+ const range = entry.formats;
2575
+ let lower, upper;
2576
+ if (typeof range === "number") {
2577
+ lower = upper = range;
2578
+ } else if (Array.isArray(range)) {
2579
+ if (range.length !== 2) throw new Error(`Invalid ${field}.formats: expected [min, max]`);
2580
+ [lower, upper] = range;
2581
+ } else {
2582
+ const bounds = object(range, `${field}.formats`);
2583
+ lower = bounds.min_inclusive;
2584
+ upper = bounds.max_inclusive;
2585
+ }
2586
+ min = [integer(lower, `${field}.formats.min_inclusive`), 0];
2587
+ max = [integer(upper, `${field}.formats.max_inclusive`), Infinity];
2588
+ }
2589
+ if (compare(min, max) > 0) throw new Error(`Invalid ${field}: minimum format exceeds maximum format`);
2590
+ return { directory, min, max };
2591
+ }
2592
+ function pattern(value, field) {
2593
+ if (value === void 0) return void 0;
2594
+ if (typeof value !== "string") throw new Error(`Invalid ${field}: expected a regular expression string`);
2595
+ try {
2596
+ return new RegExp(value);
2597
+ } catch (cause) {
2598
+ throw new Error(`Invalid ${field}: ${String(cause)}`);
2599
+ }
2600
+ }
2601
+ var PackMetadata;
2602
+ var init_PackMetadata = __esm({
2603
+ "src/assets/source/archive/PackMetadata.ts"() {
2604
+ PackMetadata = class _PackMetadata {
2605
+ constructor(overlays, filters) {
2606
+ this.overlays = overlays;
2607
+ this.filters = filters;
2608
+ this.hasOverlays = overlays.length > 0;
2609
+ }
2610
+ static parse(value) {
2611
+ const metadata = object(value, "pack.mcmeta");
2612
+ let overlays = [];
2613
+ if ("overlays" in metadata) {
2614
+ const entries = object(metadata.overlays, "overlays").entries;
2615
+ if (!Array.isArray(entries)) throw new Error("Invalid overlays.entries: expected an array");
2616
+ overlays = entries.map((entry, index) => overlay(entry, `overlays.entries[${index}]`));
2617
+ }
2618
+ let filters = [];
2619
+ if ("filter" in metadata) {
2620
+ const entries = object(metadata.filter, "filter").block;
2621
+ if (!Array.isArray(entries)) throw new Error("Invalid filter.block: expected an array");
2622
+ filters = entries.map((value2, index) => {
2623
+ const field = `filter.block[${index}]`;
2624
+ const entry = object(value2, field);
2625
+ return { namespace: pattern(entry.namespace, `${field}.namespace`), path: pattern(entry.path, `${field}.path`) };
2626
+ });
2627
+ }
2628
+ return new _PackMetadata(overlays, filters);
2629
+ }
2630
+ /** Returns applicable directories in lookup order, with the last declared overlay first. */
2631
+ overlayDirectories(target) {
2632
+ const selected = format(target, "target format");
2633
+ return this.overlays.filter((entry) => compare(selected, entry.min) >= 0 && compare(selected, entry.max) <= 0).map((entry) => entry.directory).reverse();
2634
+ }
2635
+ blocks(namespace, path) {
2636
+ return this.filters.some((entry) => entry.namespace?.test(namespace) ?? true) && this.filters.some((entry) => entry.path?.test(path) ?? true);
2637
+ }
2638
+ };
2639
+ }
2640
+ });
2641
+
2642
+ // src/assets/source/archive/PackFormats.ts
2643
+ var PackFormats;
2644
+ var init_PackFormats = __esm({
2645
+ "src/assets/source/archive/PackFormats.ts"() {
2646
+ init_Requests();
2647
+ PackFormats = class {
2648
+ static {
2649
+ this.versions = /* @__PURE__ */ new Map();
2650
+ }
2651
+ /** Resolves pack formats from Mojang's client metadata hosted on assets.mcasset.cloud. */
2652
+ static async get(version2, type) {
2653
+ let pending = this.versions.get(version2);
2654
+ if (!pending || pending.expires <= Date.now()) {
2655
+ pending = {
2656
+ formats: this.load(version2),
2657
+ expires: ["latest", "release", "snapshot"].includes(version2) ? Date.now() + 3e5 : Infinity
2658
+ };
2659
+ this.versions.set(version2, pending);
2660
+ }
2661
+ try {
2662
+ return (await pending.formats)[type];
2663
+ } catch (cause) {
2664
+ if (this.versions.get(version2) === pending) this.versions.delete(version2);
2665
+ const option = type === "assets" ? "resourcePackFormat" : "dataPackFormat";
2666
+ const reason = cause instanceof Error ? cause.message : String(cause);
2667
+ throw new Error(`Could not resolve the pack format for Minecraft ${version2}. Set ArchiveAssetSource ${option} explicitly. ${reason}`);
2668
+ }
2669
+ }
2670
+ static async load(version2) {
2671
+ const response = await Requests.genericRequest({
2672
+ url: `https://assets.mcasset.cloud/${encodeURIComponent(version2)}/game-version.json`,
2673
+ responseType: "json"
2674
+ });
2675
+ const data = response.data;
2676
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
2677
+ throw new Error("Expected version metadata to be an object");
2678
+ }
2679
+ const packs = data.pack_version;
2680
+ if (typeof packs !== "number" && (!packs || typeof packs !== "object" || Array.isArray(packs))) {
2681
+ throw new Error("Expected pack_version to be a number or an object");
2682
+ }
2683
+ const format2 = (type) => {
2684
+ const modern = typeof packs === "object" && ["resource_major", "resource_minor", "data_major", "data_minor"].some((key) => key in packs);
2685
+ const major = typeof packs === "number" ? packs : packs[modern ? `${type}_major` : type];
2686
+ const minor = modern ? packs[`${type}_minor`] : 0;
2687
+ if (typeof major !== "number" || !Number.isSafeInteger(major) || major < 0 || typeof minor !== "number" || !Number.isSafeInteger(minor) || minor < 0) {
2688
+ throw new Error(`Invalid ${type} pack format in version metadata`);
2689
+ }
2690
+ return Object.freeze([major, minor]);
2691
+ };
2692
+ return { assets: format2("resource"), data: format2("data") };
2693
+ }
2694
+ };
2695
+ }
2696
+ });
2697
+
2187
2698
  // src/assets/source/archive/ArchiveAssetSource.ts
2188
2699
  var ArchiveAssetSource;
2189
2700
  var init_ArchiveAssetSource = __esm({
@@ -2192,35 +2703,78 @@ var init_ArchiveAssetSource = __esm({
2192
2703
  init_request();
2193
2704
  init_HostedAssetSource();
2194
2705
  init_BrowserArchiveProxy();
2706
+ init_AssetLoader();
2707
+ init_PackMetadata();
2708
+ init_PackFormats();
2195
2709
  ArchiveAssetSource = class _ArchiveAssetSource extends AssetSource {
2196
- constructor(archiveProxy) {
2710
+ constructor(archiveProxy, options = {}) {
2197
2711
  super();
2198
- console.log(archiveProxy);
2199
2712
  this._archiveProxy = archiveProxy;
2713
+ const copyFormat = (format2) => typeof format2 === "object" ? [format2[0], format2[1]] : format2;
2714
+ this.options = {
2715
+ resourcePackFormat: copyFormat(options.resourcePackFormat),
2716
+ dataPackFormat: copyFormat(options.dataPackFormat)
2717
+ };
2200
2718
  }
2201
- static blob(blob) {
2202
- return new _ArchiveAssetSource(new BrowserArchiveProxy(blob));
2719
+ /** Creates a browser ZIP source from a Blob or File. */
2720
+ static blob(blob, options) {
2721
+ return new _ArchiveAssetSource(new BrowserArchiveProxy(blob), options);
2203
2722
  }
2204
2723
  get cacheId() {
2205
2724
  const id = this._archiveProxy.id;
2206
- return id === void 0 ? void 0 : `archive:${id}`;
2725
+ const formats = [this.options.resourcePackFormat ?? AssetLoader.version, this.options.dataPackFormat ?? AssetLoader.version];
2726
+ return id === void 0 ? void 0 : `archive-metadata:1:${id}:${JSON.stringify(formats)}`;
2207
2727
  }
2208
2728
  async getEntries() {
2209
- return this._archiveProxy.getEntries();
2729
+ return this.entries ??= this._archiveProxy.getEntries().catch((error) => {
2730
+ this.entries = void 0;
2731
+ throw error;
2732
+ });
2210
2733
  }
2211
2734
  async getEntry(path) {
2212
2735
  const entries = await this.getEntries();
2213
2736
  return entries.find((e) => e.filename === path);
2214
2737
  }
2738
+ async getMetadata(key) {
2739
+ try {
2740
+ return await (this.metadata ??= this.getEntry("pack.mcmeta").then(async (entry) => PackMetadata.parse(entry ? JSON.parse(await (await entry.getData()).text()) : {})).catch((error) => {
2741
+ this.metadata = void 0;
2742
+ throw error;
2743
+ }));
2744
+ } catch (cause) {
2745
+ throw new AssetLoadError(this, key, "pack.mcmeta", cause);
2746
+ }
2747
+ }
2748
+ async blocks(key) {
2749
+ if (key.rootType !== "assets" && key.rootType !== "data") return false;
2750
+ const metadata = await this.getMetadata(key);
2751
+ const path = `${key.assetType !== void 0 ? key.assetType + "/" : ""}${key.getFullPath()}${key.extension}`;
2752
+ return metadata.blocks(key.namespace, path);
2753
+ }
2215
2754
  async get(key, parser) {
2216
2755
  const responseParser = HostedAssetSource.PARSER_MAP.get(parser);
2217
2756
  return this.load(key, responseParser);
2218
2757
  }
2219
2758
  async load(key, parser) {
2220
2759
  const path = `${this.assetBasePath(key)}${key.type !== void 0 ? key.type + "/" : ""}${key.path}${key.extension}`;
2760
+ const version2 = AssetLoader.version;
2761
+ let selectedPath = path;
2221
2762
  let url;
2222
2763
  try {
2223
- const entry = await this.getEntry(path);
2764
+ let directories = [];
2765
+ if (key.rootType === "assets" || key.rootType === "data") {
2766
+ const metadata = await this.getMetadata(key);
2767
+ if (metadata.hasOverlays) {
2768
+ const format2 = (key.rootType === "assets" ? this.options.resourcePackFormat : this.options.dataPackFormat) ?? await PackFormats.get(version2, key.rootType);
2769
+ directories = metadata.overlayDirectories(format2);
2770
+ }
2771
+ }
2772
+ let entry;
2773
+ for (const directory of [...directories, ""]) {
2774
+ selectedPath = directory ? `${directory}/${path}` : path;
2775
+ entry = await this.getEntry(selectedPath);
2776
+ if (entry) break;
2777
+ }
2224
2778
  if (!entry) {
2225
2779
  return void 0;
2226
2780
  }
@@ -2233,7 +2787,8 @@ var init_ArchiveAssetSource = __esm({
2233
2787
  }
2234
2788
  return await parser.parse(response);
2235
2789
  } catch (cause) {
2236
- throw new AssetLoadError(this, key, path, cause);
2790
+ if (cause instanceof AssetLoadError) throw cause;
2791
+ throw new AssetLoadError(this, key, selectedPath, cause);
2237
2792
  } finally {
2238
2793
  if (url !== void 0) {
2239
2794
  URL.revokeObjectURL(url);
@@ -2266,6 +2821,8 @@ var init_archive = __esm({
2266
2821
  init_ArchiveEntry();
2267
2822
  init_ArchiveProxy();
2268
2823
  init_BrowserArchiveProxy();
2824
+ init_PackFormats();
2825
+ init_PackMetadata();
2269
2826
  }
2270
2827
  });
2271
2828
 
@@ -2292,6 +2849,7 @@ var init_AssetLoader = __esm({
2292
2849
  FALLBACK_ROOT = "https://raw.githubusercontent.com/InventivetalentDev/minerender-fallback-assets/master";
2293
2850
  AssetLoader = class {
2294
2851
  static {
2852
+ /** Default asset base URL. Use {@link setVersion} to select a Minecraft version. */
2295
2853
  this.ROOT = DEFAULT_ROOT;
2296
2854
  }
2297
2855
  static {
@@ -2304,8 +2862,9 @@ var init_AssetLoader = __esm({
2304
2862
  static get version() {
2305
2863
  return this.ROOT.substring(this.ROOT.lastIndexOf("/") + 1);
2306
2864
  }
2307
- static setVersion(version) {
2308
- this.ROOT = `https://assets.mcasset.cloud/${version}`;
2865
+ /** Selects an mcasset.cloud version, updates the vanilla source, and clears in-memory caches. */
2866
+ static setVersion(version2) {
2867
+ this.ROOT = `https://assets.mcasset.cloud/${version2}`;
2309
2868
  const source = new HostedAssetSource(this.ROOT, { retryDefaults: false });
2310
2869
  const index = this._SOURCES.findIndex((s) => s.key === "mcassets");
2311
2870
  if (index !== -1) {
@@ -2337,6 +2896,13 @@ var init_AssetLoader = __esm({
2337
2896
  return scope ? `${scope}
2338
2897
  ${key}` : key;
2339
2898
  }
2899
+ /**
2900
+ * Registers a source at the highest priority.
2901
+ * Call {@link Caching.clear} after changing sources to discard previously loaded assets.
2902
+ *
2903
+ * @param key - Name used to replace or remove this source.
2904
+ * @param override - Removes the first source with this name before adding the new one.
2905
+ */
2340
2906
  static addSource(key, source, override = true) {
2341
2907
  if (override) {
2342
2908
  const existing = this.removeSource(key);
@@ -2347,6 +2913,7 @@ ${key}` : key;
2347
2913
  this._SOURCES.unshift({ key, source });
2348
2914
  console.log(p4, "Added AssetSource", key);
2349
2915
  }
2916
+ /** Removes and returns the first source with this name. Call {@link Caching.clear} to reload assets. */
2350
2917
  static removeSource(key) {
2351
2918
  const index = this._SOURCES.findIndex((s) => s.key === key);
2352
2919
  if (index != -1) {
@@ -2361,14 +2928,16 @@ ${key}` : key;
2361
2928
  this.addSource("mcassets-fallback", new HostedAssetSource(FALLBACK_ROOT, { retryDefaults: false }));
2362
2929
  this.addSource("mcassets", new HostedAssetSource(this.ROOT, { retryDefaults: false }));
2363
2930
  }
2931
+ /** Loads from all sources and returns defined results in priority order. Any source failure rejects the call. */
2364
2932
  static async getAll(key, parser) {
2365
- let promises = [];
2366
- for (const source of this._SOURCES) {
2367
- promises.push(source.source.get(key, parser));
2933
+ const sources = [...this._SOURCES];
2934
+ const results = [];
2935
+ for (const { source } of sources) {
2936
+ const asset2 = await source.get(key, parser);
2937
+ if (asset2 != void 0) results.push(asset2);
2938
+ if (await source.blocks(key)) break;
2368
2939
  }
2369
- return Promise.all(promises).then((results) => {
2370
- return results.filter((r) => r != void 0);
2371
- });
2940
+ return results;
2372
2941
  }
2373
2942
  /** Returns the first defined result in source-priority order, without merging assets. */
2374
2943
  static async get(key, parser) {
@@ -2389,13 +2958,17 @@ ${key}` : key;
2389
2958
  /** Tries each path within a source before considering lower-priority sources. */
2390
2959
  static async getFirst(keys, parser) {
2391
2960
  const sources = [...this._SOURCES];
2961
+ let remaining = [...keys];
2392
2962
  for (const source of sources) {
2393
- for (const key of keys) {
2963
+ for (const key of remaining) {
2394
2964
  const result = await source.source.get(key, parser);
2395
2965
  if (result !== void 0) {
2396
2966
  return { key, asset: result };
2397
2967
  }
2398
2968
  }
2969
+ const blocked = await Promise.all(remaining.map((key) => source.source.blocks(key)));
2970
+ remaining = remaining.filter((_, index) => !blocked[index]);
2971
+ if (!remaining.length) break;
2399
2972
  }
2400
2973
  return void 0;
2401
2974
  }
@@ -2417,14 +2990,15 @@ var init_ModelTextures = __esm({
2417
2990
  static get PERSISTENT_META_CACHE() {
2418
2991
  return this._persistentMetaCache ??= PersistentCache.open("minerender-texturemeta");
2419
2992
  }
2993
+ /** Loads and decodes a texture into a readable canvas, or returns `undefined` when missing. */
2420
2994
  static async get(key) {
2421
2995
  const keyStr = key.serialize();
2422
2996
  const pending = this.preload(key);
2423
2997
  const cached2 = Caching.textureAssetCache.getIfPresent(keyStr);
2424
2998
  try {
2425
- const asset = await pending;
2426
- if (asset) {
2427
- return await ImageLoader.infoToCanvasData(asset);
2999
+ const asset2 = await pending;
3000
+ if (asset2) {
3001
+ return await ImageLoader.infoToCanvasData(asset2);
2428
3002
  }
2429
3003
  return void 0;
2430
3004
  } catch (err) {
@@ -2434,16 +3008,18 @@ var init_ModelTextures = __esm({
2434
3008
  throw err;
2435
3009
  }
2436
3010
  }
3011
+ /** Fetches and caches encoded texture bytes without decoding the pixels. */
2437
3012
  static async preload(key) {
2438
3013
  const keyStr = key.serialize();
2439
3014
  return Caching.textureAssetCache.get(keyStr, (k) => {
2440
- return AssetLoader.get(key, "image" /* IMAGE */).then((asset) => {
2441
- if (asset)
2442
- asset.key = key;
2443
- return asset;
3015
+ return AssetLoader.get(key, "image" /* IMAGE */).then((asset2) => {
3016
+ if (asset2)
3017
+ asset2.key = key;
3018
+ return asset2;
2444
3019
  });
2445
3020
  });
2446
3021
  }
3022
+ /** Loads a texture's `.mcmeta` file, or returns `undefined` when no metadata file exists. */
2447
3023
  static async getMeta(key) {
2448
3024
  if (!key.extension || !key.extension.endsWith(".mcmeta")) {
2449
3025
  key = Object.assign(new AssetKey("", ""), key);
@@ -2452,14 +3028,15 @@ var init_ModelTextures = __esm({
2452
3028
  const keyStr = key.serialize();
2453
3029
  return Caching.textureMetaCache.get(keyStr, (k) => {
2454
3030
  return this.PERSISTENT_META_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
2455
- return AssetLoader.get(key, "meta" /* META */).then((asset) => {
2456
- if (asset)
2457
- asset.key = key;
2458
- return asset;
3031
+ return AssetLoader.get(key, "meta" /* META */).then((asset2) => {
3032
+ if (asset2)
3033
+ asset2.key = key;
3034
+ return asset2;
2459
3035
  });
2460
3036
  });
2461
3037
  });
2462
3038
  }
3039
+ /** Clears persisted texture metadata. Use {@link Caching.clear} to discard in-memory textures and metadata. */
2463
3040
  static async clearCache() {
2464
3041
  await this.PERSISTENT_META_CACHE.clear();
2465
3042
  }
@@ -2547,6 +3124,7 @@ var init_Ticker = __esm({
2547
3124
  }
2548
3125
  return handle;
2549
3126
  }
3127
+ /** Starts or resumes the shared tick loop. Repeated calls have no effect. */
2550
3128
  static start() {
2551
3129
  if (this.interval) return;
2552
3130
  this.interval = this.schedule(() => {
@@ -2569,6 +3147,7 @@ var init_Ticker = __esm({
2569
3147
  _Ticker.fiveSecondTicks = 0;
2570
3148
  }, 1e3 * 5);
2571
3149
  }
3150
+ /** Pauses the loop while retaining callbacks for a later {@link start}. */
2572
3151
  static stop() {
2573
3152
  if (this.interval) clearInterval(this.interval);
2574
3153
  if (this.oneSecondTicksTracker) clearInterval(this.oneSecondTicksTracker);
@@ -2577,12 +3156,14 @@ var init_Ticker = __esm({
2577
3156
  this.oneSecondTicksTracker = void 0;
2578
3157
  this.fiveSecondTicksTracker = void 0;
2579
3158
  }
3159
+ /** Registers a callback, starts the loop if needed, and returns an ID for {@link remove}. */
2580
3160
  static add(tick) {
2581
3161
  this.start();
2582
3162
  const c = this.counter++;
2583
3163
  this._tickers.set(c, tick);
2584
3164
  return c;
2585
3165
  }
3166
+ /** Removes a callback by ID and stops the timers when no callbacks remain. */
2586
3167
  static remove(c) {
2587
3168
  if (typeof c === "number") this._tickers.delete(c);
2588
3169
  if (!this._tickers.size) this.stop();
@@ -2596,6 +3177,7 @@ var init_Ticker = __esm({
2596
3177
  static get tpsFiveSeconds() {
2597
3178
  return this.fiveSecondTps;
2598
3179
  }
3180
+ /** Stops the loop and removes all registered callbacks. */
2599
3181
  static dispose() {
2600
3182
  this.stop();
2601
3183
  this._tickers.clear();
@@ -2642,6 +3224,7 @@ var init_TextureAtlas = __esm({
2642
3224
  }
2643
3225
  };
2644
3226
  }
3227
+ /** Copies pixels for a texture variable from its current atlas rectangle. */
2645
3228
  getData(texture) {
2646
3229
  const pos = this.positions[texture];
2647
3230
  const size = this.sizes[texture];
@@ -2854,6 +3437,37 @@ var init_UVMapper = __esm({
2854
3437
  static facesToUv(faces, originalTextureSize, actualTextureSize) {
2855
3438
  return new Float32BufferAttribute2(this.facesToUvArray(faces, originalTextureSize, actualTextureSize), 2);
2856
3439
  }
3440
+ /** Bounds texture sampling to texel centres without changing cropped face UVs. */
3441
+ static setAtlasUvBounds(geometry, faces, atlas) {
3442
+ const uv = geometry.getAttribute("uv");
3443
+ const bounds = new Float32Array(uv.count * 4);
3444
+ for (const [faceIndex, faceName] of CUBE_FACES.entries()) {
3445
+ const texture = faces[faceName]?.texture?.substring(1);
3446
+ const position = texture && atlas.positions[texture];
3447
+ const size = texture && atlas.sizes[texture];
3448
+ let minU = Infinity, minV = Infinity, maxU = -Infinity, maxV = -Infinity;
3449
+ if (position && size) {
3450
+ minU = position[0] / atlas.image.width;
3451
+ maxU = (position[0] + size[0]) / atlas.image.width;
3452
+ minV = 1 - (position[1] + size[1]) / atlas.image.height;
3453
+ maxV = 1 - position[1] / atlas.image.height;
3454
+ } else {
3455
+ for (let corner = 0; corner < 4; corner++) {
3456
+ const index = faceIndex * 4 + corner;
3457
+ minU = Math.min(minU, uv.getX(index));
3458
+ minV = Math.min(minV, uv.getY(index));
3459
+ maxU = Math.max(maxU, uv.getX(index));
3460
+ maxV = Math.max(maxV, uv.getY(index));
3461
+ }
3462
+ }
3463
+ const insetU = Math.min(0.5 / atlas.image.width, (maxU - minU) / 2);
3464
+ const insetV = Math.min(0.5 / atlas.image.height, (maxV - minV) / 2);
3465
+ for (let corner = 0; corner < 4; corner++) {
3466
+ bounds.set([minU + insetU, minV + insetV, maxU - insetU, maxV - insetV], (faceIndex * 4 + corner) * 4);
3467
+ }
3468
+ }
3469
+ geometry.setAttribute("uvBounds", new Float32BufferAttribute2(bounds, 4));
3470
+ }
2857
3471
  static lockUvs(geometry, faces, atlas, rotation) {
2858
3472
  const uv = geometry.getAttribute("uv");
2859
3473
  for (let faceIndex = 0; faceIndex < CUBE_FACES.length; faceIndex++) {
@@ -2890,6 +3504,7 @@ var init_UVMapper = __esm({
2890
3504
  }
2891
3505
  uv.needsUpdate = true;
2892
3506
  }
3507
+ /** Returns the shared atlas for a keyed, merged model, creating it on first use. */
2893
3508
  static async getAtlas(model) {
2894
3509
  const keyStr = model.key.serialize();
2895
3510
  return Caching.modelTextureAtlasCache.get(keyStr, (k) => {
@@ -2960,6 +3575,11 @@ var init_UVMapper = __esm({
2960
3575
  }
2961
3576
  return [tl, tr, bl, br];
2962
3577
  }
3578
+ /**
3579
+ * Combines model textures and animation metadata into an atlas.
3580
+ * Mapped UVs are stored on copies of the model's elements, available through the returned atlas.
3581
+ * Returns `undefined` for models without a texture map.
3582
+ */
2963
3583
  static async createAtlas(originalModel) {
2964
3584
  const textureMap = {};
2965
3585
  const metaMap = {};
@@ -2980,12 +3600,12 @@ var init_UVMapper = __esm({
2980
3600
  } else {
2981
3601
  uniqueTextureNames.push(textureKey);
2982
3602
  const assetKey = AssetKey.parse("textures", textureValue, model.key);
2983
- promises.push(ModelTextures.get(assetKey).then((asset) => {
2984
- if (!asset) {
3603
+ promises.push(ModelTextures.get(assetKey).then((asset2) => {
3604
+ if (!asset2) {
2985
3605
  console.warn(p5, "Missing texture", assetKey);
2986
3606
  return;
2987
3607
  }
2988
- textureMap[textureKey] = new WrappedImage(asset);
3608
+ textureMap[textureKey] = new WrappedImage(asset2);
2989
3609
  }));
2990
3610
  promises.push(ModelTextures.getMeta(assetKey).then((meta) => {
2991
3611
  metaMap[textureKey] = meta;
@@ -3324,14 +3944,15 @@ var init_Entities = __esm({
3324
3944
  init_MineRenderError();
3325
3945
  init_ModelTextures();
3326
3946
  Entities = class {
3947
+ /** Lists model paths in the `minecraft` namespace, including subdirectories and excluding `.json`. */
3327
3948
  static async getEntityList() {
3328
3949
  const collect = async (path) => {
3329
3950
  const prefix2 = path ? `${path}/` : "";
3330
3951
  const key = new AssetKey(DEFAULT_NAMESPACE, `${prefix2}_list`, void 0, void 0, "entity-models", ".json");
3331
- const list = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get(key, "list" /* LIST */));
3332
- if (!list) return [];
3333
- const files = list.files.filter((file) => file.endsWith(".json") && file !== "_list.json").map((file) => `${prefix2}${file.slice(0, -5)}`);
3334
- const children = await Promise.all(list.directories.map((directory) => collect(`${prefix2}${directory}`)));
3952
+ const list2 = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get(key, "list" /* LIST */));
3953
+ if (!list2) return [];
3954
+ const files = list2.files.filter((file) => file.endsWith(".json") && file !== "_list.json").map((file) => `${prefix2}${file.slice(0, -5)}`);
3955
+ const children = await Promise.all(list2.directories.map((directory) => collect(`${prefix2}${directory}`)));
3335
3956
  return [...files, ...children.flat()];
3336
3957
  };
3337
3958
  return collect("");
@@ -3341,6 +3962,7 @@ var init_Entities = __esm({
3341
3962
  const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models", ".json", isAssetKey(modelKey) ? modelKey.root : void 0);
3342
3963
  return Caching.entityModelCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
3343
3964
  }
3965
+ /** Returns selectable geometry-layer names, or an empty list when the model is missing. */
3344
3966
  static async getLayerList(modelKey) {
3345
3967
  return Object.keys((await this.getModelFile(modelKey))?.layers ?? {});
3346
3968
  }
@@ -3348,6 +3970,15 @@ var init_Entities = __esm({
3348
3970
  static async getPassList(modelKey) {
3349
3971
  return (await this.getModelFile(modelKey))?.passes ?? [];
3350
3972
  }
3973
+ /**
3974
+ * Loads the layers and textures to pass to {@link MineRenderScene.addEntity}.
3975
+ * By default, selects `main`, unconditional passes, and passes enabled by `options.when`.
3976
+ *
3977
+ * @param modelKey - Dataset model, such as `new BasicAssetKey("minecraft", "zombie")`.
3978
+ * @param textureKey - Texture override for the first selected layer.
3979
+ * @param options - Layer selection, conditional passes, and per-layer texture overrides.
3980
+ * @returns The selected model, or `undefined` when its dataset file is missing.
3981
+ */
3351
3982
  static async getEntity(modelKey, textureKey, options) {
3352
3983
  const model = await this.getModelFile(modelKey);
3353
3984
  if (!model) return void 0;
@@ -3393,6 +4024,7 @@ var init_Entities = __esm({
3393
4024
  const file = await Caching.entityAnimationCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
3394
4025
  return file?.animations;
3395
4026
  }
4027
+ /** Finds a fallback texture using the model path, its directory, and vanilla variant data. */
3396
4028
  static async resolveTexture(modelKey) {
3397
4029
  const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
3398
4030
  const root = isAssetKey(modelKey) ? modelKey.root : void 0;
@@ -3404,8 +4036,8 @@ var init_Entities = __esm({
3404
4036
  if (await ModelTextures.get(texture2)) return texture2;
3405
4037
  }
3406
4038
  const listKey = new AssetKey(modelKey.namespace, "_list", `${name}_variant`, void 0, "data", ".json", root);
3407
- const list = await AssetLoader.get(listKey, "list" /* LIST */);
3408
- const files = list?.files.filter((file2) => file2.endsWith(".json") && file2 !== "_list.json");
4039
+ const list2 = await AssetLoader.get(listKey, "list" /* LIST */);
4040
+ const files = list2?.files.filter((file2) => file2.endsWith(".json") && file2 !== "_list.json");
3409
4041
  const file = files?.find((file2) => file2 === "temperate.json") ?? files?.[0];
3410
4042
  if (!file) return void 0;
3411
4043
  const variantKey = new AssetKey(modelKey.namespace, file.slice(0, -5), `${name}_variant`, void 0, "data", ".json", root);
@@ -3510,27 +4142,27 @@ var init_EntityAnimationPlayer = __esm({
3510
4142
  for (const layer of layers) {
3511
4143
  const objects = [];
3512
4144
  if (poses) {
3513
- layer.traverse((object) => {
3514
- if (poses.has(object)) objects.push(object);
4145
+ layer.traverse((object3) => {
4146
+ if (poses.has(object3)) objects.push(object3);
3515
4147
  });
3516
4148
  } else {
3517
4149
  for (const bone of Object.keys(animation.bones)) {
3518
- const object = layer.getObjectByName(`group:${bone}`);
3519
- if (object) objects.push(object);
4150
+ const object3 = layer.getObjectByName(`group:${bone}`);
4151
+ if (object3) objects.push(object3);
3520
4152
  }
3521
4153
  }
3522
- for (const object of objects) {
3523
- const bone = object.name.slice("group:".length);
3524
- const pose = poses?.get(object);
4154
+ for (const object3 of objects) {
4155
+ const bone = object3.name.slice("group:".length);
4156
+ const pose = poses?.get(object3);
3525
4157
  this.parts.push({
3526
4158
  bone,
3527
- object,
3528
- position: object.position.clone(),
3529
- rotation: object.rotation.clone(),
3530
- scale: object.scale.clone(),
3531
- basePosition: pose ? new Vector35(...pose.offset) : object.position.clone(),
3532
- baseRotation: pose ? new Euler3(...pose.rotation, "ZYX") : object.rotation.clone(),
3533
- baseScale: pose ? new Vector35(...pose.scale ?? [1, 1, 1]) : object.scale.clone()
4159
+ object: object3,
4160
+ position: object3.position.clone(),
4161
+ rotation: object3.rotation.clone(),
4162
+ scale: object3.scale.clone(),
4163
+ basePosition: pose ? new Vector35(...pose.offset) : object3.position.clone(),
4164
+ baseRotation: pose ? new Euler3(...pose.rotation, "ZYX") : object3.rotation.clone(),
4165
+ baseScale: pose ? new Vector35(...pose.scale ?? [1, 1, 1]) : object3.scale.clone()
3534
4166
  });
3535
4167
  }
3536
4168
  }
@@ -3553,12 +4185,14 @@ var init_EntityAnimationPlayer = __esm({
3553
4185
  this._animation = void 0;
3554
4186
  this._time = 0;
3555
4187
  }
4188
+ /** Applies the pose at an absolute playback time. Returns `false` when no clip is active. */
3556
4189
  setTime(seconds) {
3557
4190
  if (!this._animation) return false;
3558
4191
  this._time = seconds;
3559
4192
  this.apply();
3560
4193
  return true;
3561
4194
  }
4195
+ /** Advances playback by elapsed seconds multiplied by the speed passed to {@link play}. */
3562
4196
  advance(deltaSeconds) {
3563
4197
  return this.setTime(this._time + deltaSeconds * this.speed);
3564
4198
  }
@@ -3596,6 +4230,7 @@ var init_InstanceReference = __esm({
3596
4230
  if (!this.instanceable.isInstanceActive(this.index, this)) throw new MineRenderError("Instance has been removed");
3597
4231
  return this.instanceable;
3598
4232
  }
4233
+ /** Releases this placement's slot for reuse. Repeated calls have no effect. */
3599
4234
  removeFromScene() {
3600
4235
  if (this.instanceable.isInstanceActive(this.index, this)) this.instanceable.removeInstanceAt(this.index);
3601
4236
  }
@@ -3608,9 +4243,11 @@ var init_InstanceReference = __esm({
3608
4243
  setMatrix(matrix) {
3609
4244
  this.activeInstanceable.setMatrixAt(this.index, matrix);
3610
4245
  }
4246
+ /** Hides or restores this placement while preserving its transform and allocated slot. */
3611
4247
  setVisible(visible) {
3612
4248
  this.activeInstanceable.setInstanceVisibleAt(this.index, visible);
3613
4249
  }
4250
+ /** Updates the supplied transform components. Positions use scene units and rotations use radians. */
3614
4251
  setPositionRotationScale(position, rotation, scale) {
3615
4252
  this.activeInstanceable.setPositionRotationScaleAt(this.index, position, rotation, scale);
3616
4253
  }
@@ -3687,6 +4324,7 @@ var init_SceneObject = __esm({
3687
4324
  }
3688
4325
  async init() {
3689
4326
  }
4327
+ /** Marks ancestor scenes for redraw and emits a `change` event. */
3690
4328
  notifyDirty() {
3691
4329
  this.traverseAncestors((parent) => {
3692
4330
  const scene = parent;
@@ -3722,15 +4360,17 @@ var init_SceneObject = __esm({
3722
4360
  return obj;
3723
4361
  }
3724
4362
  /**
3725
- * Get a group by its name
4363
+ * Finds a named part, such as `head`, without the internal `group:` prefix.
4364
+ * Returns `undefined` when the group does not exist.
3726
4365
  */
3727
4366
  getGroupByName(name) {
3728
4367
  return this.getObjectByName(`group:${name}`);
3729
4368
  }
3730
4369
  /**
3731
- * Toggle visibility of a group
3732
- * @param name name of the group
3733
- * @param visible set the visibility directly, if not set toggles it
4370
+ * Changes a named group's visibility and requests a redraw.
4371
+ * @param name - Part name without the `group:` prefix.
4372
+ * @param visible - Visibility to assign. Omit it to toggle the current value.
4373
+ * @returns The resulting visibility, or `false` if the group does not exist.
3734
4374
  */
3735
4375
  toggleGroupVisibility(name, visible) {
3736
4376
  return this.toggleObjectVisibility(this.getGroupByName(name), visible);
@@ -3781,15 +4421,16 @@ var init_SceneObject = __esm({
3781
4421
  return mesh;
3782
4422
  }
3783
4423
  /**
3784
- * Get a mesh by its name
4424
+ * Finds a named mesh without the internal `mesh:` prefix, or returns `undefined`.
3785
4425
  */
3786
4426
  getMeshByName(name) {
3787
4427
  return this.getObjectByName(`mesh:${name}`);
3788
4428
  }
3789
4429
  /**
3790
- * Toggle visibility of a mesh
3791
- * @param name name of the mesh
3792
- * @param visible set the visibility directly, if not set toggles it
4430
+ * Changes a named mesh's visibility and requests a redraw.
4431
+ * @param name - Mesh name without the `mesh:` prefix.
4432
+ * @param visible - Visibility to assign. Omit it to toggle the current value.
4433
+ * @returns The resulting visibility, or `false` if the mesh does not exist.
3793
4434
  */
3794
4435
  toggleMeshVisibility(name, visible) {
3795
4436
  return this.toggleObjectVisibility(this.getMeshByName(name), visible);
@@ -3835,12 +4476,14 @@ var init_SceneObject = __esm({
3835
4476
  get isInstanced() {
3836
4477
  return this._isInstanced;
3837
4478
  }
4479
+ /** Number of live placements, excluding released instance slots. */
3838
4480
  get instanceCounter() {
3839
4481
  return this._instanceCounter;
3840
4482
  }
3841
4483
  constructInstanceReference(i) {
3842
4484
  return new InstanceReference(this, i);
3843
4485
  }
4486
+ /** Allocates a placement of this instanced object, reusing a released slot when available. */
3844
4487
  nextInstance() {
3845
4488
  const mesh = this.instanceMesh;
3846
4489
  if (!mesh) throw new MineRenderError("Object is not instanced");
@@ -3885,10 +4528,10 @@ var init_SceneObject = __esm({
3885
4528
  return new InstancedBufferAttribute(array, attribute.itemSize, attribute.normalized, attribute.meshPerAttribute).setUsage(attribute.usage);
3886
4529
  };
3887
4530
  const matrix = grow(mesh.instanceMatrix, 0);
3888
- const color = mesh.instanceColor ? grow(mesh.instanceColor, 1) : null;
4531
+ const color2 = mesh.instanceColor ? grow(mesh.instanceColor, 1) : null;
3889
4532
  mesh.dispose();
3890
4533
  mesh.instanceMatrix = matrix;
3891
- mesh.instanceColor = color;
4534
+ mesh.instanceColor = color2;
3892
4535
  }
3893
4536
  *activeInstanceIndices() {
3894
4537
  if (this.instanceMesh) {
@@ -3987,6 +4630,7 @@ var init_SceneObject = __esm({
3987
4630
  vector.setFromMatrixScale(matrix);
3988
4631
  return vector;
3989
4632
  }
4633
+ /** Updates supplied transform components. For a shared model, affects every live instance. */
3990
4634
  setPositionRotationScale(position, rotation, scale) {
3991
4635
  if (this.isInstanced) {
3992
4636
  for (const i of this.activeInstanceIndices()) {
@@ -4005,6 +4649,7 @@ var init_SceneObject = __esm({
4005
4649
  }
4006
4650
  this.notifyDirty();
4007
4651
  }
4652
+ /** Sets position in scene units. For a shared model, updates every live instance. */
4008
4653
  setPosition(position) {
4009
4654
  if (this.isInstanced) {
4010
4655
  for (const i of this.activeInstanceIndices()) {
@@ -4022,6 +4667,7 @@ var init_SceneObject = __esm({
4022
4667
  return this.position;
4023
4668
  }
4024
4669
  }
4670
+ /** Sets rotation in radians. For a shared model, updates every live instance. */
4025
4671
  setRotation(rotation) {
4026
4672
  if (this.isInstanced) {
4027
4673
  for (const i of this.activeInstanceIndices()) {
@@ -4039,6 +4685,7 @@ var init_SceneObject = __esm({
4039
4685
  return this.rotation;
4040
4686
  }
4041
4687
  }
4688
+ /** Sets scale factors. For a shared model, updates every live instance. */
4042
4689
  setScale(scale) {
4043
4690
  if (this.isInstanced) {
4044
4691
  for (const i of this.activeInstanceIndices()) {
@@ -4057,15 +4704,15 @@ var init_SceneObject = __esm({
4057
4704
  }
4058
4705
  }
4059
4706
  //</editor-fold>
4060
- toggleObjectVisibility(object, visible) {
4061
- if (object) {
4707
+ toggleObjectVisibility(object3, visible) {
4708
+ if (object3) {
4062
4709
  if (typeof visible !== "undefined") {
4063
- object.visible = visible;
4710
+ object3.visible = visible;
4064
4711
  } else {
4065
- object.visible = !object.visible;
4712
+ object3.visible = !object3.visible;
4066
4713
  }
4067
4714
  this.notifyDirty();
4068
- return object.visible;
4715
+ return object3.visible;
4069
4716
  }
4070
4717
  this.notifyDirty();
4071
4718
  return false;
@@ -4076,6 +4723,7 @@ var init_SceneObject = __esm({
4076
4723
  this.notifyDirty();
4077
4724
  super.dispose();
4078
4725
  }
4726
+ /** Releases instance slots and removes children, disposing children that implement Disposable. */
4079
4727
  disposeAndRemoveAllChildren() {
4080
4728
  if (this.instanceMesh) {
4081
4729
  if (this._scene) this._scene.stats.instanceCount -= this.instanceReferences.size;
@@ -4098,6 +4746,7 @@ var init_SceneObject = __esm({
4098
4746
  }
4099
4747
  this.notifyDirty();
4100
4748
  }
4749
+ /** Detaches the object from its parent and requests a redraw. */
4101
4750
  removeFromScene() {
4102
4751
  this.notifyDirty();
4103
4752
  this.removeFromParent();
@@ -4249,12 +4898,15 @@ var init_EntityObject = __esm({
4249
4898
  if (this.animationPlayers.length) this.notifyDirty();
4250
4899
  }
4251
4900
  //</editor-fold>
4901
+ /** Finds a selected draw by its layer key, such as `main` or `main#2`, or returns `undefined`. */
4252
4902
  getLayerGroup(name) {
4253
4903
  return super.getGroupByName(`layer:${name}`);
4254
4904
  }
4905
+ /** Finds a part name without `group:`. Supply `layerName` to restrict the search to one draw. */
4255
4906
  getGroupByName(name, layerName) {
4256
4907
  return layerName === void 0 ? super.getGroupByName(name) : this.getLayerGroup(layerName)?.getObjectByName(`group:${name}`);
4257
4908
  }
4909
+ /** Finds a mesh name without `mesh:`. Supply `layerName` to restrict the search to one draw. */
4258
4910
  getMeshByName(name, layerName) {
4259
4911
  return layerName === void 0 ? super.getMeshByName(name) : this.getLayerGroup(layerName)?.getObjectByName(`mesh:${name}`);
4260
4912
  }
@@ -4356,8 +5008,8 @@ var init_EntityObject = __esm({
4356
5008
  this.scrollMaterials.push({ material: mat, offset: mat.map.offset, speed: scroll });
4357
5009
  }
4358
5010
  }
4359
- this.getLayerGroup(name)?.traverse((object) => {
4360
- if (isMesh(object)) object.material = mat;
5011
+ this.getLayerGroup(name)?.traverse((object3) => {
5012
+ if (isMesh(object3)) object3.material = mat;
4361
5013
  });
4362
5014
  }));
4363
5015
  if (!this.children.length) {
@@ -4696,8 +5348,8 @@ function toTrianglesDrawMode(geometry, drawMode) {
4696
5348
  return geometry;
4697
5349
  }
4698
5350
  }
4699
- function computeMorphedAttributes(object) {
4700
- if (object.geometry.isBufferGeometry !== true) {
5351
+ function computeMorphedAttributes(object3) {
5352
+ if (object3.geometry.isBufferGeometry !== true) {
4701
5353
  console.error("THREE.BufferGeometryUtils: Geometry is not of type BufferGeometry.");
4702
5354
  return null;
4703
5355
  }
@@ -4710,11 +5362,11 @@ function computeMorphedAttributes(object) {
4710
5362
  const _morphA = new Vector310();
4711
5363
  const _morphB = new Vector310();
4712
5364
  const _morphC = new Vector310();
4713
- function _calculateMorphedAttributeData(object2, material2, attribute, morphAttribute, morphTargetsRelative2, a2, b2, c2, modifiedAttributeArray) {
5365
+ function _calculateMorphedAttributeData(object4, material2, attribute, morphAttribute, morphTargetsRelative2, a2, b2, c2, modifiedAttributeArray) {
4714
5366
  _vA.fromBufferAttribute(attribute, a2);
4715
5367
  _vB.fromBufferAttribute(attribute, b2);
4716
5368
  _vC.fromBufferAttribute(attribute, c2);
4717
- const morphInfluences = object2.morphTargetInfluences;
5369
+ const morphInfluences = object4.morphTargetInfluences;
4718
5370
  if (material2.morphTargets && morphAttribute && morphInfluences) {
4719
5371
  _morphA.set(0, 0, 0);
4720
5372
  _morphB.set(0, 0, 0);
@@ -4740,10 +5392,10 @@ function computeMorphedAttributes(object) {
4740
5392
  _vB.add(_morphB);
4741
5393
  _vC.add(_morphC);
4742
5394
  }
4743
- if (object2.isSkinnedMesh) {
4744
- object2.boneTransform(a2, _vA);
4745
- object2.boneTransform(b2, _vB);
4746
- object2.boneTransform(c2, _vC);
5395
+ if (object4.isSkinnedMesh) {
5396
+ object4.boneTransform(a2, _vA);
5397
+ object4.boneTransform(b2, _vB);
5398
+ object4.boneTransform(c2, _vC);
4747
5399
  }
4748
5400
  modifiedAttributeArray[a2 * 3 + 0] = _vA.x;
4749
5401
  modifiedAttributeArray[a2 * 3 + 1] = _vA.y;
@@ -4755,8 +5407,8 @@ function computeMorphedAttributes(object) {
4755
5407
  modifiedAttributeArray[c2 * 3 + 1] = _vC.y;
4756
5408
  modifiedAttributeArray[c2 * 3 + 2] = _vC.z;
4757
5409
  }
4758
- const geometry = object.geometry;
4759
- const material = object.material;
5410
+ const geometry = object3.geometry;
5411
+ const material = object3.material;
4760
5412
  let a, b, c;
4761
5413
  const index = geometry.index;
4762
5414
  const positionAttribute = geometry.attributes.position;
@@ -4783,7 +5435,7 @@ function computeMorphedAttributes(object) {
4783
5435
  b = index.getX(j + 1);
4784
5436
  c = index.getX(j + 2);
4785
5437
  _calculateMorphedAttributeData(
4786
- object,
5438
+ object3,
4787
5439
  groupMaterial,
4788
5440
  positionAttribute,
4789
5441
  morphPosition,
@@ -4794,7 +5446,7 @@ function computeMorphedAttributes(object) {
4794
5446
  modifiedPosition
4795
5447
  );
4796
5448
  _calculateMorphedAttributeData(
4797
- object,
5449
+ object3,
4798
5450
  groupMaterial,
4799
5451
  normalAttribute,
4800
5452
  morphNormal,
@@ -4814,7 +5466,7 @@ function computeMorphedAttributes(object) {
4814
5466
  b = index.getX(i + 1);
4815
5467
  c = index.getX(i + 2);
4816
5468
  _calculateMorphedAttributeData(
4817
- object,
5469
+ object3,
4818
5470
  material,
4819
5471
  positionAttribute,
4820
5472
  morphPosition,
@@ -4825,7 +5477,7 @@ function computeMorphedAttributes(object) {
4825
5477
  modifiedPosition
4826
5478
  );
4827
5479
  _calculateMorphedAttributeData(
4828
- object,
5480
+ object3,
4829
5481
  material,
4830
5482
  normalAttribute,
4831
5483
  morphNormal,
@@ -4849,7 +5501,7 @@ function computeMorphedAttributes(object) {
4849
5501
  b = j + 1;
4850
5502
  c = j + 2;
4851
5503
  _calculateMorphedAttributeData(
4852
- object,
5504
+ object3,
4853
5505
  groupMaterial,
4854
5506
  positionAttribute,
4855
5507
  morphPosition,
@@ -4860,7 +5512,7 @@ function computeMorphedAttributes(object) {
4860
5512
  modifiedPosition
4861
5513
  );
4862
5514
  _calculateMorphedAttributeData(
4863
- object,
5515
+ object3,
4864
5516
  groupMaterial,
4865
5517
  normalAttribute,
4866
5518
  morphNormal,
@@ -4880,7 +5532,7 @@ function computeMorphedAttributes(object) {
4880
5532
  b = i + 1;
4881
5533
  c = i + 2;
4882
5534
  _calculateMorphedAttributeData(
4883
- object,
5535
+ object3,
4884
5536
  material,
4885
5537
  positionAttribute,
4886
5538
  morphPosition,
@@ -4891,7 +5543,7 @@ function computeMorphedAttributes(object) {
4891
5543
  modifiedPosition
4892
5544
  );
4893
5545
  _calculateMorphedAttributeData(
4894
- object,
5546
+ object3,
4895
5547
  material,
4896
5548
  normalAttribute,
4897
5549
  morphNormal,
@@ -4925,6 +5577,7 @@ var init_ModelCulling = __esm({
4925
5577
  "src/model/ModelCulling.ts"() {
4926
5578
  init_CubeFace();
4927
5579
  ModelCulling = class {
5580
+ /** Rotates a world-direction bit mask into model space. Bits follow {@link CUBE_FACES}. */
4928
5581
  static toLocalMask(worldMask, rotation) {
4929
5582
  let localMask = 0;
4930
5583
  for (const [localFace, offset] of CUBE_FACE_OFFSETS.entries()) {
@@ -4935,6 +5588,7 @@ var init_ModelCulling = __esm({
4935
5588
  }
4936
5589
  return localMask;
4937
5590
  }
5591
+ /** Whether a model fills one block with opaque faces and can hide adjacent block faces. */
4938
5592
  static isOpaqueFullCube(atlas) {
4939
5593
  if (!atlas || atlas.hasTransparency !== false || atlas.model.elements?.length !== 1) return false;
4940
5594
  const element = atlas.model.elements[0];
@@ -4961,15 +5615,17 @@ var init_ModelCulling = __esm({
4961
5615
  });
4962
5616
 
4963
5617
  // src/model/SpecialItems.ts
4964
- import { Matrix4 as Matrix48 } from "three";
5618
+ import { Euler as Euler7, Matrix4 as Matrix48 } from "three";
4965
5619
  var SpecialItems;
4966
5620
  var init_SpecialItems = __esm({
4967
5621
  "src/model/SpecialItems.ts"() {
4968
5622
  init_AssetKey();
4969
5623
  init_Entities();
5624
+ init_BannerPatterns();
5625
+ init_ModelTextures();
4970
5626
  SpecialItems = class {
4971
5627
  /** Resolves the static entity parts drawn by a special item renderer, in model units. */
4972
- static async getParts(special, root) {
5628
+ static async getParts(special, root, components = {}) {
4973
5629
  const texture = (id, directory) => {
4974
5630
  const key = AssetKey.parse("textures", id);
4975
5631
  return new AssetKey(
@@ -4982,13 +5638,77 @@ var init_SpecialItems = __esm({
4982
5638
  root
4983
5639
  );
4984
5640
  };
4985
- const load = async (id, textureKey, transform, rotations = {}) => {
5641
+ const load = async (id, textureKey, transform, rotations = {}, layers = ["main"]) => {
4986
5642
  const key = new AssetKey("minecraft", id, void 0, void 0, "entity-models", ".json", root);
4987
- const model = await Entities.getEntity(key, textureKey, { layer: "main" });
5643
+ const model = await Entities.getEntity(key, textureKey, layers.length === 1 ? { layer: layers[0] } : {
5644
+ layers,
5645
+ textures: textureKey ? Object.fromEntries(layers.map((name) => [name, textureKey])) : void 0
5646
+ });
4988
5647
  if (!model) throw new Error(`Missing entity model minecraft:${id} for special item ${special.type}`);
4989
5648
  return { model, transform, rotations };
4990
5649
  };
4991
5650
  switch (special.type) {
5651
+ case "minecraft:trident":
5652
+ case "trident":
5653
+ case "minecraft:conduit":
5654
+ case "conduit": {
5655
+ const trident = special.type === "trident" || special.type === "minecraft:trident";
5656
+ const part = await load(
5657
+ trident ? "trident" : "conduit",
5658
+ texture(trident ? "trident" : "conduit/base", ""),
5659
+ trident ? new Matrix48().makeScale(1, -1, -1) : new Matrix48().makeTranslation(8, 8, 8),
5660
+ {},
5661
+ trident ? ["main"] : ["shell"]
5662
+ );
5663
+ part.model = { ...part.model, render: "solid", layers: part.model.layers && Object.fromEntries(
5664
+ Object.entries(part.model.layers).map(([name, layer]) => [name, { ...layer, render: "solid" }])
5665
+ ) };
5666
+ return [part];
5667
+ }
5668
+ case "minecraft:banner":
5669
+ case "banner":
5670
+ case "minecraft:shield":
5671
+ case "shield": {
5672
+ const banner = special.type === "banner" || special.type === "minecraft:banner";
5673
+ const color2 = banner ? BannerPatterns.getColor(special.color) : components["minecraft:base_color"] === void 0 ? void 0 : BannerPatterns.getColor(components["minecraft:base_color"]);
5674
+ const patterns = await BannerPatterns.getLayers(components["minecraft:banner_patterns"], banner ? "banner" : "shield", root);
5675
+ const patterned = banner || color2 !== void 0 || patterns.length > 0;
5676
+ const baseTexture = texture(banner ? "banner_base" : patterned ? "shield_base" : "shield_base_nopattern", "");
5677
+ const transform = banner ? new Matrix48().makeTranslation(8, 0, 8).multiply(new Matrix48().makeScale(2 / 3, -2 / 3, -2 / 3)) : new Matrix48().makeScale(1, -1, -1);
5678
+ const part = await load(banner ? "standing_banner" : "shield", baseTexture, transform, {}, banner ? ["main", "flag"] : ["main"]);
5679
+ const layers = Object.fromEntries(Object.entries(part.model.layers ?? { main: part.model }).map(([name, layer]) => [name, { ...layer, render: "solid" }]));
5680
+ let overlay2;
5681
+ if (banner) {
5682
+ const flagLayer = layers.flag;
5683
+ const flag = flagLayer?.layer.root.children.flag;
5684
+ if (!flag) throw new Error("Banner entity model is missing its flag layer");
5685
+ overlay2 = { ...flagLayer.layer, root: { ...flagLayer.layer.root, children: {
5686
+ ...flagLayer.layer.root.children,
5687
+ flag: { ...flag, pose: { ...flag.pose, rotation: [-25e-4 * Math.PI, flag.pose.rotation[1], flag.pose.rotation[2]] } }
5688
+ } } };
5689
+ layers.flag = { ...flagLayer, layer: overlay2 };
5690
+ } else {
5691
+ const main = layers.main.layer;
5692
+ const plate = main.root.children.plate;
5693
+ if (!plate) throw new Error("Shield entity model is missing its plate");
5694
+ overlay2 = { ...main, root: { ...main.root, cubes: [], children: { plate } } };
5695
+ }
5696
+ const tints = {};
5697
+ if (patterned) {
5698
+ const draws = [{ texture: texture(`${banner ? "banner" : "shield"}/base`, ""), color: color2 ?? DYE_COLORS.white }, ...patterns];
5699
+ draws.forEach((draw, index) => {
5700
+ const name = index === 0 ? "pattern_base" : `pattern_${index - 1}`;
5701
+ layers[name] = { key: draw.texture, texture: draw.texture, layer: overlay2, render: "no_outline", tint: name };
5702
+ tints[name] = draw.color;
5703
+ });
5704
+ }
5705
+ await Promise.all([...new Map(Object.values(layers).map((layer) => [layer.texture.serialize(), layer.texture])).values()].map(async (key) => {
5706
+ if (!await ModelTextures.preload(key)) throw new Error(`Missing special item texture ${key.toNamespacedString()}`);
5707
+ }));
5708
+ part.model = { ...part.model, ...layers.main, layers };
5709
+ part.tints = tints;
5710
+ return [part];
5711
+ }
4992
5712
  case "minecraft:chest":
4993
5713
  case "chest": {
4994
5714
  const angle = -(special.openness ?? 0) * Math.PI / 2;
@@ -4999,6 +5719,26 @@ var init_SpecialItems = __esm({
4999
5719
  chest.model = { ...chest.model, render: "solid", layers: void 0 };
5000
5720
  return [chest];
5001
5721
  }
5722
+ case "minecraft:shulker_box":
5723
+ case "shulker_box": {
5724
+ const openness = special.openness === void 0 ? 0 : special.openness;
5725
+ const orientation = special.orientation === void 0 ? "up" : special.orientation;
5726
+ const directions = {
5727
+ down: [Math.PI, 0, 0],
5728
+ up: [0, 0, 0],
5729
+ north: [Math.PI / 2, 0, Math.PI],
5730
+ south: [Math.PI / 2, 0, 0],
5731
+ west: [Math.PI / 2, 0, Math.PI / 2],
5732
+ east: [Math.PI / 2, 0, -Math.PI / 2]
5733
+ };
5734
+ if (!Object.prototype.hasOwnProperty.call(directions, orientation)) throw new Error(`Unsupported shulker-box orientation ${orientation}`);
5735
+ if (typeof openness !== "number" || !Number.isFinite(openness)) throw new Error("Shulker-box openness must be finite");
5736
+ if (typeof special.texture !== "string" || !special.texture) throw new Error("Shulker-box texture is required");
5737
+ const transform = new Matrix48().makeTranslation(8, 8, 8).multiply(new Matrix48().makeScale(0.9995, 0.9995, 0.9995)).multiply(new Matrix48().makeRotationFromEuler(new Euler7(...directions[orientation], "XYZ"))).multiply(new Matrix48().makeScale(1, -1, -1)).multiply(new Matrix48().makeTranslation(0, -16, 0));
5738
+ const box = await load("shulker_box", texture(special.texture, "shulker"), transform, { lid: [0, openness * Math.PI * 1.5, 0] });
5739
+ box.positions = { lid: [0, 24 - openness * 8, 0] };
5740
+ return [box];
5741
+ }
5002
5742
  case "minecraft:bed":
5003
5743
  case "bed": {
5004
5744
  const transform = new Matrix48().makeTranslation(0, 9, 0).multiply(new Matrix48().makeRotationX(Math.PI / 2)).multiply(new Matrix48().makeTranslation(8, 8, 8)).multiply(new Matrix48().makeRotationZ(Math.PI)).multiply(new Matrix48().makeTranslation(-8, -8, -8));
@@ -5113,6 +5853,7 @@ var init_ItemTints = __esm({
5113
5853
  static async get(model, tints) {
5114
5854
  const sources = model.tints;
5115
5855
  if (!sources?.length) return tints;
5856
+ const components = model.components ?? {};
5116
5857
  const colors = { ...tints };
5117
5858
  await Promise.all(sources.map(async (source, index) => {
5118
5859
  if (colors[index] !== void 0) return;
@@ -5127,27 +5868,71 @@ var init_ItemTints = __esm({
5127
5868
  break;
5128
5869
  case "dye":
5129
5870
  case "minecraft:dye":
5871
+ colors[index] = this.colorOrDefault(components["minecraft:dyed_color"], source.default, true);
5872
+ break;
5130
5873
  case "potion":
5131
- case "minecraft:potion":
5874
+ case "minecraft:potion": {
5875
+ const value = typeof components["minecraft:potion_contents"] === "string" ? void 0 : this.componentField(components, "potion_contents", "custom_color");
5876
+ colors[index] = this.colorOrDefault(value, source.default);
5877
+ break;
5878
+ }
5132
5879
  case "map_color":
5133
5880
  case "minecraft:map_color":
5881
+ colors[index] = this.colorOrDefault(components["minecraft:map_color"], source.default);
5882
+ break;
5134
5883
  case "firework":
5135
- case "minecraft:firework":
5884
+ case "minecraft:firework": {
5885
+ const values = this.componentField(components, "firework_explosion", "colors");
5886
+ if (values !== void 0 && !Array.isArray(values)) throw new MineRenderError("Item tint firework_explosion.colors must be an array");
5887
+ if (!values?.length) {
5888
+ colors[index] = this.rgb(source.default);
5889
+ break;
5890
+ }
5891
+ const rgb = values.map((value) => this.componentColor(value));
5892
+ const red = Math.floor(rgb.reduce((sum, color2) => sum + (color2 >> 16 & 255), 0) / rgb.length);
5893
+ const green = Math.floor(rgb.reduce((sum, color2) => sum + (color2 >> 8 & 255), 0) / rgb.length);
5894
+ const blue = Math.floor(rgb.reduce((sum, color2) => sum + (color2 & 255), 0) / rgb.length);
5895
+ colors[index] = red << 16 | green << 8 | blue;
5896
+ break;
5897
+ }
5136
5898
  case "team":
5137
5899
  case "minecraft:team":
5138
- case "custom_model_data":
5139
- case "minecraft:custom_model_data":
5140
5900
  colors[index] = this.rgb(source.default);
5141
5901
  break;
5902
+ case "custom_model_data":
5903
+ case "minecraft:custom_model_data": {
5904
+ const colorIndex = source.index ?? 0;
5905
+ if (!Number.isSafeInteger(colorIndex) || colorIndex < 0) throw new MineRenderError("Item tint custom_model_data index must be a nonnegative integer");
5906
+ const values = this.componentField(components, "custom_model_data", "colors");
5907
+ if (values !== void 0 && !Array.isArray(values)) throw new MineRenderError("Item tint custom_model_data.colors must be an array");
5908
+ colors[index] = this.colorOrDefault(values?.[colorIndex], source.default, true);
5909
+ break;
5910
+ }
5142
5911
  default:
5143
5912
  throw new MineRenderError(`Unsupported item tint source ${source.type}`);
5144
5913
  }
5145
5914
  }));
5146
5915
  return colors;
5147
5916
  }
5148
- static rgb(color) {
5149
- if (!Array.isArray(color)) return color & 16777215;
5150
- const [red, green, blue] = color.map((channel) => Math.floor(Math.fround(Math.fround(channel) * 255)));
5917
+ static componentField(components, id, field) {
5918
+ const value = components[`minecraft:${id}`];
5919
+ if (value === void 0) return void 0;
5920
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new MineRenderError(`Item tint ${id} must be an object`);
5921
+ return value[field];
5922
+ }
5923
+ static colorOrDefault(value, fallback, allowTriple = false) {
5924
+ return value === void 0 ? this.rgb(fallback) : this.componentColor(value, allowTriple);
5925
+ }
5926
+ static componentColor(value, allowTriple = false) {
5927
+ if (typeof value === "number" && Number.isInteger(value) && value >= -2147483648 && value <= 2147483647) return this.rgb(value);
5928
+ if (allowTriple && Array.isArray(value) && value.length === 3 && value.every((channel) => typeof channel === "number" && Number.isFinite(channel))) {
5929
+ return this.rgb(value) & 16777215;
5930
+ }
5931
+ throw new MineRenderError(`Item tint component color must be a packed integer${allowTriple ? " or RGB triple" : ""}`);
5932
+ }
5933
+ static rgb(color2) {
5934
+ if (!Array.isArray(color2)) return color2 & 16777215;
5935
+ const [red, green, blue] = color2.map((channel) => Math.floor(Math.fround(Math.fround(channel) * 255)) & 255);
5151
5936
  return red << 16 | green << 8 | blue;
5152
5937
  }
5153
5938
  };
@@ -5155,7 +5940,7 @@ var init_ItemTints = __esm({
5155
5940
  });
5156
5941
 
5157
5942
  // src/model/scene/ModelObject.ts
5158
- import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute5, Color as Color9, Euler as Euler7, Matrix4 as Matrix49 } from "three";
5943
+ import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute5, Color as Color9, Euler as Euler8, Matrix4 as Matrix49 } from "three";
5159
5944
  function isModelObject(obj) {
5160
5945
  return obj.isModelObject;
5161
5946
  }
@@ -5184,9 +5969,10 @@ var init_ModelObject = __esm({
5184
5969
  this.originalModel = originalModel;
5185
5970
  this.isModelObject = true;
5186
5971
  this.specialMaterials = /* @__PURE__ */ new Map();
5972
+ this.geometries = /* @__PURE__ */ new Set();
5187
5973
  this.meshesCreated = false;
5188
5974
  this.options = (0, import_ts_deepmerge5.default)({}, _ModelObject.DEFAULT_OPTIONS, options ?? {});
5189
- if (originalModel.special) this.options.instanceMeshes = false;
5975
+ if (originalModel.special || originalModel.parts) this.options.instanceMeshes = false;
5190
5976
  if (this.options.tints) this.options.tints = { ...this.options.tints };
5191
5977
  this.addEventListener("added", () => this.updateAnimationSubscription());
5192
5978
  this.addEventListener("removed", () => this.updateAnimationSubscription());
@@ -5195,21 +5981,43 @@ var init_ModelObject = __esm({
5195
5981
  this.DEFAULT_OPTIONS = (0, import_ts_deepmerge5.default)({}, SceneObject.DEFAULT_OPTIONS, {});
5196
5982
  }
5197
5983
  async init() {
5984
+ const parts = this.originalModel.parts;
5985
+ if (parts) {
5986
+ try {
5987
+ for (const part of parts) {
5988
+ const object3 = new _ModelObject(part, { ...this.options, instanceMeshes: false });
5989
+ this.add(object3);
5990
+ await object3.init();
5991
+ }
5992
+ let order = 0;
5993
+ this.iterateAllMeshes((mesh) => {
5994
+ mesh.renderOrder = order++;
5995
+ for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
5996
+ material.transparent = true;
5997
+ }
5998
+ });
5999
+ } catch (error) {
6000
+ this.disposeAndRemoveAllChildren();
6001
+ throw error;
6002
+ }
6003
+ this.notifyDirty();
6004
+ return;
6005
+ }
5198
6006
  this.options.tints = await ItemTints.get(this.originalModel, this.options.tints);
5199
6007
  const special = this.originalModel.special;
5200
6008
  if (special) {
5201
- const parts = await SpecialItems.getParts(special, this.originalModel.key?.root);
6009
+ const parts2 = await SpecialItems.getParts(special, this.originalModel.key?.root, this.originalModel.components);
5202
6010
  const transform = this.options.displayPosition ? DisplayTransforms.getMatrix(this.originalModel.display, this.options.displayPosition) : new Matrix49();
5203
6011
  transform.multiply(new Matrix49().makeTranslation(-8, -8, -8));
5204
6012
  try {
5205
- for (const part of parts) {
5206
- const object = new EntityObject(part.model, { flip: false, wireframe: this.options.wireframe });
5207
- object.matrix.copy(transform).multiply(part.transform);
5208
- object.matrixWorldNeedsUpdate = true;
5209
- object.matrixAutoUpdate = false;
5210
- this.add(object);
5211
- await object.init();
5212
- object.iterateAllMeshes((mesh) => {
6013
+ for (const part of parts2) {
6014
+ const object3 = new EntityObject(part.model, { flip: false, wireframe: this.options.wireframe, tints: part.tints });
6015
+ object3.matrix.copy(transform).multiply(part.transform);
6016
+ object3.matrixWorldNeedsUpdate = true;
6017
+ object3.matrixAutoUpdate = false;
6018
+ this.add(object3);
6019
+ await object3.init();
6020
+ object3.iterateAllMeshes((mesh) => {
5213
6021
  const source = mesh.material;
5214
6022
  let material = this.specialMaterials.get(source);
5215
6023
  if (!material) {
@@ -5220,7 +6028,10 @@ var init_ModelObject = __esm({
5220
6028
  mesh.material = material;
5221
6029
  });
5222
6030
  for (const [name, rotation] of Object.entries(part.rotations)) {
5223
- object.getGroupByName(name)?.rotation.set(...rotation, "ZYX");
6031
+ object3.getGroupByName(name)?.rotation.set(...rotation, "ZYX");
6032
+ }
6033
+ for (const [name, position] of Object.entries(part.positions ?? {})) {
6034
+ object3.getGroupByName(name)?.position.set(...position);
5224
6035
  }
5225
6036
  }
5226
6037
  } catch (error) {
@@ -5253,6 +6064,7 @@ var init_ModelObject = __esm({
5253
6064
  if (this.atlas.model.elements) {
5254
6065
  this.atlas.model.elements?.forEach((el) => {
5255
6066
  const elGeo = this._getBoxGeometryFromElement(el).clone();
6067
+ this.geometries.add(elGeo);
5256
6068
  if (this.options.cullMask) {
5257
6069
  const indices = Array.from(elGeo.getIndex().array);
5258
6070
  elGeo.setIndex(indices.filter((_, index) => {
@@ -5263,8 +6075,9 @@ var init_ModelObject = __esm({
5263
6075
  elGeo.clearGroups();
5264
6076
  }
5265
6077
  if (this.options.uvLockRotation) {
5266
- UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler7(...this.options.uvLockRotation));
6078
+ UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler8(...this.options.uvLockRotation));
5267
6079
  }
6080
+ UVMapper.setAtlasUvBounds(elGeo, el.faces, this.atlas);
5268
6081
  if (this.options.tints) {
5269
6082
  const colors = new Float32Array(elGeo.getAttribute("position").count * 3).fill(1);
5270
6083
  for (const [faceIndex, faceName] of CUBE_FACES.entries()) {
@@ -5272,9 +6085,9 @@ var init_ModelObject = __esm({
5272
6085
  if (tintIndex === void 0 || tintIndex < 0) continue;
5273
6086
  const tint = this.options.tints[tintIndex];
5274
6087
  if (tint === void 0) continue;
5275
- const color = new Color9(tint);
6088
+ const color2 = new Color9(tint);
5276
6089
  for (let vertex = 0; vertex < 4; vertex++) {
5277
- color.toArray(colors, (faceIndex * 4 + vertex) * 3);
6090
+ color2.toArray(colors, (faceIndex * 4 + vertex) * 3);
5278
6091
  }
5279
6092
  }
5280
6093
  elGeo.setAttribute("color", new BufferAttribute5(colors, 3));
@@ -5305,10 +6118,14 @@ var init_ModelObject = __esm({
5305
6118
  let combinedGeo;
5306
6119
  if (allGeos.length > 0) {
5307
6120
  combinedGeo = mergeBufferGeometries(allGeos);
6121
+ for (const geometry of allGeos) {
6122
+ this.geometries.delete(geometry);
6123
+ }
5308
6124
  } else {
5309
6125
  combinedGeo = new BoxGeometry5(16, 16, 16);
5310
6126
  if (displayTransform) DisplayTransforms.apply(combinedGeo, displayTransform);
5311
6127
  }
6128
+ this.geometries.add(combinedGeo);
5312
6129
  combinedGeo.computeBoundingBox();
5313
6130
  let mesh;
5314
6131
  if (this.options.instanceMeshes) {
@@ -5330,9 +6147,12 @@ var init_ModelObject = __esm({
5330
6147
  }
5331
6148
  applyTextures() {
5332
6149
  if (this.atlas) {
5333
- const mat = Materials.createShadedCanvasMaterial(this.atlas.image.canvas, this.atlas.hasTransparency, false);
6150
+ const mat = Materials.createShadedCanvasMaterial(this.atlas.image.canvas, this.atlas.hasTransparency, false, true);
5334
6151
  this.atlasMaterial = mat;
5335
6152
  this.atlasTexture = mat.uniforms?.map?.value ?? mat.map;
6153
+ if (this.options.displayPosition === "gui" /* GUI */ && this.originalModel.gui_light === "front" /* FRONT */ && mat.uniforms?.SHADE) {
6154
+ mat.uniforms.SHADE.value = false;
6155
+ }
5336
6156
  this.iterateAllMeshes((mesh) => {
5337
6157
  if (mesh.geometry.hasAttribute("color")) mat.vertexColors = true;
5338
6158
  mesh.material = mat;
@@ -5341,7 +6161,9 @@ var init_ModelObject = __esm({
5341
6161
  }
5342
6162
  }
5343
6163
  updateAnimationSubscription() {
5344
- const active = !!this.parent && (!this.isInstanced || this.instanceCounter > 0);
6164
+ let root = this;
6165
+ while (root.parent && isModelObject(root.parent)) root = root.parent;
6166
+ const active = !!root.parent && (!this.isInstanced || this.instanceCounter > 0);
5345
6167
  if (active && this.atlas?.hasAnimation && this.atlasTexture) {
5346
6168
  if (!this.unsubscribeAtlas) {
5347
6169
  this.atlasTexture.needsUpdate = true;
@@ -5357,6 +6179,9 @@ var init_ModelObject = __esm({
5357
6179
  this.unsubscribeAtlas?.();
5358
6180
  this.unsubscribeAtlas = void 0;
5359
6181
  }
6182
+ for (const child of this.children) {
6183
+ if (isModelObject(child)) child.updateAnimationSubscription();
6184
+ }
5360
6185
  }
5361
6186
  nextInstance() {
5362
6187
  const reference = super.nextInstance();
@@ -5376,6 +6201,8 @@ var init_ModelObject = __esm({
5376
6201
  this.atlasMaterial = void 0;
5377
6202
  for (const material of this.specialMaterials.values()) material.dispose();
5378
6203
  this.specialMaterials.clear();
6204
+ for (const geometry of this.geometries) geometry.dispose();
6205
+ this.geometries.clear();
5379
6206
  super.disposeAndRemoveAllChildren();
5380
6207
  }
5381
6208
  };
@@ -5589,9 +6416,9 @@ var init_FluidModelObject = __esm({
5589
6416
  }
5590
6417
  }
5591
6418
  geometry.clearGroups();
5592
- const color = new Color10(this.options.tints?.[0] ?? (this.kind === "water" ? 4159204 : 16777215));
6419
+ const color2 = new Color10(this.options.tints?.[0] ?? (this.kind === "water" ? 4159204 : 16777215));
5593
6420
  const colors = new Float32Array(geometry.getAttribute("position").count * 3);
5594
- for (let i = 0; i < colors.length; i += 3) color.toArray(colors, i);
6421
+ for (let i = 0; i < colors.length; i += 3) color2.toArray(colors, i);
5595
6422
  geometry.setAttribute("color", new BufferAttribute6(colors, 3));
5596
6423
  geometry.computeBoundingBox();
5597
6424
  geometry.computeBoundingSphere();
@@ -5684,9 +6511,9 @@ var NodeEnv = class {
5684
6511
  imageSize(data) {
5685
6512
  return imageSize(Buffer.isBuffer(data) ? data : Buffer.from(data));
5686
6513
  }
5687
- openCache(name, version) {
6514
+ openCache(name, version2) {
5688
6515
  return new NodeCache(NodePersist.create({
5689
- dir: name + version,
6516
+ dir: name + version2,
5690
6517
  encoding: "utf8"
5691
6518
  }));
5692
6519
  }
@@ -5718,6 +6545,7 @@ var Meshes = class _Meshes {
5718
6545
  static createBox(key) {
5719
6546
  return new Mesh(Geometries.getBox(key.geometry), Materials.getImage(key.material));
5720
6547
  }
6548
+ /** Returns a shared Mesh object. Use {@link createBox} for a mesh with its own transform and parent. */
5721
6549
  static getBox(key) {
5722
6550
  const keyStr = serializeMeshKey(key);
5723
6551
  return Caching.boxMeshCache.get(keyStr, (k) => {
@@ -5767,6 +6595,7 @@ function shutdown() {
5767
6595
  init_AssetKey();
5768
6596
  init_AssetLoader();
5769
6597
  init_Assets();
6598
+ init_BannerPatterns();
5770
6599
 
5771
6600
  // src/assets/BlockEntities.ts
5772
6601
  init_AssetKey();
@@ -5830,6 +6659,7 @@ init_AssetParsers();
5830
6659
  init_ModelTextures();
5831
6660
  init_Caching();
5832
6661
  var Fonts = class {
6662
+ /** Combines font definitions in source-priority order. Unsupported Unihex and TrueType providers are skipped. */
5833
6663
  static async get(key = "minecraft:default") {
5834
6664
  const fontKey = typeof key === "string" ? AssetKey.parse("font", key) : key;
5835
6665
  return await Caching.fontCache.get(fontKey.serialize(), () => this.load(fontKey, []));
@@ -5845,8 +6675,8 @@ var Fonts = class {
5845
6675
  const stack = await AssetLoader.getAll(key, "json" /* JSON */);
5846
6676
  if (stack.length === 0) throw new Error(`Could not load font ${key.toNamespacedString()}`);
5847
6677
  const glyphs = /* @__PURE__ */ new Map();
5848
- for (const asset of stack) {
5849
- for (const provider of asset.providers) {
6678
+ for (const asset2 of stack) {
6679
+ for (const provider of asset2.providers) {
5850
6680
  if (provider.filter?.uniform || provider.filter?.jp) continue;
5851
6681
  let provided;
5852
6682
  switch (provider.type) {
@@ -5992,13 +6822,13 @@ var OBJExporter = class {
5992
6822
  * @param {Object3D} object - The 3D object to export.
5993
6823
  * @return {string} The exported OBJ.
5994
6824
  */
5995
- parse(object) {
6825
+ parse(object3) {
5996
6826
  let output = "";
5997
6827
  let indexVertex = 0;
5998
6828
  let indexVertexUvs = 0;
5999
6829
  let indexNormals = 0;
6000
6830
  const vertex = new Vector37();
6001
- const color = new Color4();
6831
+ const color2 = new Color4();
6002
6832
  const normal = new Vector37();
6003
6833
  const uv = new Vector22();
6004
6834
  const face = [];
@@ -6097,9 +6927,9 @@ var OBJExporter = class {
6097
6927
  vertex.applyMatrix4(points.matrixWorld);
6098
6928
  output += "v " + vertex.x + " " + vertex.y + " " + vertex.z;
6099
6929
  if (colors !== void 0) {
6100
- color.fromBufferAttribute(colors, i);
6101
- ColorManagement.workingToColorSpace(color, SRGBColorSpace3);
6102
- output += " " + color.r + " " + color.g + " " + color.b;
6930
+ color2.fromBufferAttribute(colors, i);
6931
+ ColorManagement.workingToColorSpace(color2, SRGBColorSpace3);
6932
+ output += " " + color2.r + " " + color2.g + " " + color2.b;
6103
6933
  }
6104
6934
  output += "\n";
6105
6935
  }
@@ -6111,7 +6941,7 @@ var OBJExporter = class {
6111
6941
  }
6112
6942
  indexVertex += nbVertex;
6113
6943
  }
6114
- object.traverse(function(child) {
6944
+ object3.traverse(function(child) {
6115
6945
  if (child.isMesh === true) {
6116
6946
  parseMesh(child);
6117
6947
  }
@@ -6145,9 +6975,9 @@ var PLYExporter = class {
6145
6975
  * @param {PLYExporter~Options} options - The export options.
6146
6976
  * @return {?(string|ArrayBuffer)} The exported PLY.
6147
6977
  */
6148
- parse(object, onDone, options = {}) {
6978
+ parse(object3, onDone, options = {}) {
6149
6979
  function traverseMeshes(cb) {
6150
- object.traverse(function(child) {
6980
+ object3.traverse(function(child) {
6151
6981
  if (child.isMesh === true || child.isPoints) {
6152
6982
  const mesh = child;
6153
6983
  const geometry = mesh.geometry;
@@ -6180,7 +7010,7 @@ var PLYExporter = class {
6180
7010
  for (const name of customAttributeNames) customTypes[name] = "float";
6181
7011
  let vertexCount = 0;
6182
7012
  let faceCount = 0;
6183
- object.traverse(function(child) {
7013
+ object3.traverse(function(child) {
6184
7014
  if (child.isMesh === true) {
6185
7015
  const mesh = child;
6186
7016
  const geometry = mesh.geometry;
@@ -6802,8 +7632,8 @@ function equalArray(array1, array2) {
6802
7632
  return element === array2[index];
6803
7633
  });
6804
7634
  }
6805
- function stringToArrayBuffer(text) {
6806
- return new TextEncoder().encode(text).buffer;
7635
+ function stringToArrayBuffer(text2) {
7636
+ return new TextEncoder().encode(text2).buffer;
6807
7637
  }
6808
7638
  function isIdentityMatrix(matrix) {
6809
7639
  return equalArray(matrix.elements, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
@@ -6996,12 +7826,12 @@ var GLTFWriter = class {
6996
7826
  * @param {THREE.Object3D|THREE.Material|THREE.BufferGeometry|THREE.AnimationClip} object
6997
7827
  * @param {Object} objectDef
6998
7828
  */
6999
- serializeUserData(object, objectDef) {
7000
- if (Object.keys(object.userData).length === 0) return;
7829
+ serializeUserData(object3, objectDef) {
7830
+ if (Object.keys(object3.userData).length === 0) return;
7001
7831
  const options = this.options;
7002
7832
  const extensionsUsed = this.extensionsUsed;
7003
7833
  try {
7004
- const json = JSON.parse(JSON.stringify(object.userData));
7834
+ const json = JSON.parse(JSON.stringify(object3.userData));
7005
7835
  if (options.includeCustomExtensions && json.gltfExtensions) {
7006
7836
  if (objectDef.extensions === void 0) objectDef.extensions = {};
7007
7837
  for (const extensionName in json.gltfExtensions) {
@@ -7012,7 +7842,7 @@ var GLTFWriter = class {
7012
7842
  }
7013
7843
  if (Object.keys(json).length > 0) objectDef.extras = json;
7014
7844
  } catch (error) {
7015
- console.warn("THREE.GLTFExporter: userData of '" + object.name + "' won't be serialized because of JSON.stringify error - " + error.message);
7845
+ console.warn("THREE.GLTFExporter: userData of '" + object3.name + "' won't be serialized because of JSON.stringify error - " + error.message);
7016
7846
  }
7017
7847
  }
7018
7848
  /**
@@ -7385,7 +8215,7 @@ var GLTFWriter = class {
7385
8215
  * @param {string} mimeType export format
7386
8216
  * @return {number} Index of the processed texture in the "images" array
7387
8217
  */
7388
- processImage(image, format, flipY, mimeType = "image/png") {
8218
+ processImage(image, format2, flipY, mimeType = "image/png") {
7389
8219
  if (image !== null) {
7390
8220
  const writer = this;
7391
8221
  const cache = writer.cache;
@@ -7409,8 +8239,8 @@ var GLTFWriter = class {
7409
8239
  ctx.scale(1, -1);
7410
8240
  }
7411
8241
  if (image.data !== void 0) {
7412
- if (format !== RGBAFormat2) {
7413
- console.error("GLTFExporter: Only RGBAFormat is supported.", format);
8242
+ if (format2 !== RGBAFormat2) {
8243
+ console.error("GLTFExporter: Only RGBAFormat is supported.", format2);
7414
8244
  }
7415
8245
  if (image.width > options.maxTextureSize || image.height > options.maxTextureSize) {
7416
8246
  console.warn("GLTFExporter: Image size is bigger than maxTextureSize", image);
@@ -7521,9 +8351,9 @@ var GLTFWriter = class {
7521
8351
  if (material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true) {
7522
8352
  console.warn("GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.");
7523
8353
  }
7524
- const color = material.color.toArray().concat([material.opacity]);
7525
- if (!equalArray(color, [1, 1, 1, 1])) {
7526
- materialDef.pbrMetallicRoughness.baseColorFactor = color;
8354
+ const color2 = material.color.toArray().concat([material.opacity]);
8355
+ if (!equalArray(color2, [1, 1, 1, 1])) {
8356
+ materialDef.pbrMetallicRoughness.baseColorFactor = color2;
7527
8357
  }
7528
8358
  if (material.isMeshStandardMaterial) {
7529
8359
  materialDef.pbrMetallicRoughness.metallicFactor = material.metalness;
@@ -7938,13 +8768,13 @@ var GLTFWriter = class {
7938
8768
  * @param {THREE.Object3D} object
7939
8769
  * @return {?number}
7940
8770
  */
7941
- processSkin(object) {
8771
+ processSkin(object3) {
7942
8772
  const json = this.json;
7943
8773
  const nodeMap = this.nodeMap;
7944
- const node = json.nodes[nodeMap.get(object)];
7945
- const skeleton = object.skeleton;
8774
+ const node = json.nodes[nodeMap.get(object3)];
8775
+ const skeleton = object3.skeleton;
7946
8776
  if (skeleton === void 0) return null;
7947
- const rootJoint = object.skeleton.bones[0];
8777
+ const rootJoint = object3.skeleton.bones[0];
7948
8778
  if (rootJoint === void 0) return null;
7949
8779
  const joints = [];
7950
8780
  const inverseBindMatrices = new Float32Array(skeleton.bones.length * 16);
@@ -7952,7 +8782,7 @@ var GLTFWriter = class {
7952
8782
  for (let i = 0; i < skeleton.bones.length; ++i) {
7953
8783
  joints.push(nodeMap.get(skeleton.bones[i]));
7954
8784
  temporaryBoneInverse.copy(skeleton.boneInverses[i]);
7955
- temporaryBoneInverse.multiply(object.bindMatrix).toArray(inverseBindMatrices, i * 16);
8785
+ temporaryBoneInverse.multiply(object3.bindMatrix).toArray(inverseBindMatrices, i * 16);
7956
8786
  }
7957
8787
  if (json.skins === void 0) json.skins = [];
7958
8788
  json.skins.push({
@@ -7968,19 +8798,19 @@ var GLTFWriter = class {
7968
8798
  * @param {THREE.Object3D} object Object3D to processNodeAsync
7969
8799
  * @return {Promise<number>} Index of the node in the nodes list
7970
8800
  */
7971
- async processNodeAsync(object) {
8801
+ async processNodeAsync(object3) {
7972
8802
  const json = this.json;
7973
8803
  const options = this.options;
7974
8804
  const nodeMap = this.nodeMap;
7975
8805
  if (!json.nodes) json.nodes = [];
7976
- if (object.pivot !== null) {
7977
- return await this._processNodeWithPivotAsync(object);
8806
+ if (object3.pivot !== null) {
8807
+ return await this._processNodeWithPivotAsync(object3);
7978
8808
  }
7979
8809
  const nodeDef = {};
7980
8810
  if (options.trs) {
7981
- const rotation = object.quaternion.toArray();
7982
- const position = object.position.toArray();
7983
- const scale = object.scale.toArray();
8811
+ const rotation = object3.quaternion.toArray();
8812
+ const position = object3.position.toArray();
8813
+ const scale = object3.scale.toArray();
7984
8814
  if (!equalArray(rotation, [0, 0, 0, 1])) {
7985
8815
  nodeDef.rotation = rotation;
7986
8816
  }
@@ -7991,28 +8821,28 @@ var GLTFWriter = class {
7991
8821
  nodeDef.scale = scale;
7992
8822
  }
7993
8823
  } else {
7994
- if (object.matrixAutoUpdate) {
7995
- object.updateMatrix();
8824
+ if (object3.matrixAutoUpdate) {
8825
+ object3.updateMatrix();
7996
8826
  }
7997
- if (isIdentityMatrix(object.matrix) === false) {
7998
- nodeDef.matrix = object.matrix.elements;
8827
+ if (isIdentityMatrix(object3.matrix) === false) {
8828
+ nodeDef.matrix = object3.matrix.elements;
7999
8829
  }
8000
8830
  }
8001
- if (object.name !== "") nodeDef.name = String(object.name);
8002
- this.serializeUserData(object, nodeDef);
8003
- if (object.isMesh || object.isLine || object.isPoints) {
8004
- const meshIndex = await this.processMeshAsync(object);
8831
+ if (object3.name !== "") nodeDef.name = String(object3.name);
8832
+ this.serializeUserData(object3, nodeDef);
8833
+ if (object3.isMesh || object3.isLine || object3.isPoints) {
8834
+ const meshIndex = await this.processMeshAsync(object3);
8005
8835
  if (meshIndex !== null) nodeDef.mesh = meshIndex;
8006
- } else if (object.isCamera) {
8007
- nodeDef.camera = this.processCamera(object);
8836
+ } else if (object3.isCamera) {
8837
+ nodeDef.camera = this.processCamera(object3);
8008
8838
  }
8009
- if (object.isSkinnedMesh) this.skins.push(object);
8839
+ if (object3.isSkinnedMesh) this.skins.push(object3);
8010
8840
  const nodeIndex = json.nodes.push(nodeDef) - 1;
8011
- nodeMap.set(object, nodeIndex);
8012
- if (object.children.length > 0) {
8841
+ nodeMap.set(object3, nodeIndex);
8842
+ if (object3.children.length > 0) {
8013
8843
  const children = [];
8014
- for (let i = 0, l = object.children.length; i < l; i++) {
8015
- const child = object.children[i];
8844
+ for (let i = 0, l = object3.children.length; i < l; i++) {
8845
+ const child = object3.children[i];
8016
8846
  if (child.visible || options.onlyVisible === false) {
8017
8847
  const childNodeIndex = await this.processNodeAsync(child);
8018
8848
  if (childNodeIndex !== null) children.push(childNodeIndex);
@@ -8021,7 +8851,7 @@ var GLTFWriter = class {
8021
8851
  if (children.length > 0) nodeDef.children = children;
8022
8852
  }
8023
8853
  await this._invokeAllAsync(function(ext) {
8024
- ext.writeNode && ext.writeNode(object, nodeDef);
8854
+ ext.writeNode && ext.writeNode(object3, nodeDef);
8025
8855
  });
8026
8856
  return nodeIndex;
8027
8857
  }
@@ -8030,19 +8860,19 @@ var GLTFWriter = class {
8030
8860
  * @param {THREE.Object3D} object Object3D with pivot
8031
8861
  * @return {Promise<number>} Index of the container node
8032
8862
  */
8033
- async _processNodeWithPivotAsync(object) {
8863
+ async _processNodeWithPivotAsync(object3) {
8034
8864
  const json = this.json;
8035
8865
  const options = this.options;
8036
8866
  const nodeMap = this.nodeMap;
8037
- const pivot = object.pivot;
8867
+ const pivot = object3.pivot;
8038
8868
  const containerDef = {};
8039
- const rotation = object.quaternion.toArray();
8869
+ const rotation = object3.quaternion.toArray();
8040
8870
  const position = [
8041
- object.position.x + pivot.x,
8042
- object.position.y + pivot.y,
8043
- object.position.z + pivot.z
8871
+ object3.position.x + pivot.x,
8872
+ object3.position.y + pivot.y,
8873
+ object3.position.z + pivot.z
8044
8874
  ];
8045
- const scale = object.scale.toArray();
8875
+ const scale = object3.scale.toArray();
8046
8876
  if (!equalArray(rotation, [0, 0, 0, 1])) {
8047
8877
  containerDef.rotation = rotation;
8048
8878
  }
@@ -8053,28 +8883,28 @@ var GLTFWriter = class {
8053
8883
  containerDef.scale = scale;
8054
8884
  }
8055
8885
  containerDef.extras = { pivot: pivot.toArray() };
8056
- if (object.name !== "") containerDef.name = String(object.name);
8057
- this.serializeUserData(object, containerDef);
8886
+ if (object3.name !== "") containerDef.name = String(object3.name);
8887
+ this.serializeUserData(object3, containerDef);
8058
8888
  const containerIndex = json.nodes.push(containerDef) - 1;
8059
- nodeMap.set(object, containerIndex);
8889
+ nodeMap.set(object3, containerIndex);
8060
8890
  const childDef = {};
8061
8891
  const childPosition = [-pivot.x, -pivot.y, -pivot.z];
8062
8892
  if (!equalArray(childPosition, [0, 0, 0])) {
8063
8893
  childDef.translation = childPosition;
8064
8894
  }
8065
- if (object.isMesh || object.isLine || object.isPoints) {
8066
- const meshIndex = await this.processMeshAsync(object);
8895
+ if (object3.isMesh || object3.isLine || object3.isPoints) {
8896
+ const meshIndex = await this.processMeshAsync(object3);
8067
8897
  if (meshIndex !== null) childDef.mesh = meshIndex;
8068
- } else if (object.isCamera) {
8069
- childDef.camera = this.processCamera(object);
8898
+ } else if (object3.isCamera) {
8899
+ childDef.camera = this.processCamera(object3);
8070
8900
  }
8071
- if (object.isSkinnedMesh) this.skins.push(object);
8901
+ if (object3.isSkinnedMesh) this.skins.push(object3);
8072
8902
  const childIndex = json.nodes.push(childDef) - 1;
8073
8903
  const containerChildren = [childIndex];
8074
- if (object.children.length > 0) {
8904
+ if (object3.children.length > 0) {
8075
8905
  const grandchildren = [];
8076
- for (let i = 0, l = object.children.length; i < l; i++) {
8077
- const child = object.children[i];
8906
+ for (let i = 0, l = object3.children.length; i < l; i++) {
8907
+ const child = object3.children[i];
8078
8908
  if (child.visible || options.onlyVisible === false) {
8079
8909
  const childNodeIndex = await this.processNodeAsync(child);
8080
8910
  if (childNodeIndex !== null) grandchildren.push(childNodeIndex);
@@ -8084,7 +8914,7 @@ var GLTFWriter = class {
8084
8914
  }
8085
8915
  containerDef.children = containerChildren;
8086
8916
  await this._invokeAllAsync(function(ext) {
8087
- ext.writeNode && ext.writeNode(object, containerDef);
8917
+ ext.writeNode && ext.writeNode(object3, containerDef);
8088
8918
  });
8089
8919
  return containerIndex;
8090
8920
  }
@@ -8527,10 +9357,10 @@ var GLTFMeshGpuInstancing = class {
8527
9357
  this.writer = writer;
8528
9358
  this.name = "EXT_mesh_gpu_instancing";
8529
9359
  }
8530
- writeNode(object, nodeDef) {
8531
- if (!object.isInstancedMesh) return;
9360
+ writeNode(object3, nodeDef) {
9361
+ if (!object3.isInstancedMesh) return;
8532
9362
  const writer = this.writer;
8533
- const mesh = object;
9363
+ const mesh = object3;
8534
9364
  const translationAttr = new Float32Array(mesh.count * 3);
8535
9365
  const rotationAttr = new Float32Array(mesh.count * 4);
8536
9366
  const scaleAttr = new Float32Array(mesh.count * 3);
@@ -8720,8 +9550,8 @@ var SceneExporter = class {
8720
9550
  }
8721
9551
  }
8722
9552
  };
8723
- function localMatrix(object) {
8724
- return object.matrixAutoUpdate ? new Matrix47().compose(object.position, object.quaternion, object.scale) : object.matrix.clone();
9553
+ function localMatrix(object3) {
9554
+ return object3.matrixAutoUpdate ? new Matrix47().compose(object3.position, object3.quaternion, object3.scale) : object3.matrix.clone();
8725
9555
  }
8726
9556
  function createSnapshot(root, gltf, bakeColors = false) {
8727
9557
  const scene = new Scene3();
@@ -8825,24 +9655,24 @@ function createSnapshot(root, gltf, bakeColors = false) {
8825
9655
  scene.add(copy);
8826
9656
  }
8827
9657
  };
8828
- const visit = (object, parentWorld) => {
8829
- if (!object.visible) return;
8830
- const world = parentWorld.clone().multiply(localMatrix(object));
8831
- const mesh = object;
9658
+ const visit = (object3, parentWorld) => {
9659
+ if (!object3.visible) return;
9660
+ const world = parentWorld.clone().multiply(localMatrix(object3));
9661
+ const mesh = object3;
8832
9662
  if (mesh.isMesh) {
8833
9663
  const instanced = mesh;
8834
9664
  if (instanced.isInstancedMesh) {
8835
9665
  for (let i = 0; i < instanced.count; i++) {
8836
9666
  const matrix = new Matrix47();
8837
9667
  instanced.getMatrixAt(i, matrix);
8838
- const color = instanced.instanceColor ? new Color7().fromBufferAttribute(instanced.instanceColor, i) : void 0;
8839
- addMesh(mesh, world.clone().multiply(matrix), color);
9668
+ const color2 = instanced.instanceColor ? new Color7().fromBufferAttribute(instanced.instanceColor, i) : void 0;
9669
+ addMesh(mesh, world.clone().multiply(matrix), color2);
8840
9670
  }
8841
9671
  } else {
8842
9672
  addMesh(mesh, world);
8843
9673
  }
8844
9674
  }
8845
- for (const child of object.children) visit(child, world);
9675
+ for (const child of object3.children) visit(child, world);
8846
9676
  };
8847
9677
  try {
8848
9678
  const ancestors = [];
@@ -8915,16 +9745,17 @@ function copyDataTexture(source) {
8915
9745
 
8916
9746
  // src/gui/GuiHelper.ts
8917
9747
  init_AssetKey();
9748
+ init_ModelTextures();
8918
9749
 
8919
9750
  // src/gui/GuiText.ts
8920
9751
  import { BufferGeometry as BufferGeometry2, Color as Color8, Float32BufferAttribute as Float32BufferAttribute4 } from "three";
8921
- async function layoutGuiText(text, options = {}) {
9752
+ async function layoutGuiText(text2, options = {}) {
8922
9753
  const maxWidth = options.maxWidth ?? Infinity, lineHeight = options.lineHeight ?? 9;
8923
9754
  if (!(maxWidth > 0) || !Number.isFinite(lineHeight) || lineHeight <= 0) {
8924
9755
  throw new Error("Text wrap width and line height must be positive");
8925
9756
  }
8926
9757
  const font = await Fonts.get(options.font);
8927
- const runs = typeof text === "string" ? [{ text }] : text;
9758
+ const runs = typeof text2 === "string" ? [{ text: text2 }] : text2;
8928
9759
  const glyphs = [], lineWidths = [];
8929
9760
  let line = [], width = 0;
8930
9761
  let trailingNewline = false;
@@ -8978,7 +9809,7 @@ function createGuiTextGeometry(layout, shadow = true) {
8978
9809
  batch = { image: glyph?.image, positions: [], uvs: [], colors: [], indices: [] };
8979
9810
  batches.push(batch);
8980
9811
  }
8981
- const color = new Color8(isShadow ? (style.color & 16579836) >> 2 : style.color);
9812
+ const color2 = new Color8(isShadow ? (style.color & 16579836) >> 2 : style.color);
8982
9813
  const offset = isShadow ? 1 : 0;
8983
9814
  const top = glyph ? 7 - glyph.ascent : 0;
8984
9815
  const height = glyph ? glyph.height * glyph.scale : 8;
@@ -9006,7 +9837,7 @@ function createGuiTextGeometry(layout, shadow = true) {
9006
9837
  const v0 = glyph ? 1 - glyph.y / glyph.image.height : 1;
9007
9838
  const v1 = glyph ? 1 - (glyph.y + glyph.height) / glyph.image.height : 0;
9008
9839
  batch.uvs.push(u0, v0, u1, v0, u0, v1, u1, v1);
9009
- for (let i = 0; i < 4; i++) batch.colors.push(color.r, color.g, color.b);
9840
+ for (let i = 0; i < 4; i++) batch.colors.push(color2.r, color2.g, color2.b);
9010
9841
  batch.indices.push(base, base + 2, base + 1, base + 2, base + 3, base + 1);
9011
9842
  };
9012
9843
  for (const boldOffset of style.bold ? [0, 1] : [0]) {
@@ -9024,7 +9855,128 @@ function createGuiTextGeometry(layout, shadow = true) {
9024
9855
  }
9025
9856
 
9026
9857
  // src/gui/GuiHelper.ts
9858
+ var GUI_CONTAINER_LAYOUTS = {
9859
+ generic_54: {
9860
+ texture: "minecraft:gui/container/generic_54",
9861
+ crop: [0, 0, 176, 222],
9862
+ slotOrigin: [8, 18],
9863
+ slotOffset: [18, 18],
9864
+ rowSize: 9,
9865
+ slotCount: 54
9866
+ },
9867
+ crafting_table: {
9868
+ texture: "minecraft:gui/container/crafting_table",
9869
+ crop: [0, 0, 176, 166],
9870
+ slotOrigin: [30, 17],
9871
+ slotOffset: [18, 18],
9872
+ rowSize: 3,
9873
+ slotCount: 9,
9874
+ resultPosition: [124, 35]
9875
+ }
9876
+ };
9027
9877
  var GuiHelper = class {
9878
+ /**
9879
+ * Creates a 182×5 boss bar using the active resource pack. Finite progress is clamped to 0–1.
9880
+ * The fill crops the source image; explicit crops do not apply `.mcmeta` sprite scaling.
9881
+ */
9882
+ static async bossBar(options = {}) {
9883
+ const { color: color2 = "pink", progress = 1, position = [0, 0] } = options;
9884
+ this.validatePosition(position);
9885
+ if (!["pink", "blue", "red", "green", "yellow", "purple", "white"].includes(color2)) {
9886
+ throw new Error(`Unsupported boss bar color: ${color2}`);
9887
+ }
9888
+ if (!Number.isFinite(progress)) throw new Error("Boss bar progress must be finite");
9889
+ const value = Math.max(0, Math.min(1, progress));
9890
+ const width = value > 0 ? Math.floor(value * 181) + 1 : 0;
9891
+ const texture = `minecraft:gui/sprites/boss_bar/${color2}`;
9892
+ const layers = [{
9893
+ name: "boss-bar-background",
9894
+ texture: `${texture}_background`,
9895
+ position: [...position],
9896
+ size: [182, 5]
9897
+ }];
9898
+ if (width > 0) {
9899
+ layers.push({
9900
+ ...await this.cropTexture(`${texture}_progress`, [0, 0, width, 5], [182, 5], position),
9901
+ name: "boss-bar-progress"
9902
+ });
9903
+ }
9904
+ return layers;
9905
+ }
9906
+ /**
9907
+ * Creates a 192×192 book page with optional arrows and supplied text. Buttons are hidden by default.
9908
+ * Callers choose the page content and button states; this helper does not paginate or handle clicks.
9909
+ */
9910
+ static async book(options = {}) {
9911
+ const {
9912
+ position = [0, 0],
9913
+ previous = "hidden",
9914
+ next = "hidden",
9915
+ text: text2,
9916
+ color: color2 = 0,
9917
+ shadow = false,
9918
+ maxWidth = 114,
9919
+ lineHeight = 9,
9920
+ ...style
9921
+ } = options;
9922
+ this.validatePosition(position);
9923
+ for (const state of [previous, next]) {
9924
+ if (!["hidden", "normal", "hover"].includes(state)) throw new Error(`Unsupported book button state: ${state}`);
9925
+ }
9926
+ const layers = [{
9927
+ ...await this.cropTexture("minecraft:gui/book", [0, 0, 192, 192], [256, 256], position),
9928
+ name: "book-background"
9929
+ }];
9930
+ for (const [name, direction, state, x] of [
9931
+ ["previous", "backward", previous, 43],
9932
+ ["next", "forward", next, 116]
9933
+ ]) {
9934
+ if (state === "hidden") continue;
9935
+ layers.push({
9936
+ name: `book-${name}`,
9937
+ texture: `minecraft:gui/sprites/widget/page_${direction}${state === "hover" ? "_highlighted" : ""}`,
9938
+ position: [position[0] + x, position[1] + 157],
9939
+ size: [23, 13]
9940
+ });
9941
+ }
9942
+ if (text2 !== void 0) {
9943
+ layers.push({
9944
+ name: "book-text",
9945
+ text: text2,
9946
+ position: [position[0] + 36, position[1] + 30],
9947
+ color: color2,
9948
+ shadow,
9949
+ maxWidth,
9950
+ lineHeight,
9951
+ ...style
9952
+ });
9953
+ }
9954
+ return layers;
9955
+ }
9956
+ /** Creates a container background. Use {@link GUI_CONTAINER_LAYOUTS} with {@link inventorySlot} to place its items. */
9957
+ static async container(type, position = [0, 0]) {
9958
+ this.validatePosition(position);
9959
+ if (type !== "generic_54" && type !== "crafting_table") throw new Error(`Unsupported GUI container: ${type}`);
9960
+ const layout = GUI_CONTAINER_LAYOUTS[type];
9961
+ return [{ ...await this.cropTexture(layout.texture, layout.crop, [256, 256], position), name: "background" }];
9962
+ }
9963
+ static validatePosition(position) {
9964
+ if (!Array.isArray(position) || position.length !== 2 || !position.every(Number.isFinite)) {
9965
+ throw new Error("GUI position must contain two finite coordinates");
9966
+ }
9967
+ }
9968
+ static async cropTexture(texture, crop, referenceSize, position) {
9969
+ const image = await ModelTextures.preload(AssetKey.parse("textures", texture));
9970
+ if (!image) throw new Error(`Could not load GUI texture ${texture}`);
9971
+ const [x, y, width, height] = crop;
9972
+ const scaleX = image.width / referenceSize[0], scaleY = image.height / referenceSize[1];
9973
+ return {
9974
+ texture,
9975
+ crop: [x * scaleX, y * scaleY, width * scaleX, height * scaleY],
9976
+ position: [...position],
9977
+ size: [width, height]
9978
+ };
9979
+ }
9028
9980
  /** Sizes a tooltip around supplied text; the first entry is its title. */
9029
9981
  static async tooltip(lines, options = {}) {
9030
9982
  if (!lines.length) return [];
@@ -9040,20 +9992,28 @@ var GuiHelper = class {
9040
9992
  size: [width + 24, height + 24]
9041
9993
  }));
9042
9994
  let y = position[1];
9043
- lines.forEach((text, index) => {
9044
- layers.push({ ...textOptions, name: `tooltip-line-${index}`, text, position: [position[0], y] });
9995
+ lines.forEach((text2, index) => {
9996
+ layers.push({ ...textOptions, name: `tooltip-line-${index}`, text: text2, position: [position[0], y] });
9045
9997
  y += layouts[index].height + (index === 0 ? titleGap : 0);
9046
9998
  });
9047
9999
  return layers;
9048
10000
  }
10001
+ /**
10002
+ * Calculates a slot's top-left position in GUI pixels.
10003
+ * @param slot - Zero-based slot index, or `[column, row]`.
10004
+ * @param origin - Position of the first slot.
10005
+ * @param offset - Horizontal and vertical spacing between slot origins, in pixels.
10006
+ * @param rowSize - Slots per row when `slot` is an index.
10007
+ */
9049
10008
  static inventorySlot(slot, origin = [0, 0], offset = [18, 18], rowSize = 9) {
9050
10009
  const [column, row] = typeof slot === "number" ? [slot % rowSize, Math.floor(slot / rowSize)] : slot;
9051
10010
  return [origin[0] + column * offset[0], origin[1] + row * offset[1]];
9052
10011
  }
9053
10012
  /** Creates crafting-table layers for ingredients and the result; stack counts are not drawn. */
9054
10013
  static recipe(recipe, options = {}) {
10014
+ const layout = GUI_CONTAINER_LAYOUTS.crafting_table;
9055
10015
  const layers = [
9056
- { name: "background", texture: "minecraft:gui/container/crafting_table", crop: [0, 0, 176, 166] }
10016
+ { name: "background", texture: layout.texture, crop: [...layout.crop] }
9057
10017
  ];
9058
10018
  const itemKey = (ingredient) => {
9059
10019
  const item = typeof ingredient === "string" && !ingredient.startsWith("#") ? ingredient : typeof ingredient === "object" && "item" in ingredient ? ingredient.item : options.resolveIngredient?.(ingredient);
@@ -9068,17 +10028,17 @@ var GuiHelper = class {
9068
10028
  layers.push({
9069
10029
  name: `ingredient-${slot}`,
9070
10030
  item: itemKey(ingredient),
9071
- position: this.inventorySlot(slot, [30, 17], [18, 18], 3)
10031
+ position: this.inventorySlot(slot, layout.slotOrigin, layout.slotOffset, layout.rowSize)
9072
10032
  });
9073
10033
  };
9074
10034
  switch (recipe.type) {
9075
10035
  case "minecraft:crafting_shaped":
9076
10036
  case "crafting_shaped": {
9077
- const pattern = recipe.pattern;
9078
- if (!pattern.length || pattern.length > 3 || !pattern[0].length || pattern[0].length > 3 || pattern.some((row) => row.length !== pattern[0].length)) {
10037
+ const pattern2 = recipe.pattern;
10038
+ if (!pattern2.length || pattern2.length > 3 || !pattern2[0].length || pattern2[0].length > 3 || pattern2.some((row) => row.length !== pattern2[0].length)) {
9079
10039
  throw new Error("Crafting patterns must be rectangular, with 1\u20133 rows and columns");
9080
10040
  }
9081
- const cells = pattern.flatMap((row, y) => row.split("").map((symbol, x) => ({ symbol, x, y }))).filter((cell) => cell.symbol !== " ");
10041
+ const cells = pattern2.flatMap((row, y) => row.split("").map((symbol, x) => ({ symbol, x, y }))).filter((cell) => cell.symbol !== " ");
9082
10042
  if (!cells.length) throw new Error("Crafting patterns must contain an ingredient");
9083
10043
  const minX = Math.min(...cells.map((cell) => cell.x)), maxX = Math.max(...cells.map((cell) => cell.x));
9084
10044
  const minY = Math.min(...cells.map((cell) => cell.y)), maxY = Math.max(...cells.map((cell) => cell.y));
@@ -9103,7 +10063,7 @@ var GuiHelper = class {
9103
10063
  layers.push({
9104
10064
  name: "result",
9105
10065
  item: itemKey("id" in recipe.result ? recipe.result.id : recipe.result.item),
9106
- position: [124, 35]
10066
+ position: [...layout.resultPosition]
9107
10067
  });
9108
10068
  return layers;
9109
10069
  }
@@ -9197,7 +10157,6 @@ init_SceneObject();
9197
10157
  init_Materials();
9198
10158
  init_ModelObject();
9199
10159
  init_DisplayPosition();
9200
- init_GuiLight();
9201
10160
  import { Box2, Box3 as Box32, DoubleSide as DoubleSide3, Group, Matrix4 as Matrix410, Mesh as Mesh6, MeshBasicMaterial as MeshBasicMaterial5, Vector2 as Vector23 } from "three";
9202
10161
  var GuiObject = class extends SceneObject {
9203
10162
  constructor(textureLayers, options) {
@@ -9266,12 +10225,16 @@ var GuiObject = class extends SceneObject {
9266
10225
  mesh2.geometry.computeBoundingBox();
9267
10226
  bounds.union(mesh2.geometry.boundingBox.clone().applyMatrix4(new Matrix410().multiplyMatrices(inverse, mesh2.matrixWorld)));
9268
10227
  });
9269
- item.position.z = depth - bounds.min.z;
9270
- depth += bounds.max.z - bounds.min.z + 0.01;
10228
+ item.position.z = depth;
10229
+ if (!bounds.isEmpty()) {
10230
+ item.position.z -= bounds.min.z;
10231
+ depth += bounds.max.z - bounds.min.z;
10232
+ this.bounds.expandByPoint(new Vector23(bounds.min.x, -bounds.max.y));
10233
+ this.bounds.expandByPoint(new Vector23(bounds.max.x, -bounds.min.y));
10234
+ }
10235
+ depth += 0.01;
9271
10236
  this.bounds.expandByPoint(new Vector23(x, y));
9272
10237
  this.bounds.expandByPoint(new Vector23(x + width2, y + height2));
9273
- this.bounds.expandByPoint(new Vector23(bounds.min.x, -bounds.max.y));
9274
- this.bounds.expandByPoint(new Vector23(bounds.max.x, -bounds.min.y));
9275
10238
  continue;
9276
10239
  }
9277
10240
  const key = typeof layer.texture === "string" ? AssetKey.parse("textures", layer.texture) : layer.texture;
@@ -9300,7 +10263,7 @@ var GuiObject = class extends SceneObject {
9300
10263
  }
9301
10264
  async createItem(layer, index) {
9302
10265
  const key = typeof layer.item === "string" ? AssetKey.parse("models", layer.item) : layer.item;
9303
- const model = await Models.getMerged(key);
10266
+ const model = await Models.getMerged(key, { ...layer.context, displayContext: "gui" /* GUI */ });
9304
10267
  if (!model) throw new Error(`Could not load GUI item ${key.toNamespacedString()}`);
9305
10268
  const item = new ModelObject(model, {
9306
10269
  displayPosition: "gui" /* GUI */,
@@ -9311,15 +10274,13 @@ var GuiObject = class extends SceneObject {
9311
10274
  item.name = `group:${layer.name ?? index}`;
9312
10275
  this.add(item);
9313
10276
  await item.init();
9314
- item.iterateAllMeshes((mesh) => {
9315
- if (!model.special) this.geometries.add(mesh.geometry);
10277
+ const meshes = [];
10278
+ item.iterateAllMeshes((mesh) => meshes.push(mesh));
10279
+ meshes.forEach((mesh, partIndex) => {
9316
10280
  mesh.name = `mesh:${layer.name ?? index}`;
9317
- mesh.renderOrder = index;
10281
+ mesh.renderOrder = index + partIndex / (meshes.length + 1) * 0.5;
9318
10282
  for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
9319
10283
  material.transparent = true;
9320
- if (model.gui_light === "front" /* FRONT */ && material.uniforms?.SHADE) {
9321
- material.uniforms.SHADE.value = false;
9322
- }
9323
10284
  }
9324
10285
  });
9325
10286
  return item;
@@ -9490,29 +10451,29 @@ var SceneInspector = class {
9490
10451
  this.objectControlsContainer.innerHTML = "";
9491
10452
  this.addControls(targetObject, targetIntersection, instance);
9492
10453
  }
9493
- addControls(object, intersection, instance) {
10454
+ addControls(object3, intersection, instance) {
9494
10455
  const container = document.createElement("div");
9495
10456
  container.append(this.separator("Select Parent/Child"));
9496
- if (object.parent) {
9497
- container.append(this.buttonControl("Select Parent " + object.parent.constructor.name + " " + object.parent.name, "P", () => {
9498
- this.selectObject(object.parent, intersection, instance);
10457
+ if (object3.parent) {
10458
+ container.append(this.buttonControl("Select Parent " + object3.parent.constructor.name + " " + object3.parent.name, "P", () => {
10459
+ this.selectObject(object3.parent, intersection, instance);
9499
10460
  }));
9500
10461
  }
9501
- if (object.children.length > 0) {
10462
+ if (object3.children.length > 0) {
9502
10463
  let i = 1;
9503
- for (let child of object.children) {
10464
+ for (let child of object3.children) {
9504
10465
  container.append(this.buttonControl("Select Child " + child.constructor.name + " " + child.name, "C" + i++, () => {
9505
10466
  this.selectObject(child, intersection, instance);
9506
10467
  }));
9507
10468
  }
9508
10469
  }
9509
10470
  container.append(this.separator());
9510
- const selectedInstance = instance && (object === instance.instanceable || object === intersection?.object) ? instance : void 0;
10471
+ const selectedInstance = instance && (object3 === instance.instanceable || object3 === intersection?.object) ? instance : void 0;
9511
10472
  if (!selectedInstance) {
9512
- container.append(this.toggleControl("Visibility", "V", object.visible, (v) => object.visible = v));
10473
+ container.append(this.toggleControl("Visibility", "V", object3.visible, (v) => object3.visible = v));
9513
10474
  }
9514
10475
  container.append(this.separator("Mesh"));
9515
- this.addObjectControls(object, intersection, container, selectedInstance);
10476
+ this.addObjectControls(object3, intersection, container, selectedInstance);
9516
10477
  container.append(this.separator());
9517
10478
  this.objectControlsContainer.append(container);
9518
10479
  }
@@ -9703,6 +10664,7 @@ var SceneStatsDisplay = class {
9703
10664
  appendTo(el) {
9704
10665
  el.append(this.statsContainer);
9705
10666
  }
10667
+ /** Stops the refresh timer and removes the panel from the page. */
9706
10668
  dispose() {
9707
10669
  clearInterval(this.interval);
9708
10670
  this.interval = void 0;
@@ -9727,6 +10689,7 @@ var InstanceManager = class {
9727
10689
  this.isInstanceManager = true;
9728
10690
  this.instanceCache = {};
9729
10691
  }
10692
+ /** Allocates a new placement of a cached model, or returns `undefined` if the key is unknown. */
9730
10693
  async get(key) {
9731
10694
  if (key in this.instanceCache) {
9732
10695
  console.debug(p9, "key in cache", key);
@@ -9734,6 +10697,7 @@ var InstanceManager = class {
9734
10697
  }
9735
10698
  return void 0;
9736
10699
  }
10700
+ /** Allocates a placement, calling the supplier once when the shared model is first needed. */
9737
10701
  async getOrCreate(key, supplier) {
9738
10702
  if (key in this.instanceCache) {
9739
10703
  return await this.get(key);
@@ -9750,6 +10714,7 @@ var InstanceManager = class {
9750
10714
  throw error;
9751
10715
  }
9752
10716
  }
10717
+ /** Forgets cached models without disposing them or releasing their existing placements. */
9753
10718
  reset() {
9754
10719
  for (let instanceCacheKey in this.instanceCache) {
9755
10720
  delete this.instanceCache[instanceCacheKey];
@@ -9783,7 +10748,7 @@ init_SpecialItems();
9783
10748
  init_BlockStates();
9784
10749
  init_MineRenderError();
9785
10750
  init_util();
9786
- import { Euler as Euler8 } from "three";
10751
+ import { Euler as Euler9 } from "three";
9787
10752
  function matchesCondition(condition, state) {
9788
10753
  return Object.entries(condition).every(([key, value]) => {
9789
10754
  if (Array.isArray(value)) {
@@ -9795,6 +10760,7 @@ function matchesCondition(condition, state) {
9795
10760
  });
9796
10761
  }
9797
10762
  var BlockStateResolver = class {
10763
+ /** Loads vanilla property defaults, falling back to preview values inferred from the blockstate file. */
9798
10764
  static async defaults(blockState) {
9799
10765
  const defaults = blockState.key ? await BlockStates.getDefaultState(blockState.key) : void 0;
9800
10766
  const state = {};
@@ -9814,9 +10780,11 @@ var BlockStateResolver = class {
9814
10780
  }
9815
10781
  return state;
9816
10782
  }
10783
+ /** Selects matching models. Supply `choose` to control selection within each alternatives array. */
9817
10784
  static select(blockState, state, choose = this.choose) {
9818
10785
  return this.matching(blockState, state).map(choose);
9819
10786
  }
10787
+ /** Returns matching model groups before choosing weighted alternatives. */
9820
10788
  static matching(blockState, state) {
9821
10789
  const out = [];
9822
10790
  if (blockState.variants) {
@@ -9840,6 +10808,7 @@ var BlockStateResolver = class {
9840
10808
  }
9841
10809
  return out;
9842
10810
  }
10811
+ /** Selects a random alternative using positive integer weights, each defaulting to 1. */
9843
10812
  static choose(variants) {
9844
10813
  if (!Array.isArray(variants)) return variants;
9845
10814
  if (!variants.length) throw new MineRenderError("Blockstate variant arrays must not be empty");
@@ -9857,8 +10826,9 @@ var BlockStateResolver = class {
9857
10826
  }
9858
10827
  return variants[variants.length - 1];
9859
10828
  }
10829
+ /** Converts blockstate rotations in degrees into the Euler rotation used by model rendering. */
9860
10830
  static rotation(variant) {
9861
- const rotation = new Euler8();
10831
+ const rotation = new Euler9();
9862
10832
  if (typeof variant.x !== "undefined") {
9863
10833
  rotation.x = toRadians(clampRotationDegrees(variant.x));
9864
10834
  }
@@ -9919,29 +10889,29 @@ var BlockTints = class {
9919
10889
  }
9920
10890
  }
9921
10891
  if (!indices.size) return tints;
9922
- let color;
10892
+ let color2;
9923
10893
  switch (rule.source) {
9924
10894
  case "constant":
9925
- color = rule.color;
10895
+ color2 = rule.color;
9926
10896
  break;
9927
10897
  case "grass":
9928
- color = await Colormaps.grassColor(key);
10898
+ color2 = await Colormaps.grassColor(key);
9929
10899
  break;
9930
10900
  case "redstone": {
9931
10901
  const power = Number(state.power ?? 0) / 15;
9932
10902
  const red = power * 0.6 + (power > 0 ? 0.4 : 0.3);
9933
10903
  const green = Math.max(0, power * power * 0.7 - 0.5);
9934
10904
  const blue = Math.max(0, power * power * 0.6 - 0.7);
9935
- color = Math.floor(red * 255) << 16 | Math.floor(green * 255) << 8 | Math.floor(blue * 255);
10905
+ color2 = Math.floor(red * 255) << 16 | Math.floor(green * 255) << 8 | Math.floor(blue * 255);
9936
10906
  break;
9937
10907
  }
9938
10908
  case "stem": {
9939
10909
  const age = Number(state.age ?? 0);
9940
- color = age * 32 << 16 | 255 - age * 8 << 8 | age * 4;
10910
+ color2 = age * 32 << 16 | 255 - age * 8 << 8 | age * 4;
9941
10911
  break;
9942
10912
  }
9943
10913
  }
9944
- return { ...Object.fromEntries([...indices].map((index) => [index, color])), ...tints };
10914
+ return { ...Object.fromEntries([...indices].map((index) => [index, color2])), ...tints };
9945
10915
  }
9946
10916
  };
9947
10917
 
@@ -9953,7 +10923,7 @@ var import_ts_deepmerge6 = __toESM(require_dist());
9953
10923
  init_MineRenderError();
9954
10924
  init_InstanceReference();
9955
10925
  init_AssetKey();
9956
- import { Euler as Euler9, Matrix4 as Matrix411, Quaternion as Quaternion5, Vector3 as Vector312 } from "three";
10926
+ import { Euler as Euler10, Matrix4 as Matrix411, Quaternion as Quaternion5, Vector3 as Vector312 } from "three";
9957
10927
  init_ModelCulling();
9958
10928
  init_FluidGeometry();
9959
10929
  init_Entities();
@@ -10083,8 +11053,8 @@ var BlockObject = class _BlockObject extends SceneObject {
10083
11053
  return false;
10084
11054
  }
10085
11055
  return this._models.some((model) => {
10086
- const object = isInstanceReference(model) ? model.instanceable : model;
10087
- return object.isOpaqueFullCube && this.getModelCullMask(model, 63) === 63;
11056
+ const object3 = isInstanceReference(model) ? model.instanceable : model;
11057
+ return object3.isOpaqueFullCube && this.getModelCullMask(model, 63) === 63;
10088
11058
  });
10089
11059
  }
10090
11060
  getModelMatrix(model) {
@@ -10093,14 +11063,14 @@ var BlockObject = class _BlockObject extends SceneObject {
10093
11063
  return model.matrix.clone();
10094
11064
  }
10095
11065
  getModelCullMask(model, worldMask) {
10096
- const object = isInstanceReference(model) ? model.instanceable : model;
10097
- if (object.options.displayPosition) return 0;
11066
+ const object3 = isInstanceReference(model) ? model.instanceable : model;
11067
+ if (object3.options.displayPosition) return 0;
10098
11068
  const position = new Vector312();
10099
11069
  const rotation = new Quaternion5();
10100
11070
  const scale = new Vector312();
10101
11071
  this.getModelMatrix(model).decompose(position, rotation, scale);
10102
11072
  if (position.distanceToSquared(this.position) > 1e-10 || scale.distanceToSquared(new Vector312(1, 1, 1)) > 1e-10) return 0;
10103
- return ModelCulling.toLocalMask(worldMask, new Euler9().setFromQuaternion(rotation));
11073
+ return ModelCulling.toLocalMask(worldMask, new Euler10().setFromQuaternion(rotation));
10104
11074
  }
10105
11075
  async setCullMask(worldMask) {
10106
11076
  worldMask &= 63;
@@ -10111,14 +11081,14 @@ var BlockObject = class _BlockObject extends SceneObject {
10111
11081
  replacements.push(model);
10112
11082
  continue;
10113
11083
  }
10114
- const object = isInstanceReference(model) ? model.instanceable : model;
11084
+ const object3 = isInstanceReference(model) ? model.instanceable : model;
10115
11085
  const cullMask = this.getModelCullMask(model, worldMask);
10116
- if (cullMask === (object.options.cullMask ?? 0)) {
11086
+ if (cullMask === (object3.options.cullMask ?? 0)) {
10117
11087
  replacements.push(model);
10118
11088
  continue;
10119
11089
  }
10120
11090
  const matrix = this.getModelMatrix(model);
10121
- const replacement = await this.scene.addModel(object.originalModel, { ...object.options, cullMask });
11091
+ const replacement = await this.scene.addModel(object3.originalModel, { ...object3.options, cullMask });
10122
11092
  replacements.push(replacement);
10123
11093
  if (isInstanceReference(replacement)) {
10124
11094
  replacement.setMatrix(matrix);
@@ -10126,8 +11096,8 @@ var BlockObject = class _BlockObject extends SceneObject {
10126
11096
  replacement.matrix.copy(matrix);
10127
11097
  replacement.matrixWorldNeedsUpdate = true;
10128
11098
  matrix.decompose(replacement.position, replacement.quaternion, replacement.scale);
10129
- replacement.matrixAutoUpdate = object.matrixAutoUpdate;
10130
- replacement.visible = object.visible;
11099
+ replacement.matrixAutoUpdate = object3.matrixAutoUpdate;
11100
+ replacement.visible = object3.visible;
10131
11101
  }
10132
11102
  }
10133
11103
  } catch (error) {
@@ -10143,6 +11113,7 @@ var BlockObject = class _BlockObject extends SceneObject {
10143
11113
  }
10144
11114
  this.notifyDirty();
10145
11115
  }
11116
+ /** Selected property values. Use {@link setState} to change properties and rebuild the rendered models. */
10146
11117
  get state() {
10147
11118
  return this._state;
10148
11119
  }
@@ -10242,6 +11213,7 @@ var BlockObject = class _BlockObject extends SceneObject {
10242
11213
  obj.setPosition(this.position);
10243
11214
  return obj;
10244
11215
  }
11216
+ /** Clears selected properties and rebuilds models without reapplying property defaults. */
10245
11217
  async resetState() {
10246
11218
  this._previousState = this._state;
10247
11219
  this._state = {};
@@ -12045,9 +13017,9 @@ var SchematicParser = class {
12045
13017
  * Parses a legacy schematic using custom mappings before the bundled defaults.
12046
13018
  * Keys are `id:metadata`; values are block states such as `minecraft:oak_log[axis=x]`.
12047
13019
  */
12048
- static async parse(nbt, customMappings = {}) {
13020
+ static async parse(nbt, customMappings = {}, options = {}) {
12049
13021
  const tags = nbt.value;
12050
- if (tags.Materials?.type !== "string" || tags.Materials.value !== "Alpha") {
13022
+ if (!options.lenient && (tags.Materials?.type !== "string" || tags.Materials.value !== "Alpha")) {
12051
13023
  throw new MineRenderError("Schematic Materials must be Alpha");
12052
13024
  }
12053
13025
  const size = ["Width", "Height", "Length"].map((name) => {
@@ -12088,8 +13060,16 @@ var SchematicParser = class {
12088
13060
  const id = high << 8 | ids.value[index] & 255;
12089
13061
  const metadata = data.value[index] & 255;
12090
13062
  const key = `${id}:${metadata}`;
12091
- const mapped = customMappings[key] ?? mapping[key];
12092
- if (!mapped) throw new MineRenderError(`Unsupported legacy block ${id}:${metadata} at schematic index ${index}`);
13063
+ let mapped = customMappings[key] ?? mapping[key];
13064
+ if (!mapped && options.lenient) {
13065
+ mapped = customMappings[`${id}:0`] ?? mapping[`${id}:0`];
13066
+ }
13067
+ if (!mapped) {
13068
+ if (options.lenient) {
13069
+ continue;
13070
+ }
13071
+ throw new MineRenderError(`Unsupported legacy block ${id}:${metadata} at schematic index ${index}`);
13072
+ }
12093
13073
  const [type, state] = mapped.split("[");
12094
13074
  if (type === "minecraft:air") continue;
12095
13075
  const properties = {};
@@ -12133,6 +13113,11 @@ function compoundList(tags, name) {
12133
13113
  // src/model/multiblock/StructureParser.ts
12134
13114
  init_MineRenderError();
12135
13115
  var StructureParser = class {
13116
+ /**
13117
+ * Parses decoded NBT, omitting air blocks.
13118
+ * @param _nbt - Structure data decoded by {@link NBTHelper.fromBuffer}.
13119
+ * @param paletteIndex - Palette to select when the structure has several. Defaults to 0.
13120
+ */
12136
13121
  static async parse(_nbt, paletteIndex) {
12137
13122
  const nbt = _nbt.value;
12138
13123
  let palette;
@@ -12967,6 +13952,7 @@ var SkinImage = class {
12967
13952
  // src/skin/SkinTextures.ts
12968
13953
  init_Textures();
12969
13954
  var SkinTextures = class {
13955
+ /** Loads a skin URL. Pass `legacy` to override layout detection from the image dimensions. */
12970
13956
  static async get(src, legacy) {
12971
13957
  const image = await SkinImage.getData(src);
12972
13958
  const scale = image.width / 64;
@@ -13140,6 +14126,14 @@ var SkinObject = class extends SceneObject {
13140
14126
  }
13141
14127
  }
13142
14128
  }
14129
+ /**
14130
+ * Loads a skin texture, detecting slim arms and legacy layouts by default.
14131
+ * To override detection, set `slim` or `legacy` in the options passed to
14132
+ * `new SkinObject(options)` or `scene.addSkin(src, options)`.
14133
+ * Use {@link setSlim} or {@link setLegacy} to change these settings later.
14134
+ *
14135
+ * @param src - URL of the skin texture.
14136
+ */
13143
14137
  async setSkinTexture(src) {
13144
14138
  if (typeof src === "undefined") return;
13145
14139
  this.skinTextureSrc = src;
@@ -13250,59 +14244,61 @@ var MineRenderScene = class _MineRenderScene extends Scene4 {
13250
14244
  this.DEFAULT_OPTIONS = (0, import_ts_deepmerge8.default)({}, {});
13251
14245
  }
13252
14246
  add(...objects) {
13253
- for (const object of objects) {
14247
+ for (const object3 of objects) {
13254
14248
  this.dirty = true;
13255
14249
  try {
13256
- super.add(object);
14250
+ super.add(object3);
13257
14251
  } finally {
13258
- if (object?.isObject3D && object !== this) {
13259
- this.updateObjectRegistration(object, object.parent === this);
14252
+ if (object3?.isObject3D && object3 !== this) {
14253
+ this.updateObjectRegistration(object3, object3.parent === this);
13260
14254
  }
13261
14255
  }
13262
14256
  }
13263
14257
  return this;
13264
14258
  }
13265
14259
  remove(...objects) {
13266
- for (const object of objects) {
13267
- if (!this.children.includes(object)) continue;
14260
+ for (const object3 of objects) {
14261
+ if (!this.children.includes(object3)) continue;
13268
14262
  this.dirty = true;
13269
- this.updateObjectRegistration(object, false);
14263
+ this.updateObjectRegistration(object3, false);
13270
14264
  try {
13271
- super.remove(object);
14265
+ super.remove(object3);
13272
14266
  } finally {
13273
- this.updateObjectRegistration(object, object.parent === this);
14267
+ this.updateObjectRegistration(object3, object3.parent === this);
13274
14268
  }
13275
14269
  }
13276
14270
  return this;
13277
14271
  }
13278
- updateObjectRegistration(object, attached) {
13279
- if (this.observedObjects.has(object) === attached) return;
13280
- const observable = object;
14272
+ updateObjectRegistration(object3, attached) {
14273
+ if (this.observedObjects.has(object3) === attached) return;
14274
+ const observable = object3;
13281
14275
  if (attached) {
13282
- this.observedObjects.add(object);
14276
+ this.observedObjects.add(object3);
13283
14277
  observable.addEventListener("change", this.onObjectChange);
13284
14278
  } else {
13285
- this.observedObjects.delete(object);
14279
+ this.observedObjects.delete(object3);
13286
14280
  observable.removeEventListener("change", this.onObjectChange);
13287
14281
  }
13288
14282
  const change = attached ? 1 : -1;
13289
14283
  this.stats.objectCount += change;
13290
- if (isSceneObject(object)) {
14284
+ if (isSceneObject(object3)) {
13291
14285
  this.stats.sceneObjectCount += change;
13292
14286
  }
13293
14287
  }
13294
- async initAndAdd(...object) {
14288
+ /** Initializes each object before attaching it to the scene. */
14289
+ async initAndAdd(...object3) {
13295
14290
  this.dirty = true;
13296
- for (let obj of object) {
14291
+ for (let obj of object3) {
13297
14292
  await obj.init();
13298
14293
  }
13299
- return this.add(...object);
14294
+ return this.add(...object3);
13300
14295
  }
13301
- async addSceneObject(asset, objectSupplier, _options, parent = this) {
14296
+ async addSceneObject(asset2, objectSupplier, _options, parent = this) {
13302
14297
  this.dirty = true;
13303
14298
  const obj = await objectSupplier();
13304
- if (obj?.options?.instanceMeshes && asset.key && asset.key?.assetType === "models") {
13305
- let key = asset.key.serialize();
14299
+ if (isModelObject(obj)) obj.options.tints = await ItemTints.get(obj.originalModel, obj.options.tints);
14300
+ if (obj?.options?.instanceMeshes && asset2.key && asset2.key?.assetType === "models") {
14301
+ let key = asset2.key.serialize();
13306
14302
  if (isModelObject(obj)) {
13307
14303
  const { displayPosition, uvLockRotation, tints, cullMask } = obj.options;
13308
14304
  if (displayPosition) key += `|display:${displayPosition}`;
@@ -13326,13 +14322,32 @@ var MineRenderScene = class _MineRenderScene extends Scene4 {
13326
14322
  return obj;
13327
14323
  }
13328
14324
  }
14325
+ /**
14326
+ * Adds a model loaded with {@link Models.getMerged}.
14327
+ * Set `options.instanceMeshes` to `false` to create a separate ModelObject.
14328
+ *
14329
+ * @param parent - Object to attach the model to. Defaults to this scene.
14330
+ * @returns The model, or a reference to one placement of a shared model.
14331
+ */
13329
14332
  async addModel(model, options, parent = this) {
13330
- options = { ...options, tints: await ItemTints.get(model, options?.tints) };
13331
14333
  return this.addSceneObject(model, () => new ModelObject(model, options), options, parent);
13332
14334
  }
14335
+ /**
14336
+ * Adds the models selected by a blockstate loaded with {@link BlockStates.get}.
14337
+ * Set `options.initialState` to choose block properties such as `facing` or `axis`.
14338
+ * Use the returned object's `setPosition` method to move all of its model parts.
14339
+ */
13333
14340
  async addBlock(blockState, options, parent = this) {
13334
14341
  return this.addSceneObject(blockState, () => new BlockObject(blockState, options), options, parent);
13335
14342
  }
14343
+ /**
14344
+ * Loads a skin and adds an initialized player model.
14345
+ *
14346
+ * @param skin - Texture URL, such as one returned by {@link Skins.fromUuidOrUsername}.
14347
+ * Omit it to assign a texture later with {@link SkinObject.setSkinTexture}.
14348
+ * @param options - Player settings, including `slim` and `legacy` detection overrides.
14349
+ * @param parent - Object to attach the player to. Defaults to this scene.
14350
+ */
13336
14351
  async addSkin(skin, options, parent = this) {
13337
14352
  this.dirty = true;
13338
14353
  const obj = new SkinObject(options);
@@ -13344,9 +14359,14 @@ var MineRenderScene = class _MineRenderScene extends Scene4 {
13344
14359
  parent.add(obj);
13345
14360
  return obj;
13346
14361
  }
14362
+ /** Adds an entity loaded with {@link Entities.getEntity}, using its selected layers and textures. */
13347
14363
  async addEntity(entity, options, parent = this) {
13348
14364
  return this.addSceneObject(entity, () => new EntityObject(entity, options), options, parent);
13349
14365
  }
14366
+ /**
14367
+ * Adds GUI layers in draw order. One GUI pixel equals one scene unit.
14368
+ * Layer positions start at the top left, with positive GUI y pointing down.
14369
+ */
13350
14370
  async addGui(layers, options, parent = this) {
13351
14371
  const obj = new GuiObject(layers, options);
13352
14372
  obj.scene = this;
@@ -13383,11 +14403,11 @@ var _changeEvent = { type: "change" };
13383
14403
  var _startEvent = { type: "start" };
13384
14404
  var _endEvent = { type: "end" };
13385
14405
  var OrbitControls = class extends EventDispatcher {
13386
- constructor(object, domElement) {
14406
+ constructor(object3, domElement) {
13387
14407
  super();
13388
14408
  if (domElement === void 0) console.warn('THREE.OrbitControls: The second parameter "domElement" is now mandatory.');
13389
14409
  if (domElement === document) console.error('THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.');
13390
- this.object = object;
14410
+ this.object = object3;
13391
14411
  this.domElement = domElement;
13392
14412
  this.domElement.style.touchAction = "none";
13393
14413
  this.enabled = true;
@@ -13444,7 +14464,7 @@ var OrbitControls = class extends EventDispatcher {
13444
14464
  };
13445
14465
  this.update = function() {
13446
14466
  const offset = new Vector313();
13447
- const quat = new Quaternion6().setFromUnitVectors(object.up, new Vector313(0, 1, 0));
14467
+ const quat = new Quaternion6().setFromUnitVectors(object3.up, new Vector313(0, 1, 0));
13448
14468
  const quatInverse = quat.clone().invert();
13449
14469
  const lastPosition = new Vector313();
13450
14470
  const lastQuaternion = new Quaternion6();
@@ -14009,8 +15029,8 @@ var OrbitControls = class extends EventDispatcher {
14009
15029
  }
14010
15030
  };
14011
15031
  var MapControls = class extends OrbitControls {
14012
- constructor(object, domElement) {
14013
- super(object, domElement);
15032
+ constructor(object3, domElement) {
15033
+ super(object3, domElement);
14014
15034
  this.screenSpacePanning = false;
14015
15035
  this.mouseButtons.LEFT = MOUSE.PAN;
14016
15036
  this.mouseButtons.RIGHT = MOUSE.ROTATE;
@@ -14209,6 +15229,7 @@ var Renderer = class _Renderer {
14209
15229
  get element() {
14210
15230
  return this._element;
14211
15231
  }
15232
+ /** Attaches the canvas to an element and sizes it to that element's dimensions. */
14212
15233
  appendTo(element) {
14213
15234
  if (this._disposed) return;
14214
15235
  this._element = element;
@@ -14236,6 +15257,7 @@ var Renderer = class _Renderer {
14236
15257
  events.add(changeEvent2);
14237
15258
  dispatcher.addEventListener(changeEvent2, this._changeListener);
14238
15259
  }
15260
+ /** Updates the camera and canvas size. Width and height are in CSS pixels. */
14239
15261
  resize(width, height) {
14240
15262
  if (this._disposed) return;
14241
15263
  if (isPerspectiveCamera(this.camera)) {
@@ -14274,12 +15296,14 @@ var Renderer = class _Renderer {
14274
15296
  if (this._frameCallbacks.get(callback) === subscription) this._frameCallbacks.delete(callback);
14275
15297
  };
14276
15298
  }
15299
+ /** Starts or resumes the animation loop and registered frame callbacks. */
14277
15300
  start() {
14278
15301
  if (this._disposed) return;
14279
15302
  this.stop();
14280
15303
  this._running = true;
14281
15304
  if (!this._inAnimationLoop) this.renderer.setAnimationLoop(this._animationLoop);
14282
15305
  }
15306
+ /** Pauses rendering and frame callbacks. Call {@link start} to resume. */
14283
15307
  stop() {
14284
15308
  if (this._disposed) return;
14285
15309
  this._running = false;
@@ -14388,12 +15412,15 @@ var Renderer = class _Renderer {
14388
15412
  const canvas = trim ? trimCanvas(this.renderer.domElement) : this.renderer.domElement;
14389
15413
  return canvas.toDataURL(mime, quality);
14390
15414
  }
15415
+ /** Exports visible scene meshes as OBJ text, without texture images. */
14391
15416
  toObj() {
14392
15417
  return SceneExporter.toObj(this.scene);
14393
15418
  }
15419
+ /** Exports visible scene meshes as PLY. Set `options.binary` for an ArrayBuffer. */
14394
15420
  toPLY(options) {
14395
15421
  return SceneExporter.toPLY(this.scene, options);
14396
15422
  }
15423
+ /** Exports a static textured scene in the browser. Set `options.binary` for GLB output. */
14397
15424
  toGLTF(options) {
14398
15425
  return SceneExporter.toGLTF(this.scene, options);
14399
15426
  }
@@ -14432,16 +15459,244 @@ init_SceneObject();
14432
15459
  // src/index.ts
14433
15460
  init_request();
14434
15461
 
15462
+ // src/scene/SceneDocument.ts
15463
+ init_DisplayPosition();
15464
+ function fail(path, message) {
15465
+ throw new Error(`${path}: ${message}`);
15466
+ }
15467
+ function object2(value, path) {
15468
+ if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail(path, "expected a JSON object");
15469
+ for (const key of Object.keys(value)) {
15470
+ if (["__proto__", "prototype", "constructor"].includes(key)) fail(`${path}.${key}`, "unsupported property");
15471
+ }
15472
+ return value;
15473
+ }
15474
+ function fields(value, path, allowed, required = []) {
15475
+ const input = object2(value, path);
15476
+ for (const key of required) if (input[key] === void 0) fail(`${path}.${key}`, "required");
15477
+ for (const [key, entry] of Object.entries(input)) {
15478
+ if (!Object.prototype.hasOwnProperty.call(allowed, key)) fail(`${path}.${key}`, "unsupported property");
15479
+ if (entry !== void 0) allowed[key](entry, `${path}.${key}`);
15480
+ }
15481
+ return input;
15482
+ }
15483
+ var text = (value, path) => {
15484
+ if (typeof value !== "string" || !value.trim()) fail(path, "expected a nonempty string");
15485
+ };
15486
+ var literalText = (value, path) => {
15487
+ if (typeof value !== "string") fail(path, "expected a string");
15488
+ };
15489
+ var boolean = (value, path) => {
15490
+ if (typeof value !== "boolean") fail(path, "expected a boolean");
15491
+ };
15492
+ var number = (value, path) => {
15493
+ if (typeof value !== "number" || !Number.isFinite(value)) fail(path, "expected a finite number");
15494
+ };
15495
+ var positive = (value, path) => {
15496
+ number(value, path);
15497
+ if (value <= 0) fail(path, "expected a positive number");
15498
+ };
15499
+ var nonnegative = (value, path) => {
15500
+ number(value, path);
15501
+ if (value < 0) fail(path, "expected a nonnegative number");
15502
+ };
15503
+ var color = (value, path) => {
15504
+ number(value, path);
15505
+ if (!Number.isInteger(value) || value < 0 || value > 16777215) {
15506
+ fail(path, "expected an RGB integer from 0 to 16777215");
15507
+ }
15508
+ };
15509
+ var asset = (value, path) => {
15510
+ text(value, path);
15511
+ if (!/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(value) || value.split(/[:/]/).some((part) => !part || part === "." || part === "..")) {
15512
+ fail(path, "expected a resource ID such as minecraft:stone");
15513
+ }
15514
+ };
15515
+ var version = (value, path) => {
15516
+ text(value, path);
15517
+ if (!/^[a-zA-Z0-9._-]+$/.test(value)) fail(path, "expected a Minecraft version without slashes");
15518
+ };
15519
+ var tuple = (size, item = number) => (value, path) => {
15520
+ if (!Array.isArray(value) || value.length !== size) fail(path, `expected ${size} numbers`);
15521
+ for (let index = 0; index < size; index++) item(value[index], `${path}[${index}]`);
15522
+ };
15523
+ var list = (item) => (value, path) => {
15524
+ if (!Array.isArray(value)) fail(path, "expected an array");
15525
+ for (let index = 0; index < value.length; index++) item(value[index], `${path}[${index}]`);
15526
+ };
15527
+ var dictionary = (item, numericKeys = false) => (value, path) => {
15528
+ for (const [key, entry] of Object.entries(object2(value, path))) {
15529
+ if (numericKeys && !/^\d+$/.test(key)) fail(`${path}.${key}`, "expected a nonnegative tint index");
15530
+ item(entry, `${path}.${key}`);
15531
+ }
15532
+ };
15533
+ var enumeration = (values) => (value, path) => {
15534
+ if (!values.includes(value)) fail(path, `expected one of: ${values.join(", ")}`);
15535
+ };
15536
+ var textStyle = { color, bold: boolean, italic: boolean };
15537
+ function guiLayer(value, path) {
15538
+ const layer = object2(value, path);
15539
+ if (["texture", "item", "text"].filter((key) => layer[key] !== void 0).length !== 1) {
15540
+ fail(path, "expected exactly one of texture, item, or text");
15541
+ }
15542
+ const layout = { name: text, position: tuple(2), size: tuple(2, positive) };
15543
+ if (layer.texture !== void 0) {
15544
+ fields(value, path, { ...layout, texture: asset, crop: (value2, path2) => {
15545
+ tuple(4)(value2, path2);
15546
+ const [x, y, width, height] = value2;
15547
+ nonnegative(x, `${path2}[0]`);
15548
+ nonnegative(y, `${path2}[1]`);
15549
+ positive(width, `${path2}[2]`);
15550
+ positive(height, `${path2}[3]`);
15551
+ } });
15552
+ } else if (layer.item !== void 0) {
15553
+ fields(value, path, { ...layout, item: asset, tints: dictionary(color, true) });
15554
+ } else {
15555
+ fields(value, path, {
15556
+ ...layout,
15557
+ ...textStyle,
15558
+ font: asset,
15559
+ shadow: boolean,
15560
+ maxWidth: positive,
15561
+ lineHeight: positive,
15562
+ text: (value2, path2) => {
15563
+ if (typeof value2 === "string") return;
15564
+ list((run, path3) => {
15565
+ fields(run, path3, { text: literalText, ...textStyle }, ["text"]);
15566
+ })(value2, path2);
15567
+ }
15568
+ });
15569
+ }
15570
+ }
15571
+ function validateObject(value, path) {
15572
+ const input = object2(value, path);
15573
+ const common = { id: text, name: text, type: text, position: tuple(3), rotation: tuple(3), scale: tuple(3), visible: boolean };
15574
+ const required = ["id", "type"];
15575
+ const renderOptions = { wireframe: boolean };
15576
+ switch (input.type) {
15577
+ case "skin":
15578
+ fields(value, path, {
15579
+ ...common,
15580
+ skin: text,
15581
+ cape: (value2, path2) => {
15582
+ fields(value2, path2, { texture: text, layout: enumeration(["minecraft", "optifine", "labymod"]) }, ["texture"]);
15583
+ },
15584
+ hiddenParts: list(enumeration(SKIN_PARTS)),
15585
+ pose: (value2, path2) => {
15586
+ fields(value2, path2, {
15587
+ head: tuple(3),
15588
+ body: tuple(3),
15589
+ rightArm: tuple(3),
15590
+ leftArm: tuple(3),
15591
+ rightLeg: tuple(3),
15592
+ leftLeg: tuple(3),
15593
+ cape: tuple(3)
15594
+ });
15595
+ },
15596
+ options: (value2, path2) => {
15597
+ fields(value2, path2, { slim: boolean, legacy: boolean });
15598
+ }
15599
+ }, required);
15600
+ break;
15601
+ case "block":
15602
+ fields(value, path, {
15603
+ ...common,
15604
+ asset,
15605
+ state: dictionary(text),
15606
+ options: (value2, path2) => {
15607
+ fields(value2, path2, { ...renderOptions, tints: dictionary(color, true) });
15608
+ }
15609
+ }, [...required, "asset"]);
15610
+ break;
15611
+ case "item":
15612
+ case "model":
15613
+ fields(value, path, {
15614
+ ...common,
15615
+ asset,
15616
+ options: (value2, path2) => {
15617
+ fields(value2, path2, { ...renderOptions, displayPosition: enumeration(DISPLAY_POSITIONS), tints: dictionary(color, true) });
15618
+ }
15619
+ }, [...required, "asset"]);
15620
+ break;
15621
+ case "entity":
15622
+ fields(value, path, {
15623
+ ...common,
15624
+ asset,
15625
+ texture: asset,
15626
+ layers: list(text),
15627
+ when: list(text),
15628
+ textures: dictionary(asset),
15629
+ options: (value2, path2) => {
15630
+ fields(value2, path2, {
15631
+ ...renderOptions,
15632
+ flip: boolean,
15633
+ tints: dictionary((value3, path3) => {
15634
+ typeof value3 === "string" ? text(value3, path3) : color(value3, path3);
15635
+ })
15636
+ });
15637
+ },
15638
+ animation: (value2, path2) => {
15639
+ fields(value2, path2, { name: text, loop: boolean, speed: nonnegative, time: nonnegative, paused: boolean }, ["name"]);
15640
+ }
15641
+ }, [...required, "asset"]);
15642
+ if (Array.isArray(input.layers) && !input.layers.length) fail(`${path}.layers`, "expected at least one layer");
15643
+ break;
15644
+ case "gui":
15645
+ fields(value, path, { ...common, layers: list(guiLayer) }, [...required, "layers"]);
15646
+ break;
15647
+ default:
15648
+ fail(`${path}.type`, "expected skin, block, item, entity, model, or gui");
15649
+ }
15650
+ }
15651
+ function parseSceneDocument(value) {
15652
+ if (typeof value === "string") {
15653
+ try {
15654
+ value = JSON.parse(value);
15655
+ } catch {
15656
+ fail("scene", "invalid JSON");
15657
+ }
15658
+ }
15659
+ const input = fields(value, "scene", {
15660
+ format: enumeration(["minerender-scene"]),
15661
+ version: (value2, path) => {
15662
+ if (value2 !== 1) fail(path, "only scene document version 1 is supported");
15663
+ },
15664
+ minecraftVersion: version,
15665
+ camera: (value2, path) => {
15666
+ fields(value2, path, { position: tuple(3), target: tuple(3) }, ["position", "target"]);
15667
+ },
15668
+ objects: list(validateObject)
15669
+ }, ["format", "version", "objects"]);
15670
+ const ids = /* @__PURE__ */ new Set();
15671
+ for (const [index, entry] of input.objects.entries()) {
15672
+ if (ids.has(entry.id)) fail(`scene.objects[${index}].id`, `duplicate object ID "${entry.id}"`);
15673
+ ids.add(entry.id);
15674
+ }
15675
+ return JSON.parse(JSON.stringify(input));
15676
+ }
15677
+
15678
+ // src/scene/SceneDocumentLoader.ts
15679
+ init_AssetKey();
15680
+ init_AssetLoader();
15681
+ init_BlockStates();
15682
+ init_Entities();
15683
+ init_Models();
15684
+ init_DisplayPosition();
15685
+ import { Group as Group2 } from "three";
15686
+ init_SceneObject();
15687
+
14435
15688
  // src/skin/Skins.ts
14436
15689
  init_Requests();
14437
15690
  var Skins = class {
14438
15691
  //<editor-fold desc="Skins">
15692
+ /** Returns a skin URL for {@link MineRenderScene.addSkin}, or `undefined` if name lookup fails. */
14439
15693
  static async fromUuidOrUsername(uuidOrUsername) {
14440
15694
  if (uuidOrUsername.length === 32 || uuidOrUsername.length === 36) {
14441
15695
  return this.fromUuid(uuidOrUsername);
14442
15696
  }
14443
15697
  return this.fromUsername(uuidOrUsername);
14444
15698
  }
15699
+ /** Builds a proxy URL for a UUID without downloading or checking the skin. */
14445
15700
  static async fromUuid(uuid) {
14446
15701
  return this.getMcProxySkinUrl(uuid);
14447
15702
  }
@@ -14453,6 +15708,7 @@ var Skins = class {
14453
15708
  return void 0;
14454
15709
  });
14455
15710
  }
15711
+ /** Looks up a saved MineSkin skin and returns its texture URL, or `undefined` on lookup failure. */
14456
15712
  static async fromMineSkin(mineskinId) {
14457
15713
  return this.getMineSkin(mineskinId).then((mineskin) => {
14458
15714
  if (mineskin) {
@@ -14463,12 +15719,14 @@ var Skins = class {
14463
15719
  }
14464
15720
  //</editor-fold>
14465
15721
  //<editor-fold desc="Capes">
15722
+ /** Resolves a player's Minecraft cape URL for {@link SkinObject.setCapeTexture}. */
14466
15723
  static async capeFromUuidOrUsername(uuidOrUsername) {
14467
15724
  if (uuidOrUsername.length === 32 || uuidOrUsername.length === 36) {
14468
15725
  return this.capeFromUuid(uuidOrUsername);
14469
15726
  }
14470
15727
  return this.capeFromUsername(uuidOrUsername);
14471
15728
  }
15729
+ /** Builds a proxy URL without checking whether the player has a cape. */
14472
15730
  static async capeFromUuid(uuid) {
14473
15731
  return this.getMcProxyCapeUrl(uuid);
14474
15732
  }
@@ -14597,6 +15855,195 @@ var Skins = class {
14597
15855
  //</editor-fold>
14598
15856
  };
14599
15857
 
15858
+ // src/scene/SceneDocumentLoader.ts
15859
+ var DocumentObjectScene = class extends MineRenderScene {
15860
+ async addSceneObject(_asset, supplier, _options, parent = this) {
15861
+ const object3 = await supplier();
15862
+ return this.initialize(object3, parent);
15863
+ }
15864
+ async initialize(object3, parent = this) {
15865
+ object3.scene = this;
15866
+ try {
15867
+ await object3.init();
15868
+ parent.add(object3);
15869
+ return object3;
15870
+ } catch (error) {
15871
+ object3.dispose();
15872
+ object3.removeFromParent();
15873
+ throw error;
15874
+ }
15875
+ }
15876
+ dispose() {
15877
+ for (const child of [...this.children]) {
15878
+ if (child.parent !== this) continue;
15879
+ if (isSceneObject(child)) child.dispose();
15880
+ child.removeFromParent();
15881
+ }
15882
+ }
15883
+ };
15884
+ async function skinTexture(value, cape = false) {
15885
+ if (!/^[a-zA-Z0-9_-]{1,36}$/.test(value)) return value;
15886
+ const texture = await (cape ? Skins.capeFromUuidOrUsername(value) : Skins.fromUuidOrUsername(value));
15887
+ if (!texture) throw new Error(`Could not find ${cape ? "cape" : "skin"} for "${value}"`);
15888
+ return texture;
15889
+ }
15890
+ var SceneDocumentLoader = class {
15891
+ static parse(value) {
15892
+ return parseSceneDocument(value);
15893
+ }
15894
+ /**
15895
+ * Loads objects concurrently and attaches them in document order after every load succeeds.
15896
+ * A failure waits for all loads, disposes staged objects, and reports the first error in document order.
15897
+ */
15898
+ static async load(scene, value, parent = scene) {
15899
+ const definition = this.parse(value);
15900
+ if (definition.minecraftVersion !== void 0 && definition.minecraftVersion !== AssetLoader.version) {
15901
+ throw new Error(`Scene requires Minecraft ${definition.minecraftVersion}; call AssetLoader.setVersion("${definition.minecraftVersion}") before loading it`);
15902
+ }
15903
+ const root = new Group2();
15904
+ root.name = "MineRender scene document";
15905
+ const objects = [];
15906
+ let disposed = false;
15907
+ const loaded = {
15908
+ root,
15909
+ objects,
15910
+ definition,
15911
+ advanceAnimations: (delta) => {
15912
+ if (!disposed) for (const object3 of objects) object3.advanceAnimation(delta);
15913
+ },
15914
+ dispose: () => {
15915
+ if (disposed) return;
15916
+ disposed = true;
15917
+ root.removeFromParent();
15918
+ for (const object3 of objects) object3.dispose();
15919
+ root.clear();
15920
+ scene.dirty = true;
15921
+ }
15922
+ };
15923
+ try {
15924
+ const results = await Promise.allSettled(definition.objects.map((object3) => this.loadValidatedObject(scene, object3)));
15925
+ for (const result of results) if (result.status === "fulfilled") objects.push(result.value);
15926
+ const failure = results.find((result) => result.status === "rejected");
15927
+ if (failure) throw failure.reason;
15928
+ for (const object3 of objects) root.add(object3.root);
15929
+ parent.add(root);
15930
+ scene.dirty = true;
15931
+ return loaded;
15932
+ } catch (error) {
15933
+ loaded.dispose();
15934
+ throw error;
15935
+ }
15936
+ }
15937
+ /** Loads an independently editable object. Its root owns the document's position, rotation, and scale. */
15938
+ static async loadObject(scene, value, parent = scene) {
15939
+ const definition = this.parse({ format: "minerender-scene", version: 1, objects: [value] }).objects[0];
15940
+ return this.loadValidatedObject(scene, definition, parent);
15941
+ }
15942
+ static async loadValidatedObject(scene, definition, parent) {
15943
+ const root = new Group2();
15944
+ root.name = definition.name ?? definition.id;
15945
+ root.userData.sceneObjectId = definition.id;
15946
+ root.position.fromArray(definition.position ?? [0, 0, 0]);
15947
+ root.rotation.set(...definition.rotation ?? [0, 0, 0]);
15948
+ root.scale.fromArray(definition.scale ?? [1, 1, 1]);
15949
+ root.visible = definition.visible ?? true;
15950
+ const content = new DocumentObjectScene();
15951
+ root.add(content);
15952
+ let object3;
15953
+ try {
15954
+ switch (definition.type) {
15955
+ case "skin": {
15956
+ const skin = new SkinObject({ ...definition.options, instanceMeshes: false });
15957
+ object3 = skin;
15958
+ const texture = definition.skin ? await skinTexture(definition.skin) : `${AssetLoader.ROOT}/assets/minecraft/textures/entity/player/${definition.options?.slim ? "slim/alex" : "wide/steve"}.png`;
15959
+ await skin.setSkinTexture(texture);
15960
+ await content.initialize(skin);
15961
+ if (definition.cape) await skin.setCapeTexture(await skinTexture(definition.cape.texture, true), definition.cape.layout);
15962
+ for (const [part, rotation] of Object.entries(definition.pose ?? {})) {
15963
+ skin.getGroupByName(part)?.rotation.set(...rotation, "XYZ");
15964
+ }
15965
+ for (const part of definition.hiddenParts ?? []) {
15966
+ const mesh = skin.getMeshByName(part);
15967
+ if (mesh) mesh.visible = false;
15968
+ }
15969
+ break;
15970
+ }
15971
+ case "block": {
15972
+ const state = await BlockStates.get(AssetKey.parse("blockstates", definition.asset));
15973
+ if (!state) throw new Error(`Could not load block ${definition.asset}`);
15974
+ object3 = await content.addBlock(state, { ...definition.options, initialState: definition.state, instanceMeshes: false });
15975
+ break;
15976
+ }
15977
+ case "item":
15978
+ case "model": {
15979
+ let key = AssetKey.parse("models", definition.asset);
15980
+ if (definition.type === "item" && key.type !== "item") {
15981
+ key = new AssetKey(key.namespace, key.getFullPath(), "models", "item");
15982
+ }
15983
+ const model = await Models.getMerged(key);
15984
+ if (!model) throw new Error(`Could not load ${definition.type} ${definition.asset}`);
15985
+ object3 = await content.addModel(model, {
15986
+ ...definition.type === "item" ? { displayPosition: "gui" /* GUI */ } : {},
15987
+ ...definition.options,
15988
+ instanceMeshes: false
15989
+ });
15990
+ break;
15991
+ }
15992
+ case "entity": {
15993
+ const key = AssetKey.parse("entity-models", definition.asset);
15994
+ const model = await Entities.getEntity(key, definition.texture ? AssetKey.parse("textures", definition.texture) : void 0, {
15995
+ layers: definition.layers,
15996
+ when: definition.when,
15997
+ textures: definition.textures && Object.fromEntries(Object.entries(definition.textures).map(([name, texture]) => [name, AssetKey.parse("textures", texture)]))
15998
+ });
15999
+ if (!model) throw new Error(`Could not load entity ${definition.asset}`);
16000
+ const entity = await content.addEntity(model, { ...definition.options, instanceMeshes: false });
16001
+ object3 = entity;
16002
+ if (definition.animation) {
16003
+ const animations = await Entities.getAnimations(key);
16004
+ const clip = animations?.[definition.animation.name];
16005
+ if (!clip) throw new Error(`Entity ${definition.asset} has no animation "${definition.animation.name}"`);
16006
+ entity.playAnimation(clip, definition.animation);
16007
+ }
16008
+ break;
16009
+ }
16010
+ case "gui":
16011
+ object3 = await content.initialize(new GuiObject(definition.layers));
16012
+ break;
16013
+ }
16014
+ let disposed = false;
16015
+ const loaded = {
16016
+ root,
16017
+ object: object3,
16018
+ definition,
16019
+ advanceAnimation: (delta) => {
16020
+ if (!disposed && definition.type === "entity" && !definition.animation?.paused) object3.advanceAnimation(delta);
16021
+ },
16022
+ dispose: () => {
16023
+ if (disposed) return;
16024
+ disposed = true;
16025
+ root.removeFromParent();
16026
+ object3.dispose();
16027
+ object3.removeFromParent();
16028
+ content.dispose();
16029
+ root.clear();
16030
+ scene.dirty = true;
16031
+ }
16032
+ };
16033
+ parent?.add(root);
16034
+ scene.dirty = true;
16035
+ return loaded;
16036
+ } catch (error) {
16037
+ object3?.dispose();
16038
+ object3?.removeFromParent();
16039
+ content.dispose();
16040
+ root.removeFromParent();
16041
+ root.clear();
16042
+ throw new Error(`Scene object "${definition.name ?? definition.id}": ${error instanceof Error ? error.message : String(error)}`);
16043
+ }
16044
+ }
16045
+ };
16046
+
14600
16047
  // src/texture/index.ts
14601
16048
  init_Colormaps();
14602
16049
  init_TextureAtlas();
@@ -14623,16 +16070,19 @@ var ChunkData = class _ChunkData {
14623
16070
  this.freeIds = [];
14624
16071
  this.nbt = /* @__PURE__ */ new Map();
14625
16072
  }
16073
+ /** Returns a detached copy of a cell, or `undefined` for air. Indices range from 0 to 4095. */
14626
16074
  get(index) {
14627
16075
  _ChunkData.validateIndex(index);
14628
16076
  const entry = this.palette[this.indices[index]];
14629
16077
  return entry ? _ChunkData.copyBlock(entry.state, this.nbt.get(index)) : void 0;
14630
16078
  }
16079
+ /** Captures a cell's data and returns a copy-producing reader that survives later cell changes. */
14631
16080
  snapshot(index) {
14632
16081
  _ChunkData.validateIndex(index);
14633
16082
  const entry = this.palette[this.indices[index]];
14634
16083
  return entry ? _ChunkData.copyBlock.bind(void 0, entry.state, this.nbt.get(index)) : void 0;
14635
16084
  }
16085
+ /** Copies block data into a cell. An omitted block or an air block clears the cell. */
14636
16086
  set(index, block) {
14637
16087
  _ChunkData.validateIndex(index);
14638
16088
  let state;
@@ -14697,15 +16147,23 @@ var ChunkData = class _ChunkData {
14697
16147
 
14698
16148
  // src/world/AnvilParser.ts
14699
16149
  var AnvilParser = class {
16150
+ /** Lists occupied chunk positions within the region, using local coordinates from 0 to 31. */
14700
16151
  static getChunkList(data) {
14701
16152
  return this.locations(this.bytes(data)).map(({ x, z }) => ({ x, z }));
14702
16153
  }
16154
+ /** Decodes all stored chunks. Gzip, zlib, and uncompressed payloads are supported. */
14703
16155
  static async parse(data) {
14704
16156
  const bytes = this.bytes(data);
14705
16157
  const chunks = [];
14706
16158
  for (const location of this.locations(bytes)) chunks.push(await this.readChunk(bytes, location));
14707
16159
  return { chunks };
14708
16160
  }
16161
+ /**
16162
+ * Decodes one chunk for {@link MineRenderWorld.placeChunk}.
16163
+ * @param localX - Chunk x within the region, from 0 to 31.
16164
+ * @param localZ - Chunk z within the region, from 0 to 31.
16165
+ * @returns The chunk with absolute coordinates, or `undefined` if the region has no chunk there.
16166
+ */
14709
16167
  static async parseChunk(data, localX, localZ) {
14710
16168
  if (![localX, localZ].every((value) => Number.isInteger(value) && value >= 0 && value < 32)) {
14711
16169
  throw new RangeError("Region-local chunk coordinates must be integers from 0 to 31");
@@ -14910,6 +16368,7 @@ var SectionModels = class {
14910
16368
  this.templates = /* @__PURE__ */ new Set();
14911
16369
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("maxAtlasSize must be a positive integer");
14912
16370
  }
16371
+ /** Returns a cube template, or `undefined` when the block requires an individual render object. */
14913
16372
  async get(blockState, properties = {}) {
14914
16373
  if (!blockState.variants || blockState.multipart) return void 0;
14915
16374
  let states = this.states.get(blockState);
@@ -14938,16 +16397,16 @@ var SectionModels = class {
14938
16397
  if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
14939
16398
  const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
14940
16399
  const template = await cached(models, key, async () => {
14941
- const object = new SectionModel(model, {
16400
+ const object3 = new SectionModel(model, {
14942
16401
  instanceMeshes: false,
14943
16402
  mergeMeshes: false,
14944
16403
  tints,
14945
16404
  uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
14946
16405
  });
14947
- await object.init();
14948
- const geometry = object.children[0].geometry;
16406
+ await object3.init();
16407
+ const geometry = object3.children[0].geometry;
14949
16408
  geometry.applyMatrix4(new Matrix412().makeRotationFromEuler(rotation));
14950
- object.clear();
16409
+ object3.clear();
14951
16410
  const cullFaces = CUBE_FACES.map((face) => {
14952
16411
  const local = CUBE_FACES.findIndex((name) => name === atlas.model.elements[0].faces[face]?.cullface);
14953
16412
  if (local < 0) return 0;
@@ -14963,6 +16422,7 @@ var SectionModels = class {
14963
16422
  }
14964
16423
  return { variants, templates };
14965
16424
  }
16425
+ /** Disposes prepared geometry and clears this world's template caches. */
14966
16426
  clear() {
14967
16427
  for (const template of this.templates) template.geometry.dispose();
14968
16428
  this.templates.clear();
@@ -15013,6 +16473,13 @@ var MineRenderWorld = class {
15013
16473
  this.validatePosBounds(posOrX);
15014
16474
  await this.getChunkAt(posOrX)?.setBlockVisibleAt(posOrX, yOrVisible);
15015
16475
  }
16476
+ /**
16477
+ * Places a structure's blocks at their stored positions, then refreshes neighboring faces.
16478
+ * Entity NBT is retained by the structure but is not rendered.
16479
+ *
16480
+ * @param useBatches - Uses bounded batches by default. Set to `false` for sequential placement.
16481
+ * @param executor - Optional queue for batched placement. The caller remains responsible for stopping it.
16482
+ */
15016
16483
  async placeMultiBlock(multiblock, useBatches = true, executor) {
15017
16484
  const changes = /* @__PURE__ */ new Map();
15018
16485
  try {
@@ -15064,13 +16531,7 @@ var MineRenderWorld = class {
15064
16531
  async placeChunk(chunk, executor) {
15065
16532
  const changes = /* @__PURE__ */ new Map();
15066
16533
  try {
15067
- const previous = [...this._chunks.entries()].filter(([, section]) => section.x === chunk.x && section.z === chunk.z);
15068
- for (const [key, section] of previous) {
15069
- this._chunks.delete(key);
15070
- await section.clear(async (positions) => {
15071
- for (const pos of positions) changes.set(pos.toArray().join(","), pos);
15072
- });
15073
- }
16534
+ await this.clearChunkColumn(chunk.x, chunk.z, changes);
15074
16535
  for (const section of chunk.sections) {
15075
16536
  const blocks = [];
15076
16537
  for (let index = 0; index < 4096; index++) {
@@ -15090,6 +16551,32 @@ var MineRenderWorld = class {
15090
16551
  await this.updateCulling([...changes.values()]);
15091
16552
  }
15092
16553
  }
16554
+ /**
16555
+ * Removes all sections at integer chunk-column coordinates, then refreshes neighboring faces and fluids.
16556
+ * Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
16557
+ */
16558
+ async unloadChunkColumn(x, z) {
16559
+ if (!Number.isInteger(x) || !Number.isInteger(z)) {
16560
+ throw new RangeError("Chunk column coordinates must be integers");
16561
+ }
16562
+ await this.culling;
16563
+ const changes = /* @__PURE__ */ new Map();
16564
+ try {
16565
+ await this.clearChunkColumn(x, z, changes);
16566
+ } finally {
16567
+ await this.updateCulling([...changes.values()]);
16568
+ }
16569
+ }
16570
+ async clearChunkColumn(x, z, changes) {
16571
+ const previous = [...this._chunks.entries()].filter(([, section]) => section.x === x && section.z === z);
16572
+ for (const [key, section] of previous) {
16573
+ this._chunks.delete(key);
16574
+ await section.clear(async (positions) => {
16575
+ for (const pos of positions) changes.set(pos.toArray().join(","), pos);
16576
+ });
16577
+ }
16578
+ }
16579
+ /** Removes all chunks and releases their block placements and section meshes. */
15093
16580
  async clear() {
15094
16581
  await this.culling;
15095
16582
  const chunks = [...this._chunks.values()];
@@ -15135,10 +16622,10 @@ var MineRenderWorld = class {
15135
16622
  await block.object.updateFluid((x, y, z) => {
15136
16623
  const neighbor = pos.clone().add(new Vector316(x, y, z));
15137
16624
  const section = this.getChunkAt(neighbor);
15138
- const object = section?.isBlockVisibleAt(neighbor) ? section.getBlockAt(neighbor)?.object : void 0;
16625
+ const object3 = section?.isBlockVisibleAt(neighbor) ? section.getBlockAt(neighbor)?.object : void 0;
15139
16626
  return {
15140
- fluid: object?.fluidKind,
15141
- level: object?.fluidLevel,
16627
+ fluid: object3?.fluidKind,
16628
+ level: object3?.fluidLevel,
15142
16629
  solid: section?.isOccludingAt(neighbor) ?? false
15143
16630
  };
15144
16631
  });
@@ -15175,9 +16662,11 @@ var MineRenderWorld = class {
15175
16662
  }
15176
16663
  return chunk;
15177
16664
  }
16665
+ /** Returns the loaded chunk containing a world block position, without creating a chunk. */
15178
16666
  getChunkAt(pos) {
15179
16667
  return this._chunks.get(this.worldPosToChunkKey(pos));
15180
16668
  }
16669
+ /** Converts block coordinates to scene units by multiplying each component by 16. */
15181
16670
  static worldToScenePosition(pos) {
15182
16671
  return new Vector316(
15183
16672
  pos.x * 16,
@@ -15185,6 +16674,7 @@ var MineRenderWorld = class {
15185
16674
  pos.z * 16
15186
16675
  );
15187
16676
  }
16677
+ /** Converts scene units to block coordinates without rounding to an integer block. */
15188
16678
  static sceneToWorldPosition(pos) {
15189
16679
  return new Vector316(
15190
16680
  pos.x / 16,
@@ -15211,12 +16701,13 @@ init_Model();
15211
16701
  // src/world/SectionMesh.ts
15212
16702
  init_CanvasCompat();
15213
16703
  init_Materials();
15214
- import { BufferGeometry as BufferGeometry8, Float32BufferAttribute as Float32BufferAttribute8, Group as Group2, Mesh as Mesh9 } from "three";
15215
- var SectionMesh = class _SectionMesh extends Group2 {
16704
+ import { BufferGeometry as BufferGeometry8, Float32BufferAttribute as Float32BufferAttribute8, Group as Group3, Mesh as Mesh9 } from "three";
16705
+ var SectionMesh = class _SectionMesh extends Group3 {
15216
16706
  constructor() {
15217
16707
  super(...arguments);
15218
16708
  this.ownedMeshes = [];
15219
16709
  }
16710
+ /** Builds section-local meshes from visible cube faces. `maxAtlasSize` limits each atlas dimension in pixels. */
15220
16711
  static build(entries, maxAtlasSize = 2048) {
15221
16712
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
15222
16713
  const visible = entries.filter((entry) => entry.template.cullFaces.some((direction) => !(entry.cullMask & direction)));
@@ -15258,14 +16749,17 @@ var SectionMesh = class _SectionMesh extends Group2 {
15258
16749
  for (const { atlas, x, y } of page.placements) {
15259
16750
  context.drawImage(atlas.image.canvas, x, y);
15260
16751
  }
15261
- const positions = [], normals = [], uvs = [], colors = [], indices = [];
16752
+ const positions = [], normals = [], uvs = [], uvBounds = [], colors = [], indices = [];
15262
16753
  for (const { index, template, cullMask } of page.entries) {
15263
16754
  const position = template.geometry.getAttribute("position");
15264
16755
  const normal = template.geometry.getAttribute("normal");
15265
16756
  const uv = template.geometry.getAttribute("uv");
15266
- const color = template.geometry.getAttribute("color");
16757
+ const bounds = template.geometry.getAttribute("uvBounds");
16758
+ const color2 = template.geometry.getAttribute("color");
15267
16759
  const sourceIndices = template.geometry.getIndex();
15268
16760
  const placement = locations.get(template.atlas).placement;
16761
+ const mapU = (u) => (placement.x + u * template.atlas.image.width) / page.width;
16762
+ const mapV = (v) => 1 - (placement.y + (1 - v) * template.atlas.image.height) / page.height;
15269
16763
  const offsetX = index % 16 * 16;
15270
16764
  const offsetY = Math.floor(index / 256) * 16;
15271
16765
  const offsetZ = Math.floor(index / 16) % 16 * 16;
@@ -15276,11 +16770,14 @@ var SectionMesh = class _SectionMesh extends Group2 {
15276
16770
  const vertex = face * 4 + corner;
15277
16771
  positions.push(position.getX(vertex) + offsetX, position.getY(vertex) + offsetY, position.getZ(vertex) + offsetZ);
15278
16772
  normals.push(normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex));
15279
- uvs.push(
15280
- (placement.x + uv.getX(vertex) * template.atlas.image.width) / page.width,
15281
- 1 - (placement.y + (1 - uv.getY(vertex)) * template.atlas.image.height) / page.height
16773
+ uvs.push(mapU(uv.getX(vertex)), mapV(uv.getY(vertex)));
16774
+ uvBounds.push(
16775
+ mapU(bounds?.getX(vertex) ?? 0),
16776
+ mapV(bounds?.getY(vertex) ?? 0),
16777
+ mapU(bounds?.getZ(vertex) ?? 1),
16778
+ mapV(bounds?.getW(vertex) ?? 1)
15282
16779
  );
15283
- colors.push(color?.getX(vertex) ?? 1, color?.getY(vertex) ?? 1, color?.getZ(vertex) ?? 1);
16780
+ colors.push(color2?.getX(vertex) ?? 1, color2?.getY(vertex) ?? 1, color2?.getZ(vertex) ?? 1);
15284
16781
  }
15285
16782
  for (let corner = 0; corner < 6; corner++) indices.push(start + sourceIndices.getX(face * 6 + corner) - face * 4);
15286
16783
  }
@@ -15289,11 +16786,12 @@ var SectionMesh = class _SectionMesh extends Group2 {
15289
16786
  geometry.setAttribute("position", new Float32BufferAttribute8(positions, 3));
15290
16787
  geometry.setAttribute("normal", new Float32BufferAttribute8(normals, 3));
15291
16788
  geometry.setAttribute("uv", new Float32BufferAttribute8(uvs, 2));
16789
+ geometry.setAttribute("uvBounds", new Float32BufferAttribute8(uvBounds, 4));
15292
16790
  geometry.setAttribute("color", new Float32BufferAttribute8(colors, 3));
15293
16791
  geometry.setIndex(indices);
15294
16792
  geometry.computeBoundingBox();
15295
16793
  geometry.computeBoundingSphere();
15296
- const material = Materials.createShadedCanvasMaterial(canvas, false);
16794
+ const material = Materials.createShadedCanvasMaterial(canvas, false, false, true);
15297
16795
  material.vertexColors = true;
15298
16796
  const texture = material.uniforms?.map?.value ?? material.map;
15299
16797
  const mesh = new Mesh9(geometry, material);
@@ -15354,7 +16852,7 @@ var Chunk = class _Chunk {
15354
16852
  return this.setBlockInChunkAt(pos, block, worldPos);
15355
16853
  }
15356
16854
  /**
15357
- * Set a block at integer chunk-local coordinates from 0 to 15.
16855
+ * Places a block at integer chunk-local coordinates from 0 to 15.
15358
16856
  */
15359
16857
  async setBlockInChunkAt(pos, block, worldPos, onBlocksChanged = this.onBlocksChanged) {
15360
16858
  if (typeof worldPos === "undefined") {
@@ -15367,7 +16865,7 @@ var Chunk = class _Chunk {
15367
16865
  this.renderedBlocks.get(index)?.object?.removeFromScene();
15368
16866
  if (this.sectionBlocks.delete(index)) this.meshDirty = true;
15369
16867
  this.renderedBlocks.delete(index);
15370
- let object;
16868
+ let object3;
15371
16869
  try {
15372
16870
  if (!readBlock) return void 0;
15373
16871
  const stored = readBlock();
@@ -15382,27 +16880,28 @@ var Chunk = class _Chunk {
15382
16880
  this.sectionBlocks.set(index, { index, template, cullMask: 0 });
15383
16881
  this.meshDirty = true;
15384
16882
  } else {
15385
- object = await this.scene.addBlock(blockState, {
16883
+ object3 = await this.scene.addBlock(blockState, {
15386
16884
  mergeMeshes: true,
15387
16885
  instanceMeshes: true,
15388
16886
  maxInstanceCount: 2e3,
15389
16887
  initialState: stored.properties
15390
16888
  });
15391
- object.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
16889
+ object3.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
15392
16890
  }
15393
16891
  const info = { get block() {
15394
16892
  return readBlock();
15395
- }, object };
16893
+ }, object: object3 };
15396
16894
  this.renderedBlocks.set(index, info);
15397
16895
  return info;
15398
16896
  } catch (error) {
15399
16897
  this.data.set(index, void 0);
15400
- object?.removeFromScene();
16898
+ object3?.removeFromScene();
15401
16899
  throw error;
15402
16900
  } finally {
15403
16901
  await onBlocksChanged?.([worldPos]);
15404
16902
  }
15405
16903
  }
16904
+ /** Changes visibility at world block coordinates while preserving the block's data. */
15406
16905
  async setBlockVisibleAt(pos, visible) {
15407
16906
  const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
15408
16907
  const block = this.renderedBlocks.get(index);
@@ -15456,6 +16955,7 @@ var Chunk = class _Chunk {
15456
16955
  this.scene.dirty = true;
15457
16956
  this.meshDirty = false;
15458
16957
  }
16958
+ /** Removes stored blocks and their render objects, then notifies the owning world of changed positions. */
15459
16959
  async clear(onBlocksChanged = this.onBlocksChanged) {
15460
16960
  const positions = [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
15461
16961
  new Vector317(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
@@ -15476,6 +16976,7 @@ var Chunk = class _Chunk {
15476
16976
  static isAir(block) {
15477
16977
  return ChunkData.isAir(block);
15478
16978
  }
16979
+ /** Maps chunk-local coordinates from 0 to 15 to a storage index, with x varying fastest, then z, then y. */
15479
16980
  static chunkPosToBlockIndex(pos) {
15480
16981
  if ([pos.x, pos.y, pos.z].some((value) => !Number.isInteger(value) || value < 0 || value >= 16)) {
15481
16982
  throw new RangeError("Block coordinates must be integers from 0 to 15 within a chunk");
@@ -15510,6 +17011,7 @@ export {
15510
17011
  Axis,
15511
17012
  BUILTIN_ENTITY,
15512
17013
  BUILTIN_GENERATED,
17014
+ BannerPatterns,
15513
17015
  BasicAssetKey,
15514
17016
  BatchedExecutor,
15515
17017
  BlockEntities,
@@ -15532,6 +17034,7 @@ export {
15532
17034
  DEFAULT_ROOT,
15533
17035
  DEFAULT_UV,
15534
17036
  DISPLAY_POSITIONS,
17037
+ DYE_COLORS,
15535
17038
  DefaultBlockStates,
15536
17039
  DisplayPosition,
15537
17040
  DisplayTransforms,
@@ -15542,6 +17045,7 @@ export {
15542
17045
  Env,
15543
17046
  FluidModelObject,
15544
17047
  Fonts,
17048
+ GUI_CONTAINER_LAYOUTS,
15545
17049
  Geometries,
15546
17050
  GuiHelper,
15547
17051
  GuiLight,
@@ -15570,11 +17074,14 @@ export {
15570
17074
  NodeCache,
15571
17075
  NodeEnv,
15572
17076
  OrbitControls,
17077
+ PackFormats,
17078
+ PackMetadata,
15573
17079
  PersistentCache,
15574
17080
  Renderer,
15575
17081
  RequestError,
15576
17082
  Requests,
15577
17083
  SKIN_PARTS,
17084
+ SceneDocumentLoader,
15578
17085
  SceneExporter,
15579
17086
  SceneInspector,
15580
17087
  SceneObject,
@@ -15650,6 +17157,7 @@ export {
15650
17157
  mergeBufferAttributes,
15651
17158
  mergeBufferGeometries,
15652
17159
  mergeVertices,
17160
+ parseSceneDocument,
15653
17161
  prefix,
15654
17162
  registerNodeEnv,
15655
17163
  sampleEntityAnimation,