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.
@@ -65,6 +65,7 @@ var init_PersistentCache = __esm({
65
65
  constructor(backing) {
66
66
  this.backing = backing;
67
67
  }
68
+ /** Opens a named store in the active environment using the current cache schema version. */
68
69
  static open(name) {
69
70
  return Env.provider.openCache(name, _PersistentCache.VERSION);
70
71
  }
@@ -260,9 +261,11 @@ var init_Caching = __esm({
260
261
  this.entityTextureCache,
261
262
  this.entityAnimationCache,
262
263
  this.blockEntityIndexCache,
263
- this.fontCache
264
+ this.fontCache,
265
+ this.bannerPatternCache
264
266
  ];
265
267
  }
268
+ /** Invalidates in-memory entries so later lookups reload them. Persisted entries are retained. */
266
269
  static clear() {
267
270
  for (const cache of this.all) {
268
271
  cache.invalidateAll();
@@ -308,6 +311,7 @@ var init_Caching = __esm({
308
311
  _Caching.entityAnimationCache = _Caching.createAssetCache();
309
312
  _Caching.blockEntityIndexCache = _Caching.createAssetCache();
310
313
  _Caching.fontCache = _Caching.createAssetCache();
314
+ _Caching.bannerPatternCache = _Caching.createAssetCache();
311
315
  Caching = _Caching;
312
316
  }
313
317
  });
@@ -328,6 +332,7 @@ var init_Geometries = __esm({
328
332
  }
329
333
  return geometry;
330
334
  }
335
+ /** Returns shared geometry. Use {@link createBox} when the caller needs to modify or dispose it independently. */
331
336
  static getBox(key) {
332
337
  const keyStr = serializeBoxGeometryKey(key);
333
338
  return Caching.boxGeometryCache.get(keyStr, (k) => {
@@ -505,12 +510,15 @@ var init_Requests = __esm({
505
510
  }
506
511
  };
507
512
  Requests = class {
513
+ /** Queues a general request. Set `request.signal` to cancel it and `request.timeout` to limit each attempt. */
508
514
  static genericRequest(request) {
509
515
  return this.genericQueue.request(request);
510
516
  }
517
+ /** Queues a request separately from general traffic, using the configured asset base URL for relative paths. */
511
518
  static mcAssetRequest(request) {
512
519
  return this.mcAssetRequestQueue.request(request);
513
520
  }
521
+ /** Sets the base URL for relative asset requests. Use {@link AssetLoader.setVersion} to change Minecraft versions. */
514
522
  static setMcAssetRoot(root) {
515
523
  this.mcAssetRequestQueue.baseURL = root;
516
524
  }
@@ -565,6 +573,7 @@ var init_WrappedImage = __esm({
565
573
  }
566
574
  return false;
567
575
  }
576
+ /** Whether the dimensions form a vertical strip of square frames, without consulting `.mcmeta`. */
568
577
  get animated() {
569
578
  return this.height > this.width && this.height % this.width === 0;
570
579
  }
@@ -611,6 +620,7 @@ var init_ImageLoader = __esm({
611
620
  static _createImage() {
612
621
  return createImage();
613
622
  }
623
+ /** Resolves when an image URL has decoded, or rejects if decoding fails. */
614
624
  static async loadAsync(src) {
615
625
  return new Promise((resolve, reject) => {
616
626
  const image = this._createImage();
@@ -619,6 +629,7 @@ var init_ImageLoader = __esm({
619
629
  image.src = src;
620
630
  });
621
631
  }
632
+ /** Returns an image immediately. Use the callbacks to observe loading, or await {@link loadAsync}. */
622
633
  static loadElement(src, onload, onerr) {
623
634
  const image = this._createImage();
624
635
  if (onload)
@@ -646,6 +657,7 @@ var init_ImageLoader = __esm({
646
657
  throw err;
647
658
  }
648
659
  }
660
+ /** Decodes the supplied bytes into a canvas without fetching the source URL again. */
649
661
  static async infoToCanvasData(info) {
650
662
  const type = info.type === "jpg" ? "jpeg" : info.type ?? "png";
651
663
  const image = await _ImageLoader.loadAsync(`data:image/${type};base64,${info.data.toString("base64")}`);
@@ -662,12 +674,14 @@ var init_ImageLoader = __esm({
662
674
  const image = await this.infoToCanvasData(info);
663
675
  return image.data.getImageData(0, 0, image.width, image.height);
664
676
  }
677
+ /** Returns cached RGBA pixels for an image URL or data URL. Failed loads can be retried. */
665
678
  static async getData(src) {
666
679
  const keyStr = serializeImageKey({ src });
667
680
  return await Caching.imageDataCache.get(keyStr, (k) => {
668
681
  return _ImageLoader.loadData(src);
669
682
  });
670
683
  }
684
+ /** Returns a cached canvas context and dimensions for an image URL or data URL. */
671
685
  static async getCanvasData(src) {
672
686
  const keyStr = serializeImageKey({ src });
673
687
  return await Caching.canvasImageDataCache.get(keyStr, (k) => {
@@ -750,7 +764,7 @@ var init_TextureLoader = __esm({
750
764
  static createTexture() {
751
765
  return new import_three3.Texture();
752
766
  }
753
- static loadInBackground(src, format = import_three3.RGBAFormat, rotation = 0) {
767
+ static loadInBackground(src, format2 = import_three3.RGBAFormat, rotation = 0) {
754
768
  const texture = this.createTexture();
755
769
  texture.colorSpace = THREE.SRGBColorSpace;
756
770
  const image = ImageLoader.loadElement(src, () => {
@@ -760,11 +774,12 @@ var init_TextureLoader = __esm({
760
774
  console.warn("Failed to load texture", src, error);
761
775
  });
762
776
  texture.image = image;
763
- texture.format = format;
777
+ texture.format = format2;
764
778
  texture.rotation = rotation;
765
779
  return Textures.initTextureProps(texture);
766
780
  }
767
- static load(src, format = import_three3.RGBAFormat, rotation = 0) {
781
+ /** Loads image pixels in the background. `rotation` is in radians. Check {@link hasFailed} for load failures. */
782
+ static load(src, format2 = import_three3.RGBAFormat, rotation = 0) {
768
783
  const texture = new import_three3.Texture();
769
784
  texture.colorSpace = THREE.SRGBColorSpace;
770
785
  ImageLoader.getData(src).then((image) => {
@@ -774,7 +789,7 @@ var init_TextureLoader = __esm({
774
789
  this.failedTextures.add(texture);
775
790
  console.warn("Failed to load texture", src, error);
776
791
  });
777
- texture.format = format;
792
+ texture.format = format2;
778
793
  texture.rotation = rotation;
779
794
  return Textures.initTextureProps(texture);
780
795
  }
@@ -801,8 +816,8 @@ var init_Textures = __esm({
801
816
  for (let y = 0; y < size; y++) {
802
817
  for (let x = 0; x < size; x++) {
803
818
  const offset = (y * size + x) * 4;
804
- const color = (x + y) % 2 === 0 ? 255 : 0;
805
- data[offset] = data[offset + 2] = color;
819
+ const color2 = (x + y) % 2 === 0 ? 255 : 0;
820
+ data[offset] = data[offset + 2] = color2;
806
821
  data[offset + 3] = 255;
807
822
  }
808
823
  }
@@ -813,6 +828,7 @@ var init_Textures = __esm({
813
828
  return this.initTextureProps(texture);
814
829
  });
815
830
  }
831
+ /** Sets nearest-neighbor filters without changing the texture's color space. Returns the same texture. */
816
832
  static initTextureProps(texture) {
817
833
  texture.magFilter = THREE2.NearestFilter;
818
834
  texture.minFilter = THREE2.NearestFilter;
@@ -825,11 +841,13 @@ var init_Textures = __esm({
825
841
  static createAsset(key) {
826
842
  return new import_three4.Texture();
827
843
  }
844
+ /** Creates an sRGB color texture from a canvas. */
828
845
  static createCanvasTexture(canvas) {
829
846
  const texture = new import_three4.CanvasTexture(canvas);
830
847
  texture.colorSpace = THREE2.SRGBColorSpace;
831
848
  return this.initTextureProps(texture);
832
849
  }
850
+ /** Returns a shared texture immediately and loads its image asynchronously. Failed cached loads are retried. */
833
851
  static getImage(key) {
834
852
  const keyStr = serializeTextureKey(key);
835
853
  const cached2 = Caching.textureCache.peek(keyStr);
@@ -854,6 +872,7 @@ var init_Materials = __esm({
854
872
  init_TextureLoader();
855
873
  init_Caching();
856
874
  Materials = class _Materials {
875
+ /** Shared checkerboard material, created on first access. */
857
876
  static get MISSING_TEXTURE() {
858
877
  return Caching.materialCache.get("builtin:missing-texture", () => new import_three5.MeshBasicMaterial({
859
878
  map: Textures.getMissing(),
@@ -925,6 +944,7 @@ var init_Materials = __esm({
925
944
  material.depthWrite = false;
926
945
  break;
927
946
  case "eyes":
947
+ case "no_outline":
928
948
  material.transparent = true;
929
949
  material.alphaTest = 0;
930
950
  material.depthWrite = false;
@@ -957,6 +977,7 @@ var init_Materials = __esm({
957
977
  if (tint !== void 0) material.color.multiply(new import_three5.Color(tint));
958
978
  return material;
959
979
  }
980
+ /** Creates an unlit, transparent GUI material without depth writes or tone mapping. */
960
981
  static createGuiCanvasMaterial(canvas) {
961
982
  const material = new import_three5.MeshBasicMaterial({
962
983
  map: Textures.createCanvasTexture(canvas),
@@ -982,7 +1003,7 @@ var init_Materials = __esm({
982
1003
  };
983
1004
  return material;
984
1005
  }
985
- static createShadedCanvasMaterial(canvas, transparent = false, shade = false) {
1006
+ static createShadedCanvasMaterial(canvas, transparent = false, shade = false, boundUvs = false) {
986
1007
  const vertShader = `
987
1008
  uniform bool SHADE;
988
1009
 
@@ -992,6 +1013,10 @@ var init_Materials = __esm({
992
1013
  #else
993
1014
  varying vec2 vUv;
994
1015
  #endif
1016
+ #ifdef BOUND_UVS
1017
+ attribute vec4 uvBounds;
1018
+ flat out vec4 vUvBounds;
1019
+ #endif
995
1020
  varying vec3 vTint;
996
1021
  varying float light;
997
1022
  varying float lift;
@@ -1027,13 +1052,17 @@ var init_Materials = __esm({
1027
1052
  }
1028
1053
 
1029
1054
  vUv = uv;
1055
+ #ifdef BOUND_UVS
1056
+ vUvBounds = uvBounds;
1057
+ #endif
1030
1058
  vTint = color;
1031
1059
 
1060
+ vec4 mvPosition = vec4(position, 1.0);
1032
1061
  #ifdef USE_INSTANCING
1033
- gl_Position = projectionMatrix * viewMatrix * modelMatrix * instanceMatrix * vec4(position, 1.0);
1034
- #else
1035
- gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
1062
+ mvPosition = instanceMatrix * mvPosition;
1036
1063
  #endif
1064
+ mvPosition = modelViewMatrix * mvPosition;
1065
+ gl_Position = projectionMatrix * mvPosition;
1037
1066
 
1038
1067
  }
1039
1068
  `;
@@ -1053,13 +1082,22 @@ var init_Materials = __esm({
1053
1082
  #else
1054
1083
  varying vec2 vUv;
1055
1084
  #endif
1085
+ #ifdef BOUND_UVS
1086
+ flat in vec4 vUvBounds;
1087
+ #endif
1056
1088
  varying vec3 vTint;
1057
1089
  varying float light;
1058
1090
  varying float lift;
1059
1091
 
1060
1092
  void main(void)
1061
1093
  {
1062
- vec4 color = texture2D(map, vUv);
1094
+ #ifdef BOUND_UVS
1095
+ // Keep edge samples inside their texture even without centroid interpolation.
1096
+ vec2 mapUv = clamp(vUv, vUvBounds.xy, vUvBounds.zw);
1097
+ #else
1098
+ vec2 mapUv = vUv;
1099
+ #endif
1100
+ vec4 color = texture2D(map, mapUv);
1063
1101
  color.rgb *= vTint;
1064
1102
 
1065
1103
  if (color.a < 0.01) discard;
@@ -1080,7 +1118,8 @@ var init_Materials = __esm({
1080
1118
  }
1081
1119
  `;
1082
1120
  try {
1083
- return new import_three5.ShaderMaterial({
1121
+ const material = new import_three5.ShaderMaterial({
1122
+ defines: boundUvs ? { BOUND_UVS: true } : {},
1084
1123
  uniforms: {
1085
1124
  SHADE: { value: true },
1086
1125
  BRIGHTNESS: { value: 1 },
@@ -1098,11 +1137,14 @@ var init_Materials = __esm({
1098
1137
  side: transparent ? import_three5.DoubleSide : import_three5.FrontSide,
1099
1138
  alphaTest: 0.5
1100
1139
  });
1140
+ Object.assign(material.defaultAttributeValues, { uvBounds: [0, 0, 1, 1] });
1141
+ return material;
1101
1142
  } catch (e) {
1102
1143
  console.warn(e);
1103
1144
  }
1104
1145
  return this.createBasicCanvasMaterial(canvas, transparent, shade);
1105
1146
  }
1147
+ /** Returns a shared image material. Use {@link createImage} for a separate material instance. */
1106
1148
  static getImage(key) {
1107
1149
  const keyStr = serializeMaterialKey(key);
1108
1150
  const map = Caching.materialCache.peek(keyStr)?.map;
@@ -1277,6 +1319,14 @@ var init_AssetKey = __esm({
1277
1319
  }
1278
1320
  };
1279
1321
  AssetKey = class _AssetKey extends BasicAssetKey {
1322
+ /**
1323
+ * Creates a key from separate path components. Use {@link parse} for a combined reference.
1324
+ *
1325
+ * @param assetType - Asset directory, such as `models`, `textures`, or `blockstates`.
1326
+ * @param type - Subdirectory, such as `block` or `item`.
1327
+ * @param rootType - Top-level directory, usually `assets` or `data`.
1328
+ * @param root - Base URL override for hosted asset sources.
1329
+ */
1280
1330
  constructor(namespace, path, assetType, type, rootType = "assets", extension = ".json", root) {
1281
1331
  super(namespace, path);
1282
1332
  this.namespace = namespace;
@@ -1288,6 +1338,16 @@ var init_AssetKey = __esm({
1288
1338
  this.root = root;
1289
1339
  this.isAssetKey = true;
1290
1340
  }
1341
+ /**
1342
+ * Parses an asset reference such as `minecraft:block/stone`.
1343
+ * A missing namespace defaults to the origin's namespace or `minecraft`.
1344
+ *
1345
+ * @param assetType - Asset category, such as `models` or `textures`.
1346
+ * @param str - Path with an optional namespace and file extension.
1347
+ * @param origin - Supplies the asset root, default namespace, and non-texture extension.
1348
+ * @returns A key with the matching extension removed from its path. Textures use `.png`;
1349
+ * other assets use the origin's extension or `.json`.
1350
+ */
1291
1351
  static parse(assetType, str, origin) {
1292
1352
  let namespace = origin?.namespace || DEFAULT_NAMESPACE;
1293
1353
  if (str.includes(":")) {
@@ -1319,6 +1379,7 @@ var init_AssetKey = __esm({
1319
1379
  ];
1320
1380
  return a.join("/");
1321
1381
  }
1382
+ /** Joins the type subdirectory and path without adding a namespace or file extension. */
1322
1383
  getFullPath() {
1323
1384
  let p10 = [];
1324
1385
  if (this.type) {
@@ -1330,6 +1391,7 @@ var init_AssetKey = __esm({
1330
1391
  toNamespacedString() {
1331
1392
  return this.namespace + ":" + this.getFullPath();
1332
1393
  }
1394
+ /** Returns a cache identifier that includes the explicit root or {@link AssetLoader.ROOT}. */
1333
1395
  serialize() {
1334
1396
  return this.toString();
1335
1397
  }
@@ -1341,17 +1403,17 @@ var init_AssetKey = __esm({
1341
1403
  var DisplayPosition, DISPLAY_POSITIONS;
1342
1404
  var init_DisplayPosition = __esm({
1343
1405
  "src/model/DisplayPosition.ts"() {
1344
- DisplayPosition = /* @__PURE__ */ ((DisplayPosition2) => {
1345
- DisplayPosition2["THIRDPERSON_RIGHTHAND"] = "thirdperson_righthand";
1346
- DisplayPosition2["THIRDPERSON_LEFTHAND"] = "thirdperson_lefthand";
1347
- DisplayPosition2["FIRSTPERSON_RIGHTHAND"] = "firstperson_righthand";
1348
- DisplayPosition2["FIRSTPERSON_LEFTHAND"] = "firstperson_lefthand";
1349
- DisplayPosition2["GUI"] = "gui";
1350
- DisplayPosition2["HEAD"] = "head";
1351
- DisplayPosition2["GROUND"] = "ground";
1352
- DisplayPosition2["FIXED"] = "fixed";
1353
- DisplayPosition2["ON_SHELF"] = "on_shelf";
1354
- return DisplayPosition2;
1406
+ DisplayPosition = /* @__PURE__ */ ((DisplayPosition3) => {
1407
+ DisplayPosition3["THIRDPERSON_RIGHTHAND"] = "thirdperson_righthand";
1408
+ DisplayPosition3["THIRDPERSON_LEFTHAND"] = "thirdperson_lefthand";
1409
+ DisplayPosition3["FIRSTPERSON_RIGHTHAND"] = "firstperson_righthand";
1410
+ DisplayPosition3["FIRSTPERSON_LEFTHAND"] = "firstperson_lefthand";
1411
+ DisplayPosition3["GUI"] = "gui";
1412
+ DisplayPosition3["HEAD"] = "head";
1413
+ DisplayPosition3["GROUND"] = "ground";
1414
+ DisplayPosition3["FIXED"] = "fixed";
1415
+ DisplayPosition3["ON_SHELF"] = "on_shelf";
1416
+ return DisplayPosition3;
1355
1417
  })(DisplayPosition || {});
1356
1418
  DISPLAY_POSITIONS = Object.values(DisplayPosition);
1357
1419
  }
@@ -1389,6 +1451,7 @@ var init_DisplayTransforms = __esm({
1389
1451
  const scale = new import_three7.Vector3(...transform.scale ?? [1, 1, 1]).clampScalar(-4, 4);
1390
1452
  return new import_three7.Matrix4().compose(translation, rotation, scale);
1391
1453
  }
1454
+ /** Transforms geometry in place and reverses triangle winding when the matrix reflects it. */
1392
1455
  static apply(geometry, matrix) {
1393
1456
  geometry.applyMatrix4(matrix);
1394
1457
  if (matrix.determinant() < 0) {
@@ -1416,7 +1479,10 @@ var init_ModelMerger = __esm({
1416
1479
  init_AssetKey();
1417
1480
  init_DisplayTransforms();
1418
1481
  ModelMerger = class {
1482
+ /** Loads the parent chain and returns a merged model without modifying the supplied definition. */
1419
1483
  static async mergeWithParents(model) {
1484
+ const parts = model.parts;
1485
+ if (parts) return { ...model, parts: await Promise.all(parts.map((part) => this.mergeWithParents(part))) };
1420
1486
  const models = await this.collectAllParents(model);
1421
1487
  let merged = {};
1422
1488
  for (const source of [...models, model]) {
@@ -1428,6 +1494,7 @@ var init_ModelMerger = __esm({
1428
1494
  }
1429
1495
  merged.hierarchy = models.map((m) => m.parent).filter((p10) => `${p10}`);
1430
1496
  merged.hierarchy.push(`${merged.parent}`);
1497
+ if (model.components) merged.components = model.components;
1431
1498
  return merged;
1432
1499
  }
1433
1500
  static async collectAllParents(model) {
@@ -1466,6 +1533,79 @@ var init_AssetParsers = __esm({
1466
1533
  }
1467
1534
  });
1468
1535
 
1536
+ // src/assets/BannerPatterns.ts
1537
+ var DYE_COLORS, BannerPatterns;
1538
+ var init_BannerPatterns = __esm({
1539
+ "src/assets/BannerPatterns.ts"() {
1540
+ init_AssetKey();
1541
+ init_AssetLoader();
1542
+ init_AssetParsers();
1543
+ init_Caching();
1544
+ DYE_COLORS = Object.freeze({
1545
+ white: 16383998,
1546
+ orange: 16351261,
1547
+ magenta: 13061821,
1548
+ light_blue: 3847130,
1549
+ yellow: 16701501,
1550
+ lime: 8439583,
1551
+ pink: 15961002,
1552
+ gray: 4673362,
1553
+ light_gray: 10329495,
1554
+ cyan: 1481884,
1555
+ purple: 8991416,
1556
+ blue: 3949738,
1557
+ brown: 8606770,
1558
+ green: 6192150,
1559
+ red: 11546150,
1560
+ black: 1908001
1561
+ });
1562
+ BannerPatterns = class {
1563
+ /** Lists namespaced pattern IDs from the selected namespace's data directory. */
1564
+ static async getList(namespace = "minecraft", root) {
1565
+ const collect = async (path) => {
1566
+ const prefix2 = path ? `${path}/` : "";
1567
+ const key = new AssetKey(namespace, `${prefix2}_list`, "banner_pattern", void 0, "data", ".json", root);
1568
+ const list2 = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get(key, "list" /* LIST */));
1569
+ if (!list2) return [];
1570
+ const files = list2.files.filter((file) => file.endsWith(".json") && file !== "_list.json").map((file) => `${namespace}:${prefix2}${file.slice(0, -5)}`);
1571
+ const children = await Promise.all(list2.directories.map((directory) => collect(`${prefix2}${directory}`)));
1572
+ return [...files, ...children.flat()];
1573
+ };
1574
+ return collect("");
1575
+ }
1576
+ /** Returns the named dye's sRGB value, rejecting unknown colors. */
1577
+ static getColor(color2) {
1578
+ if (typeof color2 !== "string" || !Object.prototype.hasOwnProperty.call(DYE_COLORS, color2)) throw new Error(`Unsupported dye color ${color2}`);
1579
+ return DYE_COLORS[color2];
1580
+ }
1581
+ /** Resolves the first 16 layers of a `banner_patterns` component, preserving their order. */
1582
+ static async getLayers(value, target, root) {
1583
+ if (value === void 0) return [];
1584
+ if (!Array.isArray(value)) throw new Error("banner_patterns must be an array");
1585
+ return Promise.all(value.slice(0, 16).map(async (layer) => {
1586
+ if (!layer || typeof layer !== "object" || Array.isArray(layer)) throw new Error("Each banner pattern requires a pattern and dye color");
1587
+ const color2 = this.getColor(layer.color);
1588
+ let pattern2 = layer.pattern;
1589
+ if (typeof pattern2 === "string") {
1590
+ const id = this.identifier(pattern2);
1591
+ const key = new AssetKey(id.namespace, id.getFullPath(), "banner_pattern", void 0, "data", ".json", root);
1592
+ pattern2 = await Caching.bannerPatternCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
1593
+ if (!pattern2) throw new Error(`Missing banner pattern ${layer.pattern}`);
1594
+ }
1595
+ if (!pattern2 || typeof pattern2 !== "object" || Array.isArray(pattern2) || typeof pattern2.translation_key !== "string") throw new Error("Banner patterns require asset_id and translation_key");
1596
+ const asset2 = this.identifier(pattern2.asset_id);
1597
+ return { color: color2, texture: new AssetKey(asset2.namespace, `${target}/${asset2.getFullPath()}`, "textures", "entity", "assets", ".png", root) };
1598
+ }));
1599
+ }
1600
+ static identifier(value) {
1601
+ if (typeof value !== "string" || !/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(value)) throw new Error(`Invalid banner-pattern identifier ${value}`);
1602
+ const [namespace, path] = value.includes(":") ? value.split(":") : ["minecraft", value];
1603
+ return new AssetKey(namespace, path);
1604
+ }
1605
+ };
1606
+ }
1607
+ });
1608
+
1469
1609
  // src/assets/Models.ts
1470
1610
  var Models;
1471
1611
  var init_Models = __esm({
@@ -1477,12 +1617,15 @@ var init_Models = __esm({
1477
1617
  init_PersistentCache();
1478
1618
  init_AssetKey();
1479
1619
  init_AssetParsers();
1620
+ init_DisplayPosition();
1621
+ init_BannerPatterns();
1480
1622
  Models = class _Models {
1481
1623
  // opened lazily: touching the store at import time would hit IndexedDB/disk just for loading
1482
1624
  // the library, and would require an EnvProvider before any entry has had a chance to register
1483
1625
  static get PERSISTENT_CACHE() {
1484
1626
  return this._persistentCache ?? (this._persistentCache = PersistentCache.open("minerender-models"));
1485
1627
  }
1628
+ /** Lists item filenames from the asset source's `_list.json`, with a legacy model-directory fallback. */
1486
1629
  static async getItemList() {
1487
1630
  const key = new AssetKey(DEFAULT_NAMESPACE, "_list", "items");
1488
1631
  const legacyKey = new AssetKey(DEFAULT_NAMESPACE, "_list", "models", "item");
@@ -1490,34 +1633,174 @@ var init_Models = __esm({
1490
1633
  return (await AssetLoader.getFirst([key, legacyKey], "list" /* LIST */))?.asset;
1491
1634
  }).then((r) => r?.files ?? []);
1492
1635
  }
1493
- static async loadAndMerge(key) {
1494
- const model = key.type === "item" ? await this.getItemModel(key) : await this.getRaw(key);
1636
+ static async loadAndMerge(key, context = {}) {
1637
+ const model = key.type === "item" ? await this.getItemModel(key, context) : await this.getRaw(key);
1495
1638
  if (!model) {
1496
1639
  return void 0;
1497
1640
  }
1498
1641
  return ModelMerger.mergeWithParents(model);
1499
1642
  }
1500
- static async getItemModel(key) {
1643
+ static async getItemModel(key, context = {}) {
1644
+ const preview = this.snapshotContext(key, context);
1501
1645
  const itemKey = new AssetKey(key.namespace, key.path, "items", void 0, key.rootType, ".json", key.root);
1502
- const model = await this.PERSISTENT_CACHE.getOrLoad(`item-v2:${AssetLoader.persistentKey(itemKey.serialize())}`, async () => {
1646
+ const cacheKey = itemKey.serialize() + this.contextKey(preview);
1647
+ const model = await this.PERSISTENT_CACHE.getOrLoad(`item-v4:${AssetLoader.persistentKey(cacheKey)}`, async () => {
1503
1648
  const result = await AssetLoader.getFirst([itemKey, key], "json" /* JSON */);
1504
1649
  if (!result) return void 0;
1505
- if (result.key.assetType !== "items") return { ...result.asset, key };
1506
- const selected = this.defaultItemModel(result.asset.model, key);
1507
- const modelKey = AssetKey.parse("models", selected.model);
1508
- modelKey.root = key.root;
1509
- const model2 = await this.getRaw(modelKey);
1510
- if (!model2) throw new Error(`Item ${key.toNamespacedString()} references missing model ${selected.model}`);
1511
- return { ...model2, ...selected.special && { special: selected.special }, ...selected.tints && { tints: selected.tints } };
1650
+ if (result.key.assetType !== "items") return { ...result.asset, key, components: preview.components };
1651
+ const load = async (selected) => {
1652
+ if ("parts" in selected) {
1653
+ return { key, parts: await Promise.all(selected.parts.map(load)) };
1654
+ }
1655
+ if ("item" in selected) {
1656
+ const model3 = await this.getItemModel(selected.item, { displayContext: preview.displayContext });
1657
+ if (!model3) throw new Error(`Item ${key.toNamespacedString()} references missing item ${selected.item.toNamespacedString()}`);
1658
+ return model3;
1659
+ }
1660
+ const modelKey = AssetKey.parse("models", selected.model);
1661
+ modelKey.root = key.root;
1662
+ const model2 = await this.getRaw(modelKey);
1663
+ if (!model2) throw new Error(`Item ${key.toNamespacedString()} references missing model ${selected.model}`);
1664
+ return { ...model2, components: preview.components, ...selected.special && { special: selected.special }, ...selected.tints && { tints: selected.tints } };
1665
+ };
1666
+ return load(this.selectItemModel(result.asset.model, key, preview));
1667
+ });
1668
+ const restore = (model2) => ({
1669
+ ...model2,
1670
+ key: Object.assign(new AssetKey("", ""), model2.key),
1671
+ ...model2.parts && { parts: model2.parts.map(restore) }
1512
1672
  });
1513
- return model ? { ...model, key: Object.assign(new AssetKey("", ""), model.key) } : void 0;
1673
+ return model ? restore(model) : void 0;
1674
+ }
1675
+ static contextIdentifier(id) {
1676
+ if (typeof id !== "string" || !/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(id)) {
1677
+ throw new Error(`Invalid item-preview identifier: ${id}`);
1678
+ }
1679
+ return id.includes(":") ? id : `minecraft:${id}`;
1680
+ }
1681
+ static snapshotContext(key, context) {
1682
+ const displayContext = context.displayContext ?? "gui" /* GUI */;
1683
+ if (displayContext !== "none" && !Object.values(DisplayPosition).includes(displayContext)) {
1684
+ throw new Error(`Unsupported item display context: ${displayContext}`);
1685
+ }
1686
+ for (const map of [context.properties, context.components, context.itemReferences]) {
1687
+ if (map !== void 0 && (!map || typeof map !== "object" || Array.isArray(map))) {
1688
+ throw new Error("Item-preview properties, components, and references must be objects keyed by identifier");
1689
+ }
1690
+ }
1691
+ const count = context.count === void 0 ? 1 : context.count;
1692
+ if (!Number.isSafeInteger(count) || count < 0) throw new Error("Item-preview count must be a nonnegative safe integer");
1693
+ const properties = {};
1694
+ for (const [id, value] of Object.entries(context.properties ?? {})) {
1695
+ const property = this.contextIdentifier(id);
1696
+ if (!["boolean", "string", "number"].includes(typeof value) || typeof value === "number" && !Number.isFinite(value)) {
1697
+ throw new Error(`Item-preview property ${property} must be a boolean, string, or finite number`);
1698
+ }
1699
+ if (Object.prototype.hasOwnProperty.call(properties, property)) throw new Error(`Duplicate item-preview property: ${property}`);
1700
+ properties[property] = value;
1701
+ }
1702
+ const components = {};
1703
+ for (const [id, value] of Object.entries(context.components ?? {})) {
1704
+ const component = this.contextIdentifier(id);
1705
+ if (Object.prototype.hasOwnProperty.call(components, component)) throw new Error(`Duplicate item-preview component: ${component}`);
1706
+ components[component] = this.snapshotJson(value);
1707
+ }
1708
+ const itemReferences = {};
1709
+ for (const [id, value] of Object.entries(context.itemReferences ?? {})) {
1710
+ const reference = this.contextIdentifier(id);
1711
+ if (!isAssetKey(value) || value.assetType !== "models" || value.type !== "item") {
1712
+ throw new Error(`Item-preview reference ${reference} must be an item-model AssetKey`);
1713
+ }
1714
+ this.contextIdentifier(`${value.namespace}:${value.path}`);
1715
+ if (Object.prototype.hasOwnProperty.call(itemReferences, reference)) throw new Error(`Duplicate item-preview reference: ${reference}`);
1716
+ itemReferences[reference] = Object.assign(new AssetKey("", ""), value, { root: value.root ?? key.root });
1717
+ }
1718
+ return { displayContext, properties, components, count, itemReferences };
1719
+ }
1720
+ static contextKey(context) {
1721
+ const itemReferences = Object.fromEntries(Object.entries(context.itemReferences).map(([id, key]) => [id, key.serialize()]));
1722
+ return `|item-v4:${JSON.stringify(this.snapshotJson({ ...context, itemReferences }))}`;
1514
1723
  }
1515
- // Item previews use the GUI context, false conditions, and zero numeric properties.
1516
- static defaultItemModel(node, key) {
1724
+ static snapshotJson(value, ancestors = /* @__PURE__ */ new Set()) {
1725
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) return value;
1726
+ if (typeof value !== "object" || ancestors.has(value) || !Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
1727
+ throw new Error("Item-preview components must contain finite JSON values without circular references");
1728
+ }
1729
+ ancestors.add(value);
1730
+ 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)]));
1731
+ ancestors.delete(value);
1732
+ return copy;
1733
+ }
1734
+ static propertyValue(node, context) {
1735
+ const kind = node.type.replace(/^minecraft:/, "");
1736
+ const property = node.property ? this.contextIdentifier(node.property) : void 0;
1737
+ if (kind === "select" && property === "minecraft:display_context") return context.displayContext;
1738
+ if (property && Object.prototype.hasOwnProperty.call(context.properties, property)) return context.properties[property];
1739
+ const fallback = kind === "condition" ? false : kind === "range_dispatch" ? 0 : void 0;
1740
+ const component = (id) => context.components[this.contextIdentifier(id)];
1741
+ if (property === "minecraft:custom_model_data") {
1742
+ const index = node.index ?? 0;
1743
+ if (!Number.isSafeInteger(index) || index < 0) throw new Error("Item-model custom_model_data index must be a nonnegative integer");
1744
+ const data = component("custom_model_data");
1745
+ if (data === void 0) return fallback;
1746
+ if (!data || typeof data !== "object" || Array.isArray(data)) throw new Error("Item-preview custom_model_data must be an object");
1747
+ const field = kind === "condition" ? "flags" : kind === "select" ? "strings" : "floats";
1748
+ const values = data[field];
1749
+ if (values === void 0) return fallback;
1750
+ if (!Array.isArray(values)) throw new Error(`Item-preview custom_model_data.${field} must be an array`);
1751
+ return index < values.length ? values[index] : fallback;
1752
+ }
1753
+ if (kind === "condition" && property === "minecraft:has_component") {
1754
+ if (node.ignore_default) throw new Error("Item-preview has_component with ignore_default requires an explicit properties override; item defaults are unavailable");
1755
+ return Object.prototype.hasOwnProperty.call(context.components, this.contextIdentifier(node.component));
1756
+ }
1757
+ if (kind === "select") {
1758
+ switch (property) {
1759
+ case "minecraft:component":
1760
+ return component(node.component);
1761
+ case "minecraft:block_state": {
1762
+ const state = component("block_state");
1763
+ if (state === void 0) return void 0;
1764
+ if (!state || typeof state !== "object" || Array.isArray(state)) throw new Error("Item-preview block_state must be an object");
1765
+ if (typeof node.block_state_property !== "string") throw new Error("Item-model block_state requires block_state_property");
1766
+ return Object.prototype.hasOwnProperty.call(state, node.block_state_property) ? state[node.block_state_property] : void 0;
1767
+ }
1768
+ case "minecraft:charge_type": {
1769
+ const projectiles = component("charged_projectiles");
1770
+ if (projectiles === void 0) return "none";
1771
+ if (!Array.isArray(projectiles)) throw new Error("Item-preview charged_projectiles must be an array of item stacks");
1772
+ const ids = projectiles.map((stack) => {
1773
+ if (!stack || typeof stack !== "object" || Array.isArray(stack)) throw new Error("Item-preview charged_projectiles must contain item-stack objects");
1774
+ return this.contextIdentifier(stack.id);
1775
+ });
1776
+ return !ids.length ? "none" : ids.includes("minecraft:firework_rocket") ? "rocket" : "arrow";
1777
+ }
1778
+ }
1779
+ }
1780
+ if (kind === "range_dispatch" && (property === "minecraft:damage" || property === "minecraft:count")) {
1781
+ const damage = property === "minecraft:damage";
1782
+ const maximumComponent = component(damage ? "max_damage" : "max_stack_size");
1783
+ const maximum = maximumComponent === void 0 ? damage ? 0 : 1 : maximumComponent;
1784
+ const damageComponent = component("damage");
1785
+ const value = damage ? damageComponent === void 0 ? 0 : damageComponent : context.count;
1786
+ if (typeof value !== "number" || typeof maximum !== "number" || maximum < 0) throw new Error(`Item-preview ${property} requires numeric components and a nonnegative maximum`);
1787
+ const clamped = Math.max(0, Math.min(maximum, value));
1788
+ return node.normalize === false ? clamped : clamped / maximum;
1789
+ }
1790
+ return fallback;
1791
+ }
1792
+ static selectItemModel(node, key, context) {
1517
1793
  if (!node || typeof node.type !== "string") {
1518
1794
  throw new Error(`Unsupported item model definition for ${key.toNamespacedString()}`);
1519
1795
  }
1520
1796
  switch (node.type.replace(/^minecraft:/, "")) {
1797
+ case "composite":
1798
+ if (Array.isArray(node.models)) return { parts: node.models.map((child) => this.selectItemModel(child, key, context)) };
1799
+ break;
1800
+ case "bundle/selected_item": {
1801
+ const item = context.itemReferences["minecraft:bundle/selected_item"];
1802
+ return item ? { item } : { parts: [] };
1803
+ }
1521
1804
  case "model":
1522
1805
  if (typeof node.model === "string" && node.model) return { model: node.model, tints: node.tints };
1523
1806
  break;
@@ -1525,6 +1808,23 @@ var init_Models = __esm({
1525
1808
  if (!node.base || !node.model || typeof node.model !== "object") break;
1526
1809
  const special = node.model;
1527
1810
  switch (special.type) {
1811
+ case "banner":
1812
+ case "minecraft:banner":
1813
+ if (typeof special.color === "string" && Object.prototype.hasOwnProperty.call(DYE_COLORS, special.color)) return { model: node.base, special };
1814
+ break;
1815
+ case "shield":
1816
+ case "minecraft:shield":
1817
+ case "trident":
1818
+ case "minecraft:trident":
1819
+ case "conduit":
1820
+ case "minecraft:conduit":
1821
+ return { model: node.base, special };
1822
+ case "shulker_box":
1823
+ case "minecraft:shulker_box":
1824
+ 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))) {
1825
+ return { model: node.base, special };
1826
+ }
1827
+ break;
1528
1828
  case "chest":
1529
1829
  case "minecraft:chest":
1530
1830
  case "bed":
@@ -1538,21 +1838,34 @@ var init_Models = __esm({
1538
1838
  }
1539
1839
  throw new Error(`Unsupported special item renderer ${special.type} for ${key.toNamespacedString()}`);
1540
1840
  }
1541
- case "condition":
1542
- return this.defaultItemModel(node.on_false, key);
1841
+ case "condition": {
1842
+ const value = this.propertyValue(node, context);
1843
+ if (typeof value !== "boolean") throw new Error(`Item-preview condition ${node.property} requires a boolean`);
1844
+ return this.selectItemModel(value ? node.on_true : node.on_false, key, context);
1845
+ }
1543
1846
  case "select": {
1544
- 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;
1545
- return this.defaultItemModel(selected ?? node.fallback, key);
1847
+ const value = this.propertyValue(node, context);
1848
+ const component = node.property && this.contextIdentifier(node.property) === "minecraft:component";
1849
+ if (!component && value !== void 0 && typeof value !== "string") throw new Error(`Item-preview selector ${node.property} requires a string`);
1850
+ const matches = (candidate) => component ? JSON.stringify(this.snapshotJson(candidate)) === JSON.stringify(this.snapshotJson(value)) : candidate === value;
1851
+ const selected = value === void 0 ? void 0 : node.cases?.find((entry) => matches(entry.when) || Array.isArray(entry.when) && entry.when.some(matches))?.model;
1852
+ return this.selectItemModel(selected ?? node.fallback, key, context);
1546
1853
  }
1547
1854
  case "range_dispatch": {
1855
+ const value = this.propertyValue(node, context);
1856
+ if (typeof value !== "number") throw new Error(`Item-preview range ${node.property} requires a number`);
1857
+ if (Number.isNaN(value)) return this.selectItemModel(node.fallback, key, context);
1858
+ const scaled = value * (node.scale ?? 1);
1859
+ if (!Number.isFinite(scaled)) throw new Error(`Item-preview range ${node.property} requires a finite value and scale`);
1548
1860
  const selected = node.entries?.reduce((match, entry) => {
1549
- return entry.threshold <= 0 && (!match || entry.threshold >= match.threshold) ? entry : match;
1861
+ return entry.threshold <= scaled && (!match || entry.threshold >= match.threshold) ? entry : match;
1550
1862
  }, void 0);
1551
- return this.defaultItemModel(selected?.model ?? node.fallback, key);
1863
+ return this.selectItemModel(selected?.model ?? node.fallback, key, context);
1552
1864
  }
1553
1865
  }
1554
1866
  throw new Error(`Unsupported item model ${node?.type ?? "definition"} for ${key.toNamespacedString()}`);
1555
1867
  }
1868
+ /** Loads one model file without resolving its parents. Returns `undefined` when the file is missing. */
1556
1869
  static async getRaw(key) {
1557
1870
  if (!key.assetType) {
1558
1871
  key.assetType = "models";
@@ -1565,28 +1878,39 @@ var init_Models = __esm({
1565
1878
  return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
1566
1879
  return AssetLoader.get(key, "model" /* MODEL */);
1567
1880
  });
1568
- }).then((asset) => {
1569
- if (asset) {
1570
- asset.key = key;
1881
+ }).then((asset2) => {
1882
+ if (asset2) {
1883
+ asset2.key = key;
1571
1884
  }
1572
- return asset;
1885
+ return asset2;
1573
1886
  });
1574
1887
  }
1575
- static async getMerged(key) {
1888
+ /**
1889
+ * Loads a model and resolves its parent chain. Returns `undefined` when the model is missing.
1890
+ * Item keys default to GUI context and a stack count of 1. Unresolved conditions are false and numeric properties are zero.
1891
+ * Pass `context` to supply component values, property overrides, and item references for a preview.
1892
+ * Composite items retain independently merged children in `ItemModel.parts`.
1893
+ *
1894
+ * @param key - Model key, for example `AssetKey.parse("models", "minecraft:item/diamond_sword")`.
1895
+ */
1896
+ static async getMerged(key, context = {}) {
1576
1897
  if (!key.assetType) {
1577
1898
  key.assetType = "models";
1578
1899
  }
1579
1900
  if (!key.extension) {
1580
1901
  key.extension = ".json";
1581
1902
  }
1582
- const keyStr = key.serialize();
1903
+ const preview = this.snapshotContext(key, context);
1904
+ const keyStr = key.serialize() + (key.type === "item" ? this.contextKey(preview) : "");
1583
1905
  return Caching.mergedModelCache.get(keyStr, (k) => {
1584
- return _Models.loadAndMerge(key);
1906
+ return _Models.loadAndMerge(key, preview);
1585
1907
  });
1586
1908
  }
1587
- static async get(key) {
1588
- return this.getMerged(key);
1909
+ /** Alias for {@link getMerged}. */
1910
+ static async get(key, context = {}) {
1911
+ return this.getMerged(key, context);
1589
1912
  }
1913
+ /** Clears persisted models. Use {@link Caching.clear} to also discard in-memory assets. */
1590
1914
  static async clearCache() {
1591
1915
  return this.PERSISTENT_CACHE.clear();
1592
1916
  }
@@ -1609,6 +1933,7 @@ var init_BlockStates = __esm({
1609
1933
  return this._persistentCache ?? (this._persistentCache = PersistentCache.open("minerender-blockstates"));
1610
1934
  }
1611
1935
  // BlockState names are hardcoded
1936
+ /** Returns blockstate filenames from the selected version's `_list.json`, or an empty list. */
1612
1937
  static async getList() {
1613
1938
  const key = new AssetKey(
1614
1939
  DEFAULT_NAMESPACE,
@@ -1629,6 +1954,7 @@ var init_BlockStates = __esm({
1629
1954
  return AssetLoader.get(key, "json" /* JSON */);
1630
1955
  });
1631
1956
  }
1957
+ /** Looks up vanilla property definitions for the key's block path, or returns `undefined`. */
1632
1958
  static async getDefaultState(key) {
1633
1959
  const defaultStates = await this.getDefaultStates();
1634
1960
  if (!defaultStates) {
@@ -1636,6 +1962,7 @@ var init_BlockStates = __esm({
1636
1962
  }
1637
1963
  return defaultStates["minecraft:" + key.path];
1638
1964
  }
1965
+ /** Loads a cached blockstate definition for {@link MineRenderScene.addBlock}, or returns `undefined`. */
1639
1966
  static async get(key) {
1640
1967
  if (!key.assetType) {
1641
1968
  key.assetType = "blockstates";
@@ -1648,11 +1975,11 @@ var init_BlockStates = __esm({
1648
1975
  return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
1649
1976
  return AssetLoader.get(key, "blockstate" /* BLOCKSTATE */);
1650
1977
  });
1651
- }).then((asset) => {
1652
- if (asset) {
1653
- asset.key = key;
1978
+ }).then((asset2) => {
1979
+ if (asset2) {
1980
+ asset2.key = key;
1654
1981
  }
1655
- return asset;
1982
+ return asset2;
1656
1983
  });
1657
1984
  }
1658
1985
  static getAll(keys) {
@@ -1662,6 +1989,7 @@ var init_BlockStates = __esm({
1662
1989
  }
1663
1990
  return Promise.all(promises);
1664
1991
  }
1992
+ /** Clears persisted blockstates. Use {@link Caching.clear} to also discard in-memory assets. */
1665
1993
  static async clearCache() {
1666
1994
  await this.PERSISTENT_CACHE.clear();
1667
1995
  }
@@ -1687,9 +2015,11 @@ var init_Assets = __esm({
1687
2015
  static parseAssetKey(assetType, str, origin) {
1688
2016
  return AssetKey.parse(assetType, str, origin);
1689
2017
  }
2018
+ /** Loads a model with its parent chain resolved. See {@link Models.getMerged}. */
1690
2019
  static async getModel(key) {
1691
2020
  return Models.getMerged(key);
1692
2021
  }
2022
+ /** Loads a blockstate definition. See {@link BlockStates.get}. */
1693
2023
  static async getBlockState(key) {
1694
2024
  return BlockStates.get(key);
1695
2025
  }
@@ -1704,6 +2034,10 @@ var init_AssetSource = __esm({
1704
2034
  AssetSource = class {
1705
2035
  constructor() {
1706
2036
  }
2037
+ /** Whether this source prevents lower-priority sources from supplying the key. Its own assets remain available. */
2038
+ blocks(key) {
2039
+ return false;
2040
+ }
1707
2041
  /**
1708
2042
  * Identifies this source's content across sessions for persistent caching. Sources that
1709
2043
  * cannot tell whether their content changed (an unnamed archive, for example) return
@@ -1732,6 +2066,10 @@ var init_BatchedExecutor = __esm({
1732
2066
  "src/util/BatchedExecutor.ts"() {
1733
2067
  import_jobqu2 = require("jobqu");
1734
2068
  BatchedExecutor = class {
2069
+ /**
2070
+ * @param interval - Delay between queue runs, in milliseconds.
2071
+ * @param batch - Maximum tasks started per run and maximum tasks active at once.
2072
+ */
1735
2073
  constructor(interval = 1, batch = 30) {
1736
2074
  if (!Number.isFinite(interval) || interval < 0) throw new RangeError("Batch interval must be finite and nonnegative");
1737
2075
  if (!Number.isInteger(batch) || batch < 1) throw new RangeError("Batch size must be a positive integer");
@@ -1739,9 +2077,11 @@ var init_BatchedExecutor = __esm({
1739
2077
  this.batch = batch;
1740
2078
  this.queue = new import_jobqu2.JobQueue(async (task) => task(), { interval, maxPerRun: batch, maxActive: batch });
1741
2079
  }
2080
+ /** Queues a task and resolves with its result, or rejects with its error. */
1742
2081
  submit(task) {
1743
2082
  return this.queue.add(() => task());
1744
2083
  }
2084
+ /** Rejects queued and future tasks. Tasks already running are allowed to finish. */
1745
2085
  stop() {
1746
2086
  this.queue.end();
1747
2087
  }
@@ -1788,29 +2128,29 @@ function applyGenericRotation(axis, rotation, obj) {
1788
2128
  break;
1789
2129
  }
1790
2130
  }
1791
- function addBox3WireframeToObject(box, obj, color = 16777215, w = 2) {
2131
+ function addBox3WireframeToObject(box, obj, color2 = 16777215, w = 2) {
1792
2132
  let size = new import_three8.Vector3();
1793
2133
  box.getSize(size);
1794
2134
  let boxGeo = new import_three8.BoxGeometry(size.x, size.y, size.z, 1, 1, 1);
1795
2135
  let wireGeo = new import_three8.EdgesGeometry(boxGeo);
1796
- let wireMat = new import_three8.LineBasicMaterial({ color, linewidth: w });
2136
+ let wireMat = new import_three8.LineBasicMaterial({ color: color2, linewidth: w });
1797
2137
  let wireframe = new import_three8.LineSegments(wireGeo, wireMat);
1798
2138
  obj.add(wireframe);
1799
2139
  let axes = new import_three8.AxesHelper(1);
1800
2140
  obj.add(axes);
1801
2141
  }
1802
- function addWireframeToObject(obj, color = 16777215, w = 2) {
2142
+ function addWireframeToObject(obj, color2 = 16777215, w = 2) {
1803
2143
  let boxGeo = new import_three8.BoxGeometry(16, 16, 16, 1, 1, 1);
1804
2144
  let wireGeo = new import_three8.EdgesGeometry(boxGeo);
1805
- let wireMat = new import_three8.LineBasicMaterial({ color, linewidth: w });
2145
+ let wireMat = new import_three8.LineBasicMaterial({ color: color2, linewidth: w });
1806
2146
  let wireframe = new import_three8.LineSegments(wireGeo, wireMat);
1807
2147
  obj.add(wireframe);
1808
2148
  let axes = new import_three8.AxesHelper(1);
1809
2149
  obj.add(axes);
1810
2150
  }
1811
- function addWireframeToMesh(geo, mesh, color = 16777215, w = 2) {
2151
+ function addWireframeToMesh(geo, mesh, color2 = 16777215, w = 2) {
1812
2152
  let wireGeo = new import_three8.EdgesGeometry(geo);
1813
- let wireMat = new import_three8.LineBasicMaterial({ color, linewidth: w });
2153
+ let wireMat = new import_three8.LineBasicMaterial({ color: color2, linewidth: w });
1814
2154
  let wireframe = new import_three8.LineSegments(wireGeo, wireMat);
1815
2155
  mesh.add(wireframe);
1816
2156
  let axes = new import_three8.AxesHelper(1);
@@ -1884,10 +2224,11 @@ var init_NBTHelper = __esm({
1884
2224
  "src/nbt/NBTHelper.ts"() {
1885
2225
  import_buffer2 = require("buffer");
1886
2226
  NBTHelper = class {
1887
- static async fromBuffer(data, format) {
2227
+ /** Parses NBT bytes and preserves format and compression metadata. Omit `format` for automatic detection. */
2228
+ static async fromBuffer(data, format2) {
1888
2229
  const buffer = import_buffer2.Buffer.from(data instanceof Uint8Array ? data : new Uint8Array(data));
1889
2230
  const prismarineNbt = await import("prismarine-nbt");
1890
- const { parsed, type, metadata } = await prismarineNbt.parse(buffer, format);
2231
+ const { parsed, type, metadata } = await prismarineNbt.parse(buffer, format2);
1891
2232
  return Object.assign(parsed, {
1892
2233
  format: type,
1893
2234
  metadata,
@@ -1952,6 +2293,7 @@ var init_HostedAssetSource = __esm({
1952
2293
  retryDefaults: true
1953
2294
  };
1954
2295
  _HostedAssetSource = class _HostedAssetSource extends AssetSource {
2296
+ /** Retries missing assets with the `minecraft` namespace and default root. Other failures reject. */
1955
2297
  async loadOrRetryWithDefaults(key, parser) {
1956
2298
  const direct = await this.load(key, parser);
1957
2299
  if (typeof direct !== "undefined") {
@@ -2009,6 +2351,10 @@ var init_HostedAssetSource = __esm({
2009
2351
  assetBasePath(key) {
2010
2352
  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 + "/" : ""}`;
2011
2353
  }
2354
+ /**
2355
+ * Creates a hosted source for {@link AssetLoader.addSource}.
2356
+ * Set `options.retryDefaults` to `false` to restrict lookups to the requested namespace and root.
2357
+ */
2012
2358
  constructor(root, options) {
2013
2359
  super();
2014
2360
  this._root = root;
@@ -2101,6 +2447,11 @@ var init_BrowserArchiveProxy = __esm({
2101
2447
  "src/assets/source/archive/BrowserArchiveProxy.ts"() {
2102
2448
  import_zip = require("@zip.js/zip.js");
2103
2449
  BrowserArchiveProxy = class {
2450
+ /**
2451
+ * Opens a ZIP for asset lookup.
2452
+ * @param id - Cache identity for this content. Change it when the pack changes.
2453
+ * A File supplies an identity from its name, size, and modification time when omitted.
2454
+ */
2104
2455
  constructor(blob, id) {
2105
2456
  this._blob = blob;
2106
2457
  this._reader = new import_zip.ZipReader(new import_zip.BlobReader(this._blob));
@@ -2121,6 +2472,162 @@ var init_BrowserArchiveProxy = __esm({
2121
2472
  }
2122
2473
  });
2123
2474
 
2475
+ // src/assets/source/archive/PackMetadata.ts
2476
+ function object(value, field) {
2477
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
2478
+ throw new Error(`Invalid ${field}: expected an object`);
2479
+ }
2480
+ return value;
2481
+ }
2482
+ function integer(value, field) {
2483
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
2484
+ throw new Error(`Invalid ${field}: expected a nonnegative integer`);
2485
+ }
2486
+ return value;
2487
+ }
2488
+ function format(value, field, upper = false) {
2489
+ const parts = Array.isArray(value) ? value : [value];
2490
+ if (parts.length < 1 || parts.length > 2) {
2491
+ throw new Error(`Invalid ${field}: expected a major version or [major, minor]`);
2492
+ }
2493
+ return [integer(parts[0], field), parts.length === 2 ? integer(parts[1], field) : upper ? Infinity : 0];
2494
+ }
2495
+ function compare(a, b) {
2496
+ return a[0] === b[0] ? a[1] - b[1] : a[0] - b[0];
2497
+ }
2498
+ function overlay(value, field) {
2499
+ const entry = object(value, field);
2500
+ const directory = entry.directory;
2501
+ if (typeof directory !== "string" || !directory || /[^-_a-zA-Z0-9.]/.test(directory) || directory === "." || directory === "..") {
2502
+ throw new Error(`Invalid ${field}.directory: expected a directory name without path separators`);
2503
+ }
2504
+ let min, max;
2505
+ if ("min_format" in entry || "max_format" in entry) {
2506
+ min = format(entry.min_format, `${field}.min_format`);
2507
+ max = format(entry.max_format, `${field}.max_format`, true);
2508
+ } else {
2509
+ const range = entry.formats;
2510
+ let lower, upper;
2511
+ if (typeof range === "number") {
2512
+ lower = upper = range;
2513
+ } else if (Array.isArray(range)) {
2514
+ if (range.length !== 2) throw new Error(`Invalid ${field}.formats: expected [min, max]`);
2515
+ [lower, upper] = range;
2516
+ } else {
2517
+ const bounds = object(range, `${field}.formats`);
2518
+ lower = bounds.min_inclusive;
2519
+ upper = bounds.max_inclusive;
2520
+ }
2521
+ min = [integer(lower, `${field}.formats.min_inclusive`), 0];
2522
+ max = [integer(upper, `${field}.formats.max_inclusive`), Infinity];
2523
+ }
2524
+ if (compare(min, max) > 0) throw new Error(`Invalid ${field}: minimum format exceeds maximum format`);
2525
+ return { directory, min, max };
2526
+ }
2527
+ function pattern(value, field) {
2528
+ if (value === void 0) return void 0;
2529
+ if (typeof value !== "string") throw new Error(`Invalid ${field}: expected a regular expression string`);
2530
+ try {
2531
+ return new RegExp(value);
2532
+ } catch (cause) {
2533
+ throw new Error(`Invalid ${field}: ${String(cause)}`);
2534
+ }
2535
+ }
2536
+ var PackMetadata;
2537
+ var init_PackMetadata = __esm({
2538
+ "src/assets/source/archive/PackMetadata.ts"() {
2539
+ PackMetadata = class _PackMetadata {
2540
+ constructor(overlays, filters) {
2541
+ this.overlays = overlays;
2542
+ this.filters = filters;
2543
+ this.hasOverlays = overlays.length > 0;
2544
+ }
2545
+ static parse(value) {
2546
+ const metadata = object(value, "pack.mcmeta");
2547
+ let overlays = [];
2548
+ if ("overlays" in metadata) {
2549
+ const entries = object(metadata.overlays, "overlays").entries;
2550
+ if (!Array.isArray(entries)) throw new Error("Invalid overlays.entries: expected an array");
2551
+ overlays = entries.map((entry, index) => overlay(entry, `overlays.entries[${index}]`));
2552
+ }
2553
+ let filters = [];
2554
+ if ("filter" in metadata) {
2555
+ const entries = object(metadata.filter, "filter").block;
2556
+ if (!Array.isArray(entries)) throw new Error("Invalid filter.block: expected an array");
2557
+ filters = entries.map((value2, index) => {
2558
+ const field = `filter.block[${index}]`;
2559
+ const entry = object(value2, field);
2560
+ return { namespace: pattern(entry.namespace, `${field}.namespace`), path: pattern(entry.path, `${field}.path`) };
2561
+ });
2562
+ }
2563
+ return new _PackMetadata(overlays, filters);
2564
+ }
2565
+ /** Returns applicable directories in lookup order, with the last declared overlay first. */
2566
+ overlayDirectories(target) {
2567
+ const selected = format(target, "target format");
2568
+ return this.overlays.filter((entry) => compare(selected, entry.min) >= 0 && compare(selected, entry.max) <= 0).map((entry) => entry.directory).reverse();
2569
+ }
2570
+ blocks(namespace, path) {
2571
+ return this.filters.some((entry) => entry.namespace?.test(namespace) ?? true) && this.filters.some((entry) => entry.path?.test(path) ?? true);
2572
+ }
2573
+ };
2574
+ }
2575
+ });
2576
+
2577
+ // src/assets/source/archive/PackFormats.ts
2578
+ var PackFormats;
2579
+ var init_PackFormats = __esm({
2580
+ "src/assets/source/archive/PackFormats.ts"() {
2581
+ init_Requests();
2582
+ PackFormats = class {
2583
+ /** Resolves pack formats from Mojang's client metadata hosted on assets.mcasset.cloud. */
2584
+ static async get(version2, type) {
2585
+ let pending = this.versions.get(version2);
2586
+ if (!pending || pending.expires <= Date.now()) {
2587
+ pending = {
2588
+ formats: this.load(version2),
2589
+ expires: ["latest", "release", "snapshot"].includes(version2) ? Date.now() + 3e5 : Infinity
2590
+ };
2591
+ this.versions.set(version2, pending);
2592
+ }
2593
+ try {
2594
+ return (await pending.formats)[type];
2595
+ } catch (cause) {
2596
+ if (this.versions.get(version2) === pending) this.versions.delete(version2);
2597
+ const option = type === "assets" ? "resourcePackFormat" : "dataPackFormat";
2598
+ const reason = cause instanceof Error ? cause.message : String(cause);
2599
+ throw new Error(`Could not resolve the pack format for Minecraft ${version2}. Set ArchiveAssetSource ${option} explicitly. ${reason}`);
2600
+ }
2601
+ }
2602
+ static async load(version2) {
2603
+ const response = await Requests.genericRequest({
2604
+ url: `https://assets.mcasset.cloud/${encodeURIComponent(version2)}/game-version.json`,
2605
+ responseType: "json"
2606
+ });
2607
+ const data = response.data;
2608
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
2609
+ throw new Error("Expected version metadata to be an object");
2610
+ }
2611
+ const packs = data.pack_version;
2612
+ if (typeof packs !== "number" && (!packs || typeof packs !== "object" || Array.isArray(packs))) {
2613
+ throw new Error("Expected pack_version to be a number or an object");
2614
+ }
2615
+ const format2 = (type) => {
2616
+ const modern = typeof packs === "object" && ["resource_major", "resource_minor", "data_major", "data_minor"].some((key) => key in packs);
2617
+ const major = typeof packs === "number" ? packs : packs[modern ? `${type}_major` : type];
2618
+ const minor = modern ? packs[`${type}_minor`] : 0;
2619
+ if (typeof major !== "number" || !Number.isSafeInteger(major) || major < 0 || typeof minor !== "number" || !Number.isSafeInteger(minor) || minor < 0) {
2620
+ throw new Error(`Invalid ${type} pack format in version metadata`);
2621
+ }
2622
+ return Object.freeze([major, minor]);
2623
+ };
2624
+ return { assets: format2("resource"), data: format2("data") };
2625
+ }
2626
+ };
2627
+ PackFormats.versions = /* @__PURE__ */ new Map();
2628
+ }
2629
+ });
2630
+
2124
2631
  // src/assets/source/archive/ArchiveAssetSource.ts
2125
2632
  var ArchiveAssetSource;
2126
2633
  var init_ArchiveAssetSource = __esm({
@@ -2129,35 +2636,78 @@ var init_ArchiveAssetSource = __esm({
2129
2636
  init_request();
2130
2637
  init_HostedAssetSource();
2131
2638
  init_BrowserArchiveProxy();
2639
+ init_AssetLoader();
2640
+ init_PackMetadata();
2641
+ init_PackFormats();
2132
2642
  ArchiveAssetSource = class _ArchiveAssetSource extends AssetSource {
2133
- constructor(archiveProxy) {
2643
+ constructor(archiveProxy, options = {}) {
2134
2644
  super();
2135
- console.log(archiveProxy);
2136
2645
  this._archiveProxy = archiveProxy;
2646
+ const copyFormat = (format2) => typeof format2 === "object" ? [format2[0], format2[1]] : format2;
2647
+ this.options = {
2648
+ resourcePackFormat: copyFormat(options.resourcePackFormat),
2649
+ dataPackFormat: copyFormat(options.dataPackFormat)
2650
+ };
2137
2651
  }
2138
- static blob(blob) {
2139
- return new _ArchiveAssetSource(new BrowserArchiveProxy(blob));
2652
+ /** Creates a browser ZIP source from a Blob or File. */
2653
+ static blob(blob, options) {
2654
+ return new _ArchiveAssetSource(new BrowserArchiveProxy(blob), options);
2140
2655
  }
2141
2656
  get cacheId() {
2142
2657
  const id = this._archiveProxy.id;
2143
- return id === void 0 ? void 0 : `archive:${id}`;
2658
+ const formats = [this.options.resourcePackFormat ?? AssetLoader.version, this.options.dataPackFormat ?? AssetLoader.version];
2659
+ return id === void 0 ? void 0 : `archive-metadata:1:${id}:${JSON.stringify(formats)}`;
2144
2660
  }
2145
2661
  async getEntries() {
2146
- return this._archiveProxy.getEntries();
2662
+ return this.entries ?? (this.entries = this._archiveProxy.getEntries().catch((error) => {
2663
+ this.entries = void 0;
2664
+ throw error;
2665
+ }));
2147
2666
  }
2148
2667
  async getEntry(path) {
2149
2668
  const entries = await this.getEntries();
2150
2669
  return entries.find((e) => e.filename === path);
2151
2670
  }
2671
+ async getMetadata(key) {
2672
+ try {
2673
+ return await (this.metadata ?? (this.metadata = this.getEntry("pack.mcmeta").then(async (entry) => PackMetadata.parse(entry ? JSON.parse(await (await entry.getData()).text()) : {})).catch((error) => {
2674
+ this.metadata = void 0;
2675
+ throw error;
2676
+ })));
2677
+ } catch (cause) {
2678
+ throw new AssetLoadError(this, key, "pack.mcmeta", cause);
2679
+ }
2680
+ }
2681
+ async blocks(key) {
2682
+ if (key.rootType !== "assets" && key.rootType !== "data") return false;
2683
+ const metadata = await this.getMetadata(key);
2684
+ const path = `${key.assetType !== void 0 ? key.assetType + "/" : ""}${key.getFullPath()}${key.extension}`;
2685
+ return metadata.blocks(key.namespace, path);
2686
+ }
2152
2687
  async get(key, parser) {
2153
2688
  const responseParser = HostedAssetSource.PARSER_MAP.get(parser);
2154
2689
  return this.load(key, responseParser);
2155
2690
  }
2156
2691
  async load(key, parser) {
2157
2692
  const path = `${this.assetBasePath(key)}${key.type !== void 0 ? key.type + "/" : ""}${key.path}${key.extension}`;
2693
+ const version2 = AssetLoader.version;
2694
+ let selectedPath = path;
2158
2695
  let url;
2159
2696
  try {
2160
- const entry = await this.getEntry(path);
2697
+ let directories = [];
2698
+ if (key.rootType === "assets" || key.rootType === "data") {
2699
+ const metadata = await this.getMetadata(key);
2700
+ if (metadata.hasOverlays) {
2701
+ const format2 = (key.rootType === "assets" ? this.options.resourcePackFormat : this.options.dataPackFormat) ?? await PackFormats.get(version2, key.rootType);
2702
+ directories = metadata.overlayDirectories(format2);
2703
+ }
2704
+ }
2705
+ let entry;
2706
+ for (const directory of [...directories, ""]) {
2707
+ selectedPath = directory ? `${directory}/${path}` : path;
2708
+ entry = await this.getEntry(selectedPath);
2709
+ if (entry) break;
2710
+ }
2161
2711
  if (!entry) {
2162
2712
  return void 0;
2163
2713
  }
@@ -2170,7 +2720,8 @@ var init_ArchiveAssetSource = __esm({
2170
2720
  }
2171
2721
  return await parser.parse(response);
2172
2722
  } catch (cause) {
2173
- throw new AssetLoadError(this, key, path, cause);
2723
+ if (cause instanceof AssetLoadError) throw cause;
2724
+ throw new AssetLoadError(this, key, selectedPath, cause);
2174
2725
  } finally {
2175
2726
  if (url !== void 0) {
2176
2727
  URL.revokeObjectURL(url);
@@ -2203,6 +2754,8 @@ var init_archive = __esm({
2203
2754
  init_ArchiveEntry();
2204
2755
  init_ArchiveProxy();
2205
2756
  init_BrowserArchiveProxy();
2757
+ init_PackFormats();
2758
+ init_PackMetadata();
2206
2759
  }
2207
2760
  });
2208
2761
 
@@ -2231,8 +2784,9 @@ var init_AssetLoader = __esm({
2231
2784
  static get version() {
2232
2785
  return this.ROOT.substring(this.ROOT.lastIndexOf("/") + 1);
2233
2786
  }
2234
- static setVersion(version) {
2235
- this.ROOT = `https://assets.mcasset.cloud/${version}`;
2787
+ /** Selects an mcasset.cloud version, updates the vanilla source, and clears in-memory caches. */
2788
+ static setVersion(version2) {
2789
+ this.ROOT = `https://assets.mcasset.cloud/${version2}`;
2236
2790
  const source = new HostedAssetSource(this.ROOT, { retryDefaults: false });
2237
2791
  const index = this._SOURCES.findIndex((s) => s.key === "mcassets");
2238
2792
  if (index !== -1) {
@@ -2264,6 +2818,13 @@ var init_AssetLoader = __esm({
2264
2818
  return scope ? `${scope}
2265
2819
  ${key}` : key;
2266
2820
  }
2821
+ /**
2822
+ * Registers a source at the highest priority.
2823
+ * Call {@link Caching.clear} after changing sources to discard previously loaded assets.
2824
+ *
2825
+ * @param key - Name used to replace or remove this source.
2826
+ * @param override - Removes the first source with this name before adding the new one.
2827
+ */
2267
2828
  static addSource(key, source, override = true) {
2268
2829
  if (override) {
2269
2830
  const existing = this.removeSource(key);
@@ -2274,6 +2835,7 @@ ${key}` : key;
2274
2835
  this._SOURCES.unshift({ key, source });
2275
2836
  console.log(p4, "Added AssetSource", key);
2276
2837
  }
2838
+ /** Removes and returns the first source with this name. Call {@link Caching.clear} to reload assets. */
2277
2839
  static removeSource(key) {
2278
2840
  const index = this._SOURCES.findIndex((s) => s.key === key);
2279
2841
  if (index != -1) {
@@ -2284,14 +2846,16 @@ ${key}` : key;
2284
2846
  }
2285
2847
  return void 0;
2286
2848
  }
2849
+ /** Loads from all sources and returns defined results in priority order. Any source failure rejects the call. */
2287
2850
  static async getAll(key, parser) {
2288
- let promises = [];
2289
- for (const source of this._SOURCES) {
2290
- promises.push(source.source.get(key, parser));
2851
+ const sources = [...this._SOURCES];
2852
+ const results = [];
2853
+ for (const { source } of sources) {
2854
+ const asset2 = await source.get(key, parser);
2855
+ if (asset2 != void 0) results.push(asset2);
2856
+ if (await source.blocks(key)) break;
2291
2857
  }
2292
- return Promise.all(promises).then((results) => {
2293
- return results.filter((r) => r != void 0);
2294
- });
2858
+ return results;
2295
2859
  }
2296
2860
  /** Returns the first defined result in source-priority order, without merging assets. */
2297
2861
  static async get(key, parser) {
@@ -2312,17 +2876,22 @@ ${key}` : key;
2312
2876
  /** Tries each path within a source before considering lower-priority sources. */
2313
2877
  static async getFirst(keys, parser) {
2314
2878
  const sources = [...this._SOURCES];
2879
+ let remaining = [...keys];
2315
2880
  for (const source of sources) {
2316
- for (const key of keys) {
2881
+ for (const key of remaining) {
2317
2882
  const result = await source.source.get(key, parser);
2318
2883
  if (result !== void 0) {
2319
2884
  return { key, asset: result };
2320
2885
  }
2321
2886
  }
2887
+ const blocked = await Promise.all(remaining.map((key) => source.source.blocks(key)));
2888
+ remaining = remaining.filter((_, index) => !blocked[index]);
2889
+ if (!remaining.length) break;
2322
2890
  }
2323
2891
  return void 0;
2324
2892
  }
2325
2893
  };
2894
+ /** Default asset base URL. Use {@link setVersion} to select a Minecraft version. */
2326
2895
  _AssetLoader.ROOT = DEFAULT_ROOT;
2327
2896
  _AssetLoader._SOURCES = [];
2328
2897
  /** Used when a registered source cannot identify its content, so nothing persists past this session. */
@@ -2347,14 +2916,15 @@ var init_ModelTextures = __esm({
2347
2916
  static get PERSISTENT_META_CACHE() {
2348
2917
  return this._persistentMetaCache ?? (this._persistentMetaCache = PersistentCache.open("minerender-texturemeta"));
2349
2918
  }
2919
+ /** Loads and decodes a texture into a readable canvas, or returns `undefined` when missing. */
2350
2920
  static async get(key) {
2351
2921
  const keyStr = key.serialize();
2352
2922
  const pending = this.preload(key);
2353
2923
  const cached2 = Caching.textureAssetCache.getIfPresent(keyStr);
2354
2924
  try {
2355
- const asset = await pending;
2356
- if (asset) {
2357
- return await ImageLoader.infoToCanvasData(asset);
2925
+ const asset2 = await pending;
2926
+ if (asset2) {
2927
+ return await ImageLoader.infoToCanvasData(asset2);
2358
2928
  }
2359
2929
  return void 0;
2360
2930
  } catch (err) {
@@ -2364,16 +2934,18 @@ var init_ModelTextures = __esm({
2364
2934
  throw err;
2365
2935
  }
2366
2936
  }
2937
+ /** Fetches and caches encoded texture bytes without decoding the pixels. */
2367
2938
  static async preload(key) {
2368
2939
  const keyStr = key.serialize();
2369
2940
  return Caching.textureAssetCache.get(keyStr, (k) => {
2370
- return AssetLoader.get(key, "image" /* IMAGE */).then((asset) => {
2371
- if (asset)
2372
- asset.key = key;
2373
- return asset;
2941
+ return AssetLoader.get(key, "image" /* IMAGE */).then((asset2) => {
2942
+ if (asset2)
2943
+ asset2.key = key;
2944
+ return asset2;
2374
2945
  });
2375
2946
  });
2376
2947
  }
2948
+ /** Loads a texture's `.mcmeta` file, or returns `undefined` when no metadata file exists. */
2377
2949
  static async getMeta(key) {
2378
2950
  if (!key.extension || !key.extension.endsWith(".mcmeta")) {
2379
2951
  key = Object.assign(new AssetKey("", ""), key);
@@ -2382,14 +2954,15 @@ var init_ModelTextures = __esm({
2382
2954
  const keyStr = key.serialize();
2383
2955
  return Caching.textureMetaCache.get(keyStr, (k) => {
2384
2956
  return this.PERSISTENT_META_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
2385
- return AssetLoader.get(key, "meta" /* META */).then((asset) => {
2386
- if (asset)
2387
- asset.key = key;
2388
- return asset;
2957
+ return AssetLoader.get(key, "meta" /* META */).then((asset2) => {
2958
+ if (asset2)
2959
+ asset2.key = key;
2960
+ return asset2;
2389
2961
  });
2390
2962
  });
2391
2963
  });
2392
2964
  }
2965
+ /** Clears persisted texture metadata. Use {@link Caching.clear} to discard in-memory textures and metadata. */
2393
2966
  static async clearCache() {
2394
2967
  await this.PERSISTENT_META_CACHE.clear();
2395
2968
  }
@@ -2456,6 +3029,7 @@ var init_Ticker = __esm({
2456
3029
  }
2457
3030
  return handle;
2458
3031
  }
3032
+ /** Starts or resumes the shared tick loop. Repeated calls have no effect. */
2459
3033
  static start() {
2460
3034
  if (this.interval) return;
2461
3035
  this.interval = this.schedule(() => {
@@ -2478,6 +3052,7 @@ var init_Ticker = __esm({
2478
3052
  _Ticker.fiveSecondTicks = 0;
2479
3053
  }, 1e3 * 5);
2480
3054
  }
3055
+ /** Pauses the loop while retaining callbacks for a later {@link start}. */
2481
3056
  static stop() {
2482
3057
  if (this.interval) clearInterval(this.interval);
2483
3058
  if (this.oneSecondTicksTracker) clearInterval(this.oneSecondTicksTracker);
@@ -2486,12 +3061,14 @@ var init_Ticker = __esm({
2486
3061
  this.oneSecondTicksTracker = void 0;
2487
3062
  this.fiveSecondTicksTracker = void 0;
2488
3063
  }
3064
+ /** Registers a callback, starts the loop if needed, and returns an ID for {@link remove}. */
2489
3065
  static add(tick) {
2490
3066
  this.start();
2491
3067
  const c = this.counter++;
2492
3068
  this._tickers.set(c, tick);
2493
3069
  return c;
2494
3070
  }
3071
+ /** Removes a callback by ID and stops the timers when no callbacks remain. */
2495
3072
  static remove(c) {
2496
3073
  if (typeof c === "number") this._tickers.delete(c);
2497
3074
  if (!this._tickers.size) this.stop();
@@ -2505,6 +3082,7 @@ var init_Ticker = __esm({
2505
3082
  static get tpsFiveSeconds() {
2506
3083
  return this.fiveSecondTps;
2507
3084
  }
3085
+ /** Stops the loop and removes all registered callbacks. */
2508
3086
  static dispose() {
2509
3087
  this.stop();
2510
3088
  this._tickers.clear();
@@ -2559,6 +3137,7 @@ var init_TextureAtlas = __esm({
2559
3137
  }
2560
3138
  };
2561
3139
  }
3140
+ /** Copies pixels for a texture variable from its current atlas rectangle. */
2562
3141
  getData(texture) {
2563
3142
  const pos = this.positions[texture];
2564
3143
  const size = this.sizes[texture];
@@ -2769,6 +3348,37 @@ var init_UVMapper = __esm({
2769
3348
  static facesToUv(faces, originalTextureSize, actualTextureSize) {
2770
3349
  return new import_three9.Float32BufferAttribute(this.facesToUvArray(faces, originalTextureSize, actualTextureSize), 2);
2771
3350
  }
3351
+ /** Bounds texture sampling to texel centres without changing cropped face UVs. */
3352
+ static setAtlasUvBounds(geometry, faces, atlas) {
3353
+ const uv = geometry.getAttribute("uv");
3354
+ const bounds = new Float32Array(uv.count * 4);
3355
+ for (const [faceIndex, faceName] of CUBE_FACES.entries()) {
3356
+ const texture = faces[faceName]?.texture?.substring(1);
3357
+ const position = texture && atlas.positions[texture];
3358
+ const size = texture && atlas.sizes[texture];
3359
+ let minU = Infinity, minV = Infinity, maxU = -Infinity, maxV = -Infinity;
3360
+ if (position && size) {
3361
+ minU = position[0] / atlas.image.width;
3362
+ maxU = (position[0] + size[0]) / atlas.image.width;
3363
+ minV = 1 - (position[1] + size[1]) / atlas.image.height;
3364
+ maxV = 1 - position[1] / atlas.image.height;
3365
+ } else {
3366
+ for (let corner = 0; corner < 4; corner++) {
3367
+ const index = faceIndex * 4 + corner;
3368
+ minU = Math.min(minU, uv.getX(index));
3369
+ minV = Math.min(minV, uv.getY(index));
3370
+ maxU = Math.max(maxU, uv.getX(index));
3371
+ maxV = Math.max(maxV, uv.getY(index));
3372
+ }
3373
+ }
3374
+ const insetU = Math.min(0.5 / atlas.image.width, (maxU - minU) / 2);
3375
+ const insetV = Math.min(0.5 / atlas.image.height, (maxV - minV) / 2);
3376
+ for (let corner = 0; corner < 4; corner++) {
3377
+ bounds.set([minU + insetU, minV + insetV, maxU - insetU, maxV - insetV], (faceIndex * 4 + corner) * 4);
3378
+ }
3379
+ }
3380
+ geometry.setAttribute("uvBounds", new import_three9.Float32BufferAttribute(bounds, 4));
3381
+ }
2772
3382
  static lockUvs(geometry, faces, atlas, rotation) {
2773
3383
  const uv = geometry.getAttribute("uv");
2774
3384
  for (let faceIndex = 0; faceIndex < CUBE_FACES.length; faceIndex++) {
@@ -2805,6 +3415,7 @@ var init_UVMapper = __esm({
2805
3415
  }
2806
3416
  uv.needsUpdate = true;
2807
3417
  }
3418
+ /** Returns the shared atlas for a keyed, merged model, creating it on first use. */
2808
3419
  static async getAtlas(model) {
2809
3420
  const keyStr = model.key.serialize();
2810
3421
  return Caching.modelTextureAtlasCache.get(keyStr, (k) => {
@@ -2875,6 +3486,11 @@ var init_UVMapper = __esm({
2875
3486
  }
2876
3487
  return [tl, tr, bl, br];
2877
3488
  }
3489
+ /**
3490
+ * Combines model textures and animation metadata into an atlas.
3491
+ * Mapped UVs are stored on copies of the model's elements, available through the returned atlas.
3492
+ * Returns `undefined` for models without a texture map.
3493
+ */
2878
3494
  static async createAtlas(originalModel) {
2879
3495
  const textureMap = {};
2880
3496
  const metaMap = {};
@@ -2895,12 +3511,12 @@ var init_UVMapper = __esm({
2895
3511
  } else {
2896
3512
  uniqueTextureNames.push(textureKey);
2897
3513
  const assetKey = AssetKey.parse("textures", textureValue, model.key);
2898
- promises.push(ModelTextures.get(assetKey).then((asset) => {
2899
- if (!asset) {
3514
+ promises.push(ModelTextures.get(assetKey).then((asset2) => {
3515
+ if (!asset2) {
2900
3516
  console.warn(p5, "Missing texture", assetKey);
2901
3517
  return;
2902
3518
  }
2903
- textureMap[textureKey] = new WrappedImage(asset);
3519
+ textureMap[textureKey] = new WrappedImage(asset2);
2904
3520
  }));
2905
3521
  promises.push(ModelTextures.getMeta(assetKey).then((meta) => {
2906
3522
  metaMap[textureKey] = meta;
@@ -3239,14 +3855,15 @@ var init_Entities = __esm({
3239
3855
  init_MineRenderError();
3240
3856
  init_ModelTextures();
3241
3857
  Entities = class {
3858
+ /** Lists model paths in the `minecraft` namespace, including subdirectories and excluding `.json`. */
3242
3859
  static async getEntityList() {
3243
3860
  const collect = async (path) => {
3244
3861
  const prefix2 = path ? `${path}/` : "";
3245
3862
  const key = new AssetKey(DEFAULT_NAMESPACE, `${prefix2}_list`, void 0, void 0, "entity-models", ".json");
3246
- const list = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get(key, "list" /* LIST */));
3247
- if (!list) return [];
3248
- const files = list.files.filter((file) => file.endsWith(".json") && file !== "_list.json").map((file) => `${prefix2}${file.slice(0, -5)}`);
3249
- const children = await Promise.all(list.directories.map((directory) => collect(`${prefix2}${directory}`)));
3863
+ const list2 = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get(key, "list" /* LIST */));
3864
+ if (!list2) return [];
3865
+ const files = list2.files.filter((file) => file.endsWith(".json") && file !== "_list.json").map((file) => `${prefix2}${file.slice(0, -5)}`);
3866
+ const children = await Promise.all(list2.directories.map((directory) => collect(`${prefix2}${directory}`)));
3250
3867
  return [...files, ...children.flat()];
3251
3868
  };
3252
3869
  return collect("");
@@ -3256,6 +3873,7 @@ var init_Entities = __esm({
3256
3873
  const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models", ".json", isAssetKey(modelKey) ? modelKey.root : void 0);
3257
3874
  return Caching.entityModelCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
3258
3875
  }
3876
+ /** Returns selectable geometry-layer names, or an empty list when the model is missing. */
3259
3877
  static async getLayerList(modelKey) {
3260
3878
  return Object.keys((await this.getModelFile(modelKey))?.layers ?? {});
3261
3879
  }
@@ -3263,6 +3881,15 @@ var init_Entities = __esm({
3263
3881
  static async getPassList(modelKey) {
3264
3882
  return (await this.getModelFile(modelKey))?.passes ?? [];
3265
3883
  }
3884
+ /**
3885
+ * Loads the layers and textures to pass to {@link MineRenderScene.addEntity}.
3886
+ * By default, selects `main`, unconditional passes, and passes enabled by `options.when`.
3887
+ *
3888
+ * @param modelKey - Dataset model, such as `new BasicAssetKey("minecraft", "zombie")`.
3889
+ * @param textureKey - Texture override for the first selected layer.
3890
+ * @param options - Layer selection, conditional passes, and per-layer texture overrides.
3891
+ * @returns The selected model, or `undefined` when its dataset file is missing.
3892
+ */
3266
3893
  static async getEntity(modelKey, textureKey, options) {
3267
3894
  const model = await this.getModelFile(modelKey);
3268
3895
  if (!model) return void 0;
@@ -3308,6 +3935,7 @@ var init_Entities = __esm({
3308
3935
  const file = await Caching.entityAnimationCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
3309
3936
  return file?.animations;
3310
3937
  }
3938
+ /** Finds a fallback texture using the model path, its directory, and vanilla variant data. */
3311
3939
  static async resolveTexture(modelKey) {
3312
3940
  const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
3313
3941
  const root = isAssetKey(modelKey) ? modelKey.root : void 0;
@@ -3319,8 +3947,8 @@ var init_Entities = __esm({
3319
3947
  if (await ModelTextures.get(texture2)) return texture2;
3320
3948
  }
3321
3949
  const listKey = new AssetKey(modelKey.namespace, "_list", `${name}_variant`, void 0, "data", ".json", root);
3322
- const list = await AssetLoader.get(listKey, "list" /* LIST */);
3323
- const files = list?.files.filter((file2) => file2.endsWith(".json") && file2 !== "_list.json");
3950
+ const list2 = await AssetLoader.get(listKey, "list" /* LIST */);
3951
+ const files = list2?.files.filter((file2) => file2.endsWith(".json") && file2 !== "_list.json");
3324
3952
  const file = files?.find((file2) => file2 === "temperate.json") ?? files?.[0];
3325
3953
  if (!file) return void 0;
3326
3954
  const variantKey = new AssetKey(modelKey.namespace, file.slice(0, -5), `${name}_variant`, void 0, "data", ".json", root);
@@ -3425,27 +4053,27 @@ var init_EntityAnimationPlayer = __esm({
3425
4053
  for (const layer of layers) {
3426
4054
  const objects = [];
3427
4055
  if (poses) {
3428
- layer.traverse((object) => {
3429
- if (poses.has(object)) objects.push(object);
4056
+ layer.traverse((object3) => {
4057
+ if (poses.has(object3)) objects.push(object3);
3430
4058
  });
3431
4059
  } else {
3432
4060
  for (const bone of Object.keys(animation.bones)) {
3433
- const object = layer.getObjectByName(`group:${bone}`);
3434
- if (object) objects.push(object);
4061
+ const object3 = layer.getObjectByName(`group:${bone}`);
4062
+ if (object3) objects.push(object3);
3435
4063
  }
3436
4064
  }
3437
- for (const object of objects) {
3438
- const bone = object.name.slice("group:".length);
3439
- const pose = poses?.get(object);
4065
+ for (const object3 of objects) {
4066
+ const bone = object3.name.slice("group:".length);
4067
+ const pose = poses?.get(object3);
3440
4068
  this.parts.push({
3441
4069
  bone,
3442
- object,
3443
- position: object.position.clone(),
3444
- rotation: object.rotation.clone(),
3445
- scale: object.scale.clone(),
3446
- basePosition: pose ? new import_three11.Vector3(...pose.offset) : object.position.clone(),
3447
- baseRotation: pose ? new import_three11.Euler(...pose.rotation, "ZYX") : object.rotation.clone(),
3448
- baseScale: pose ? new import_three11.Vector3(...pose.scale ?? [1, 1, 1]) : object.scale.clone()
4070
+ object: object3,
4071
+ position: object3.position.clone(),
4072
+ rotation: object3.rotation.clone(),
4073
+ scale: object3.scale.clone(),
4074
+ basePosition: pose ? new import_three11.Vector3(...pose.offset) : object3.position.clone(),
4075
+ baseRotation: pose ? new import_three11.Euler(...pose.rotation, "ZYX") : object3.rotation.clone(),
4076
+ baseScale: pose ? new import_three11.Vector3(...pose.scale ?? [1, 1, 1]) : object3.scale.clone()
3449
4077
  });
3450
4078
  }
3451
4079
  }
@@ -3468,12 +4096,14 @@ var init_EntityAnimationPlayer = __esm({
3468
4096
  this._animation = void 0;
3469
4097
  this._time = 0;
3470
4098
  }
4099
+ /** Applies the pose at an absolute playback time. Returns `false` when no clip is active. */
3471
4100
  setTime(seconds) {
3472
4101
  if (!this._animation) return false;
3473
4102
  this._time = seconds;
3474
4103
  this.apply();
3475
4104
  return true;
3476
4105
  }
4106
+ /** Advances playback by elapsed seconds multiplied by the speed passed to {@link play}. */
3477
4107
  advance(deltaSeconds) {
3478
4108
  return this.setTime(this._time + deltaSeconds * this.speed);
3479
4109
  }
@@ -3511,6 +4141,7 @@ var init_InstanceReference = __esm({
3511
4141
  if (!this.instanceable.isInstanceActive(this.index, this)) throw new MineRenderError("Instance has been removed");
3512
4142
  return this.instanceable;
3513
4143
  }
4144
+ /** Releases this placement's slot for reuse. Repeated calls have no effect. */
3514
4145
  removeFromScene() {
3515
4146
  if (this.instanceable.isInstanceActive(this.index, this)) this.instanceable.removeInstanceAt(this.index);
3516
4147
  }
@@ -3523,9 +4154,11 @@ var init_InstanceReference = __esm({
3523
4154
  setMatrix(matrix) {
3524
4155
  this.activeInstanceable.setMatrixAt(this.index, matrix);
3525
4156
  }
4157
+ /** Hides or restores this placement while preserving its transform and allocated slot. */
3526
4158
  setVisible(visible) {
3527
4159
  this.activeInstanceable.setInstanceVisibleAt(this.index, visible);
3528
4160
  }
4161
+ /** Updates the supplied transform components. Positions use scene units and rotations use radians. */
3529
4162
  setPositionRotationScale(position, rotation, scale) {
3530
4163
  this.activeInstanceable.setPositionRotationScaleAt(this.index, position, rotation, scale);
3531
4164
  }
@@ -3594,6 +4227,7 @@ var init_SceneObject = __esm({
3594
4227
  }
3595
4228
  async init() {
3596
4229
  }
4230
+ /** Marks ancestor scenes for redraw and emits a `change` event. */
3597
4231
  notifyDirty() {
3598
4232
  this.traverseAncestors((parent) => {
3599
4233
  const scene = parent;
@@ -3629,15 +4263,17 @@ var init_SceneObject = __esm({
3629
4263
  return obj;
3630
4264
  }
3631
4265
  /**
3632
- * Get a group by its name
4266
+ * Finds a named part, such as `head`, without the internal `group:` prefix.
4267
+ * Returns `undefined` when the group does not exist.
3633
4268
  */
3634
4269
  getGroupByName(name) {
3635
4270
  return this.getObjectByName(`group:${name}`);
3636
4271
  }
3637
4272
  /**
3638
- * Toggle visibility of a group
3639
- * @param name name of the group
3640
- * @param visible set the visibility directly, if not set toggles it
4273
+ * Changes a named group's visibility and requests a redraw.
4274
+ * @param name - Part name without the `group:` prefix.
4275
+ * @param visible - Visibility to assign. Omit it to toggle the current value.
4276
+ * @returns The resulting visibility, or `false` if the group does not exist.
3641
4277
  */
3642
4278
  toggleGroupVisibility(name, visible) {
3643
4279
  return this.toggleObjectVisibility(this.getGroupByName(name), visible);
@@ -3688,15 +4324,16 @@ var init_SceneObject = __esm({
3688
4324
  return mesh;
3689
4325
  }
3690
4326
  /**
3691
- * Get a mesh by its name
4327
+ * Finds a named mesh without the internal `mesh:` prefix, or returns `undefined`.
3692
4328
  */
3693
4329
  getMeshByName(name) {
3694
4330
  return this.getObjectByName(`mesh:${name}`);
3695
4331
  }
3696
4332
  /**
3697
- * Toggle visibility of a mesh
3698
- * @param name name of the mesh
3699
- * @param visible set the visibility directly, if not set toggles it
4333
+ * Changes a named mesh's visibility and requests a redraw.
4334
+ * @param name - Mesh name without the `mesh:` prefix.
4335
+ * @param visible - Visibility to assign. Omit it to toggle the current value.
4336
+ * @returns The resulting visibility, or `false` if the mesh does not exist.
3700
4337
  */
3701
4338
  toggleMeshVisibility(name, visible) {
3702
4339
  return this.toggleObjectVisibility(this.getMeshByName(name), visible);
@@ -3742,12 +4379,14 @@ var init_SceneObject = __esm({
3742
4379
  get isInstanced() {
3743
4380
  return this._isInstanced;
3744
4381
  }
4382
+ /** Number of live placements, excluding released instance slots. */
3745
4383
  get instanceCounter() {
3746
4384
  return this._instanceCounter;
3747
4385
  }
3748
4386
  constructInstanceReference(i) {
3749
4387
  return new InstanceReference(this, i);
3750
4388
  }
4389
+ /** Allocates a placement of this instanced object, reusing a released slot when available. */
3751
4390
  nextInstance() {
3752
4391
  const mesh = this.instanceMesh;
3753
4392
  if (!mesh) throw new MineRenderError("Object is not instanced");
@@ -3792,10 +4431,10 @@ var init_SceneObject = __esm({
3792
4431
  return new import_three12.InstancedBufferAttribute(array, attribute.itemSize, attribute.normalized, attribute.meshPerAttribute).setUsage(attribute.usage);
3793
4432
  };
3794
4433
  const matrix = grow(mesh.instanceMatrix, 0);
3795
- const color = mesh.instanceColor ? grow(mesh.instanceColor, 1) : null;
4434
+ const color2 = mesh.instanceColor ? grow(mesh.instanceColor, 1) : null;
3796
4435
  mesh.dispose();
3797
4436
  mesh.instanceMatrix = matrix;
3798
- mesh.instanceColor = color;
4437
+ mesh.instanceColor = color2;
3799
4438
  }
3800
4439
  *activeInstanceIndices() {
3801
4440
  if (this.instanceMesh) {
@@ -3894,6 +4533,7 @@ var init_SceneObject = __esm({
3894
4533
  vector.setFromMatrixScale(matrix);
3895
4534
  return vector;
3896
4535
  }
4536
+ /** Updates supplied transform components. For a shared model, affects every live instance. */
3897
4537
  setPositionRotationScale(position, rotation, scale) {
3898
4538
  if (this.isInstanced) {
3899
4539
  for (const i of this.activeInstanceIndices()) {
@@ -3912,6 +4552,7 @@ var init_SceneObject = __esm({
3912
4552
  }
3913
4553
  this.notifyDirty();
3914
4554
  }
4555
+ /** Sets position in scene units. For a shared model, updates every live instance. */
3915
4556
  setPosition(position) {
3916
4557
  if (this.isInstanced) {
3917
4558
  for (const i of this.activeInstanceIndices()) {
@@ -3929,6 +4570,7 @@ var init_SceneObject = __esm({
3929
4570
  return this.position;
3930
4571
  }
3931
4572
  }
4573
+ /** Sets rotation in radians. For a shared model, updates every live instance. */
3932
4574
  setRotation(rotation) {
3933
4575
  if (this.isInstanced) {
3934
4576
  for (const i of this.activeInstanceIndices()) {
@@ -3946,6 +4588,7 @@ var init_SceneObject = __esm({
3946
4588
  return this.rotation;
3947
4589
  }
3948
4590
  }
4591
+ /** Sets scale factors. For a shared model, updates every live instance. */
3949
4592
  setScale(scale) {
3950
4593
  if (this.isInstanced) {
3951
4594
  for (const i of this.activeInstanceIndices()) {
@@ -3964,15 +4607,15 @@ var init_SceneObject = __esm({
3964
4607
  }
3965
4608
  }
3966
4609
  //</editor-fold>
3967
- toggleObjectVisibility(object, visible) {
3968
- if (object) {
4610
+ toggleObjectVisibility(object3, visible) {
4611
+ if (object3) {
3969
4612
  if (typeof visible !== "undefined") {
3970
- object.visible = visible;
4613
+ object3.visible = visible;
3971
4614
  } else {
3972
- object.visible = !object.visible;
4615
+ object3.visible = !object3.visible;
3973
4616
  }
3974
4617
  this.notifyDirty();
3975
- return object.visible;
4618
+ return object3.visible;
3976
4619
  }
3977
4620
  this.notifyDirty();
3978
4621
  return false;
@@ -3983,6 +4626,7 @@ var init_SceneObject = __esm({
3983
4626
  this.notifyDirty();
3984
4627
  super.dispose();
3985
4628
  }
4629
+ /** Releases instance slots and removes children, disposing children that implement Disposable. */
3986
4630
  disposeAndRemoveAllChildren() {
3987
4631
  if (this.instanceMesh) {
3988
4632
  if (this._scene) this._scene.stats.instanceCount -= this.instanceReferences.size;
@@ -4005,6 +4649,7 @@ var init_SceneObject = __esm({
4005
4649
  }
4006
4650
  this.notifyDirty();
4007
4651
  }
4652
+ /** Detaches the object from its parent and requests a redraw. */
4008
4653
  removeFromScene() {
4009
4654
  this.notifyDirty();
4010
4655
  this.removeFromParent();
@@ -4160,12 +4805,15 @@ var init_EntityObject = __esm({
4160
4805
  if (this.animationPlayers.length) this.notifyDirty();
4161
4806
  }
4162
4807
  //</editor-fold>
4808
+ /** Finds a selected draw by its layer key, such as `main` or `main#2`, or returns `undefined`. */
4163
4809
  getLayerGroup(name) {
4164
4810
  return super.getGroupByName(`layer:${name}`);
4165
4811
  }
4812
+ /** Finds a part name without `group:`. Supply `layerName` to restrict the search to one draw. */
4166
4813
  getGroupByName(name, layerName) {
4167
4814
  return layerName === void 0 ? super.getGroupByName(name) : this.getLayerGroup(layerName)?.getObjectByName(`group:${name}`);
4168
4815
  }
4816
+ /** Finds a mesh name without `mesh:`. Supply `layerName` to restrict the search to one draw. */
4169
4817
  getMeshByName(name, layerName) {
4170
4818
  return layerName === void 0 ? super.getMeshByName(name) : this.getLayerGroup(layerName)?.getObjectByName(`mesh:${name}`);
4171
4819
  }
@@ -4267,8 +4915,8 @@ var init_EntityObject = __esm({
4267
4915
  this.scrollMaterials.push({ material: mat, offset: mat.map.offset, speed: scroll });
4268
4916
  }
4269
4917
  }
4270
- this.getLayerGroup(name)?.traverse((object) => {
4271
- if (isMesh(object)) object.material = mat;
4918
+ this.getLayerGroup(name)?.traverse((object3) => {
4919
+ if (isMesh(object3)) object3.material = mat;
4272
4920
  });
4273
4921
  }));
4274
4922
  if (!this.children.length) {
@@ -4598,8 +5246,8 @@ function toTrianglesDrawMode(geometry, drawMode) {
4598
5246
  return geometry;
4599
5247
  }
4600
5248
  }
4601
- function computeMorphedAttributes(object) {
4602
- if (object.geometry.isBufferGeometry !== true) {
5249
+ function computeMorphedAttributes(object3) {
5250
+ if (object3.geometry.isBufferGeometry !== true) {
4603
5251
  console.error("THREE.BufferGeometryUtils: Geometry is not of type BufferGeometry.");
4604
5252
  return null;
4605
5253
  }
@@ -4612,11 +5260,11 @@ function computeMorphedAttributes(object) {
4612
5260
  const _morphA = new import_three22.Vector3();
4613
5261
  const _morphB = new import_three22.Vector3();
4614
5262
  const _morphC = new import_three22.Vector3();
4615
- function _calculateMorphedAttributeData(object2, material2, attribute, morphAttribute, morphTargetsRelative2, a2, b2, c2, modifiedAttributeArray) {
5263
+ function _calculateMorphedAttributeData(object4, material2, attribute, morphAttribute, morphTargetsRelative2, a2, b2, c2, modifiedAttributeArray) {
4616
5264
  _vA.fromBufferAttribute(attribute, a2);
4617
5265
  _vB.fromBufferAttribute(attribute, b2);
4618
5266
  _vC.fromBufferAttribute(attribute, c2);
4619
- const morphInfluences = object2.morphTargetInfluences;
5267
+ const morphInfluences = object4.morphTargetInfluences;
4620
5268
  if (material2.morphTargets && morphAttribute && morphInfluences) {
4621
5269
  _morphA.set(0, 0, 0);
4622
5270
  _morphB.set(0, 0, 0);
@@ -4642,10 +5290,10 @@ function computeMorphedAttributes(object) {
4642
5290
  _vB.add(_morphB);
4643
5291
  _vC.add(_morphC);
4644
5292
  }
4645
- if (object2.isSkinnedMesh) {
4646
- object2.boneTransform(a2, _vA);
4647
- object2.boneTransform(b2, _vB);
4648
- object2.boneTransform(c2, _vC);
5293
+ if (object4.isSkinnedMesh) {
5294
+ object4.boneTransform(a2, _vA);
5295
+ object4.boneTransform(b2, _vB);
5296
+ object4.boneTransform(c2, _vC);
4649
5297
  }
4650
5298
  modifiedAttributeArray[a2 * 3 + 0] = _vA.x;
4651
5299
  modifiedAttributeArray[a2 * 3 + 1] = _vA.y;
@@ -4657,8 +5305,8 @@ function computeMorphedAttributes(object) {
4657
5305
  modifiedAttributeArray[c2 * 3 + 1] = _vC.y;
4658
5306
  modifiedAttributeArray[c2 * 3 + 2] = _vC.z;
4659
5307
  }
4660
- const geometry = object.geometry;
4661
- const material = object.material;
5308
+ const geometry = object3.geometry;
5309
+ const material = object3.material;
4662
5310
  let a, b, c;
4663
5311
  const index = geometry.index;
4664
5312
  const positionAttribute = geometry.attributes.position;
@@ -4685,7 +5333,7 @@ function computeMorphedAttributes(object) {
4685
5333
  b = index.getX(j + 1);
4686
5334
  c = index.getX(j + 2);
4687
5335
  _calculateMorphedAttributeData(
4688
- object,
5336
+ object3,
4689
5337
  groupMaterial,
4690
5338
  positionAttribute,
4691
5339
  morphPosition,
@@ -4696,7 +5344,7 @@ function computeMorphedAttributes(object) {
4696
5344
  modifiedPosition
4697
5345
  );
4698
5346
  _calculateMorphedAttributeData(
4699
- object,
5347
+ object3,
4700
5348
  groupMaterial,
4701
5349
  normalAttribute,
4702
5350
  morphNormal,
@@ -4716,7 +5364,7 @@ function computeMorphedAttributes(object) {
4716
5364
  b = index.getX(i + 1);
4717
5365
  c = index.getX(i + 2);
4718
5366
  _calculateMorphedAttributeData(
4719
- object,
5367
+ object3,
4720
5368
  material,
4721
5369
  positionAttribute,
4722
5370
  morphPosition,
@@ -4727,7 +5375,7 @@ function computeMorphedAttributes(object) {
4727
5375
  modifiedPosition
4728
5376
  );
4729
5377
  _calculateMorphedAttributeData(
4730
- object,
5378
+ object3,
4731
5379
  material,
4732
5380
  normalAttribute,
4733
5381
  morphNormal,
@@ -4751,7 +5399,7 @@ function computeMorphedAttributes(object) {
4751
5399
  b = j + 1;
4752
5400
  c = j + 2;
4753
5401
  _calculateMorphedAttributeData(
4754
- object,
5402
+ object3,
4755
5403
  groupMaterial,
4756
5404
  positionAttribute,
4757
5405
  morphPosition,
@@ -4762,7 +5410,7 @@ function computeMorphedAttributes(object) {
4762
5410
  modifiedPosition
4763
5411
  );
4764
5412
  _calculateMorphedAttributeData(
4765
- object,
5413
+ object3,
4766
5414
  groupMaterial,
4767
5415
  normalAttribute,
4768
5416
  morphNormal,
@@ -4782,7 +5430,7 @@ function computeMorphedAttributes(object) {
4782
5430
  b = i + 1;
4783
5431
  c = i + 2;
4784
5432
  _calculateMorphedAttributeData(
4785
- object,
5433
+ object3,
4786
5434
  material,
4787
5435
  positionAttribute,
4788
5436
  morphPosition,
@@ -4793,7 +5441,7 @@ function computeMorphedAttributes(object) {
4793
5441
  modifiedPosition
4794
5442
  );
4795
5443
  _calculateMorphedAttributeData(
4796
- object,
5444
+ object3,
4797
5445
  material,
4798
5446
  normalAttribute,
4799
5447
  morphNormal,
@@ -4829,6 +5477,7 @@ var init_ModelCulling = __esm({
4829
5477
  import_three23 = require("three");
4830
5478
  init_CubeFace();
4831
5479
  ModelCulling = class {
5480
+ /** Rotates a world-direction bit mask into model space. Bits follow {@link CUBE_FACES}. */
4832
5481
  static toLocalMask(worldMask, rotation) {
4833
5482
  let localMask = 0;
4834
5483
  for (const [localFace, offset] of CUBE_FACE_OFFSETS.entries()) {
@@ -4839,6 +5488,7 @@ var init_ModelCulling = __esm({
4839
5488
  }
4840
5489
  return localMask;
4841
5490
  }
5491
+ /** Whether a model fills one block with opaque faces and can hide adjacent block faces. */
4842
5492
  static isOpaqueFullCube(atlas) {
4843
5493
  if (!atlas || atlas.hasTransparency !== false || atlas.model.elements?.length !== 1) return false;
4844
5494
  const element = atlas.model.elements[0];
@@ -4871,9 +5521,11 @@ var init_SpecialItems = __esm({
4871
5521
  import_three24 = require("three");
4872
5522
  init_AssetKey();
4873
5523
  init_Entities();
5524
+ init_BannerPatterns();
5525
+ init_ModelTextures();
4874
5526
  SpecialItems = class {
4875
5527
  /** Resolves the static entity parts drawn by a special item renderer, in model units. */
4876
- static async getParts(special, root) {
5528
+ static async getParts(special, root, components = {}) {
4877
5529
  const texture = (id, directory) => {
4878
5530
  const key = AssetKey.parse("textures", id);
4879
5531
  return new AssetKey(
@@ -4886,13 +5538,77 @@ var init_SpecialItems = __esm({
4886
5538
  root
4887
5539
  );
4888
5540
  };
4889
- const load = async (id, textureKey, transform, rotations = {}) => {
5541
+ const load = async (id, textureKey, transform, rotations = {}, layers = ["main"]) => {
4890
5542
  const key = new AssetKey("minecraft", id, void 0, void 0, "entity-models", ".json", root);
4891
- const model = await Entities.getEntity(key, textureKey, { layer: "main" });
5543
+ const model = await Entities.getEntity(key, textureKey, layers.length === 1 ? { layer: layers[0] } : {
5544
+ layers,
5545
+ textures: textureKey ? Object.fromEntries(layers.map((name) => [name, textureKey])) : void 0
5546
+ });
4892
5547
  if (!model) throw new Error(`Missing entity model minecraft:${id} for special item ${special.type}`);
4893
5548
  return { model, transform, rotations };
4894
5549
  };
4895
5550
  switch (special.type) {
5551
+ case "minecraft:trident":
5552
+ case "trident":
5553
+ case "minecraft:conduit":
5554
+ case "conduit": {
5555
+ const trident = special.type === "trident" || special.type === "minecraft:trident";
5556
+ const part = await load(
5557
+ trident ? "trident" : "conduit",
5558
+ texture(trident ? "trident" : "conduit/base", ""),
5559
+ trident ? new import_three24.Matrix4().makeScale(1, -1, -1) : new import_three24.Matrix4().makeTranslation(8, 8, 8),
5560
+ {},
5561
+ trident ? ["main"] : ["shell"]
5562
+ );
5563
+ part.model = { ...part.model, render: "solid", layers: part.model.layers && Object.fromEntries(
5564
+ Object.entries(part.model.layers).map(([name, layer]) => [name, { ...layer, render: "solid" }])
5565
+ ) };
5566
+ return [part];
5567
+ }
5568
+ case "minecraft:banner":
5569
+ case "banner":
5570
+ case "minecraft:shield":
5571
+ case "shield": {
5572
+ const banner = special.type === "banner" || special.type === "minecraft:banner";
5573
+ const color2 = banner ? BannerPatterns.getColor(special.color) : components["minecraft:base_color"] === void 0 ? void 0 : BannerPatterns.getColor(components["minecraft:base_color"]);
5574
+ const patterns = await BannerPatterns.getLayers(components["minecraft:banner_patterns"], banner ? "banner" : "shield", root);
5575
+ const patterned = banner || color2 !== void 0 || patterns.length > 0;
5576
+ const baseTexture = texture(banner ? "banner_base" : patterned ? "shield_base" : "shield_base_nopattern", "");
5577
+ const transform = banner ? new import_three24.Matrix4().makeTranslation(8, 0, 8).multiply(new import_three24.Matrix4().makeScale(2 / 3, -2 / 3, -2 / 3)) : new import_three24.Matrix4().makeScale(1, -1, -1);
5578
+ const part = await load(banner ? "standing_banner" : "shield", baseTexture, transform, {}, banner ? ["main", "flag"] : ["main"]);
5579
+ const layers = Object.fromEntries(Object.entries(part.model.layers ?? { main: part.model }).map(([name, layer]) => [name, { ...layer, render: "solid" }]));
5580
+ let overlay2;
5581
+ if (banner) {
5582
+ const flagLayer = layers.flag;
5583
+ const flag = flagLayer?.layer.root.children.flag;
5584
+ if (!flag) throw new Error("Banner entity model is missing its flag layer");
5585
+ overlay2 = { ...flagLayer.layer, root: { ...flagLayer.layer.root, children: {
5586
+ ...flagLayer.layer.root.children,
5587
+ flag: { ...flag, pose: { ...flag.pose, rotation: [-25e-4 * Math.PI, flag.pose.rotation[1], flag.pose.rotation[2]] } }
5588
+ } } };
5589
+ layers.flag = { ...flagLayer, layer: overlay2 };
5590
+ } else {
5591
+ const main = layers.main.layer;
5592
+ const plate = main.root.children.plate;
5593
+ if (!plate) throw new Error("Shield entity model is missing its plate");
5594
+ overlay2 = { ...main, root: { ...main.root, cubes: [], children: { plate } } };
5595
+ }
5596
+ const tints = {};
5597
+ if (patterned) {
5598
+ const draws = [{ texture: texture(`${banner ? "banner" : "shield"}/base`, ""), color: color2 ?? DYE_COLORS.white }, ...patterns];
5599
+ draws.forEach((draw, index) => {
5600
+ const name = index === 0 ? "pattern_base" : `pattern_${index - 1}`;
5601
+ layers[name] = { key: draw.texture, texture: draw.texture, layer: overlay2, render: "no_outline", tint: name };
5602
+ tints[name] = draw.color;
5603
+ });
5604
+ }
5605
+ await Promise.all([...new Map(Object.values(layers).map((layer) => [layer.texture.serialize(), layer.texture])).values()].map(async (key) => {
5606
+ if (!await ModelTextures.preload(key)) throw new Error(`Missing special item texture ${key.toNamespacedString()}`);
5607
+ }));
5608
+ part.model = { ...part.model, ...layers.main, layers };
5609
+ part.tints = tints;
5610
+ return [part];
5611
+ }
4896
5612
  case "minecraft:chest":
4897
5613
  case "chest": {
4898
5614
  const angle = -(special.openness ?? 0) * Math.PI / 2;
@@ -4903,6 +5619,26 @@ var init_SpecialItems = __esm({
4903
5619
  chest.model = { ...chest.model, render: "solid", layers: void 0 };
4904
5620
  return [chest];
4905
5621
  }
5622
+ case "minecraft:shulker_box":
5623
+ case "shulker_box": {
5624
+ const openness = special.openness === void 0 ? 0 : special.openness;
5625
+ const orientation = special.orientation === void 0 ? "up" : special.orientation;
5626
+ const directions = {
5627
+ down: [Math.PI, 0, 0],
5628
+ up: [0, 0, 0],
5629
+ north: [Math.PI / 2, 0, Math.PI],
5630
+ south: [Math.PI / 2, 0, 0],
5631
+ west: [Math.PI / 2, 0, Math.PI / 2],
5632
+ east: [Math.PI / 2, 0, -Math.PI / 2]
5633
+ };
5634
+ if (!Object.prototype.hasOwnProperty.call(directions, orientation)) throw new Error(`Unsupported shulker-box orientation ${orientation}`);
5635
+ if (typeof openness !== "number" || !Number.isFinite(openness)) throw new Error("Shulker-box openness must be finite");
5636
+ if (typeof special.texture !== "string" || !special.texture) throw new Error("Shulker-box texture is required");
5637
+ const transform = new import_three24.Matrix4().makeTranslation(8, 8, 8).multiply(new import_three24.Matrix4().makeScale(0.9995, 0.9995, 0.9995)).multiply(new import_three24.Matrix4().makeRotationFromEuler(new import_three24.Euler(...directions[orientation], "XYZ"))).multiply(new import_three24.Matrix4().makeScale(1, -1, -1)).multiply(new import_three24.Matrix4().makeTranslation(0, -16, 0));
5638
+ const box = await load("shulker_box", texture(special.texture, "shulker"), transform, { lid: [0, openness * Math.PI * 1.5, 0] });
5639
+ box.positions = { lid: [0, 24 - openness * 8, 0] };
5640
+ return [box];
5641
+ }
4906
5642
  case "minecraft:bed":
4907
5643
  case "bed": {
4908
5644
  const transform = new import_three24.Matrix4().makeTranslation(0, 9, 0).multiply(new import_three24.Matrix4().makeRotationX(Math.PI / 2)).multiply(new import_three24.Matrix4().makeTranslation(8, 8, 8)).multiply(new import_three24.Matrix4().makeRotationZ(Math.PI)).multiply(new import_three24.Matrix4().makeTranslation(-8, -8, -8));
@@ -5017,6 +5753,7 @@ var init_ItemTints = __esm({
5017
5753
  static async get(model, tints) {
5018
5754
  const sources = model.tints;
5019
5755
  if (!sources?.length) return tints;
5756
+ const components = model.components ?? {};
5020
5757
  const colors = { ...tints };
5021
5758
  await Promise.all(sources.map(async (source, index) => {
5022
5759
  if (colors[index] !== void 0) return;
@@ -5031,27 +5768,71 @@ var init_ItemTints = __esm({
5031
5768
  break;
5032
5769
  case "dye":
5033
5770
  case "minecraft:dye":
5771
+ colors[index] = this.colorOrDefault(components["minecraft:dyed_color"], source.default, true);
5772
+ break;
5034
5773
  case "potion":
5035
- case "minecraft:potion":
5774
+ case "minecraft:potion": {
5775
+ const value = typeof components["minecraft:potion_contents"] === "string" ? void 0 : this.componentField(components, "potion_contents", "custom_color");
5776
+ colors[index] = this.colorOrDefault(value, source.default);
5777
+ break;
5778
+ }
5036
5779
  case "map_color":
5037
5780
  case "minecraft:map_color":
5781
+ colors[index] = this.colorOrDefault(components["minecraft:map_color"], source.default);
5782
+ break;
5038
5783
  case "firework":
5039
- case "minecraft:firework":
5784
+ case "minecraft:firework": {
5785
+ const values = this.componentField(components, "firework_explosion", "colors");
5786
+ if (values !== void 0 && !Array.isArray(values)) throw new MineRenderError("Item tint firework_explosion.colors must be an array");
5787
+ if (!values?.length) {
5788
+ colors[index] = this.rgb(source.default);
5789
+ break;
5790
+ }
5791
+ const rgb = values.map((value) => this.componentColor(value));
5792
+ const red = Math.floor(rgb.reduce((sum, color2) => sum + (color2 >> 16 & 255), 0) / rgb.length);
5793
+ const green = Math.floor(rgb.reduce((sum, color2) => sum + (color2 >> 8 & 255), 0) / rgb.length);
5794
+ const blue = Math.floor(rgb.reduce((sum, color2) => sum + (color2 & 255), 0) / rgb.length);
5795
+ colors[index] = red << 16 | green << 8 | blue;
5796
+ break;
5797
+ }
5040
5798
  case "team":
5041
5799
  case "minecraft:team":
5042
- case "custom_model_data":
5043
- case "minecraft:custom_model_data":
5044
5800
  colors[index] = this.rgb(source.default);
5045
5801
  break;
5802
+ case "custom_model_data":
5803
+ case "minecraft:custom_model_data": {
5804
+ const colorIndex = source.index ?? 0;
5805
+ if (!Number.isSafeInteger(colorIndex) || colorIndex < 0) throw new MineRenderError("Item tint custom_model_data index must be a nonnegative integer");
5806
+ const values = this.componentField(components, "custom_model_data", "colors");
5807
+ if (values !== void 0 && !Array.isArray(values)) throw new MineRenderError("Item tint custom_model_data.colors must be an array");
5808
+ colors[index] = this.colorOrDefault(values?.[colorIndex], source.default, true);
5809
+ break;
5810
+ }
5046
5811
  default:
5047
5812
  throw new MineRenderError(`Unsupported item tint source ${source.type}`);
5048
5813
  }
5049
5814
  }));
5050
5815
  return colors;
5051
5816
  }
5052
- static rgb(color) {
5053
- if (!Array.isArray(color)) return color & 16777215;
5054
- const [red, green, blue] = color.map((channel) => Math.floor(Math.fround(Math.fround(channel) * 255)));
5817
+ static componentField(components, id, field) {
5818
+ const value = components[`minecraft:${id}`];
5819
+ if (value === void 0) return void 0;
5820
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new MineRenderError(`Item tint ${id} must be an object`);
5821
+ return value[field];
5822
+ }
5823
+ static colorOrDefault(value, fallback, allowTriple = false) {
5824
+ return value === void 0 ? this.rgb(fallback) : this.componentColor(value, allowTriple);
5825
+ }
5826
+ static componentColor(value, allowTriple = false) {
5827
+ if (typeof value === "number" && Number.isInteger(value) && value >= -2147483648 && value <= 2147483647) return this.rgb(value);
5828
+ if (allowTriple && Array.isArray(value) && value.length === 3 && value.every((channel) => typeof channel === "number" && Number.isFinite(channel))) {
5829
+ return this.rgb(value) & 16777215;
5830
+ }
5831
+ throw new MineRenderError(`Item tint component color must be a packed integer${allowTriple ? " or RGB triple" : ""}`);
5832
+ }
5833
+ static rgb(color2) {
5834
+ if (!Array.isArray(color2)) return color2 & 16777215;
5835
+ const [red, green, blue] = color2.map((channel) => Math.floor(Math.fround(Math.fround(channel) * 255)) & 255);
5055
5836
  return red << 16 | green << 8 | blue;
5056
5837
  }
5057
5838
  };
@@ -5088,29 +5869,52 @@ var init_ModelObject = __esm({
5088
5869
  this.originalModel = originalModel;
5089
5870
  this.isModelObject = true;
5090
5871
  this.specialMaterials = /* @__PURE__ */ new Map();
5872
+ this.geometries = /* @__PURE__ */ new Set();
5091
5873
  this.meshesCreated = false;
5092
5874
  this.options = (0, import_ts_deepmerge5.default)({}, _ModelObject.DEFAULT_OPTIONS, options ?? {});
5093
- if (originalModel.special) this.options.instanceMeshes = false;
5875
+ if (originalModel.special || originalModel.parts) this.options.instanceMeshes = false;
5094
5876
  if (this.options.tints) this.options.tints = { ...this.options.tints };
5095
5877
  this.addEventListener("added", () => this.updateAnimationSubscription());
5096
5878
  this.addEventListener("removed", () => this.updateAnimationSubscription());
5097
5879
  }
5098
5880
  async init() {
5881
+ const parts = this.originalModel.parts;
5882
+ if (parts) {
5883
+ try {
5884
+ for (const part of parts) {
5885
+ const object3 = new _ModelObject(part, { ...this.options, instanceMeshes: false });
5886
+ this.add(object3);
5887
+ await object3.init();
5888
+ }
5889
+ let order = 0;
5890
+ this.iterateAllMeshes((mesh) => {
5891
+ mesh.renderOrder = order++;
5892
+ for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
5893
+ material.transparent = true;
5894
+ }
5895
+ });
5896
+ } catch (error) {
5897
+ this.disposeAndRemoveAllChildren();
5898
+ throw error;
5899
+ }
5900
+ this.notifyDirty();
5901
+ return;
5902
+ }
5099
5903
  this.options.tints = await ItemTints.get(this.originalModel, this.options.tints);
5100
5904
  const special = this.originalModel.special;
5101
5905
  if (special) {
5102
- const parts = await SpecialItems.getParts(special, this.originalModel.key?.root);
5906
+ const parts2 = await SpecialItems.getParts(special, this.originalModel.key?.root, this.originalModel.components);
5103
5907
  const transform = this.options.displayPosition ? DisplayTransforms.getMatrix(this.originalModel.display, this.options.displayPosition) : new import_three25.Matrix4();
5104
5908
  transform.multiply(new import_three25.Matrix4().makeTranslation(-8, -8, -8));
5105
5909
  try {
5106
- for (const part of parts) {
5107
- const object = new EntityObject(part.model, { flip: false, wireframe: this.options.wireframe });
5108
- object.matrix.copy(transform).multiply(part.transform);
5109
- object.matrixWorldNeedsUpdate = true;
5110
- object.matrixAutoUpdate = false;
5111
- this.add(object);
5112
- await object.init();
5113
- object.iterateAllMeshes((mesh) => {
5910
+ for (const part of parts2) {
5911
+ const object3 = new EntityObject(part.model, { flip: false, wireframe: this.options.wireframe, tints: part.tints });
5912
+ object3.matrix.copy(transform).multiply(part.transform);
5913
+ object3.matrixWorldNeedsUpdate = true;
5914
+ object3.matrixAutoUpdate = false;
5915
+ this.add(object3);
5916
+ await object3.init();
5917
+ object3.iterateAllMeshes((mesh) => {
5114
5918
  const source = mesh.material;
5115
5919
  let material = this.specialMaterials.get(source);
5116
5920
  if (!material) {
@@ -5121,7 +5925,10 @@ var init_ModelObject = __esm({
5121
5925
  mesh.material = material;
5122
5926
  });
5123
5927
  for (const [name, rotation] of Object.entries(part.rotations)) {
5124
- object.getGroupByName(name)?.rotation.set(...rotation, "ZYX");
5928
+ object3.getGroupByName(name)?.rotation.set(...rotation, "ZYX");
5929
+ }
5930
+ for (const [name, position] of Object.entries(part.positions ?? {})) {
5931
+ object3.getGroupByName(name)?.position.set(...position);
5125
5932
  }
5126
5933
  }
5127
5934
  } catch (error) {
@@ -5154,6 +5961,7 @@ var init_ModelObject = __esm({
5154
5961
  if (this.atlas.model.elements) {
5155
5962
  this.atlas.model.elements?.forEach((el) => {
5156
5963
  const elGeo = this._getBoxGeometryFromElement(el).clone();
5964
+ this.geometries.add(elGeo);
5157
5965
  if (this.options.cullMask) {
5158
5966
  const indices = Array.from(elGeo.getIndex().array);
5159
5967
  elGeo.setIndex(indices.filter((_, index) => {
@@ -5166,6 +5974,7 @@ var init_ModelObject = __esm({
5166
5974
  if (this.options.uvLockRotation) {
5167
5975
  UVMapper.lockUvs(elGeo, el.faces, this.atlas, new import_three25.Euler(...this.options.uvLockRotation));
5168
5976
  }
5977
+ UVMapper.setAtlasUvBounds(elGeo, el.faces, this.atlas);
5169
5978
  if (this.options.tints) {
5170
5979
  const colors = new Float32Array(elGeo.getAttribute("position").count * 3).fill(1);
5171
5980
  for (const [faceIndex, faceName] of CUBE_FACES.entries()) {
@@ -5173,9 +5982,9 @@ var init_ModelObject = __esm({
5173
5982
  if (tintIndex === void 0 || tintIndex < 0) continue;
5174
5983
  const tint = this.options.tints[tintIndex];
5175
5984
  if (tint === void 0) continue;
5176
- const color = new import_three25.Color(tint);
5985
+ const color2 = new import_three25.Color(tint);
5177
5986
  for (let vertex = 0; vertex < 4; vertex++) {
5178
- color.toArray(colors, (faceIndex * 4 + vertex) * 3);
5987
+ color2.toArray(colors, (faceIndex * 4 + vertex) * 3);
5179
5988
  }
5180
5989
  }
5181
5990
  elGeo.setAttribute("color", new import_three25.BufferAttribute(colors, 3));
@@ -5206,10 +6015,14 @@ var init_ModelObject = __esm({
5206
6015
  let combinedGeo;
5207
6016
  if (allGeos.length > 0) {
5208
6017
  combinedGeo = mergeBufferGeometries(allGeos);
6018
+ for (const geometry of allGeos) {
6019
+ this.geometries.delete(geometry);
6020
+ }
5209
6021
  } else {
5210
6022
  combinedGeo = new import_three25.BoxGeometry(16, 16, 16);
5211
6023
  if (displayTransform) DisplayTransforms.apply(combinedGeo, displayTransform);
5212
6024
  }
6025
+ this.geometries.add(combinedGeo);
5213
6026
  combinedGeo.computeBoundingBox();
5214
6027
  let mesh;
5215
6028
  if (this.options.instanceMeshes) {
@@ -5231,9 +6044,12 @@ var init_ModelObject = __esm({
5231
6044
  }
5232
6045
  applyTextures() {
5233
6046
  if (this.atlas) {
5234
- const mat = Materials.createShadedCanvasMaterial(this.atlas.image.canvas, this.atlas.hasTransparency, false);
6047
+ const mat = Materials.createShadedCanvasMaterial(this.atlas.image.canvas, this.atlas.hasTransparency, false, true);
5235
6048
  this.atlasMaterial = mat;
5236
6049
  this.atlasTexture = mat.uniforms?.map?.value ?? mat.map;
6050
+ if (this.options.displayPosition === "gui" /* GUI */ && this.originalModel.gui_light === "front" /* FRONT */ && mat.uniforms?.SHADE) {
6051
+ mat.uniforms.SHADE.value = false;
6052
+ }
5237
6053
  this.iterateAllMeshes((mesh) => {
5238
6054
  if (mesh.geometry.hasAttribute("color")) mat.vertexColors = true;
5239
6055
  mesh.material = mat;
@@ -5242,7 +6058,9 @@ var init_ModelObject = __esm({
5242
6058
  }
5243
6059
  }
5244
6060
  updateAnimationSubscription() {
5245
- const active = !!this.parent && (!this.isInstanced || this.instanceCounter > 0);
6061
+ let root = this;
6062
+ while (root.parent && isModelObject(root.parent)) root = root.parent;
6063
+ const active = !!root.parent && (!this.isInstanced || this.instanceCounter > 0);
5246
6064
  if (active && this.atlas?.hasAnimation && this.atlasTexture) {
5247
6065
  if (!this.unsubscribeAtlas) {
5248
6066
  this.atlasTexture.needsUpdate = true;
@@ -5258,6 +6076,9 @@ var init_ModelObject = __esm({
5258
6076
  this.unsubscribeAtlas?.();
5259
6077
  this.unsubscribeAtlas = void 0;
5260
6078
  }
6079
+ for (const child of this.children) {
6080
+ if (isModelObject(child)) child.updateAnimationSubscription();
6081
+ }
5261
6082
  }
5262
6083
  nextInstance() {
5263
6084
  const reference = super.nextInstance();
@@ -5277,6 +6098,8 @@ var init_ModelObject = __esm({
5277
6098
  this.atlasMaterial = void 0;
5278
6099
  for (const material of this.specialMaterials.values()) material.dispose();
5279
6100
  this.specialMaterials.clear();
6101
+ for (const geometry of this.geometries) geometry.dispose();
6102
+ this.geometries.clear();
5280
6103
  super.disposeAndRemoveAllChildren();
5281
6104
  }
5282
6105
  };
@@ -5492,9 +6315,9 @@ var init_FluidModelObject = __esm({
5492
6315
  }
5493
6316
  }
5494
6317
  geometry.clearGroups();
5495
- const color = new import_three31.Color(this.options.tints?.[0] ?? (this.kind === "water" ? 4159204 : 16777215));
6318
+ const color2 = new import_three31.Color(this.options.tints?.[0] ?? (this.kind === "water" ? 4159204 : 16777215));
5496
6319
  const colors = new Float32Array(geometry.getAttribute("position").count * 3);
5497
- for (let i = 0; i < colors.length; i += 3) color.toArray(colors, i);
6320
+ for (let i = 0; i < colors.length; i += 3) color2.toArray(colors, i);
5498
6321
  geometry.setAttribute("color", new import_three31.BufferAttribute(colors, 3));
5499
6322
  geometry.computeBoundingBox();
5500
6323
  geometry.computeBoundingSphere();
@@ -5538,6 +6361,7 @@ __export(index_browser_exports, {
5538
6361
  Axis: () => Axis,
5539
6362
  BUILTIN_ENTITY: () => BUILTIN_ENTITY,
5540
6363
  BUILTIN_GENERATED: () => BUILTIN_GENERATED,
6364
+ BannerPatterns: () => BannerPatterns,
5541
6365
  BasicAssetKey: () => BasicAssetKey,
5542
6366
  BatchedExecutor: () => BatchedExecutor,
5543
6367
  BlockEntities: () => BlockEntities,
@@ -5562,6 +6386,7 @@ __export(index_browser_exports, {
5562
6386
  DEFAULT_ROOT: () => DEFAULT_ROOT,
5563
6387
  DEFAULT_UV: () => DEFAULT_UV,
5564
6388
  DISPLAY_POSITIONS: () => DISPLAY_POSITIONS,
6389
+ DYE_COLORS: () => DYE_COLORS,
5565
6390
  DefaultBlockStates: () => DefaultBlockStates,
5566
6391
  DisplayPosition: () => DisplayPosition,
5567
6392
  DisplayTransforms: () => DisplayTransforms,
@@ -5572,6 +6397,7 @@ __export(index_browser_exports, {
5572
6397
  Env: () => Env,
5573
6398
  FluidModelObject: () => FluidModelObject,
5574
6399
  Fonts: () => Fonts,
6400
+ GUI_CONTAINER_LAYOUTS: () => GUI_CONTAINER_LAYOUTS,
5575
6401
  Geometries: () => Geometries,
5576
6402
  GuiHelper: () => GuiHelper,
5577
6403
  GuiLight: () => GuiLight,
@@ -5598,11 +6424,14 @@ __export(index_browser_exports, {
5598
6424
  Models: () => Models,
5599
6425
  NBTHelper: () => NBTHelper,
5600
6426
  OrbitControls: () => OrbitControls,
6427
+ PackFormats: () => PackFormats,
6428
+ PackMetadata: () => PackMetadata,
5601
6429
  PersistentCache: () => PersistentCache,
5602
6430
  Renderer: () => Renderer,
5603
6431
  RequestError: () => RequestError,
5604
6432
  Requests: () => Requests,
5605
6433
  SKIN_PARTS: () => SKIN_PARTS,
6434
+ SceneDocumentLoader: () => SceneDocumentLoader,
5606
6435
  SceneExporter: () => SceneExporter,
5607
6436
  SceneInspector: () => SceneInspector,
5608
6437
  SceneObject: () => SceneObject,
@@ -5678,6 +6507,7 @@ __export(index_browser_exports, {
5678
6507
  mergeBufferAttributes: () => mergeBufferAttributes,
5679
6508
  mergeBufferGeometries: () => mergeBufferGeometries,
5680
6509
  mergeVertices: () => mergeVertices,
6510
+ parseSceneDocument: () => parseSceneDocument,
5681
6511
  prefix: () => prefix,
5682
6512
  probeImageSize: () => probeImageSize,
5683
6513
  registerBrowserEnv: () => registerBrowserEnv,
@@ -5806,11 +6636,11 @@ var BrowserEnv = class {
5806
6636
  imageSize(data) {
5807
6637
  return probeImageSize(data);
5808
6638
  }
5809
- openCache(name, version) {
6639
+ openCache(name, version2) {
5810
6640
  return new BrowserCache(import_localforage.default.createInstance({
5811
6641
  // IndexedDB schema upgrades retain records; each cache generation needs its own database.
5812
- name: name + version,
5813
- version
6642
+ name: name + version2,
6643
+ version: version2
5814
6644
  }));
5815
6645
  }
5816
6646
  };
@@ -5841,6 +6671,7 @@ var Meshes = class _Meshes {
5841
6671
  static createBox(key) {
5842
6672
  return new import_three6.Mesh(Geometries.getBox(key.geometry), Materials.getImage(key.material));
5843
6673
  }
6674
+ /** Returns a shared Mesh object. Use {@link createBox} for a mesh with its own transform and parent. */
5844
6675
  static getBox(key) {
5845
6676
  const keyStr = serializeMeshKey(key);
5846
6677
  return Caching.boxMeshCache.get(keyStr, (k) => {
@@ -5890,6 +6721,7 @@ function shutdown() {
5890
6721
  init_AssetKey();
5891
6722
  init_AssetLoader();
5892
6723
  init_Assets();
6724
+ init_BannerPatterns();
5893
6725
 
5894
6726
  // src/assets/BlockEntities.ts
5895
6727
  var import_three10 = require("three");
@@ -5953,6 +6785,7 @@ init_AssetParsers();
5953
6785
  init_ModelTextures();
5954
6786
  init_Caching();
5955
6787
  var Fonts = class {
6788
+ /** Combines font definitions in source-priority order. Unsupported Unihex and TrueType providers are skipped. */
5956
6789
  static async get(key = "minecraft:default") {
5957
6790
  const fontKey = typeof key === "string" ? AssetKey.parse("font", key) : key;
5958
6791
  return await Caching.fontCache.get(fontKey.serialize(), () => this.load(fontKey, []));
@@ -5968,8 +6801,8 @@ var Fonts = class {
5968
6801
  const stack = await AssetLoader.getAll(key, "json" /* JSON */);
5969
6802
  if (stack.length === 0) throw new Error(`Could not load font ${key.toNamespacedString()}`);
5970
6803
  const glyphs = /* @__PURE__ */ new Map();
5971
- for (const asset of stack) {
5972
- for (const provider of asset.providers) {
6804
+ for (const asset2 of stack) {
6805
+ for (const provider of asset2.providers) {
5973
6806
  if (provider.filter?.uniform || provider.filter?.jp) continue;
5974
6807
  let provided;
5975
6808
  switch (provider.type) {
@@ -6108,13 +6941,13 @@ var OBJExporter = class {
6108
6941
  * @param {Object3D} object - The 3D object to export.
6109
6942
  * @return {string} The exported OBJ.
6110
6943
  */
6111
- parse(object) {
6944
+ parse(object3) {
6112
6945
  let output = "";
6113
6946
  let indexVertex = 0;
6114
6947
  let indexVertexUvs = 0;
6115
6948
  let indexNormals = 0;
6116
6949
  const vertex = new import_three16.Vector3();
6117
- const color = new import_three16.Color();
6950
+ const color2 = new import_three16.Color();
6118
6951
  const normal = new import_three16.Vector3();
6119
6952
  const uv = new import_three16.Vector2();
6120
6953
  const face = [];
@@ -6213,9 +7046,9 @@ var OBJExporter = class {
6213
7046
  vertex.applyMatrix4(points.matrixWorld);
6214
7047
  output += "v " + vertex.x + " " + vertex.y + " " + vertex.z;
6215
7048
  if (colors !== void 0) {
6216
- color.fromBufferAttribute(colors, i);
6217
- import_three16.ColorManagement.workingToColorSpace(color, import_three16.SRGBColorSpace);
6218
- output += " " + color.r + " " + color.g + " " + color.b;
7049
+ color2.fromBufferAttribute(colors, i);
7050
+ import_three16.ColorManagement.workingToColorSpace(color2, import_three16.SRGBColorSpace);
7051
+ output += " " + color2.r + " " + color2.g + " " + color2.b;
6219
7052
  }
6220
7053
  output += "\n";
6221
7054
  }
@@ -6227,7 +7060,7 @@ var OBJExporter = class {
6227
7060
  }
6228
7061
  indexVertex += nbVertex;
6229
7062
  }
6230
- object.traverse(function(child) {
7063
+ object3.traverse(function(child) {
6231
7064
  if (child.isMesh === true) {
6232
7065
  parseMesh(child);
6233
7066
  }
@@ -6255,9 +7088,9 @@ var PLYExporter = class {
6255
7088
  * @param {PLYExporter~Options} options - The export options.
6256
7089
  * @return {?(string|ArrayBuffer)} The exported PLY.
6257
7090
  */
6258
- parse(object, onDone, options = {}) {
7091
+ parse(object3, onDone, options = {}) {
6259
7092
  function traverseMeshes(cb) {
6260
- object.traverse(function(child) {
7093
+ object3.traverse(function(child) {
6261
7094
  if (child.isMesh === true || child.isPoints) {
6262
7095
  const mesh = child;
6263
7096
  const geometry = mesh.geometry;
@@ -6290,7 +7123,7 @@ var PLYExporter = class {
6290
7123
  for (const name of customAttributeNames) customTypes[name] = "float";
6291
7124
  let vertexCount = 0;
6292
7125
  let faceCount = 0;
6293
- object.traverse(function(child) {
7126
+ object3.traverse(function(child) {
6294
7127
  if (child.isMesh === true) {
6295
7128
  const mesh = child;
6296
7129
  const geometry = mesh.geometry;
@@ -6884,8 +7717,8 @@ function equalArray(array1, array2) {
6884
7717
  return element === array2[index];
6885
7718
  });
6886
7719
  }
6887
- function stringToArrayBuffer(text) {
6888
- return new TextEncoder().encode(text).buffer;
7720
+ function stringToArrayBuffer(text2) {
7721
+ return new TextEncoder().encode(text2).buffer;
6889
7722
  }
6890
7723
  function isIdentityMatrix(matrix) {
6891
7724
  return equalArray(matrix.elements, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
@@ -7078,12 +7911,12 @@ var GLTFWriter = class {
7078
7911
  * @param {THREE.Object3D|THREE.Material|THREE.BufferGeometry|THREE.AnimationClip} object
7079
7912
  * @param {Object} objectDef
7080
7913
  */
7081
- serializeUserData(object, objectDef) {
7082
- if (Object.keys(object.userData).length === 0) return;
7914
+ serializeUserData(object3, objectDef) {
7915
+ if (Object.keys(object3.userData).length === 0) return;
7083
7916
  const options = this.options;
7084
7917
  const extensionsUsed = this.extensionsUsed;
7085
7918
  try {
7086
- const json = JSON.parse(JSON.stringify(object.userData));
7919
+ const json = JSON.parse(JSON.stringify(object3.userData));
7087
7920
  if (options.includeCustomExtensions && json.gltfExtensions) {
7088
7921
  if (objectDef.extensions === void 0) objectDef.extensions = {};
7089
7922
  for (const extensionName in json.gltfExtensions) {
@@ -7094,7 +7927,7 @@ var GLTFWriter = class {
7094
7927
  }
7095
7928
  if (Object.keys(json).length > 0) objectDef.extras = json;
7096
7929
  } catch (error) {
7097
- console.warn("THREE.GLTFExporter: userData of '" + object.name + "' won't be serialized because of JSON.stringify error - " + error.message);
7930
+ console.warn("THREE.GLTFExporter: userData of '" + object3.name + "' won't be serialized because of JSON.stringify error - " + error.message);
7098
7931
  }
7099
7932
  }
7100
7933
  /**
@@ -7467,7 +8300,7 @@ var GLTFWriter = class {
7467
8300
  * @param {string} mimeType export format
7468
8301
  * @return {number} Index of the processed texture in the "images" array
7469
8302
  */
7470
- processImage(image, format, flipY, mimeType = "image/png") {
8303
+ processImage(image, format2, flipY, mimeType = "image/png") {
7471
8304
  if (image !== null) {
7472
8305
  const writer = this;
7473
8306
  const cache = writer.cache;
@@ -7491,8 +8324,8 @@ var GLTFWriter = class {
7491
8324
  ctx.scale(1, -1);
7492
8325
  }
7493
8326
  if (image.data !== void 0) {
7494
- if (format !== import_three18.RGBAFormat) {
7495
- console.error("GLTFExporter: Only RGBAFormat is supported.", format);
8327
+ if (format2 !== import_three18.RGBAFormat) {
8328
+ console.error("GLTFExporter: Only RGBAFormat is supported.", format2);
7496
8329
  }
7497
8330
  if (image.width > options.maxTextureSize || image.height > options.maxTextureSize) {
7498
8331
  console.warn("GLTFExporter: Image size is bigger than maxTextureSize", image);
@@ -7603,9 +8436,9 @@ var GLTFWriter = class {
7603
8436
  if (material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true) {
7604
8437
  console.warn("GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.");
7605
8438
  }
7606
- const color = material.color.toArray().concat([material.opacity]);
7607
- if (!equalArray(color, [1, 1, 1, 1])) {
7608
- materialDef.pbrMetallicRoughness.baseColorFactor = color;
8439
+ const color2 = material.color.toArray().concat([material.opacity]);
8440
+ if (!equalArray(color2, [1, 1, 1, 1])) {
8441
+ materialDef.pbrMetallicRoughness.baseColorFactor = color2;
7609
8442
  }
7610
8443
  if (material.isMeshStandardMaterial) {
7611
8444
  materialDef.pbrMetallicRoughness.metallicFactor = material.metalness;
@@ -8020,13 +8853,13 @@ var GLTFWriter = class {
8020
8853
  * @param {THREE.Object3D} object
8021
8854
  * @return {?number}
8022
8855
  */
8023
- processSkin(object) {
8856
+ processSkin(object3) {
8024
8857
  const json = this.json;
8025
8858
  const nodeMap = this.nodeMap;
8026
- const node = json.nodes[nodeMap.get(object)];
8027
- const skeleton = object.skeleton;
8859
+ const node = json.nodes[nodeMap.get(object3)];
8860
+ const skeleton = object3.skeleton;
8028
8861
  if (skeleton === void 0) return null;
8029
- const rootJoint = object.skeleton.bones[0];
8862
+ const rootJoint = object3.skeleton.bones[0];
8030
8863
  if (rootJoint === void 0) return null;
8031
8864
  const joints = [];
8032
8865
  const inverseBindMatrices = new Float32Array(skeleton.bones.length * 16);
@@ -8034,7 +8867,7 @@ var GLTFWriter = class {
8034
8867
  for (let i = 0; i < skeleton.bones.length; ++i) {
8035
8868
  joints.push(nodeMap.get(skeleton.bones[i]));
8036
8869
  temporaryBoneInverse.copy(skeleton.boneInverses[i]);
8037
- temporaryBoneInverse.multiply(object.bindMatrix).toArray(inverseBindMatrices, i * 16);
8870
+ temporaryBoneInverse.multiply(object3.bindMatrix).toArray(inverseBindMatrices, i * 16);
8038
8871
  }
8039
8872
  if (json.skins === void 0) json.skins = [];
8040
8873
  json.skins.push({
@@ -8050,19 +8883,19 @@ var GLTFWriter = class {
8050
8883
  * @param {THREE.Object3D} object Object3D to processNodeAsync
8051
8884
  * @return {Promise<number>} Index of the node in the nodes list
8052
8885
  */
8053
- async processNodeAsync(object) {
8886
+ async processNodeAsync(object3) {
8054
8887
  const json = this.json;
8055
8888
  const options = this.options;
8056
8889
  const nodeMap = this.nodeMap;
8057
8890
  if (!json.nodes) json.nodes = [];
8058
- if (object.pivot !== null) {
8059
- return await this._processNodeWithPivotAsync(object);
8891
+ if (object3.pivot !== null) {
8892
+ return await this._processNodeWithPivotAsync(object3);
8060
8893
  }
8061
8894
  const nodeDef = {};
8062
8895
  if (options.trs) {
8063
- const rotation = object.quaternion.toArray();
8064
- const position = object.position.toArray();
8065
- const scale = object.scale.toArray();
8896
+ const rotation = object3.quaternion.toArray();
8897
+ const position = object3.position.toArray();
8898
+ const scale = object3.scale.toArray();
8066
8899
  if (!equalArray(rotation, [0, 0, 0, 1])) {
8067
8900
  nodeDef.rotation = rotation;
8068
8901
  }
@@ -8073,28 +8906,28 @@ var GLTFWriter = class {
8073
8906
  nodeDef.scale = scale;
8074
8907
  }
8075
8908
  } else {
8076
- if (object.matrixAutoUpdate) {
8077
- object.updateMatrix();
8909
+ if (object3.matrixAutoUpdate) {
8910
+ object3.updateMatrix();
8078
8911
  }
8079
- if (isIdentityMatrix(object.matrix) === false) {
8080
- nodeDef.matrix = object.matrix.elements;
8912
+ if (isIdentityMatrix(object3.matrix) === false) {
8913
+ nodeDef.matrix = object3.matrix.elements;
8081
8914
  }
8082
8915
  }
8083
- if (object.name !== "") nodeDef.name = String(object.name);
8084
- this.serializeUserData(object, nodeDef);
8085
- if (object.isMesh || object.isLine || object.isPoints) {
8086
- const meshIndex = await this.processMeshAsync(object);
8916
+ if (object3.name !== "") nodeDef.name = String(object3.name);
8917
+ this.serializeUserData(object3, nodeDef);
8918
+ if (object3.isMesh || object3.isLine || object3.isPoints) {
8919
+ const meshIndex = await this.processMeshAsync(object3);
8087
8920
  if (meshIndex !== null) nodeDef.mesh = meshIndex;
8088
- } else if (object.isCamera) {
8089
- nodeDef.camera = this.processCamera(object);
8921
+ } else if (object3.isCamera) {
8922
+ nodeDef.camera = this.processCamera(object3);
8090
8923
  }
8091
- if (object.isSkinnedMesh) this.skins.push(object);
8924
+ if (object3.isSkinnedMesh) this.skins.push(object3);
8092
8925
  const nodeIndex = json.nodes.push(nodeDef) - 1;
8093
- nodeMap.set(object, nodeIndex);
8094
- if (object.children.length > 0) {
8926
+ nodeMap.set(object3, nodeIndex);
8927
+ if (object3.children.length > 0) {
8095
8928
  const children = [];
8096
- for (let i = 0, l = object.children.length; i < l; i++) {
8097
- const child = object.children[i];
8929
+ for (let i = 0, l = object3.children.length; i < l; i++) {
8930
+ const child = object3.children[i];
8098
8931
  if (child.visible || options.onlyVisible === false) {
8099
8932
  const childNodeIndex = await this.processNodeAsync(child);
8100
8933
  if (childNodeIndex !== null) children.push(childNodeIndex);
@@ -8103,7 +8936,7 @@ var GLTFWriter = class {
8103
8936
  if (children.length > 0) nodeDef.children = children;
8104
8937
  }
8105
8938
  await this._invokeAllAsync(function(ext) {
8106
- ext.writeNode && ext.writeNode(object, nodeDef);
8939
+ ext.writeNode && ext.writeNode(object3, nodeDef);
8107
8940
  });
8108
8941
  return nodeIndex;
8109
8942
  }
@@ -8112,19 +8945,19 @@ var GLTFWriter = class {
8112
8945
  * @param {THREE.Object3D} object Object3D with pivot
8113
8946
  * @return {Promise<number>} Index of the container node
8114
8947
  */
8115
- async _processNodeWithPivotAsync(object) {
8948
+ async _processNodeWithPivotAsync(object3) {
8116
8949
  const json = this.json;
8117
8950
  const options = this.options;
8118
8951
  const nodeMap = this.nodeMap;
8119
- const pivot = object.pivot;
8952
+ const pivot = object3.pivot;
8120
8953
  const containerDef = {};
8121
- const rotation = object.quaternion.toArray();
8954
+ const rotation = object3.quaternion.toArray();
8122
8955
  const position = [
8123
- object.position.x + pivot.x,
8124
- object.position.y + pivot.y,
8125
- object.position.z + pivot.z
8956
+ object3.position.x + pivot.x,
8957
+ object3.position.y + pivot.y,
8958
+ object3.position.z + pivot.z
8126
8959
  ];
8127
- const scale = object.scale.toArray();
8960
+ const scale = object3.scale.toArray();
8128
8961
  if (!equalArray(rotation, [0, 0, 0, 1])) {
8129
8962
  containerDef.rotation = rotation;
8130
8963
  }
@@ -8135,28 +8968,28 @@ var GLTFWriter = class {
8135
8968
  containerDef.scale = scale;
8136
8969
  }
8137
8970
  containerDef.extras = { pivot: pivot.toArray() };
8138
- if (object.name !== "") containerDef.name = String(object.name);
8139
- this.serializeUserData(object, containerDef);
8971
+ if (object3.name !== "") containerDef.name = String(object3.name);
8972
+ this.serializeUserData(object3, containerDef);
8140
8973
  const containerIndex = json.nodes.push(containerDef) - 1;
8141
- nodeMap.set(object, containerIndex);
8974
+ nodeMap.set(object3, containerIndex);
8142
8975
  const childDef = {};
8143
8976
  const childPosition = [-pivot.x, -pivot.y, -pivot.z];
8144
8977
  if (!equalArray(childPosition, [0, 0, 0])) {
8145
8978
  childDef.translation = childPosition;
8146
8979
  }
8147
- if (object.isMesh || object.isLine || object.isPoints) {
8148
- const meshIndex = await this.processMeshAsync(object);
8980
+ if (object3.isMesh || object3.isLine || object3.isPoints) {
8981
+ const meshIndex = await this.processMeshAsync(object3);
8149
8982
  if (meshIndex !== null) childDef.mesh = meshIndex;
8150
- } else if (object.isCamera) {
8151
- childDef.camera = this.processCamera(object);
8983
+ } else if (object3.isCamera) {
8984
+ childDef.camera = this.processCamera(object3);
8152
8985
  }
8153
- if (object.isSkinnedMesh) this.skins.push(object);
8986
+ if (object3.isSkinnedMesh) this.skins.push(object3);
8154
8987
  const childIndex = json.nodes.push(childDef) - 1;
8155
8988
  const containerChildren = [childIndex];
8156
- if (object.children.length > 0) {
8989
+ if (object3.children.length > 0) {
8157
8990
  const grandchildren = [];
8158
- for (let i = 0, l = object.children.length; i < l; i++) {
8159
- const child = object.children[i];
8991
+ for (let i = 0, l = object3.children.length; i < l; i++) {
8992
+ const child = object3.children[i];
8160
8993
  if (child.visible || options.onlyVisible === false) {
8161
8994
  const childNodeIndex = await this.processNodeAsync(child);
8162
8995
  if (childNodeIndex !== null) grandchildren.push(childNodeIndex);
@@ -8166,7 +8999,7 @@ var GLTFWriter = class {
8166
8999
  }
8167
9000
  containerDef.children = containerChildren;
8168
9001
  await this._invokeAllAsync(function(ext) {
8169
- ext.writeNode && ext.writeNode(object, containerDef);
9002
+ ext.writeNode && ext.writeNode(object3, containerDef);
8170
9003
  });
8171
9004
  return containerIndex;
8172
9005
  }
@@ -8609,10 +9442,10 @@ var GLTFMeshGpuInstancing = class {
8609
9442
  this.writer = writer;
8610
9443
  this.name = "EXT_mesh_gpu_instancing";
8611
9444
  }
8612
- writeNode(object, nodeDef) {
8613
- if (!object.isInstancedMesh) return;
9445
+ writeNode(object3, nodeDef) {
9446
+ if (!object3.isInstancedMesh) return;
8614
9447
  const writer = this.writer;
8615
- const mesh = object;
9448
+ const mesh = object3;
8616
9449
  const translationAttr = new Float32Array(mesh.count * 3);
8617
9450
  const rotationAttr = new Float32Array(mesh.count * 4);
8618
9451
  const scaleAttr = new Float32Array(mesh.count * 3);
@@ -8802,8 +9635,8 @@ var SceneExporter = class {
8802
9635
  }
8803
9636
  }
8804
9637
  };
8805
- function localMatrix(object) {
8806
- return object.matrixAutoUpdate ? new import_three19.Matrix4().compose(object.position, object.quaternion, object.scale) : object.matrix.clone();
9638
+ function localMatrix(object3) {
9639
+ return object3.matrixAutoUpdate ? new import_three19.Matrix4().compose(object3.position, object3.quaternion, object3.scale) : object3.matrix.clone();
8807
9640
  }
8808
9641
  function createSnapshot(root, gltf, bakeColors = false) {
8809
9642
  const scene = new import_three19.Scene();
@@ -8907,24 +9740,24 @@ function createSnapshot(root, gltf, bakeColors = false) {
8907
9740
  scene.add(copy);
8908
9741
  }
8909
9742
  };
8910
- const visit = (object, parentWorld) => {
8911
- if (!object.visible) return;
8912
- const world = parentWorld.clone().multiply(localMatrix(object));
8913
- const mesh = object;
9743
+ const visit = (object3, parentWorld) => {
9744
+ if (!object3.visible) return;
9745
+ const world = parentWorld.clone().multiply(localMatrix(object3));
9746
+ const mesh = object3;
8914
9747
  if (mesh.isMesh) {
8915
9748
  const instanced = mesh;
8916
9749
  if (instanced.isInstancedMesh) {
8917
9750
  for (let i = 0; i < instanced.count; i++) {
8918
9751
  const matrix = new import_three19.Matrix4();
8919
9752
  instanced.getMatrixAt(i, matrix);
8920
- const color = instanced.instanceColor ? new import_three19.Color().fromBufferAttribute(instanced.instanceColor, i) : void 0;
8921
- addMesh(mesh, world.clone().multiply(matrix), color);
9753
+ const color2 = instanced.instanceColor ? new import_three19.Color().fromBufferAttribute(instanced.instanceColor, i) : void 0;
9754
+ addMesh(mesh, world.clone().multiply(matrix), color2);
8922
9755
  }
8923
9756
  } else {
8924
9757
  addMesh(mesh, world);
8925
9758
  }
8926
9759
  }
8927
- for (const child of object.children) visit(child, world);
9760
+ for (const child of object3.children) visit(child, world);
8928
9761
  };
8929
9762
  try {
8930
9763
  const ancestors = [];
@@ -8997,16 +9830,17 @@ function copyDataTexture(source) {
8997
9830
 
8998
9831
  // src/gui/GuiHelper.ts
8999
9832
  init_AssetKey();
9833
+ init_ModelTextures();
9000
9834
 
9001
9835
  // src/gui/GuiText.ts
9002
9836
  var import_three20 = require("three");
9003
- async function layoutGuiText(text, options = {}) {
9837
+ async function layoutGuiText(text2, options = {}) {
9004
9838
  const maxWidth = options.maxWidth ?? Infinity, lineHeight = options.lineHeight ?? 9;
9005
9839
  if (!(maxWidth > 0) || !Number.isFinite(lineHeight) || lineHeight <= 0) {
9006
9840
  throw new Error("Text wrap width and line height must be positive");
9007
9841
  }
9008
9842
  const font = await Fonts.get(options.font);
9009
- const runs = typeof text === "string" ? [{ text }] : text;
9843
+ const runs = typeof text2 === "string" ? [{ text: text2 }] : text2;
9010
9844
  const glyphs = [], lineWidths = [];
9011
9845
  let line = [], width = 0;
9012
9846
  let trailingNewline = false;
@@ -9060,7 +9894,7 @@ function createGuiTextGeometry(layout, shadow = true) {
9060
9894
  batch = { image: glyph?.image, positions: [], uvs: [], colors: [], indices: [] };
9061
9895
  batches.push(batch);
9062
9896
  }
9063
- const color = new import_three20.Color(isShadow ? (style.color & 16579836) >> 2 : style.color);
9897
+ const color2 = new import_three20.Color(isShadow ? (style.color & 16579836) >> 2 : style.color);
9064
9898
  const offset = isShadow ? 1 : 0;
9065
9899
  const top = glyph ? 7 - glyph.ascent : 0;
9066
9900
  const height = glyph ? glyph.height * glyph.scale : 8;
@@ -9088,7 +9922,7 @@ function createGuiTextGeometry(layout, shadow = true) {
9088
9922
  const v0 = glyph ? 1 - glyph.y / glyph.image.height : 1;
9089
9923
  const v1 = glyph ? 1 - (glyph.y + glyph.height) / glyph.image.height : 0;
9090
9924
  batch.uvs.push(u0, v0, u1, v0, u0, v1, u1, v1);
9091
- for (let i = 0; i < 4; i++) batch.colors.push(color.r, color.g, color.b);
9925
+ for (let i = 0; i < 4; i++) batch.colors.push(color2.r, color2.g, color2.b);
9092
9926
  batch.indices.push(base, base + 2, base + 1, base + 2, base + 3, base + 1);
9093
9927
  };
9094
9928
  for (const boldOffset of style.bold ? [0, 1] : [0]) {
@@ -9106,7 +9940,128 @@ function createGuiTextGeometry(layout, shadow = true) {
9106
9940
  }
9107
9941
 
9108
9942
  // src/gui/GuiHelper.ts
9943
+ var GUI_CONTAINER_LAYOUTS = {
9944
+ generic_54: {
9945
+ texture: "minecraft:gui/container/generic_54",
9946
+ crop: [0, 0, 176, 222],
9947
+ slotOrigin: [8, 18],
9948
+ slotOffset: [18, 18],
9949
+ rowSize: 9,
9950
+ slotCount: 54
9951
+ },
9952
+ crafting_table: {
9953
+ texture: "minecraft:gui/container/crafting_table",
9954
+ crop: [0, 0, 176, 166],
9955
+ slotOrigin: [30, 17],
9956
+ slotOffset: [18, 18],
9957
+ rowSize: 3,
9958
+ slotCount: 9,
9959
+ resultPosition: [124, 35]
9960
+ }
9961
+ };
9109
9962
  var GuiHelper = class {
9963
+ /**
9964
+ * Creates a 182×5 boss bar using the active resource pack. Finite progress is clamped to 0–1.
9965
+ * The fill crops the source image; explicit crops do not apply `.mcmeta` sprite scaling.
9966
+ */
9967
+ static async bossBar(options = {}) {
9968
+ const { color: color2 = "pink", progress = 1, position = [0, 0] } = options;
9969
+ this.validatePosition(position);
9970
+ if (!["pink", "blue", "red", "green", "yellow", "purple", "white"].includes(color2)) {
9971
+ throw new Error(`Unsupported boss bar color: ${color2}`);
9972
+ }
9973
+ if (!Number.isFinite(progress)) throw new Error("Boss bar progress must be finite");
9974
+ const value = Math.max(0, Math.min(1, progress));
9975
+ const width = value > 0 ? Math.floor(value * 181) + 1 : 0;
9976
+ const texture = `minecraft:gui/sprites/boss_bar/${color2}`;
9977
+ const layers = [{
9978
+ name: "boss-bar-background",
9979
+ texture: `${texture}_background`,
9980
+ position: [...position],
9981
+ size: [182, 5]
9982
+ }];
9983
+ if (width > 0) {
9984
+ layers.push({
9985
+ ...await this.cropTexture(`${texture}_progress`, [0, 0, width, 5], [182, 5], position),
9986
+ name: "boss-bar-progress"
9987
+ });
9988
+ }
9989
+ return layers;
9990
+ }
9991
+ /**
9992
+ * Creates a 192×192 book page with optional arrows and supplied text. Buttons are hidden by default.
9993
+ * Callers choose the page content and button states; this helper does not paginate or handle clicks.
9994
+ */
9995
+ static async book(options = {}) {
9996
+ const {
9997
+ position = [0, 0],
9998
+ previous = "hidden",
9999
+ next = "hidden",
10000
+ text: text2,
10001
+ color: color2 = 0,
10002
+ shadow = false,
10003
+ maxWidth = 114,
10004
+ lineHeight = 9,
10005
+ ...style
10006
+ } = options;
10007
+ this.validatePosition(position);
10008
+ for (const state of [previous, next]) {
10009
+ if (!["hidden", "normal", "hover"].includes(state)) throw new Error(`Unsupported book button state: ${state}`);
10010
+ }
10011
+ const layers = [{
10012
+ ...await this.cropTexture("minecraft:gui/book", [0, 0, 192, 192], [256, 256], position),
10013
+ name: "book-background"
10014
+ }];
10015
+ for (const [name, direction, state, x] of [
10016
+ ["previous", "backward", previous, 43],
10017
+ ["next", "forward", next, 116]
10018
+ ]) {
10019
+ if (state === "hidden") continue;
10020
+ layers.push({
10021
+ name: `book-${name}`,
10022
+ texture: `minecraft:gui/sprites/widget/page_${direction}${state === "hover" ? "_highlighted" : ""}`,
10023
+ position: [position[0] + x, position[1] + 157],
10024
+ size: [23, 13]
10025
+ });
10026
+ }
10027
+ if (text2 !== void 0) {
10028
+ layers.push({
10029
+ name: "book-text",
10030
+ text: text2,
10031
+ position: [position[0] + 36, position[1] + 30],
10032
+ color: color2,
10033
+ shadow,
10034
+ maxWidth,
10035
+ lineHeight,
10036
+ ...style
10037
+ });
10038
+ }
10039
+ return layers;
10040
+ }
10041
+ /** Creates a container background. Use {@link GUI_CONTAINER_LAYOUTS} with {@link inventorySlot} to place its items. */
10042
+ static async container(type, position = [0, 0]) {
10043
+ this.validatePosition(position);
10044
+ if (type !== "generic_54" && type !== "crafting_table") throw new Error(`Unsupported GUI container: ${type}`);
10045
+ const layout = GUI_CONTAINER_LAYOUTS[type];
10046
+ return [{ ...await this.cropTexture(layout.texture, layout.crop, [256, 256], position), name: "background" }];
10047
+ }
10048
+ static validatePosition(position) {
10049
+ if (!Array.isArray(position) || position.length !== 2 || !position.every(Number.isFinite)) {
10050
+ throw new Error("GUI position must contain two finite coordinates");
10051
+ }
10052
+ }
10053
+ static async cropTexture(texture, crop, referenceSize, position) {
10054
+ const image = await ModelTextures.preload(AssetKey.parse("textures", texture));
10055
+ if (!image) throw new Error(`Could not load GUI texture ${texture}`);
10056
+ const [x, y, width, height] = crop;
10057
+ const scaleX = image.width / referenceSize[0], scaleY = image.height / referenceSize[1];
10058
+ return {
10059
+ texture,
10060
+ crop: [x * scaleX, y * scaleY, width * scaleX, height * scaleY],
10061
+ position: [...position],
10062
+ size: [width, height]
10063
+ };
10064
+ }
9110
10065
  /** Sizes a tooltip around supplied text; the first entry is its title. */
9111
10066
  static async tooltip(lines, options = {}) {
9112
10067
  if (!lines.length) return [];
@@ -9122,20 +10077,28 @@ var GuiHelper = class {
9122
10077
  size: [width + 24, height + 24]
9123
10078
  }));
9124
10079
  let y = position[1];
9125
- lines.forEach((text, index) => {
9126
- layers.push({ ...textOptions, name: `tooltip-line-${index}`, text, position: [position[0], y] });
10080
+ lines.forEach((text2, index) => {
10081
+ layers.push({ ...textOptions, name: `tooltip-line-${index}`, text: text2, position: [position[0], y] });
9127
10082
  y += layouts[index].height + (index === 0 ? titleGap : 0);
9128
10083
  });
9129
10084
  return layers;
9130
10085
  }
10086
+ /**
10087
+ * Calculates a slot's top-left position in GUI pixels.
10088
+ * @param slot - Zero-based slot index, or `[column, row]`.
10089
+ * @param origin - Position of the first slot.
10090
+ * @param offset - Horizontal and vertical spacing between slot origins, in pixels.
10091
+ * @param rowSize - Slots per row when `slot` is an index.
10092
+ */
9131
10093
  static inventorySlot(slot, origin = [0, 0], offset = [18, 18], rowSize = 9) {
9132
10094
  const [column, row] = typeof slot === "number" ? [slot % rowSize, Math.floor(slot / rowSize)] : slot;
9133
10095
  return [origin[0] + column * offset[0], origin[1] + row * offset[1]];
9134
10096
  }
9135
10097
  /** Creates crafting-table layers for ingredients and the result; stack counts are not drawn. */
9136
10098
  static recipe(recipe, options = {}) {
10099
+ const layout = GUI_CONTAINER_LAYOUTS.crafting_table;
9137
10100
  const layers = [
9138
- { name: "background", texture: "minecraft:gui/container/crafting_table", crop: [0, 0, 176, 166] }
10101
+ { name: "background", texture: layout.texture, crop: [...layout.crop] }
9139
10102
  ];
9140
10103
  const itemKey = (ingredient) => {
9141
10104
  const item = typeof ingredient === "string" && !ingredient.startsWith("#") ? ingredient : typeof ingredient === "object" && "item" in ingredient ? ingredient.item : options.resolveIngredient?.(ingredient);
@@ -9150,17 +10113,17 @@ var GuiHelper = class {
9150
10113
  layers.push({
9151
10114
  name: `ingredient-${slot}`,
9152
10115
  item: itemKey(ingredient),
9153
- position: this.inventorySlot(slot, [30, 17], [18, 18], 3)
10116
+ position: this.inventorySlot(slot, layout.slotOrigin, layout.slotOffset, layout.rowSize)
9154
10117
  });
9155
10118
  };
9156
10119
  switch (recipe.type) {
9157
10120
  case "minecraft:crafting_shaped":
9158
10121
  case "crafting_shaped": {
9159
- const pattern = recipe.pattern;
9160
- if (!pattern.length || pattern.length > 3 || !pattern[0].length || pattern[0].length > 3 || pattern.some((row) => row.length !== pattern[0].length)) {
10122
+ const pattern2 = recipe.pattern;
10123
+ if (!pattern2.length || pattern2.length > 3 || !pattern2[0].length || pattern2[0].length > 3 || pattern2.some((row) => row.length !== pattern2[0].length)) {
9161
10124
  throw new Error("Crafting patterns must be rectangular, with 1\u20133 rows and columns");
9162
10125
  }
9163
- const cells = pattern.flatMap((row, y) => row.split("").map((symbol, x) => ({ symbol, x, y }))).filter((cell) => cell.symbol !== " ");
10126
+ const cells = pattern2.flatMap((row, y) => row.split("").map((symbol, x) => ({ symbol, x, y }))).filter((cell) => cell.symbol !== " ");
9164
10127
  if (!cells.length) throw new Error("Crafting patterns must contain an ingredient");
9165
10128
  const minX = Math.min(...cells.map((cell) => cell.x)), maxX = Math.max(...cells.map((cell) => cell.x));
9166
10129
  const minY = Math.min(...cells.map((cell) => cell.y)), maxY = Math.max(...cells.map((cell) => cell.y));
@@ -9185,7 +10148,7 @@ var GuiHelper = class {
9185
10148
  layers.push({
9186
10149
  name: "result",
9187
10150
  item: itemKey("id" in recipe.result ? recipe.result.id : recipe.result.item),
9188
- position: [124, 35]
10151
+ position: [...layout.resultPosition]
9189
10152
  });
9190
10153
  return layers;
9191
10154
  }
@@ -9280,7 +10243,6 @@ init_SceneObject();
9280
10243
  init_Materials();
9281
10244
  init_ModelObject();
9282
10245
  init_DisplayPosition();
9283
- init_GuiLight();
9284
10246
  var GuiObject = class extends SceneObject {
9285
10247
  constructor(textureLayers, options) {
9286
10248
  super({ ...options, instanceMeshes: false, mergeMeshes: false });
@@ -9348,12 +10310,16 @@ var GuiObject = class extends SceneObject {
9348
10310
  mesh2.geometry.computeBoundingBox();
9349
10311
  bounds.union(mesh2.geometry.boundingBox.clone().applyMatrix4(new import_three26.Matrix4().multiplyMatrices(inverse, mesh2.matrixWorld)));
9350
10312
  });
9351
- item.position.z = depth - bounds.min.z;
9352
- depth += bounds.max.z - bounds.min.z + 0.01;
10313
+ item.position.z = depth;
10314
+ if (!bounds.isEmpty()) {
10315
+ item.position.z -= bounds.min.z;
10316
+ depth += bounds.max.z - bounds.min.z;
10317
+ this.bounds.expandByPoint(new import_three26.Vector2(bounds.min.x, -bounds.max.y));
10318
+ this.bounds.expandByPoint(new import_three26.Vector2(bounds.max.x, -bounds.min.y));
10319
+ }
10320
+ depth += 0.01;
9353
10321
  this.bounds.expandByPoint(new import_three26.Vector2(x, y));
9354
10322
  this.bounds.expandByPoint(new import_three26.Vector2(x + width2, y + height2));
9355
- this.bounds.expandByPoint(new import_three26.Vector2(bounds.min.x, -bounds.max.y));
9356
- this.bounds.expandByPoint(new import_three26.Vector2(bounds.max.x, -bounds.min.y));
9357
10323
  continue;
9358
10324
  }
9359
10325
  const key = typeof layer.texture === "string" ? AssetKey.parse("textures", layer.texture) : layer.texture;
@@ -9382,7 +10348,7 @@ var GuiObject = class extends SceneObject {
9382
10348
  }
9383
10349
  async createItem(layer, index) {
9384
10350
  const key = typeof layer.item === "string" ? AssetKey.parse("models", layer.item) : layer.item;
9385
- const model = await Models.getMerged(key);
10351
+ const model = await Models.getMerged(key, { ...layer.context, displayContext: "gui" /* GUI */ });
9386
10352
  if (!model) throw new Error(`Could not load GUI item ${key.toNamespacedString()}`);
9387
10353
  const item = new ModelObject(model, {
9388
10354
  displayPosition: "gui" /* GUI */,
@@ -9393,15 +10359,13 @@ var GuiObject = class extends SceneObject {
9393
10359
  item.name = `group:${layer.name ?? index}`;
9394
10360
  this.add(item);
9395
10361
  await item.init();
9396
- item.iterateAllMeshes((mesh) => {
9397
- if (!model.special) this.geometries.add(mesh.geometry);
10362
+ const meshes = [];
10363
+ item.iterateAllMeshes((mesh) => meshes.push(mesh));
10364
+ meshes.forEach((mesh, partIndex) => {
9398
10365
  mesh.name = `mesh:${layer.name ?? index}`;
9399
- mesh.renderOrder = index;
10366
+ mesh.renderOrder = index + partIndex / (meshes.length + 1) * 0.5;
9400
10367
  for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
9401
10368
  material.transparent = true;
9402
- if (model.gui_light === "front" /* FRONT */ && material.uniforms?.SHADE) {
9403
- material.uniforms.SHADE.value = false;
9404
- }
9405
10369
  }
9406
10370
  });
9407
10371
  return item;
@@ -9572,29 +10536,29 @@ var SceneInspector = class {
9572
10536
  this.objectControlsContainer.innerHTML = "";
9573
10537
  this.addControls(targetObject, targetIntersection, instance);
9574
10538
  }
9575
- addControls(object, intersection, instance) {
10539
+ addControls(object3, intersection, instance) {
9576
10540
  const container = document.createElement("div");
9577
10541
  container.append(this.separator("Select Parent/Child"));
9578
- if (object.parent) {
9579
- container.append(this.buttonControl("Select Parent " + object.parent.constructor.name + " " + object.parent.name, "P", () => {
9580
- this.selectObject(object.parent, intersection, instance);
10542
+ if (object3.parent) {
10543
+ container.append(this.buttonControl("Select Parent " + object3.parent.constructor.name + " " + object3.parent.name, "P", () => {
10544
+ this.selectObject(object3.parent, intersection, instance);
9581
10545
  }));
9582
10546
  }
9583
- if (object.children.length > 0) {
10547
+ if (object3.children.length > 0) {
9584
10548
  let i = 1;
9585
- for (let child of object.children) {
10549
+ for (let child of object3.children) {
9586
10550
  container.append(this.buttonControl("Select Child " + child.constructor.name + " " + child.name, "C" + i++, () => {
9587
10551
  this.selectObject(child, intersection, instance);
9588
10552
  }));
9589
10553
  }
9590
10554
  }
9591
10555
  container.append(this.separator());
9592
- const selectedInstance = instance && (object === instance.instanceable || object === intersection?.object) ? instance : void 0;
10556
+ const selectedInstance = instance && (object3 === instance.instanceable || object3 === intersection?.object) ? instance : void 0;
9593
10557
  if (!selectedInstance) {
9594
- container.append(this.toggleControl("Visibility", "V", object.visible, (v) => object.visible = v));
10558
+ container.append(this.toggleControl("Visibility", "V", object3.visible, (v) => object3.visible = v));
9595
10559
  }
9596
10560
  container.append(this.separator("Mesh"));
9597
- this.addObjectControls(object, intersection, container, selectedInstance);
10561
+ this.addObjectControls(object3, intersection, container, selectedInstance);
9598
10562
  container.append(this.separator());
9599
10563
  this.objectControlsContainer.append(container);
9600
10564
  }
@@ -9785,6 +10749,7 @@ var SceneStatsDisplay = class {
9785
10749
  appendTo(el) {
9786
10750
  el.append(this.statsContainer);
9787
10751
  }
10752
+ /** Stops the refresh timer and removes the panel from the page. */
9788
10753
  dispose() {
9789
10754
  clearInterval(this.interval);
9790
10755
  this.interval = void 0;
@@ -9809,6 +10774,7 @@ var InstanceManager = class {
9809
10774
  this.isInstanceManager = true;
9810
10775
  this.instanceCache = {};
9811
10776
  }
10777
+ /** Allocates a new placement of a cached model, or returns `undefined` if the key is unknown. */
9812
10778
  async get(key) {
9813
10779
  if (key in this.instanceCache) {
9814
10780
  console.debug(p9, "key in cache", key);
@@ -9816,6 +10782,7 @@ var InstanceManager = class {
9816
10782
  }
9817
10783
  return void 0;
9818
10784
  }
10785
+ /** Allocates a placement, calling the supplier once when the shared model is first needed. */
9819
10786
  async getOrCreate(key, supplier) {
9820
10787
  if (key in this.instanceCache) {
9821
10788
  return await this.get(key);
@@ -9832,6 +10799,7 @@ var InstanceManager = class {
9832
10799
  throw error;
9833
10800
  }
9834
10801
  }
10802
+ /** Forgets cached models without disposing them or releasing their existing placements. */
9835
10803
  reset() {
9836
10804
  for (let instanceCacheKey in this.instanceCache) {
9837
10805
  delete this.instanceCache[instanceCacheKey];
@@ -9877,6 +10845,7 @@ function matchesCondition(condition, state) {
9877
10845
  });
9878
10846
  }
9879
10847
  var BlockStateResolver = class {
10848
+ /** Loads vanilla property defaults, falling back to preview values inferred from the blockstate file. */
9880
10849
  static async defaults(blockState) {
9881
10850
  const defaults = blockState.key ? await BlockStates.getDefaultState(blockState.key) : void 0;
9882
10851
  const state = {};
@@ -9896,9 +10865,11 @@ var BlockStateResolver = class {
9896
10865
  }
9897
10866
  return state;
9898
10867
  }
10868
+ /** Selects matching models. Supply `choose` to control selection within each alternatives array. */
9899
10869
  static select(blockState, state, choose = this.choose) {
9900
10870
  return this.matching(blockState, state).map(choose);
9901
10871
  }
10872
+ /** Returns matching model groups before choosing weighted alternatives. */
9902
10873
  static matching(blockState, state) {
9903
10874
  const out = [];
9904
10875
  if (blockState.variants) {
@@ -9922,6 +10893,7 @@ var BlockStateResolver = class {
9922
10893
  }
9923
10894
  return out;
9924
10895
  }
10896
+ /** Selects a random alternative using positive integer weights, each defaulting to 1. */
9925
10897
  static choose(variants) {
9926
10898
  if (!Array.isArray(variants)) return variants;
9927
10899
  if (!variants.length) throw new MineRenderError("Blockstate variant arrays must not be empty");
@@ -9939,6 +10911,7 @@ var BlockStateResolver = class {
9939
10911
  }
9940
10912
  return variants[variants.length - 1];
9941
10913
  }
10914
+ /** Converts blockstate rotations in degrees into the Euler rotation used by model rendering. */
9942
10915
  static rotation(variant) {
9943
10916
  const rotation = new import_three29.Euler();
9944
10917
  if (typeof variant.x !== "undefined") {
@@ -10001,29 +10974,29 @@ var BlockTints = class {
10001
10974
  }
10002
10975
  }
10003
10976
  if (!indices.size) return tints;
10004
- let color;
10977
+ let color2;
10005
10978
  switch (rule.source) {
10006
10979
  case "constant":
10007
- color = rule.color;
10980
+ color2 = rule.color;
10008
10981
  break;
10009
10982
  case "grass":
10010
- color = await Colormaps.grassColor(key);
10983
+ color2 = await Colormaps.grassColor(key);
10011
10984
  break;
10012
10985
  case "redstone": {
10013
10986
  const power = Number(state.power ?? 0) / 15;
10014
10987
  const red = power * 0.6 + (power > 0 ? 0.4 : 0.3);
10015
10988
  const green = Math.max(0, power * power * 0.7 - 0.5);
10016
10989
  const blue = Math.max(0, power * power * 0.6 - 0.7);
10017
- color = Math.floor(red * 255) << 16 | Math.floor(green * 255) << 8 | Math.floor(blue * 255);
10990
+ color2 = Math.floor(red * 255) << 16 | Math.floor(green * 255) << 8 | Math.floor(blue * 255);
10018
10991
  break;
10019
10992
  }
10020
10993
  case "stem": {
10021
10994
  const age = Number(state.age ?? 0);
10022
- color = age * 32 << 16 | 255 - age * 8 << 8 | age * 4;
10995
+ color2 = age * 32 << 16 | 255 - age * 8 << 8 | age * 4;
10023
10996
  break;
10024
10997
  }
10025
10998
  }
10026
- return { ...Object.fromEntries([...indices].map((index) => [index, color])), ...tints };
10999
+ return { ...Object.fromEntries([...indices].map((index) => [index, color2])), ...tints };
10027
11000
  }
10028
11001
  };
10029
11002
 
@@ -10160,8 +11133,8 @@ var _BlockObject = class _BlockObject extends SceneObject {
10160
11133
  return false;
10161
11134
  }
10162
11135
  return this._models.some((model) => {
10163
- const object = isInstanceReference(model) ? model.instanceable : model;
10164
- return object.isOpaqueFullCube && this.getModelCullMask(model, 63) === 63;
11136
+ const object3 = isInstanceReference(model) ? model.instanceable : model;
11137
+ return object3.isOpaqueFullCube && this.getModelCullMask(model, 63) === 63;
10165
11138
  });
10166
11139
  }
10167
11140
  getModelMatrix(model) {
@@ -10170,8 +11143,8 @@ var _BlockObject = class _BlockObject extends SceneObject {
10170
11143
  return model.matrix.clone();
10171
11144
  }
10172
11145
  getModelCullMask(model, worldMask) {
10173
- const object = isInstanceReference(model) ? model.instanceable : model;
10174
- if (object.options.displayPosition) return 0;
11146
+ const object3 = isInstanceReference(model) ? model.instanceable : model;
11147
+ if (object3.options.displayPosition) return 0;
10175
11148
  const position = new import_three32.Vector3();
10176
11149
  const rotation = new import_three32.Quaternion();
10177
11150
  const scale = new import_three32.Vector3();
@@ -10188,14 +11161,14 @@ var _BlockObject = class _BlockObject extends SceneObject {
10188
11161
  replacements.push(model);
10189
11162
  continue;
10190
11163
  }
10191
- const object = isInstanceReference(model) ? model.instanceable : model;
11164
+ const object3 = isInstanceReference(model) ? model.instanceable : model;
10192
11165
  const cullMask = this.getModelCullMask(model, worldMask);
10193
- if (cullMask === (object.options.cullMask ?? 0)) {
11166
+ if (cullMask === (object3.options.cullMask ?? 0)) {
10194
11167
  replacements.push(model);
10195
11168
  continue;
10196
11169
  }
10197
11170
  const matrix = this.getModelMatrix(model);
10198
- const replacement = await this.scene.addModel(object.originalModel, { ...object.options, cullMask });
11171
+ const replacement = await this.scene.addModel(object3.originalModel, { ...object3.options, cullMask });
10199
11172
  replacements.push(replacement);
10200
11173
  if (isInstanceReference(replacement)) {
10201
11174
  replacement.setMatrix(matrix);
@@ -10203,8 +11176,8 @@ var _BlockObject = class _BlockObject extends SceneObject {
10203
11176
  replacement.matrix.copy(matrix);
10204
11177
  replacement.matrixWorldNeedsUpdate = true;
10205
11178
  matrix.decompose(replacement.position, replacement.quaternion, replacement.scale);
10206
- replacement.matrixAutoUpdate = object.matrixAutoUpdate;
10207
- replacement.visible = object.visible;
11179
+ replacement.matrixAutoUpdate = object3.matrixAutoUpdate;
11180
+ replacement.visible = object3.visible;
10208
11181
  }
10209
11182
  }
10210
11183
  } catch (error) {
@@ -10220,6 +11193,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
10220
11193
  }
10221
11194
  this.notifyDirty();
10222
11195
  }
11196
+ /** Selected property values. Use {@link setState} to change properties and rebuild the rendered models. */
10223
11197
  get state() {
10224
11198
  return this._state;
10225
11199
  }
@@ -10319,6 +11293,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
10319
11293
  obj.setPosition(this.position);
10320
11294
  return obj;
10321
11295
  }
11296
+ /** Clears selected properties and rebuilds models without reapplying property defaults. */
10322
11297
  async resetState() {
10323
11298
  this._previousState = this._state;
10324
11299
  this._state = {};
@@ -12126,9 +13101,9 @@ var SchematicParser = class {
12126
13101
  * Parses a legacy schematic using custom mappings before the bundled defaults.
12127
13102
  * Keys are `id:metadata`; values are block states such as `minecraft:oak_log[axis=x]`.
12128
13103
  */
12129
- static async parse(nbt, customMappings = {}) {
13104
+ static async parse(nbt, customMappings = {}, options = {}) {
12130
13105
  const tags = nbt.value;
12131
- if (tags.Materials?.type !== "string" || tags.Materials.value !== "Alpha") {
13106
+ if (!options.lenient && (tags.Materials?.type !== "string" || tags.Materials.value !== "Alpha")) {
12132
13107
  throw new MineRenderError("Schematic Materials must be Alpha");
12133
13108
  }
12134
13109
  const size = ["Width", "Height", "Length"].map((name) => {
@@ -12169,8 +13144,16 @@ var SchematicParser = class {
12169
13144
  const id = high << 8 | ids.value[index] & 255;
12170
13145
  const metadata = data.value[index] & 255;
12171
13146
  const key = `${id}:${metadata}`;
12172
- const mapped = customMappings[key] ?? mapping[key];
12173
- if (!mapped) throw new MineRenderError(`Unsupported legacy block ${id}:${metadata} at schematic index ${index}`);
13147
+ let mapped = customMappings[key] ?? mapping[key];
13148
+ if (!mapped && options.lenient) {
13149
+ mapped = customMappings[`${id}:0`] ?? mapping[`${id}:0`];
13150
+ }
13151
+ if (!mapped) {
13152
+ if (options.lenient) {
13153
+ continue;
13154
+ }
13155
+ throw new MineRenderError(`Unsupported legacy block ${id}:${metadata} at schematic index ${index}`);
13156
+ }
12174
13157
  const [type, state] = mapped.split("[");
12175
13158
  if (type === "minecraft:air") continue;
12176
13159
  const properties = {};
@@ -12214,6 +13197,11 @@ function compoundList(tags, name) {
12214
13197
  // src/model/multiblock/StructureParser.ts
12215
13198
  init_MineRenderError();
12216
13199
  var StructureParser = class {
13200
+ /**
13201
+ * Parses decoded NBT, omitting air blocks.
13202
+ * @param _nbt - Structure data decoded by {@link NBTHelper.fromBuffer}.
13203
+ * @param paletteIndex - Palette to select when the structure has several. Defaults to 0.
13204
+ */
12217
13205
  static async parse(_nbt, paletteIndex) {
12218
13206
  const nbt = _nbt.value;
12219
13207
  let palette;
@@ -13048,6 +14036,7 @@ var SkinImage = class {
13048
14036
  // src/skin/SkinTextures.ts
13049
14037
  init_Textures();
13050
14038
  var SkinTextures = class {
14039
+ /** Loads a skin URL. Pass `legacy` to override layout detection from the image dimensions. */
13051
14040
  static async get(src, legacy) {
13052
14041
  const image = await SkinImage.getData(src);
13053
14042
  const scale = image.width / 64;
@@ -13221,6 +14210,14 @@ var SkinObject = class extends SceneObject {
13221
14210
  }
13222
14211
  }
13223
14212
  }
14213
+ /**
14214
+ * Loads a skin texture, detecting slim arms and legacy layouts by default.
14215
+ * To override detection, set `slim` or `legacy` in the options passed to
14216
+ * `new SkinObject(options)` or `scene.addSkin(src, options)`.
14217
+ * Use {@link setSlim} or {@link setLegacy} to change these settings later.
14218
+ *
14219
+ * @param src - URL of the skin texture.
14220
+ */
13224
14221
  async setSkinTexture(src) {
13225
14222
  if (typeof src === "undefined") return;
13226
14223
  this.skinTextureSrc = src;
@@ -13328,59 +14325,61 @@ var _MineRenderScene = class _MineRenderScene extends import_three34.Scene {
13328
14325
  this.options = (0, import_ts_deepmerge8.default)({}, _MineRenderScene.DEFAULT_OPTIONS, options ?? {});
13329
14326
  }
13330
14327
  add(...objects) {
13331
- for (const object of objects) {
14328
+ for (const object3 of objects) {
13332
14329
  this.dirty = true;
13333
14330
  try {
13334
- super.add(object);
14331
+ super.add(object3);
13335
14332
  } finally {
13336
- if (object?.isObject3D && object !== this) {
13337
- this.updateObjectRegistration(object, object.parent === this);
14333
+ if (object3?.isObject3D && object3 !== this) {
14334
+ this.updateObjectRegistration(object3, object3.parent === this);
13338
14335
  }
13339
14336
  }
13340
14337
  }
13341
14338
  return this;
13342
14339
  }
13343
14340
  remove(...objects) {
13344
- for (const object of objects) {
13345
- if (!this.children.includes(object)) continue;
14341
+ for (const object3 of objects) {
14342
+ if (!this.children.includes(object3)) continue;
13346
14343
  this.dirty = true;
13347
- this.updateObjectRegistration(object, false);
14344
+ this.updateObjectRegistration(object3, false);
13348
14345
  try {
13349
- super.remove(object);
14346
+ super.remove(object3);
13350
14347
  } finally {
13351
- this.updateObjectRegistration(object, object.parent === this);
14348
+ this.updateObjectRegistration(object3, object3.parent === this);
13352
14349
  }
13353
14350
  }
13354
14351
  return this;
13355
14352
  }
13356
- updateObjectRegistration(object, attached) {
13357
- if (this.observedObjects.has(object) === attached) return;
13358
- const observable = object;
14353
+ updateObjectRegistration(object3, attached) {
14354
+ if (this.observedObjects.has(object3) === attached) return;
14355
+ const observable = object3;
13359
14356
  if (attached) {
13360
- this.observedObjects.add(object);
14357
+ this.observedObjects.add(object3);
13361
14358
  observable.addEventListener("change", this.onObjectChange);
13362
14359
  } else {
13363
- this.observedObjects.delete(object);
14360
+ this.observedObjects.delete(object3);
13364
14361
  observable.removeEventListener("change", this.onObjectChange);
13365
14362
  }
13366
14363
  const change = attached ? 1 : -1;
13367
14364
  this.stats.objectCount += change;
13368
- if (isSceneObject(object)) {
14365
+ if (isSceneObject(object3)) {
13369
14366
  this.stats.sceneObjectCount += change;
13370
14367
  }
13371
14368
  }
13372
- async initAndAdd(...object) {
14369
+ /** Initializes each object before attaching it to the scene. */
14370
+ async initAndAdd(...object3) {
13373
14371
  this.dirty = true;
13374
- for (let obj of object) {
14372
+ for (let obj of object3) {
13375
14373
  await obj.init();
13376
14374
  }
13377
- return this.add(...object);
14375
+ return this.add(...object3);
13378
14376
  }
13379
- async addSceneObject(asset, objectSupplier, _options, parent = this) {
14377
+ async addSceneObject(asset2, objectSupplier, _options, parent = this) {
13380
14378
  this.dirty = true;
13381
14379
  const obj = await objectSupplier();
13382
- if (obj?.options?.instanceMeshes && asset.key && asset.key?.assetType === "models") {
13383
- let key = asset.key.serialize();
14380
+ if (isModelObject(obj)) obj.options.tints = await ItemTints.get(obj.originalModel, obj.options.tints);
14381
+ if (obj?.options?.instanceMeshes && asset2.key && asset2.key?.assetType === "models") {
14382
+ let key = asset2.key.serialize();
13384
14383
  if (isModelObject(obj)) {
13385
14384
  const { displayPosition, uvLockRotation, tints, cullMask } = obj.options;
13386
14385
  if (displayPosition) key += `|display:${displayPosition}`;
@@ -13404,13 +14403,32 @@ var _MineRenderScene = class _MineRenderScene extends import_three34.Scene {
13404
14403
  return obj;
13405
14404
  }
13406
14405
  }
14406
+ /**
14407
+ * Adds a model loaded with {@link Models.getMerged}.
14408
+ * Set `options.instanceMeshes` to `false` to create a separate ModelObject.
14409
+ *
14410
+ * @param parent - Object to attach the model to. Defaults to this scene.
14411
+ * @returns The model, or a reference to one placement of a shared model.
14412
+ */
13407
14413
  async addModel(model, options, parent = this) {
13408
- options = { ...options, tints: await ItemTints.get(model, options?.tints) };
13409
14414
  return this.addSceneObject(model, () => new ModelObject(model, options), options, parent);
13410
14415
  }
14416
+ /**
14417
+ * Adds the models selected by a blockstate loaded with {@link BlockStates.get}.
14418
+ * Set `options.initialState` to choose block properties such as `facing` or `axis`.
14419
+ * Use the returned object's `setPosition` method to move all of its model parts.
14420
+ */
13411
14421
  async addBlock(blockState, options, parent = this) {
13412
14422
  return this.addSceneObject(blockState, () => new BlockObject(blockState, options), options, parent);
13413
14423
  }
14424
+ /**
14425
+ * Loads a skin and adds an initialized player model.
14426
+ *
14427
+ * @param skin - Texture URL, such as one returned by {@link Skins.fromUuidOrUsername}.
14428
+ * Omit it to assign a texture later with {@link SkinObject.setSkinTexture}.
14429
+ * @param options - Player settings, including `slim` and `legacy` detection overrides.
14430
+ * @param parent - Object to attach the player to. Defaults to this scene.
14431
+ */
13414
14432
  async addSkin(skin, options, parent = this) {
13415
14433
  this.dirty = true;
13416
14434
  const obj = new SkinObject(options);
@@ -13422,9 +14440,14 @@ var _MineRenderScene = class _MineRenderScene extends import_three34.Scene {
13422
14440
  parent.add(obj);
13423
14441
  return obj;
13424
14442
  }
14443
+ /** Adds an entity loaded with {@link Entities.getEntity}, using its selected layers and textures. */
13425
14444
  async addEntity(entity, options, parent = this) {
13426
14445
  return this.addSceneObject(entity, () => new EntityObject(entity, options), options, parent);
13427
14446
  }
14447
+ /**
14448
+ * Adds GUI layers in draw order. One GUI pixel equals one scene unit.
14449
+ * Layer positions start at the top left, with positive GUI y pointing down.
14450
+ */
13428
14451
  async addGui(layers, options, parent = this) {
13429
14452
  const obj = new GuiObject(layers, options);
13430
14453
  obj.scene = this;
@@ -13455,11 +14478,11 @@ var _changeEvent = { type: "change" };
13455
14478
  var _startEvent = { type: "start" };
13456
14479
  var _endEvent = { type: "end" };
13457
14480
  var OrbitControls = class extends import_three35.EventDispatcher {
13458
- constructor(object, domElement) {
14481
+ constructor(object3, domElement) {
13459
14482
  super();
13460
14483
  if (domElement === void 0) console.warn('THREE.OrbitControls: The second parameter "domElement" is now mandatory.');
13461
14484
  if (domElement === document) console.error('THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.');
13462
- this.object = object;
14485
+ this.object = object3;
13463
14486
  this.domElement = domElement;
13464
14487
  this.domElement.style.touchAction = "none";
13465
14488
  this.enabled = true;
@@ -13516,7 +14539,7 @@ var OrbitControls = class extends import_three35.EventDispatcher {
13516
14539
  };
13517
14540
  this.update = function() {
13518
14541
  const offset = new import_three35.Vector3();
13519
- const quat = new import_three35.Quaternion().setFromUnitVectors(object.up, new import_three35.Vector3(0, 1, 0));
14542
+ const quat = new import_three35.Quaternion().setFromUnitVectors(object3.up, new import_three35.Vector3(0, 1, 0));
13520
14543
  const quatInverse = quat.clone().invert();
13521
14544
  const lastPosition = new import_three35.Vector3();
13522
14545
  const lastQuaternion = new import_three35.Quaternion();
@@ -14081,8 +15104,8 @@ var OrbitControls = class extends import_three35.EventDispatcher {
14081
15104
  }
14082
15105
  };
14083
15106
  var MapControls = class extends OrbitControls {
14084
- constructor(object, domElement) {
14085
- super(object, domElement);
15107
+ constructor(object3, domElement) {
15108
+ super(object3, domElement);
14086
15109
  this.screenSpacePanning = false;
14087
15110
  this.mouseButtons.LEFT = import_three35.MOUSE.PAN;
14088
15111
  this.mouseButtons.RIGHT = import_three35.MOUSE.ROTATE;
@@ -14241,6 +15264,7 @@ var _Renderer = class _Renderer {
14241
15264
  get element() {
14242
15265
  return this._element;
14243
15266
  }
15267
+ /** Attaches the canvas to an element and sizes it to that element's dimensions. */
14244
15268
  appendTo(element) {
14245
15269
  if (this._disposed) return;
14246
15270
  this._element = element;
@@ -14268,6 +15292,7 @@ var _Renderer = class _Renderer {
14268
15292
  events.add(changeEvent2);
14269
15293
  dispatcher.addEventListener(changeEvent2, this._changeListener);
14270
15294
  }
15295
+ /** Updates the camera and canvas size. Width and height are in CSS pixels. */
14271
15296
  resize(width, height) {
14272
15297
  if (this._disposed) return;
14273
15298
  if (isPerspectiveCamera(this.camera)) {
@@ -14306,12 +15331,14 @@ var _Renderer = class _Renderer {
14306
15331
  if (this._frameCallbacks.get(callback) === subscription) this._frameCallbacks.delete(callback);
14307
15332
  };
14308
15333
  }
15334
+ /** Starts or resumes the animation loop and registered frame callbacks. */
14309
15335
  start() {
14310
15336
  if (this._disposed) return;
14311
15337
  this.stop();
14312
15338
  this._running = true;
14313
15339
  if (!this._inAnimationLoop) this.renderer.setAnimationLoop(this._animationLoop);
14314
15340
  }
15341
+ /** Pauses rendering and frame callbacks. Call {@link start} to resume. */
14315
15342
  stop() {
14316
15343
  if (this._disposed) return;
14317
15344
  this._running = false;
@@ -14420,12 +15447,15 @@ var _Renderer = class _Renderer {
14420
15447
  const canvas = trim ? trimCanvas(this.renderer.domElement) : this.renderer.domElement;
14421
15448
  return canvas.toDataURL(mime, quality);
14422
15449
  }
15450
+ /** Exports visible scene meshes as OBJ text, without texture images. */
14423
15451
  toObj() {
14424
15452
  return SceneExporter.toObj(this.scene);
14425
15453
  }
15454
+ /** Exports visible scene meshes as PLY. Set `options.binary` for an ArrayBuffer. */
14426
15455
  toPLY(options) {
14427
15456
  return SceneExporter.toPLY(this.scene, options);
14428
15457
  }
15458
+ /** Exports a static textured scene in the browser. Set `options.binary` for GLB output. */
14429
15459
  toGLTF(options) {
14430
15460
  return SceneExporter.toGLTF(this.scene, options);
14431
15461
  }
@@ -14503,16 +15533,244 @@ init_SceneObject();
14503
15533
  // src/index.ts
14504
15534
  init_request();
14505
15535
 
15536
+ // src/scene/SceneDocument.ts
15537
+ init_DisplayPosition();
15538
+ function fail(path, message) {
15539
+ throw new Error(`${path}: ${message}`);
15540
+ }
15541
+ function object2(value, path) {
15542
+ if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail(path, "expected a JSON object");
15543
+ for (const key of Object.keys(value)) {
15544
+ if (["__proto__", "prototype", "constructor"].includes(key)) fail(`${path}.${key}`, "unsupported property");
15545
+ }
15546
+ return value;
15547
+ }
15548
+ function fields(value, path, allowed, required = []) {
15549
+ const input = object2(value, path);
15550
+ for (const key of required) if (input[key] === void 0) fail(`${path}.${key}`, "required");
15551
+ for (const [key, entry] of Object.entries(input)) {
15552
+ if (!Object.prototype.hasOwnProperty.call(allowed, key)) fail(`${path}.${key}`, "unsupported property");
15553
+ if (entry !== void 0) allowed[key](entry, `${path}.${key}`);
15554
+ }
15555
+ return input;
15556
+ }
15557
+ var text = (value, path) => {
15558
+ if (typeof value !== "string" || !value.trim()) fail(path, "expected a nonempty string");
15559
+ };
15560
+ var literalText = (value, path) => {
15561
+ if (typeof value !== "string") fail(path, "expected a string");
15562
+ };
15563
+ var boolean = (value, path) => {
15564
+ if (typeof value !== "boolean") fail(path, "expected a boolean");
15565
+ };
15566
+ var number = (value, path) => {
15567
+ if (typeof value !== "number" || !Number.isFinite(value)) fail(path, "expected a finite number");
15568
+ };
15569
+ var positive = (value, path) => {
15570
+ number(value, path);
15571
+ if (value <= 0) fail(path, "expected a positive number");
15572
+ };
15573
+ var nonnegative = (value, path) => {
15574
+ number(value, path);
15575
+ if (value < 0) fail(path, "expected a nonnegative number");
15576
+ };
15577
+ var color = (value, path) => {
15578
+ number(value, path);
15579
+ if (!Number.isInteger(value) || value < 0 || value > 16777215) {
15580
+ fail(path, "expected an RGB integer from 0 to 16777215");
15581
+ }
15582
+ };
15583
+ var asset = (value, path) => {
15584
+ text(value, path);
15585
+ if (!/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(value) || value.split(/[:/]/).some((part) => !part || part === "." || part === "..")) {
15586
+ fail(path, "expected a resource ID such as minecraft:stone");
15587
+ }
15588
+ };
15589
+ var version = (value, path) => {
15590
+ text(value, path);
15591
+ if (!/^[a-zA-Z0-9._-]+$/.test(value)) fail(path, "expected a Minecraft version without slashes");
15592
+ };
15593
+ var tuple = (size, item = number) => (value, path) => {
15594
+ if (!Array.isArray(value) || value.length !== size) fail(path, `expected ${size} numbers`);
15595
+ for (let index = 0; index < size; index++) item(value[index], `${path}[${index}]`);
15596
+ };
15597
+ var list = (item) => (value, path) => {
15598
+ if (!Array.isArray(value)) fail(path, "expected an array");
15599
+ for (let index = 0; index < value.length; index++) item(value[index], `${path}[${index}]`);
15600
+ };
15601
+ var dictionary = (item, numericKeys = false) => (value, path) => {
15602
+ for (const [key, entry] of Object.entries(object2(value, path))) {
15603
+ if (numericKeys && !/^\d+$/.test(key)) fail(`${path}.${key}`, "expected a nonnegative tint index");
15604
+ item(entry, `${path}.${key}`);
15605
+ }
15606
+ };
15607
+ var enumeration = (values) => (value, path) => {
15608
+ if (!values.includes(value)) fail(path, `expected one of: ${values.join(", ")}`);
15609
+ };
15610
+ var textStyle = { color, bold: boolean, italic: boolean };
15611
+ function guiLayer(value, path) {
15612
+ const layer = object2(value, path);
15613
+ if (["texture", "item", "text"].filter((key) => layer[key] !== void 0).length !== 1) {
15614
+ fail(path, "expected exactly one of texture, item, or text");
15615
+ }
15616
+ const layout = { name: text, position: tuple(2), size: tuple(2, positive) };
15617
+ if (layer.texture !== void 0) {
15618
+ fields(value, path, { ...layout, texture: asset, crop: (value2, path2) => {
15619
+ tuple(4)(value2, path2);
15620
+ const [x, y, width, height] = value2;
15621
+ nonnegative(x, `${path2}[0]`);
15622
+ nonnegative(y, `${path2}[1]`);
15623
+ positive(width, `${path2}[2]`);
15624
+ positive(height, `${path2}[3]`);
15625
+ } });
15626
+ } else if (layer.item !== void 0) {
15627
+ fields(value, path, { ...layout, item: asset, tints: dictionary(color, true) });
15628
+ } else {
15629
+ fields(value, path, {
15630
+ ...layout,
15631
+ ...textStyle,
15632
+ font: asset,
15633
+ shadow: boolean,
15634
+ maxWidth: positive,
15635
+ lineHeight: positive,
15636
+ text: (value2, path2) => {
15637
+ if (typeof value2 === "string") return;
15638
+ list((run, path3) => {
15639
+ fields(run, path3, { text: literalText, ...textStyle }, ["text"]);
15640
+ })(value2, path2);
15641
+ }
15642
+ });
15643
+ }
15644
+ }
15645
+ function validateObject(value, path) {
15646
+ const input = object2(value, path);
15647
+ const common = { id: text, name: text, type: text, position: tuple(3), rotation: tuple(3), scale: tuple(3), visible: boolean };
15648
+ const required = ["id", "type"];
15649
+ const renderOptions = { wireframe: boolean };
15650
+ switch (input.type) {
15651
+ case "skin":
15652
+ fields(value, path, {
15653
+ ...common,
15654
+ skin: text,
15655
+ cape: (value2, path2) => {
15656
+ fields(value2, path2, { texture: text, layout: enumeration(["minecraft", "optifine", "labymod"]) }, ["texture"]);
15657
+ },
15658
+ hiddenParts: list(enumeration(SKIN_PARTS)),
15659
+ pose: (value2, path2) => {
15660
+ fields(value2, path2, {
15661
+ head: tuple(3),
15662
+ body: tuple(3),
15663
+ rightArm: tuple(3),
15664
+ leftArm: tuple(3),
15665
+ rightLeg: tuple(3),
15666
+ leftLeg: tuple(3),
15667
+ cape: tuple(3)
15668
+ });
15669
+ },
15670
+ options: (value2, path2) => {
15671
+ fields(value2, path2, { slim: boolean, legacy: boolean });
15672
+ }
15673
+ }, required);
15674
+ break;
15675
+ case "block":
15676
+ fields(value, path, {
15677
+ ...common,
15678
+ asset,
15679
+ state: dictionary(text),
15680
+ options: (value2, path2) => {
15681
+ fields(value2, path2, { ...renderOptions, tints: dictionary(color, true) });
15682
+ }
15683
+ }, [...required, "asset"]);
15684
+ break;
15685
+ case "item":
15686
+ case "model":
15687
+ fields(value, path, {
15688
+ ...common,
15689
+ asset,
15690
+ options: (value2, path2) => {
15691
+ fields(value2, path2, { ...renderOptions, displayPosition: enumeration(DISPLAY_POSITIONS), tints: dictionary(color, true) });
15692
+ }
15693
+ }, [...required, "asset"]);
15694
+ break;
15695
+ case "entity":
15696
+ fields(value, path, {
15697
+ ...common,
15698
+ asset,
15699
+ texture: asset,
15700
+ layers: list(text),
15701
+ when: list(text),
15702
+ textures: dictionary(asset),
15703
+ options: (value2, path2) => {
15704
+ fields(value2, path2, {
15705
+ ...renderOptions,
15706
+ flip: boolean,
15707
+ tints: dictionary((value3, path3) => {
15708
+ typeof value3 === "string" ? text(value3, path3) : color(value3, path3);
15709
+ })
15710
+ });
15711
+ },
15712
+ animation: (value2, path2) => {
15713
+ fields(value2, path2, { name: text, loop: boolean, speed: nonnegative, time: nonnegative, paused: boolean }, ["name"]);
15714
+ }
15715
+ }, [...required, "asset"]);
15716
+ if (Array.isArray(input.layers) && !input.layers.length) fail(`${path}.layers`, "expected at least one layer");
15717
+ break;
15718
+ case "gui":
15719
+ fields(value, path, { ...common, layers: list(guiLayer) }, [...required, "layers"]);
15720
+ break;
15721
+ default:
15722
+ fail(`${path}.type`, "expected skin, block, item, entity, model, or gui");
15723
+ }
15724
+ }
15725
+ function parseSceneDocument(value) {
15726
+ if (typeof value === "string") {
15727
+ try {
15728
+ value = JSON.parse(value);
15729
+ } catch {
15730
+ fail("scene", "invalid JSON");
15731
+ }
15732
+ }
15733
+ const input = fields(value, "scene", {
15734
+ format: enumeration(["minerender-scene"]),
15735
+ version: (value2, path) => {
15736
+ if (value2 !== 1) fail(path, "only scene document version 1 is supported");
15737
+ },
15738
+ minecraftVersion: version,
15739
+ camera: (value2, path) => {
15740
+ fields(value2, path, { position: tuple(3), target: tuple(3) }, ["position", "target"]);
15741
+ },
15742
+ objects: list(validateObject)
15743
+ }, ["format", "version", "objects"]);
15744
+ const ids = /* @__PURE__ */ new Set();
15745
+ for (const [index, entry] of input.objects.entries()) {
15746
+ if (ids.has(entry.id)) fail(`scene.objects[${index}].id`, `duplicate object ID "${entry.id}"`);
15747
+ ids.add(entry.id);
15748
+ }
15749
+ return JSON.parse(JSON.stringify(input));
15750
+ }
15751
+
15752
+ // src/scene/SceneDocumentLoader.ts
15753
+ var import_three38 = require("three");
15754
+ init_AssetKey();
15755
+ init_AssetLoader();
15756
+ init_BlockStates();
15757
+ init_Entities();
15758
+ init_Models();
15759
+ init_DisplayPosition();
15760
+ init_SceneObject();
15761
+
14506
15762
  // src/skin/Skins.ts
14507
15763
  init_Requests();
14508
15764
  var Skins = class {
14509
15765
  //<editor-fold desc="Skins">
15766
+ /** Returns a skin URL for {@link MineRenderScene.addSkin}, or `undefined` if name lookup fails. */
14510
15767
  static async fromUuidOrUsername(uuidOrUsername) {
14511
15768
  if (uuidOrUsername.length === 32 || uuidOrUsername.length === 36) {
14512
15769
  return this.fromUuid(uuidOrUsername);
14513
15770
  }
14514
15771
  return this.fromUsername(uuidOrUsername);
14515
15772
  }
15773
+ /** Builds a proxy URL for a UUID without downloading or checking the skin. */
14516
15774
  static async fromUuid(uuid) {
14517
15775
  return this.getMcProxySkinUrl(uuid);
14518
15776
  }
@@ -14524,6 +15782,7 @@ var Skins = class {
14524
15782
  return void 0;
14525
15783
  });
14526
15784
  }
15785
+ /** Looks up a saved MineSkin skin and returns its texture URL, or `undefined` on lookup failure. */
14527
15786
  static async fromMineSkin(mineskinId) {
14528
15787
  return this.getMineSkin(mineskinId).then((mineskin) => {
14529
15788
  if (mineskin) {
@@ -14534,12 +15793,14 @@ var Skins = class {
14534
15793
  }
14535
15794
  //</editor-fold>
14536
15795
  //<editor-fold desc="Capes">
15796
+ /** Resolves a player's Minecraft cape URL for {@link SkinObject.setCapeTexture}. */
14537
15797
  static async capeFromUuidOrUsername(uuidOrUsername) {
14538
15798
  if (uuidOrUsername.length === 32 || uuidOrUsername.length === 36) {
14539
15799
  return this.capeFromUuid(uuidOrUsername);
14540
15800
  }
14541
15801
  return this.capeFromUsername(uuidOrUsername);
14542
15802
  }
15803
+ /** Builds a proxy URL without checking whether the player has a cape. */
14543
15804
  static async capeFromUuid(uuid) {
14544
15805
  return this.getMcProxyCapeUrl(uuid);
14545
15806
  }
@@ -14668,6 +15929,195 @@ var Skins = class {
14668
15929
  //</editor-fold>
14669
15930
  };
14670
15931
 
15932
+ // src/scene/SceneDocumentLoader.ts
15933
+ var DocumentObjectScene = class extends MineRenderScene {
15934
+ async addSceneObject(_asset, supplier, _options, parent = this) {
15935
+ const object3 = await supplier();
15936
+ return this.initialize(object3, parent);
15937
+ }
15938
+ async initialize(object3, parent = this) {
15939
+ object3.scene = this;
15940
+ try {
15941
+ await object3.init();
15942
+ parent.add(object3);
15943
+ return object3;
15944
+ } catch (error) {
15945
+ object3.dispose();
15946
+ object3.removeFromParent();
15947
+ throw error;
15948
+ }
15949
+ }
15950
+ dispose() {
15951
+ for (const child of [...this.children]) {
15952
+ if (child.parent !== this) continue;
15953
+ if (isSceneObject(child)) child.dispose();
15954
+ child.removeFromParent();
15955
+ }
15956
+ }
15957
+ };
15958
+ async function skinTexture(value, cape = false) {
15959
+ if (!/^[a-zA-Z0-9_-]{1,36}$/.test(value)) return value;
15960
+ const texture = await (cape ? Skins.capeFromUuidOrUsername(value) : Skins.fromUuidOrUsername(value));
15961
+ if (!texture) throw new Error(`Could not find ${cape ? "cape" : "skin"} for "${value}"`);
15962
+ return texture;
15963
+ }
15964
+ var SceneDocumentLoader = class {
15965
+ static parse(value) {
15966
+ return parseSceneDocument(value);
15967
+ }
15968
+ /**
15969
+ * Loads objects concurrently and attaches them in document order after every load succeeds.
15970
+ * A failure waits for all loads, disposes staged objects, and reports the first error in document order.
15971
+ */
15972
+ static async load(scene, value, parent = scene) {
15973
+ const definition = this.parse(value);
15974
+ if (definition.minecraftVersion !== void 0 && definition.minecraftVersion !== AssetLoader.version) {
15975
+ throw new Error(`Scene requires Minecraft ${definition.minecraftVersion}; call AssetLoader.setVersion("${definition.minecraftVersion}") before loading it`);
15976
+ }
15977
+ const root = new import_three38.Group();
15978
+ root.name = "MineRender scene document";
15979
+ const objects = [];
15980
+ let disposed = false;
15981
+ const loaded = {
15982
+ root,
15983
+ objects,
15984
+ definition,
15985
+ advanceAnimations: (delta) => {
15986
+ if (!disposed) for (const object3 of objects) object3.advanceAnimation(delta);
15987
+ },
15988
+ dispose: () => {
15989
+ if (disposed) return;
15990
+ disposed = true;
15991
+ root.removeFromParent();
15992
+ for (const object3 of objects) object3.dispose();
15993
+ root.clear();
15994
+ scene.dirty = true;
15995
+ }
15996
+ };
15997
+ try {
15998
+ const results = await Promise.allSettled(definition.objects.map((object3) => this.loadValidatedObject(scene, object3)));
15999
+ for (const result of results) if (result.status === "fulfilled") objects.push(result.value);
16000
+ const failure = results.find((result) => result.status === "rejected");
16001
+ if (failure) throw failure.reason;
16002
+ for (const object3 of objects) root.add(object3.root);
16003
+ parent.add(root);
16004
+ scene.dirty = true;
16005
+ return loaded;
16006
+ } catch (error) {
16007
+ loaded.dispose();
16008
+ throw error;
16009
+ }
16010
+ }
16011
+ /** Loads an independently editable object. Its root owns the document's position, rotation, and scale. */
16012
+ static async loadObject(scene, value, parent = scene) {
16013
+ const definition = this.parse({ format: "minerender-scene", version: 1, objects: [value] }).objects[0];
16014
+ return this.loadValidatedObject(scene, definition, parent);
16015
+ }
16016
+ static async loadValidatedObject(scene, definition, parent) {
16017
+ const root = new import_three38.Group();
16018
+ root.name = definition.name ?? definition.id;
16019
+ root.userData.sceneObjectId = definition.id;
16020
+ root.position.fromArray(definition.position ?? [0, 0, 0]);
16021
+ root.rotation.set(...definition.rotation ?? [0, 0, 0]);
16022
+ root.scale.fromArray(definition.scale ?? [1, 1, 1]);
16023
+ root.visible = definition.visible ?? true;
16024
+ const content = new DocumentObjectScene();
16025
+ root.add(content);
16026
+ let object3;
16027
+ try {
16028
+ switch (definition.type) {
16029
+ case "skin": {
16030
+ const skin = new SkinObject({ ...definition.options, instanceMeshes: false });
16031
+ object3 = skin;
16032
+ const texture = definition.skin ? await skinTexture(definition.skin) : `${AssetLoader.ROOT}/assets/minecraft/textures/entity/player/${definition.options?.slim ? "slim/alex" : "wide/steve"}.png`;
16033
+ await skin.setSkinTexture(texture);
16034
+ await content.initialize(skin);
16035
+ if (definition.cape) await skin.setCapeTexture(await skinTexture(definition.cape.texture, true), definition.cape.layout);
16036
+ for (const [part, rotation] of Object.entries(definition.pose ?? {})) {
16037
+ skin.getGroupByName(part)?.rotation.set(...rotation, "XYZ");
16038
+ }
16039
+ for (const part of definition.hiddenParts ?? []) {
16040
+ const mesh = skin.getMeshByName(part);
16041
+ if (mesh) mesh.visible = false;
16042
+ }
16043
+ break;
16044
+ }
16045
+ case "block": {
16046
+ const state = await BlockStates.get(AssetKey.parse("blockstates", definition.asset));
16047
+ if (!state) throw new Error(`Could not load block ${definition.asset}`);
16048
+ object3 = await content.addBlock(state, { ...definition.options, initialState: definition.state, instanceMeshes: false });
16049
+ break;
16050
+ }
16051
+ case "item":
16052
+ case "model": {
16053
+ let key = AssetKey.parse("models", definition.asset);
16054
+ if (definition.type === "item" && key.type !== "item") {
16055
+ key = new AssetKey(key.namespace, key.getFullPath(), "models", "item");
16056
+ }
16057
+ const model = await Models.getMerged(key);
16058
+ if (!model) throw new Error(`Could not load ${definition.type} ${definition.asset}`);
16059
+ object3 = await content.addModel(model, {
16060
+ ...definition.type === "item" ? { displayPosition: "gui" /* GUI */ } : {},
16061
+ ...definition.options,
16062
+ instanceMeshes: false
16063
+ });
16064
+ break;
16065
+ }
16066
+ case "entity": {
16067
+ const key = AssetKey.parse("entity-models", definition.asset);
16068
+ const model = await Entities.getEntity(key, definition.texture ? AssetKey.parse("textures", definition.texture) : void 0, {
16069
+ layers: definition.layers,
16070
+ when: definition.when,
16071
+ textures: definition.textures && Object.fromEntries(Object.entries(definition.textures).map(([name, texture]) => [name, AssetKey.parse("textures", texture)]))
16072
+ });
16073
+ if (!model) throw new Error(`Could not load entity ${definition.asset}`);
16074
+ const entity = await content.addEntity(model, { ...definition.options, instanceMeshes: false });
16075
+ object3 = entity;
16076
+ if (definition.animation) {
16077
+ const animations = await Entities.getAnimations(key);
16078
+ const clip = animations?.[definition.animation.name];
16079
+ if (!clip) throw new Error(`Entity ${definition.asset} has no animation "${definition.animation.name}"`);
16080
+ entity.playAnimation(clip, definition.animation);
16081
+ }
16082
+ break;
16083
+ }
16084
+ case "gui":
16085
+ object3 = await content.initialize(new GuiObject(definition.layers));
16086
+ break;
16087
+ }
16088
+ let disposed = false;
16089
+ const loaded = {
16090
+ root,
16091
+ object: object3,
16092
+ definition,
16093
+ advanceAnimation: (delta) => {
16094
+ if (!disposed && definition.type === "entity" && !definition.animation?.paused) object3.advanceAnimation(delta);
16095
+ },
16096
+ dispose: () => {
16097
+ if (disposed) return;
16098
+ disposed = true;
16099
+ root.removeFromParent();
16100
+ object3.dispose();
16101
+ object3.removeFromParent();
16102
+ content.dispose();
16103
+ root.clear();
16104
+ scene.dirty = true;
16105
+ }
16106
+ };
16107
+ parent?.add(root);
16108
+ scene.dirty = true;
16109
+ return loaded;
16110
+ } catch (error) {
16111
+ object3?.dispose();
16112
+ object3?.removeFromParent();
16113
+ content.dispose();
16114
+ root.removeFromParent();
16115
+ root.clear();
16116
+ throw new Error(`Scene object "${definition.name ?? definition.id}": ${error instanceof Error ? error.message : String(error)}`);
16117
+ }
16118
+ }
16119
+ };
16120
+
14671
16121
  // src/texture/index.ts
14672
16122
  init_Colormaps();
14673
16123
  init_TextureAtlas();
@@ -14694,16 +16144,19 @@ var ChunkData = class _ChunkData {
14694
16144
  this.freeIds = [];
14695
16145
  this.nbt = /* @__PURE__ */ new Map();
14696
16146
  }
16147
+ /** Returns a detached copy of a cell, or `undefined` for air. Indices range from 0 to 4095. */
14697
16148
  get(index) {
14698
16149
  _ChunkData.validateIndex(index);
14699
16150
  const entry = this.palette[this.indices[index]];
14700
16151
  return entry ? _ChunkData.copyBlock(entry.state, this.nbt.get(index)) : void 0;
14701
16152
  }
16153
+ /** Captures a cell's data and returns a copy-producing reader that survives later cell changes. */
14702
16154
  snapshot(index) {
14703
16155
  _ChunkData.validateIndex(index);
14704
16156
  const entry = this.palette[this.indices[index]];
14705
16157
  return entry ? _ChunkData.copyBlock.bind(void 0, entry.state, this.nbt.get(index)) : void 0;
14706
16158
  }
16159
+ /** Copies block data into a cell. An omitted block or an air block clears the cell. */
14707
16160
  set(index, block) {
14708
16161
  _ChunkData.validateIndex(index);
14709
16162
  let state;
@@ -14768,15 +16221,23 @@ var ChunkData = class _ChunkData {
14768
16221
 
14769
16222
  // src/world/AnvilParser.ts
14770
16223
  var AnvilParser = class {
16224
+ /** Lists occupied chunk positions within the region, using local coordinates from 0 to 31. */
14771
16225
  static getChunkList(data) {
14772
16226
  return this.locations(this.bytes(data)).map(({ x, z }) => ({ x, z }));
14773
16227
  }
16228
+ /** Decodes all stored chunks. Gzip, zlib, and uncompressed payloads are supported. */
14774
16229
  static async parse(data) {
14775
16230
  const bytes = this.bytes(data);
14776
16231
  const chunks = [];
14777
16232
  for (const location of this.locations(bytes)) chunks.push(await this.readChunk(bytes, location));
14778
16233
  return { chunks };
14779
16234
  }
16235
+ /**
16236
+ * Decodes one chunk for {@link MineRenderWorld.placeChunk}.
16237
+ * @param localX - Chunk x within the region, from 0 to 31.
16238
+ * @param localZ - Chunk z within the region, from 0 to 31.
16239
+ * @returns The chunk with absolute coordinates, or `undefined` if the region has no chunk there.
16240
+ */
14780
16241
  static async parseChunk(data, localX, localZ) {
14781
16242
  if (![localX, localZ].every((value) => Number.isInteger(value) && value >= 0 && value < 32)) {
14782
16243
  throw new RangeError("Region-local chunk coordinates must be integers from 0 to 31");
@@ -14938,12 +16399,12 @@ var AnvilParser = class {
14938
16399
  };
14939
16400
 
14940
16401
  // src/world/Chunk.ts
14941
- var import_three41 = require("three");
16402
+ var import_three42 = require("three");
14942
16403
  init_BlockStates();
14943
16404
  init_AssetKey();
14944
16405
 
14945
16406
  // src/world/MineRenderWorld.ts
14946
- var import_three39 = require("three");
16407
+ var import_three40 = require("three");
14947
16408
  init_Model();
14948
16409
  init_BatchedExecutor();
14949
16410
  init_AssetKey();
@@ -14951,7 +16412,7 @@ init_BlockStates();
14951
16412
  init_CubeFace();
14952
16413
 
14953
16414
  // src/world/SectionModels.ts
14954
- var import_three38 = require("three");
16415
+ var import_three39 = require("three");
14955
16416
  init_AssetKey();
14956
16417
  init_Models();
14957
16418
  init_CubeFace();
@@ -14981,6 +16442,7 @@ var SectionModels = class {
14981
16442
  this.templates = /* @__PURE__ */ new Set();
14982
16443
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("maxAtlasSize must be a positive integer");
14983
16444
  }
16445
+ /** Returns a cube template, or `undefined` when the block requires an individual render object. */
14984
16446
  async get(blockState, properties = {}) {
14985
16447
  if (!blockState.variants || blockState.multipart) return void 0;
14986
16448
  let states = this.states.get(blockState);
@@ -15009,20 +16471,20 @@ var SectionModels = class {
15009
16471
  if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
15010
16472
  const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
15011
16473
  const template = await cached(models, key, async () => {
15012
- const object = new SectionModel(model, {
16474
+ const object3 = new SectionModel(model, {
15013
16475
  instanceMeshes: false,
15014
16476
  mergeMeshes: false,
15015
16477
  tints,
15016
16478
  uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
15017
16479
  });
15018
- await object.init();
15019
- const geometry = object.children[0].geometry;
15020
- geometry.applyMatrix4(new import_three38.Matrix4().makeRotationFromEuler(rotation));
15021
- object.clear();
16480
+ await object3.init();
16481
+ const geometry = object3.children[0].geometry;
16482
+ geometry.applyMatrix4(new import_three39.Matrix4().makeRotationFromEuler(rotation));
16483
+ object3.clear();
15022
16484
  const cullFaces = CUBE_FACES.map((face) => {
15023
16485
  const local = CUBE_FACES.findIndex((name) => name === atlas.model.elements[0].faces[face]?.cullface);
15024
16486
  if (local < 0) return 0;
15025
- const normal = new import_three38.Vector3(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);
16487
+ const normal = new import_three39.Vector3(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);
15026
16488
  const world = CUBE_FACE_OFFSETS.findIndex(([x, y, z]) => Math.abs(normal.x - x) < 1e-6 && Math.abs(normal.y - y) < 1e-6 && Math.abs(normal.z - z) < 1e-6);
15027
16489
  return 1 << world;
15028
16490
  });
@@ -15034,6 +16496,7 @@ var SectionModels = class {
15034
16496
  }
15035
16497
  return { variants, templates };
15036
16498
  }
16499
+ /** Disposes prepared geometry and clears this world's template caches. */
15037
16500
  clear() {
15038
16501
  for (const template of this.templates) template.geometry.dispose();
15039
16502
  this.templates.clear();
@@ -15052,20 +16515,20 @@ var MineRenderWorld = class {
15052
16515
  }
15053
16516
  getBlockAt(posOrX, y, z) {
15054
16517
  if (typeof posOrX == "number") {
15055
- return this.getBlockAt(new import_three39.Vector3(posOrX, y, z));
16518
+ return this.getBlockAt(new import_three40.Vector3(posOrX, y, z));
15056
16519
  }
15057
16520
  if (isTripleArray(posOrX)) {
15058
- return this.getBlockAt(new import_three39.Vector3(posOrX[0], posOrX[1], posOrX[2]));
16521
+ return this.getBlockAt(new import_three40.Vector3(posOrX[0], posOrX[1], posOrX[2]));
15059
16522
  }
15060
16523
  this.validatePosBounds(posOrX);
15061
16524
  return this.getChunkAt(posOrX)?.getBlockAt(posOrX);
15062
16525
  }
15063
16526
  async setBlockAt(posOrX, yOrBlock, z, block) {
15064
16527
  if (typeof posOrX == "number") {
15065
- return this.setBlockAt(new import_three39.Vector3(posOrX, yOrBlock, z), block);
16528
+ return this.setBlockAt(new import_three40.Vector3(posOrX, yOrBlock, z), block);
15066
16529
  }
15067
16530
  if (isTripleArray(posOrX)) {
15068
- return this.setBlockAt(new import_three39.Vector3(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
16531
+ return this.setBlockAt(new import_three40.Vector3(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
15069
16532
  }
15070
16533
  return this.placeBlock(posOrX, yOrBlock);
15071
16534
  }
@@ -15076,14 +16539,21 @@ var MineRenderWorld = class {
15076
16539
  }
15077
16540
  async setBlockVisibleAt(posOrX, yOrVisible, z, visible) {
15078
16541
  if (typeof posOrX === "number") {
15079
- return this.setBlockVisibleAt(new import_three39.Vector3(posOrX, yOrVisible, z), visible);
16542
+ return this.setBlockVisibleAt(new import_three40.Vector3(posOrX, yOrVisible, z), visible);
15080
16543
  }
15081
16544
  if (isTripleArray(posOrX)) {
15082
- return this.setBlockVisibleAt(new import_three39.Vector3(...posOrX), yOrVisible);
16545
+ return this.setBlockVisibleAt(new import_three40.Vector3(...posOrX), yOrVisible);
15083
16546
  }
15084
16547
  this.validatePosBounds(posOrX);
15085
16548
  await this.getChunkAt(posOrX)?.setBlockVisibleAt(posOrX, yOrVisible);
15086
16549
  }
16550
+ /**
16551
+ * Places a structure's blocks at their stored positions, then refreshes neighboring faces.
16552
+ * Entity NBT is retained by the structure but is not rendered.
16553
+ *
16554
+ * @param useBatches - Uses bounded batches by default. Set to `false` for sequential placement.
16555
+ * @param executor - Optional queue for batched placement. The caller remains responsible for stopping it.
16556
+ */
15087
16557
  async placeMultiBlock(multiblock, useBatches = true, executor) {
15088
16558
  const changes = /* @__PURE__ */ new Map();
15089
16559
  try {
@@ -15096,7 +16566,7 @@ var MineRenderWorld = class {
15096
16566
  const collect = async (positions2) => {
15097
16567
  for (const pos of positions2) changes.set(pos.toArray().join(","), pos);
15098
16568
  };
15099
- const place = (block) => this.placeBlock(new import_three39.Vector3(...block.position), block, collect);
16569
+ const place = (block) => this.placeBlock(new import_three40.Vector3(...block.position), block, collect);
15100
16570
  const keys = /* @__PURE__ */ new Map();
15101
16571
  for (const block of multiblock.blocks) {
15102
16572
  if (Chunk.isAir(block)) continue;
@@ -15135,13 +16605,7 @@ var MineRenderWorld = class {
15135
16605
  async placeChunk(chunk, executor) {
15136
16606
  const changes = /* @__PURE__ */ new Map();
15137
16607
  try {
15138
- const previous = [...this._chunks.entries()].filter(([, section]) => section.x === chunk.x && section.z === chunk.z);
15139
- for (const [key, section] of previous) {
15140
- this._chunks.delete(key);
15141
- await section.clear(async (positions) => {
15142
- for (const pos of positions) changes.set(pos.toArray().join(","), pos);
15143
- });
15144
- }
16608
+ await this.clearChunkColumn(chunk.x, chunk.z, changes);
15145
16609
  for (const section of chunk.sections) {
15146
16610
  const blocks = [];
15147
16611
  for (let index = 0; index < 4096; index++) {
@@ -15161,6 +16625,32 @@ var MineRenderWorld = class {
15161
16625
  await this.updateCulling([...changes.values()]);
15162
16626
  }
15163
16627
  }
16628
+ /**
16629
+ * Removes all sections at integer chunk-column coordinates, then refreshes neighboring faces and fluids.
16630
+ * Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
16631
+ */
16632
+ async unloadChunkColumn(x, z) {
16633
+ if (!Number.isInteger(x) || !Number.isInteger(z)) {
16634
+ throw new RangeError("Chunk column coordinates must be integers");
16635
+ }
16636
+ await this.culling;
16637
+ const changes = /* @__PURE__ */ new Map();
16638
+ try {
16639
+ await this.clearChunkColumn(x, z, changes);
16640
+ } finally {
16641
+ await this.updateCulling([...changes.values()]);
16642
+ }
16643
+ }
16644
+ async clearChunkColumn(x, z, changes) {
16645
+ const previous = [...this._chunks.entries()].filter(([, section]) => section.x === x && section.z === z);
16646
+ for (const [key, section] of previous) {
16647
+ this._chunks.delete(key);
16648
+ await section.clear(async (positions) => {
16649
+ for (const pos of positions) changes.set(pos.toArray().join(","), pos);
16650
+ });
16651
+ }
16652
+ }
16653
+ /** Removes all chunks and releases their block placements and section meshes. */
15164
16654
  async clear() {
15165
16655
  await this.culling;
15166
16656
  const chunks = [...this._chunks.values()];
@@ -15174,13 +16664,13 @@ var MineRenderWorld = class {
15174
16664
  for (const pos of positions) {
15175
16665
  this.pendingCulling.set(pos.toArray().join(","), pos.clone());
15176
16666
  for (const offset of CUBE_FACE_OFFSETS) {
15177
- const neighbor = pos.clone().add(new import_three39.Vector3(...offset));
16667
+ const neighbor = pos.clone().add(new import_three40.Vector3(...offset));
15178
16668
  this.pendingCulling.set(neighbor.toArray().join(","), neighbor);
15179
16669
  }
15180
16670
  for (let y = -1; y <= 1; y++) {
15181
16671
  for (let z = -1; z <= 1; z++) {
15182
16672
  for (let x = -1; x <= 1; x++) {
15183
- const neighbor = pos.clone().add(new import_three39.Vector3(x, y, z));
16673
+ const neighbor = pos.clone().add(new import_three40.Vector3(x, y, z));
15184
16674
  if (this.getBlockAt(neighbor)?.object?.fluidKind) {
15185
16675
  this.pendingCulling.set(neighbor.toArray().join(","), neighbor);
15186
16676
  }
@@ -15204,19 +16694,19 @@ var MineRenderWorld = class {
15204
16694
  }
15205
16695
  if (block.object?.fluidKind) {
15206
16696
  await block.object.updateFluid((x, y, z) => {
15207
- const neighbor = pos.clone().add(new import_three39.Vector3(x, y, z));
16697
+ const neighbor = pos.clone().add(new import_three40.Vector3(x, y, z));
15208
16698
  const section = this.getChunkAt(neighbor);
15209
- const object = section?.isBlockVisibleAt(neighbor) ? section.getBlockAt(neighbor)?.object : void 0;
16699
+ const object3 = section?.isBlockVisibleAt(neighbor) ? section.getBlockAt(neighbor)?.object : void 0;
15210
16700
  return {
15211
- fluid: object?.fluidKind,
15212
- level: object?.fluidLevel,
16701
+ fluid: object3?.fluidKind,
16702
+ level: object3?.fluidLevel,
15213
16703
  solid: section?.isOccludingAt(neighbor) ?? false
15214
16704
  };
15215
16705
  });
15216
16706
  }
15217
16707
  let mask = 0;
15218
16708
  for (const [face, offset] of CUBE_FACE_OFFSETS.entries()) {
15219
- const neighbor = pos.clone().add(new import_three39.Vector3(...offset));
16709
+ const neighbor = pos.clone().add(new import_three40.Vector3(...offset));
15220
16710
  if (this.getChunkAt(neighbor)?.isOccludingAt(neighbor)) {
15221
16711
  mask |= 1 << face;
15222
16712
  }
@@ -15246,18 +16736,21 @@ var MineRenderWorld = class {
15246
16736
  }
15247
16737
  return chunk;
15248
16738
  }
16739
+ /** Returns the loaded chunk containing a world block position, without creating a chunk. */
15249
16740
  getChunkAt(pos) {
15250
16741
  return this._chunks.get(this.worldPosToChunkKey(pos));
15251
16742
  }
16743
+ /** Converts block coordinates to scene units by multiplying each component by 16. */
15252
16744
  static worldToScenePosition(pos) {
15253
- return new import_three39.Vector3(
16745
+ return new import_three40.Vector3(
15254
16746
  pos.x * 16,
15255
16747
  pos.y * 16,
15256
16748
  pos.z * 16
15257
16749
  );
15258
16750
  }
16751
+ /** Converts scene units to block coordinates without rounding to an integer block. */
15259
16752
  static sceneToWorldPosition(pos) {
15260
- return new import_three39.Vector3(
16753
+ return new import_three40.Vector3(
15261
16754
  pos.x / 16,
15262
16755
  pos.y / 16,
15263
16756
  pos.z / 16
@@ -15280,14 +16773,15 @@ var MineRenderWorld = class {
15280
16773
  init_Model();
15281
16774
 
15282
16775
  // src/world/SectionMesh.ts
15283
- var import_three40 = require("three");
16776
+ var import_three41 = require("three");
15284
16777
  init_CanvasCompat();
15285
16778
  init_Materials();
15286
- var SectionMesh = class _SectionMesh extends import_three40.Group {
16779
+ var SectionMesh = class _SectionMesh extends import_three41.Group {
15287
16780
  constructor() {
15288
16781
  super(...arguments);
15289
16782
  this.ownedMeshes = [];
15290
16783
  }
16784
+ /** Builds section-local meshes from visible cube faces. `maxAtlasSize` limits each atlas dimension in pixels. */
15291
16785
  static build(entries, maxAtlasSize = 2048) {
15292
16786
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
15293
16787
  const visible = entries.filter((entry) => entry.template.cullFaces.some((direction) => !(entry.cullMask & direction)));
@@ -15329,14 +16823,17 @@ var SectionMesh = class _SectionMesh extends import_three40.Group {
15329
16823
  for (const { atlas, x, y } of page.placements) {
15330
16824
  context.drawImage(atlas.image.canvas, x, y);
15331
16825
  }
15332
- const positions = [], normals = [], uvs = [], colors = [], indices = [];
16826
+ const positions = [], normals = [], uvs = [], uvBounds = [], colors = [], indices = [];
15333
16827
  for (const { index, template, cullMask } of page.entries) {
15334
16828
  const position = template.geometry.getAttribute("position");
15335
16829
  const normal = template.geometry.getAttribute("normal");
15336
16830
  const uv = template.geometry.getAttribute("uv");
15337
- const color = template.geometry.getAttribute("color");
16831
+ const bounds = template.geometry.getAttribute("uvBounds");
16832
+ const color2 = template.geometry.getAttribute("color");
15338
16833
  const sourceIndices = template.geometry.getIndex();
15339
16834
  const placement = locations.get(template.atlas).placement;
16835
+ const mapU = (u) => (placement.x + u * template.atlas.image.width) / page.width;
16836
+ const mapV = (v) => 1 - (placement.y + (1 - v) * template.atlas.image.height) / page.height;
15340
16837
  const offsetX = index % 16 * 16;
15341
16838
  const offsetY = Math.floor(index / 256) * 16;
15342
16839
  const offsetZ = Math.floor(index / 16) % 16 * 16;
@@ -15347,27 +16844,31 @@ var SectionMesh = class _SectionMesh extends import_three40.Group {
15347
16844
  const vertex = face * 4 + corner;
15348
16845
  positions.push(position.getX(vertex) + offsetX, position.getY(vertex) + offsetY, position.getZ(vertex) + offsetZ);
15349
16846
  normals.push(normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex));
15350
- uvs.push(
15351
- (placement.x + uv.getX(vertex) * template.atlas.image.width) / page.width,
15352
- 1 - (placement.y + (1 - uv.getY(vertex)) * template.atlas.image.height) / page.height
16847
+ uvs.push(mapU(uv.getX(vertex)), mapV(uv.getY(vertex)));
16848
+ uvBounds.push(
16849
+ mapU(bounds?.getX(vertex) ?? 0),
16850
+ mapV(bounds?.getY(vertex) ?? 0),
16851
+ mapU(bounds?.getZ(vertex) ?? 1),
16852
+ mapV(bounds?.getW(vertex) ?? 1)
15353
16853
  );
15354
- colors.push(color?.getX(vertex) ?? 1, color?.getY(vertex) ?? 1, color?.getZ(vertex) ?? 1);
16854
+ colors.push(color2?.getX(vertex) ?? 1, color2?.getY(vertex) ?? 1, color2?.getZ(vertex) ?? 1);
15355
16855
  }
15356
16856
  for (let corner = 0; corner < 6; corner++) indices.push(start + sourceIndices.getX(face * 6 + corner) - face * 4);
15357
16857
  }
15358
16858
  }
15359
- const geometry = new import_three40.BufferGeometry();
15360
- geometry.setAttribute("position", new import_three40.Float32BufferAttribute(positions, 3));
15361
- geometry.setAttribute("normal", new import_three40.Float32BufferAttribute(normals, 3));
15362
- geometry.setAttribute("uv", new import_three40.Float32BufferAttribute(uvs, 2));
15363
- geometry.setAttribute("color", new import_three40.Float32BufferAttribute(colors, 3));
16859
+ const geometry = new import_three41.BufferGeometry();
16860
+ geometry.setAttribute("position", new import_three41.Float32BufferAttribute(positions, 3));
16861
+ geometry.setAttribute("normal", new import_three41.Float32BufferAttribute(normals, 3));
16862
+ geometry.setAttribute("uv", new import_three41.Float32BufferAttribute(uvs, 2));
16863
+ geometry.setAttribute("uvBounds", new import_three41.Float32BufferAttribute(uvBounds, 4));
16864
+ geometry.setAttribute("color", new import_three41.Float32BufferAttribute(colors, 3));
15364
16865
  geometry.setIndex(indices);
15365
16866
  geometry.computeBoundingBox();
15366
16867
  geometry.computeBoundingSphere();
15367
- const material = Materials.createShadedCanvasMaterial(canvas, false);
16868
+ const material = Materials.createShadedCanvasMaterial(canvas, false, false, true);
15368
16869
  material.vertexColors = true;
15369
16870
  const texture = material.uniforms?.map?.value ?? material.map;
15370
- const mesh = new import_three40.Mesh(geometry, material);
16871
+ const mesh = new import_three41.Mesh(geometry, material);
15371
16872
  section.add(mesh);
15372
16873
  section.ownedMeshes.push({ mesh, texture });
15373
16874
  }
@@ -15403,10 +16904,10 @@ var Chunk = class _Chunk {
15403
16904
  }
15404
16905
  getBlockAt(posOrX, y, z) {
15405
16906
  if (typeof posOrX == "number") {
15406
- return this.getBlockAt(new import_three41.Vector3(posOrX, y, z));
16907
+ return this.getBlockAt(new import_three42.Vector3(posOrX, y, z));
15407
16908
  }
15408
16909
  if (isTripleArray(posOrX)) {
15409
- return this.getBlockAt(new import_three41.Vector3(posOrX[0], posOrX[1], posOrX[2]));
16910
+ return this.getBlockAt(new import_three42.Vector3(posOrX[0], posOrX[1], posOrX[2]));
15410
16911
  }
15411
16912
  const pos = this.worldPosToChunkPos(posOrX);
15412
16913
  const index = _Chunk.chunkPosToBlockIndex(pos);
@@ -15414,10 +16915,10 @@ var Chunk = class _Chunk {
15414
16915
  }
15415
16916
  async setBlockAt(posOrX, yOrBlock, z, block) {
15416
16917
  if (typeof posOrX == "number") {
15417
- return this.setBlockAt(new import_three41.Vector3(posOrX, yOrBlock, z), block);
16918
+ return this.setBlockAt(new import_three42.Vector3(posOrX, yOrBlock, z), block);
15418
16919
  }
15419
16920
  if (isTripleArray(posOrX)) {
15420
- return this.setBlockAt(new import_three41.Vector3(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
16921
+ return this.setBlockAt(new import_three42.Vector3(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
15421
16922
  }
15422
16923
  const worldPos = posOrX;
15423
16924
  const pos = this.worldPosToChunkPos(worldPos);
@@ -15425,7 +16926,7 @@ var Chunk = class _Chunk {
15425
16926
  return this.setBlockInChunkAt(pos, block, worldPos);
15426
16927
  }
15427
16928
  /**
15428
- * Set a block at integer chunk-local coordinates from 0 to 15.
16929
+ * Places a block at integer chunk-local coordinates from 0 to 15.
15429
16930
  */
15430
16931
  async setBlockInChunkAt(pos, block, worldPos, onBlocksChanged = this.onBlocksChanged) {
15431
16932
  if (typeof worldPos === "undefined") {
@@ -15438,7 +16939,7 @@ var Chunk = class _Chunk {
15438
16939
  this.renderedBlocks.get(index)?.object?.removeFromScene();
15439
16940
  if (this.sectionBlocks.delete(index)) this.meshDirty = true;
15440
16941
  this.renderedBlocks.delete(index);
15441
- let object;
16942
+ let object3;
15442
16943
  try {
15443
16944
  if (!readBlock) return void 0;
15444
16945
  const stored = readBlock();
@@ -15453,27 +16954,28 @@ var Chunk = class _Chunk {
15453
16954
  this.sectionBlocks.set(index, { index, template, cullMask: 0 });
15454
16955
  this.meshDirty = true;
15455
16956
  } else {
15456
- object = await this.scene.addBlock(blockState, {
16957
+ object3 = await this.scene.addBlock(blockState, {
15457
16958
  mergeMeshes: true,
15458
16959
  instanceMeshes: true,
15459
16960
  maxInstanceCount: 2e3,
15460
16961
  initialState: stored.properties
15461
16962
  });
15462
- object.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
16963
+ object3.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
15463
16964
  }
15464
16965
  const info = { get block() {
15465
16966
  return readBlock();
15466
- }, object };
16967
+ }, object: object3 };
15467
16968
  this.renderedBlocks.set(index, info);
15468
16969
  return info;
15469
16970
  } catch (error) {
15470
16971
  this.data.set(index, void 0);
15471
- object?.removeFromScene();
16972
+ object3?.removeFromScene();
15472
16973
  throw error;
15473
16974
  } finally {
15474
16975
  await onBlocksChanged?.([worldPos]);
15475
16976
  }
15476
16977
  }
16978
+ /** Changes visibility at world block coordinates while preserving the block's data. */
15477
16979
  async setBlockVisibleAt(pos, visible) {
15478
16980
  const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
15479
16981
  const block = this.renderedBlocks.get(index);
@@ -15527,9 +17029,10 @@ var Chunk = class _Chunk {
15527
17029
  this.scene.dirty = true;
15528
17030
  this.meshDirty = false;
15529
17031
  }
17032
+ /** Removes stored blocks and their render objects, then notifies the owning world of changed positions. */
15530
17033
  async clear(onBlocksChanged = this.onBlocksChanged) {
15531
17034
  const positions = [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
15532
- new import_three41.Vector3(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
17035
+ new import_three42.Vector3(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
15533
17036
  ));
15534
17037
  for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
15535
17038
  this.sectionBlocks.clear();
@@ -15547,6 +17050,7 @@ var Chunk = class _Chunk {
15547
17050
  static isAir(block) {
15548
17051
  return ChunkData.isAir(block);
15549
17052
  }
17053
+ /** Maps chunk-local coordinates from 0 to 15 to a storage index, with x varying fastest, then z, then y. */
15550
17054
  static chunkPosToBlockIndex(pos) {
15551
17055
  if ([pos.x, pos.y, pos.z].some((value) => !Number.isInteger(value) || value < 0 || value >= 16)) {
15552
17056
  throw new RangeError("Block coordinates must be integers from 0 to 15 within a chunk");
@@ -15554,14 +17058,14 @@ var Chunk = class _Chunk {
15554
17058
  return pos.y * 16 * 16 + pos.z * 16 + pos.x;
15555
17059
  }
15556
17060
  worldPosToChunkPos(pos) {
15557
- return new import_three41.Vector3(
17061
+ return new import_three42.Vector3(
15558
17062
  pos.x - this.x * 16,
15559
17063
  pos.y - this.y * 16,
15560
17064
  pos.z - this.z * 16
15561
17065
  );
15562
17066
  }
15563
17067
  chunkPosToWorldPos(pos) {
15564
- return new import_three41.Vector3(
17068
+ return new import_three42.Vector3(
15565
17069
  this.x * 16 + pos.x,
15566
17070
  this.y * 16 + pos.y,
15567
17071
  this.z * 16 + pos.z