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.
@@ -69,6 +69,7 @@ var init_PersistentCache = __esm({
69
69
  /** Bump to invalidate every persisted entry. */
70
70
  this.VERSION = 2;
71
71
  }
72
+ /** Opens a named store in the active environment using the current cache schema version. */
72
73
  static open(name) {
73
74
  return Env.provider.openCache(name, _PersistentCache.VERSION);
74
75
  }
@@ -300,6 +301,9 @@ var init_Caching = __esm({
300
301
  static {
301
302
  this.fontCache = _Caching.createAssetCache();
302
303
  }
304
+ static {
305
+ this.bannerPatternCache = _Caching.createAssetCache();
306
+ }
303
307
  static createAssetCache() {
304
308
  return import_loading_cache.Caches.builder().expireAfterWrite(import_time.Time.minutes(10)).expireAfterAccess(import_time.Time.minutes(5)).expirationInterval(import_time.Time.seconds(30)).buildAsync();
305
309
  }
@@ -327,9 +331,11 @@ var init_Caching = __esm({
327
331
  this.entityTextureCache,
328
332
  this.entityAnimationCache,
329
333
  this.blockEntityIndexCache,
330
- this.fontCache
334
+ this.fontCache,
335
+ this.bannerPatternCache
331
336
  ];
332
337
  }
338
+ /** Invalidates in-memory entries so later lookups reload them. Persisted entries are retained. */
333
339
  static clear() {
334
340
  for (const cache of this.all) {
335
341
  cache.invalidateAll();
@@ -372,6 +378,7 @@ var init_Geometries = __esm({
372
378
  }
373
379
  return geometry;
374
380
  }
381
+ /** Returns shared geometry. Use {@link createBox} when the caller needs to modify or dispose it independently. */
375
382
  static getBox(key) {
376
383
  const keyStr = serializeBoxGeometryKey(key);
377
384
  return Caching.boxGeometryCache.get(keyStr, (k) => {
@@ -555,12 +562,15 @@ var init_Requests = __esm({
555
562
  static {
556
563
  this.mcAssetRequestQueue = new RequestQueue();
557
564
  }
565
+ /** Queues a general request. Set `request.signal` to cancel it and `request.timeout` to limit each attempt. */
558
566
  static genericRequest(request) {
559
567
  return this.genericQueue.request(request);
560
568
  }
569
+ /** Queues a request separately from general traffic, using the configured asset base URL for relative paths. */
561
570
  static mcAssetRequest(request) {
562
571
  return this.mcAssetRequestQueue.request(request);
563
572
  }
573
+ /** Sets the base URL for relative asset requests. Use {@link AssetLoader.setVersion} to change Minecraft versions. */
564
574
  static setMcAssetRoot(root) {
565
575
  this.mcAssetRequestQueue.baseURL = root;
566
576
  }
@@ -613,6 +623,7 @@ var init_WrappedImage = __esm({
613
623
  }
614
624
  return false;
615
625
  }
626
+ /** Whether the dimensions form a vertical strip of square frames, without consulting `.mcmeta`. */
616
627
  get animated() {
617
628
  return this.height > this.width && this.height % this.width === 0;
618
629
  }
@@ -659,6 +670,7 @@ var init_ImageLoader = __esm({
659
670
  static _createImage() {
660
671
  return createImage();
661
672
  }
673
+ /** Resolves when an image URL has decoded, or rejects if decoding fails. */
662
674
  static async loadAsync(src) {
663
675
  return new Promise((resolve, reject) => {
664
676
  const image = this._createImage();
@@ -667,6 +679,7 @@ var init_ImageLoader = __esm({
667
679
  image.src = src;
668
680
  });
669
681
  }
682
+ /** Returns an image immediately. Use the callbacks to observe loading, or await {@link loadAsync}. */
670
683
  static loadElement(src, onload, onerr) {
671
684
  const image = this._createImage();
672
685
  if (onload)
@@ -694,6 +707,7 @@ var init_ImageLoader = __esm({
694
707
  throw err;
695
708
  }
696
709
  }
710
+ /** Decodes the supplied bytes into a canvas without fetching the source URL again. */
697
711
  static async infoToCanvasData(info) {
698
712
  const type = info.type === "jpg" ? "jpeg" : info.type ?? "png";
699
713
  const image = await _ImageLoader.loadAsync(`data:image/${type};base64,${info.data.toString("base64")}`);
@@ -710,12 +724,14 @@ var init_ImageLoader = __esm({
710
724
  const image = await this.infoToCanvasData(info);
711
725
  return image.data.getImageData(0, 0, image.width, image.height);
712
726
  }
727
+ /** Returns cached RGBA pixels for an image URL or data URL. Failed loads can be retried. */
713
728
  static async getData(src) {
714
729
  const keyStr = serializeImageKey({ src });
715
730
  return await Caching.imageDataCache.get(keyStr, (k) => {
716
731
  return _ImageLoader.loadData(src);
717
732
  });
718
733
  }
734
+ /** Returns a cached canvas context and dimensions for an image URL or data URL. */
719
735
  static async getCanvasData(src) {
720
736
  const keyStr = serializeImageKey({ src });
721
737
  return await Caching.canvasImageDataCache.get(keyStr, (k) => {
@@ -801,7 +817,7 @@ var init_TextureLoader = __esm({
801
817
  static createTexture() {
802
818
  return new import_three3.Texture();
803
819
  }
804
- static loadInBackground(src, format = import_three3.RGBAFormat, rotation = 0) {
820
+ static loadInBackground(src, format2 = import_three3.RGBAFormat, rotation = 0) {
805
821
  const texture = this.createTexture();
806
822
  texture.colorSpace = THREE.SRGBColorSpace;
807
823
  const image = ImageLoader.loadElement(src, () => {
@@ -811,11 +827,12 @@ var init_TextureLoader = __esm({
811
827
  console.warn("Failed to load texture", src, error);
812
828
  });
813
829
  texture.image = image;
814
- texture.format = format;
830
+ texture.format = format2;
815
831
  texture.rotation = rotation;
816
832
  return Textures.initTextureProps(texture);
817
833
  }
818
- static load(src, format = import_three3.RGBAFormat, rotation = 0) {
834
+ /** Loads image pixels in the background. `rotation` is in radians. Check {@link hasFailed} for load failures. */
835
+ static load(src, format2 = import_three3.RGBAFormat, rotation = 0) {
819
836
  const texture = new import_three3.Texture();
820
837
  texture.colorSpace = THREE.SRGBColorSpace;
821
838
  ImageLoader.getData(src).then((image) => {
@@ -825,7 +842,7 @@ var init_TextureLoader = __esm({
825
842
  this.failedTextures.add(texture);
826
843
  console.warn("Failed to load texture", src, error);
827
844
  });
828
- texture.format = format;
845
+ texture.format = format2;
829
846
  texture.rotation = rotation;
830
847
  return Textures.initTextureProps(texture);
831
848
  }
@@ -851,8 +868,8 @@ var init_Textures = __esm({
851
868
  for (let y = 0; y < size; y++) {
852
869
  for (let x = 0; x < size; x++) {
853
870
  const offset = (y * size + x) * 4;
854
- const color = (x + y) % 2 === 0 ? 255 : 0;
855
- data[offset] = data[offset + 2] = color;
871
+ const color2 = (x + y) % 2 === 0 ? 255 : 0;
872
+ data[offset] = data[offset + 2] = color2;
856
873
  data[offset + 3] = 255;
857
874
  }
858
875
  }
@@ -863,6 +880,7 @@ var init_Textures = __esm({
863
880
  return this.initTextureProps(texture);
864
881
  });
865
882
  }
883
+ /** Sets nearest-neighbor filters without changing the texture's color space. Returns the same texture. */
866
884
  static initTextureProps(texture) {
867
885
  texture.magFilter = THREE2.NearestFilter;
868
886
  texture.minFilter = THREE2.NearestFilter;
@@ -875,11 +893,13 @@ var init_Textures = __esm({
875
893
  static createAsset(key) {
876
894
  return new import_three4.Texture();
877
895
  }
896
+ /** Creates an sRGB color texture from a canvas. */
878
897
  static createCanvasTexture(canvas) {
879
898
  const texture = new import_three4.CanvasTexture(canvas);
880
899
  texture.colorSpace = THREE2.SRGBColorSpace;
881
900
  return this.initTextureProps(texture);
882
901
  }
902
+ /** Returns a shared texture immediately and loads its image asynchronously. Failed cached loads are retried. */
883
903
  static getImage(key) {
884
904
  const keyStr = serializeTextureKey(key);
885
905
  const cached2 = Caching.textureCache.peek(keyStr);
@@ -904,6 +924,7 @@ var init_Materials = __esm({
904
924
  init_TextureLoader();
905
925
  init_Caching();
906
926
  Materials = class _Materials {
927
+ /** Shared checkerboard material, created on first access. */
907
928
  static get MISSING_TEXTURE() {
908
929
  return Caching.materialCache.get("builtin:missing-texture", () => new import_three5.MeshBasicMaterial({
909
930
  map: Textures.getMissing(),
@@ -975,6 +996,7 @@ var init_Materials = __esm({
975
996
  material.depthWrite = false;
976
997
  break;
977
998
  case "eyes":
999
+ case "no_outline":
978
1000
  material.transparent = true;
979
1001
  material.alphaTest = 0;
980
1002
  material.depthWrite = false;
@@ -1007,6 +1029,7 @@ var init_Materials = __esm({
1007
1029
  if (tint !== void 0) material.color.multiply(new import_three5.Color(tint));
1008
1030
  return material;
1009
1031
  }
1032
+ /** Creates an unlit, transparent GUI material without depth writes or tone mapping. */
1010
1033
  static createGuiCanvasMaterial(canvas) {
1011
1034
  const material = new import_three5.MeshBasicMaterial({
1012
1035
  map: Textures.createCanvasTexture(canvas),
@@ -1032,7 +1055,7 @@ var init_Materials = __esm({
1032
1055
  };
1033
1056
  return material;
1034
1057
  }
1035
- static createShadedCanvasMaterial(canvas, transparent = false, shade = false) {
1058
+ static createShadedCanvasMaterial(canvas, transparent = false, shade = false, boundUvs = false) {
1036
1059
  const vertShader = `
1037
1060
  uniform bool SHADE;
1038
1061
 
@@ -1042,6 +1065,10 @@ var init_Materials = __esm({
1042
1065
  #else
1043
1066
  varying vec2 vUv;
1044
1067
  #endif
1068
+ #ifdef BOUND_UVS
1069
+ attribute vec4 uvBounds;
1070
+ flat out vec4 vUvBounds;
1071
+ #endif
1045
1072
  varying vec3 vTint;
1046
1073
  varying float light;
1047
1074
  varying float lift;
@@ -1077,13 +1104,17 @@ var init_Materials = __esm({
1077
1104
  }
1078
1105
 
1079
1106
  vUv = uv;
1107
+ #ifdef BOUND_UVS
1108
+ vUvBounds = uvBounds;
1109
+ #endif
1080
1110
  vTint = color;
1081
1111
 
1112
+ vec4 mvPosition = vec4(position, 1.0);
1082
1113
  #ifdef USE_INSTANCING
1083
- gl_Position = projectionMatrix * viewMatrix * modelMatrix * instanceMatrix * vec4(position, 1.0);
1084
- #else
1085
- gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
1114
+ mvPosition = instanceMatrix * mvPosition;
1086
1115
  #endif
1116
+ mvPosition = modelViewMatrix * mvPosition;
1117
+ gl_Position = projectionMatrix * mvPosition;
1087
1118
 
1088
1119
  }
1089
1120
  `;
@@ -1103,13 +1134,22 @@ var init_Materials = __esm({
1103
1134
  #else
1104
1135
  varying vec2 vUv;
1105
1136
  #endif
1137
+ #ifdef BOUND_UVS
1138
+ flat in vec4 vUvBounds;
1139
+ #endif
1106
1140
  varying vec3 vTint;
1107
1141
  varying float light;
1108
1142
  varying float lift;
1109
1143
 
1110
1144
  void main(void)
1111
1145
  {
1112
- vec4 color = texture2D(map, vUv);
1146
+ #ifdef BOUND_UVS
1147
+ // Keep edge samples inside their texture even without centroid interpolation.
1148
+ vec2 mapUv = clamp(vUv, vUvBounds.xy, vUvBounds.zw);
1149
+ #else
1150
+ vec2 mapUv = vUv;
1151
+ #endif
1152
+ vec4 color = texture2D(map, mapUv);
1113
1153
  color.rgb *= vTint;
1114
1154
 
1115
1155
  if (color.a < 0.01) discard;
@@ -1130,7 +1170,8 @@ var init_Materials = __esm({
1130
1170
  }
1131
1171
  `;
1132
1172
  try {
1133
- return new import_three5.ShaderMaterial({
1173
+ const material = new import_three5.ShaderMaterial({
1174
+ defines: boundUvs ? { BOUND_UVS: true } : {},
1134
1175
  uniforms: {
1135
1176
  SHADE: { value: true },
1136
1177
  BRIGHTNESS: { value: 1 },
@@ -1148,11 +1189,14 @@ var init_Materials = __esm({
1148
1189
  side: transparent ? import_three5.DoubleSide : import_three5.FrontSide,
1149
1190
  alphaTest: 0.5
1150
1191
  });
1192
+ Object.assign(material.defaultAttributeValues, { uvBounds: [0, 0, 1, 1] });
1193
+ return material;
1151
1194
  } catch (e) {
1152
1195
  console.warn(e);
1153
1196
  }
1154
1197
  return this.createBasicCanvasMaterial(canvas, transparent, shade);
1155
1198
  }
1199
+ /** Returns a shared image material. Use {@link createImage} for a separate material instance. */
1156
1200
  static getImage(key) {
1157
1201
  const keyStr = serializeMaterialKey(key);
1158
1202
  const map = Caching.materialCache.peek(keyStr)?.map;
@@ -1327,6 +1371,14 @@ var init_AssetKey = __esm({
1327
1371
  }
1328
1372
  };
1329
1373
  AssetKey = class _AssetKey extends BasicAssetKey {
1374
+ /**
1375
+ * Creates a key from separate path components. Use {@link parse} for a combined reference.
1376
+ *
1377
+ * @param assetType - Asset directory, such as `models`, `textures`, or `blockstates`.
1378
+ * @param type - Subdirectory, such as `block` or `item`.
1379
+ * @param rootType - Top-level directory, usually `assets` or `data`.
1380
+ * @param root - Base URL override for hosted asset sources.
1381
+ */
1330
1382
  constructor(namespace, path, assetType, type, rootType = "assets", extension = ".json", root) {
1331
1383
  super(namespace, path);
1332
1384
  this.namespace = namespace;
@@ -1338,6 +1390,16 @@ var init_AssetKey = __esm({
1338
1390
  this.root = root;
1339
1391
  this.isAssetKey = true;
1340
1392
  }
1393
+ /**
1394
+ * Parses an asset reference such as `minecraft:block/stone`.
1395
+ * A missing namespace defaults to the origin's namespace or `minecraft`.
1396
+ *
1397
+ * @param assetType - Asset category, such as `models` or `textures`.
1398
+ * @param str - Path with an optional namespace and file extension.
1399
+ * @param origin - Supplies the asset root, default namespace, and non-texture extension.
1400
+ * @returns A key with the matching extension removed from its path. Textures use `.png`;
1401
+ * other assets use the origin's extension or `.json`.
1402
+ */
1341
1403
  static parse(assetType, str, origin) {
1342
1404
  let namespace = origin?.namespace || DEFAULT_NAMESPACE;
1343
1405
  if (str.includes(":")) {
@@ -1369,6 +1431,7 @@ var init_AssetKey = __esm({
1369
1431
  ];
1370
1432
  return a.join("/");
1371
1433
  }
1434
+ /** Joins the type subdirectory and path without adding a namespace or file extension. */
1372
1435
  getFullPath() {
1373
1436
  let p10 = [];
1374
1437
  if (this.type) {
@@ -1380,6 +1443,7 @@ var init_AssetKey = __esm({
1380
1443
  toNamespacedString() {
1381
1444
  return this.namespace + ":" + this.getFullPath();
1382
1445
  }
1446
+ /** Returns a cache identifier that includes the explicit root or {@link AssetLoader.ROOT}. */
1383
1447
  serialize() {
1384
1448
  return this.toString();
1385
1449
  }
@@ -1391,17 +1455,17 @@ var init_AssetKey = __esm({
1391
1455
  var DisplayPosition, DISPLAY_POSITIONS;
1392
1456
  var init_DisplayPosition = __esm({
1393
1457
  "src/model/DisplayPosition.ts"() {
1394
- DisplayPosition = /* @__PURE__ */ ((DisplayPosition2) => {
1395
- DisplayPosition2["THIRDPERSON_RIGHTHAND"] = "thirdperson_righthand";
1396
- DisplayPosition2["THIRDPERSON_LEFTHAND"] = "thirdperson_lefthand";
1397
- DisplayPosition2["FIRSTPERSON_RIGHTHAND"] = "firstperson_righthand";
1398
- DisplayPosition2["FIRSTPERSON_LEFTHAND"] = "firstperson_lefthand";
1399
- DisplayPosition2["GUI"] = "gui";
1400
- DisplayPosition2["HEAD"] = "head";
1401
- DisplayPosition2["GROUND"] = "ground";
1402
- DisplayPosition2["FIXED"] = "fixed";
1403
- DisplayPosition2["ON_SHELF"] = "on_shelf";
1404
- return DisplayPosition2;
1458
+ DisplayPosition = /* @__PURE__ */ ((DisplayPosition3) => {
1459
+ DisplayPosition3["THIRDPERSON_RIGHTHAND"] = "thirdperson_righthand";
1460
+ DisplayPosition3["THIRDPERSON_LEFTHAND"] = "thirdperson_lefthand";
1461
+ DisplayPosition3["FIRSTPERSON_RIGHTHAND"] = "firstperson_righthand";
1462
+ DisplayPosition3["FIRSTPERSON_LEFTHAND"] = "firstperson_lefthand";
1463
+ DisplayPosition3["GUI"] = "gui";
1464
+ DisplayPosition3["HEAD"] = "head";
1465
+ DisplayPosition3["GROUND"] = "ground";
1466
+ DisplayPosition3["FIXED"] = "fixed";
1467
+ DisplayPosition3["ON_SHELF"] = "on_shelf";
1468
+ return DisplayPosition3;
1405
1469
  })(DisplayPosition || {});
1406
1470
  DISPLAY_POSITIONS = Object.values(DisplayPosition);
1407
1471
  }
@@ -1439,6 +1503,7 @@ var init_DisplayTransforms = __esm({
1439
1503
  const scale = new import_three7.Vector3(...transform.scale ?? [1, 1, 1]).clampScalar(-4, 4);
1440
1504
  return new import_three7.Matrix4().compose(translation, rotation, scale);
1441
1505
  }
1506
+ /** Transforms geometry in place and reverses triangle winding when the matrix reflects it. */
1442
1507
  static apply(geometry, matrix) {
1443
1508
  geometry.applyMatrix4(matrix);
1444
1509
  if (matrix.determinant() < 0) {
@@ -1466,7 +1531,10 @@ var init_ModelMerger = __esm({
1466
1531
  init_AssetKey();
1467
1532
  init_DisplayTransforms();
1468
1533
  ModelMerger = class {
1534
+ /** Loads the parent chain and returns a merged model without modifying the supplied definition. */
1469
1535
  static async mergeWithParents(model) {
1536
+ const parts = model.parts;
1537
+ if (parts) return { ...model, parts: await Promise.all(parts.map((part) => this.mergeWithParents(part))) };
1470
1538
  const models = await this.collectAllParents(model);
1471
1539
  let merged = {};
1472
1540
  for (const source of [...models, model]) {
@@ -1478,6 +1546,7 @@ var init_ModelMerger = __esm({
1478
1546
  }
1479
1547
  merged.hierarchy = models.map((m) => m.parent).filter((p10) => `${p10}`);
1480
1548
  merged.hierarchy.push(`${merged.parent}`);
1549
+ if (model.components) merged.components = model.components;
1481
1550
  return merged;
1482
1551
  }
1483
1552
  static async collectAllParents(model) {
@@ -1516,6 +1585,79 @@ var init_AssetParsers = __esm({
1516
1585
  }
1517
1586
  });
1518
1587
 
1588
+ // src/assets/BannerPatterns.ts
1589
+ var DYE_COLORS, BannerPatterns;
1590
+ var init_BannerPatterns = __esm({
1591
+ "src/assets/BannerPatterns.ts"() {
1592
+ init_AssetKey();
1593
+ init_AssetLoader();
1594
+ init_AssetParsers();
1595
+ init_Caching();
1596
+ DYE_COLORS = Object.freeze({
1597
+ white: 16383998,
1598
+ orange: 16351261,
1599
+ magenta: 13061821,
1600
+ light_blue: 3847130,
1601
+ yellow: 16701501,
1602
+ lime: 8439583,
1603
+ pink: 15961002,
1604
+ gray: 4673362,
1605
+ light_gray: 10329495,
1606
+ cyan: 1481884,
1607
+ purple: 8991416,
1608
+ blue: 3949738,
1609
+ brown: 8606770,
1610
+ green: 6192150,
1611
+ red: 11546150,
1612
+ black: 1908001
1613
+ });
1614
+ BannerPatterns = class {
1615
+ /** Lists namespaced pattern IDs from the selected namespace's data directory. */
1616
+ static async getList(namespace = "minecraft", root) {
1617
+ const collect = async (path) => {
1618
+ const prefix2 = path ? `${path}/` : "";
1619
+ const key = new AssetKey(namespace, `${prefix2}_list`, "banner_pattern", void 0, "data", ".json", root);
1620
+ const list2 = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get(key, "list" /* LIST */));
1621
+ if (!list2) return [];
1622
+ const files = list2.files.filter((file) => file.endsWith(".json") && file !== "_list.json").map((file) => `${namespace}:${prefix2}${file.slice(0, -5)}`);
1623
+ const children = await Promise.all(list2.directories.map((directory) => collect(`${prefix2}${directory}`)));
1624
+ return [...files, ...children.flat()];
1625
+ };
1626
+ return collect("");
1627
+ }
1628
+ /** Returns the named dye's sRGB value, rejecting unknown colors. */
1629
+ static getColor(color2) {
1630
+ if (typeof color2 !== "string" || !Object.prototype.hasOwnProperty.call(DYE_COLORS, color2)) throw new Error(`Unsupported dye color ${color2}`);
1631
+ return DYE_COLORS[color2];
1632
+ }
1633
+ /** Resolves the first 16 layers of a `banner_patterns` component, preserving their order. */
1634
+ static async getLayers(value, target, root) {
1635
+ if (value === void 0) return [];
1636
+ if (!Array.isArray(value)) throw new Error("banner_patterns must be an array");
1637
+ return Promise.all(value.slice(0, 16).map(async (layer) => {
1638
+ if (!layer || typeof layer !== "object" || Array.isArray(layer)) throw new Error("Each banner pattern requires a pattern and dye color");
1639
+ const color2 = this.getColor(layer.color);
1640
+ let pattern2 = layer.pattern;
1641
+ if (typeof pattern2 === "string") {
1642
+ const id = this.identifier(pattern2);
1643
+ const key = new AssetKey(id.namespace, id.getFullPath(), "banner_pattern", void 0, "data", ".json", root);
1644
+ pattern2 = await Caching.bannerPatternCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
1645
+ if (!pattern2) throw new Error(`Missing banner pattern ${layer.pattern}`);
1646
+ }
1647
+ if (!pattern2 || typeof pattern2 !== "object" || Array.isArray(pattern2) || typeof pattern2.translation_key !== "string") throw new Error("Banner patterns require asset_id and translation_key");
1648
+ const asset2 = this.identifier(pattern2.asset_id);
1649
+ return { color: color2, texture: new AssetKey(asset2.namespace, `${target}/${asset2.getFullPath()}`, "textures", "entity", "assets", ".png", root) };
1650
+ }));
1651
+ }
1652
+ static identifier(value) {
1653
+ if (typeof value !== "string" || !/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(value)) throw new Error(`Invalid banner-pattern identifier ${value}`);
1654
+ const [namespace, path] = value.includes(":") ? value.split(":") : ["minecraft", value];
1655
+ return new AssetKey(namespace, path);
1656
+ }
1657
+ };
1658
+ }
1659
+ });
1660
+
1519
1661
  // src/assets/Models.ts
1520
1662
  var Models;
1521
1663
  var init_Models = __esm({
@@ -1527,12 +1669,15 @@ var init_Models = __esm({
1527
1669
  init_PersistentCache();
1528
1670
  init_AssetKey();
1529
1671
  init_AssetParsers();
1672
+ init_DisplayPosition();
1673
+ init_BannerPatterns();
1530
1674
  Models = class _Models {
1531
1675
  // opened lazily: touching the store at import time would hit IndexedDB/disk just for loading
1532
1676
  // the library, and would require an EnvProvider before any entry has had a chance to register
1533
1677
  static get PERSISTENT_CACHE() {
1534
1678
  return this._persistentCache ??= PersistentCache.open("minerender-models");
1535
1679
  }
1680
+ /** Lists item filenames from the asset source's `_list.json`, with a legacy model-directory fallback. */
1536
1681
  static async getItemList() {
1537
1682
  const key = new AssetKey(DEFAULT_NAMESPACE, "_list", "items");
1538
1683
  const legacyKey = new AssetKey(DEFAULT_NAMESPACE, "_list", "models", "item");
@@ -1540,34 +1685,174 @@ var init_Models = __esm({
1540
1685
  return (await AssetLoader.getFirst([key, legacyKey], "list" /* LIST */))?.asset;
1541
1686
  }).then((r) => r?.files ?? []);
1542
1687
  }
1543
- static async loadAndMerge(key) {
1544
- const model = key.type === "item" ? await this.getItemModel(key) : await this.getRaw(key);
1688
+ static async loadAndMerge(key, context = {}) {
1689
+ const model = key.type === "item" ? await this.getItemModel(key, context) : await this.getRaw(key);
1545
1690
  if (!model) {
1546
1691
  return void 0;
1547
1692
  }
1548
1693
  return ModelMerger.mergeWithParents(model);
1549
1694
  }
1550
- static async getItemModel(key) {
1695
+ static async getItemModel(key, context = {}) {
1696
+ const preview = this.snapshotContext(key, context);
1551
1697
  const itemKey = new AssetKey(key.namespace, key.path, "items", void 0, key.rootType, ".json", key.root);
1552
- const model = await this.PERSISTENT_CACHE.getOrLoad(`item-v2:${AssetLoader.persistentKey(itemKey.serialize())}`, async () => {
1698
+ const cacheKey = itemKey.serialize() + this.contextKey(preview);
1699
+ const model = await this.PERSISTENT_CACHE.getOrLoad(`item-v4:${AssetLoader.persistentKey(cacheKey)}`, async () => {
1553
1700
  const result = await AssetLoader.getFirst([itemKey, key], "json" /* JSON */);
1554
1701
  if (!result) return void 0;
1555
- if (result.key.assetType !== "items") return { ...result.asset, key };
1556
- const selected = this.defaultItemModel(result.asset.model, key);
1557
- const modelKey = AssetKey.parse("models", selected.model);
1558
- modelKey.root = key.root;
1559
- const model2 = await this.getRaw(modelKey);
1560
- if (!model2) throw new Error(`Item ${key.toNamespacedString()} references missing model ${selected.model}`);
1561
- return { ...model2, ...selected.special && { special: selected.special }, ...selected.tints && { tints: selected.tints } };
1702
+ if (result.key.assetType !== "items") return { ...result.asset, key, components: preview.components };
1703
+ const load = async (selected) => {
1704
+ if ("parts" in selected) {
1705
+ return { key, parts: await Promise.all(selected.parts.map(load)) };
1706
+ }
1707
+ if ("item" in selected) {
1708
+ const model3 = await this.getItemModel(selected.item, { displayContext: preview.displayContext });
1709
+ if (!model3) throw new Error(`Item ${key.toNamespacedString()} references missing item ${selected.item.toNamespacedString()}`);
1710
+ return model3;
1711
+ }
1712
+ const modelKey = AssetKey.parse("models", selected.model);
1713
+ modelKey.root = key.root;
1714
+ const model2 = await this.getRaw(modelKey);
1715
+ if (!model2) throw new Error(`Item ${key.toNamespacedString()} references missing model ${selected.model}`);
1716
+ return { ...model2, components: preview.components, ...selected.special && { special: selected.special }, ...selected.tints && { tints: selected.tints } };
1717
+ };
1718
+ return load(this.selectItemModel(result.asset.model, key, preview));
1719
+ });
1720
+ const restore = (model2) => ({
1721
+ ...model2,
1722
+ key: Object.assign(new AssetKey("", ""), model2.key),
1723
+ ...model2.parts && { parts: model2.parts.map(restore) }
1562
1724
  });
1563
- return model ? { ...model, key: Object.assign(new AssetKey("", ""), model.key) } : void 0;
1725
+ return model ? restore(model) : void 0;
1726
+ }
1727
+ static contextIdentifier(id) {
1728
+ if (typeof id !== "string" || !/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(id)) {
1729
+ throw new Error(`Invalid item-preview identifier: ${id}`);
1730
+ }
1731
+ return id.includes(":") ? id : `minecraft:${id}`;
1732
+ }
1733
+ static snapshotContext(key, context) {
1734
+ const displayContext = context.displayContext ?? "gui" /* GUI */;
1735
+ if (displayContext !== "none" && !Object.values(DisplayPosition).includes(displayContext)) {
1736
+ throw new Error(`Unsupported item display context: ${displayContext}`);
1737
+ }
1738
+ for (const map of [context.properties, context.components, context.itemReferences]) {
1739
+ if (map !== void 0 && (!map || typeof map !== "object" || Array.isArray(map))) {
1740
+ throw new Error("Item-preview properties, components, and references must be objects keyed by identifier");
1741
+ }
1742
+ }
1743
+ const count = context.count === void 0 ? 1 : context.count;
1744
+ if (!Number.isSafeInteger(count) || count < 0) throw new Error("Item-preview count must be a nonnegative safe integer");
1745
+ const properties = {};
1746
+ for (const [id, value] of Object.entries(context.properties ?? {})) {
1747
+ const property = this.contextIdentifier(id);
1748
+ if (!["boolean", "string", "number"].includes(typeof value) || typeof value === "number" && !Number.isFinite(value)) {
1749
+ throw new Error(`Item-preview property ${property} must be a boolean, string, or finite number`);
1750
+ }
1751
+ if (Object.prototype.hasOwnProperty.call(properties, property)) throw new Error(`Duplicate item-preview property: ${property}`);
1752
+ properties[property] = value;
1753
+ }
1754
+ const components = {};
1755
+ for (const [id, value] of Object.entries(context.components ?? {})) {
1756
+ const component = this.contextIdentifier(id);
1757
+ if (Object.prototype.hasOwnProperty.call(components, component)) throw new Error(`Duplicate item-preview component: ${component}`);
1758
+ components[component] = this.snapshotJson(value);
1759
+ }
1760
+ const itemReferences = {};
1761
+ for (const [id, value] of Object.entries(context.itemReferences ?? {})) {
1762
+ const reference = this.contextIdentifier(id);
1763
+ if (!isAssetKey(value) || value.assetType !== "models" || value.type !== "item") {
1764
+ throw new Error(`Item-preview reference ${reference} must be an item-model AssetKey`);
1765
+ }
1766
+ this.contextIdentifier(`${value.namespace}:${value.path}`);
1767
+ if (Object.prototype.hasOwnProperty.call(itemReferences, reference)) throw new Error(`Duplicate item-preview reference: ${reference}`);
1768
+ itemReferences[reference] = Object.assign(new AssetKey("", ""), value, { root: value.root ?? key.root });
1769
+ }
1770
+ return { displayContext, properties, components, count, itemReferences };
1771
+ }
1772
+ static contextKey(context) {
1773
+ const itemReferences = Object.fromEntries(Object.entries(context.itemReferences).map(([id, key]) => [id, key.serialize()]));
1774
+ return `|item-v4:${JSON.stringify(this.snapshotJson({ ...context, itemReferences }))}`;
1564
1775
  }
1565
- // Item previews use the GUI context, false conditions, and zero numeric properties.
1566
- static defaultItemModel(node, key) {
1776
+ static snapshotJson(value, ancestors = /* @__PURE__ */ new Set()) {
1777
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) return value;
1778
+ if (typeof value !== "object" || ancestors.has(value) || !Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
1779
+ throw new Error("Item-preview components must contain finite JSON values without circular references");
1780
+ }
1781
+ ancestors.add(value);
1782
+ 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)]));
1783
+ ancestors.delete(value);
1784
+ return copy;
1785
+ }
1786
+ static propertyValue(node, context) {
1787
+ const kind = node.type.replace(/^minecraft:/, "");
1788
+ const property = node.property ? this.contextIdentifier(node.property) : void 0;
1789
+ if (kind === "select" && property === "minecraft:display_context") return context.displayContext;
1790
+ if (property && Object.prototype.hasOwnProperty.call(context.properties, property)) return context.properties[property];
1791
+ const fallback = kind === "condition" ? false : kind === "range_dispatch" ? 0 : void 0;
1792
+ const component = (id) => context.components[this.contextIdentifier(id)];
1793
+ if (property === "minecraft:custom_model_data") {
1794
+ const index = node.index ?? 0;
1795
+ if (!Number.isSafeInteger(index) || index < 0) throw new Error("Item-model custom_model_data index must be a nonnegative integer");
1796
+ const data = component("custom_model_data");
1797
+ if (data === void 0) return fallback;
1798
+ if (!data || typeof data !== "object" || Array.isArray(data)) throw new Error("Item-preview custom_model_data must be an object");
1799
+ const field = kind === "condition" ? "flags" : kind === "select" ? "strings" : "floats";
1800
+ const values = data[field];
1801
+ if (values === void 0) return fallback;
1802
+ if (!Array.isArray(values)) throw new Error(`Item-preview custom_model_data.${field} must be an array`);
1803
+ return index < values.length ? values[index] : fallback;
1804
+ }
1805
+ if (kind === "condition" && property === "minecraft:has_component") {
1806
+ if (node.ignore_default) throw new Error("Item-preview has_component with ignore_default requires an explicit properties override; item defaults are unavailable");
1807
+ return Object.prototype.hasOwnProperty.call(context.components, this.contextIdentifier(node.component));
1808
+ }
1809
+ if (kind === "select") {
1810
+ switch (property) {
1811
+ case "minecraft:component":
1812
+ return component(node.component);
1813
+ case "minecraft:block_state": {
1814
+ const state = component("block_state");
1815
+ if (state === void 0) return void 0;
1816
+ if (!state || typeof state !== "object" || Array.isArray(state)) throw new Error("Item-preview block_state must be an object");
1817
+ if (typeof node.block_state_property !== "string") throw new Error("Item-model block_state requires block_state_property");
1818
+ return Object.prototype.hasOwnProperty.call(state, node.block_state_property) ? state[node.block_state_property] : void 0;
1819
+ }
1820
+ case "minecraft:charge_type": {
1821
+ const projectiles = component("charged_projectiles");
1822
+ if (projectiles === void 0) return "none";
1823
+ if (!Array.isArray(projectiles)) throw new Error("Item-preview charged_projectiles must be an array of item stacks");
1824
+ const ids = projectiles.map((stack) => {
1825
+ if (!stack || typeof stack !== "object" || Array.isArray(stack)) throw new Error("Item-preview charged_projectiles must contain item-stack objects");
1826
+ return this.contextIdentifier(stack.id);
1827
+ });
1828
+ return !ids.length ? "none" : ids.includes("minecraft:firework_rocket") ? "rocket" : "arrow";
1829
+ }
1830
+ }
1831
+ }
1832
+ if (kind === "range_dispatch" && (property === "minecraft:damage" || property === "minecraft:count")) {
1833
+ const damage = property === "minecraft:damage";
1834
+ const maximumComponent = component(damage ? "max_damage" : "max_stack_size");
1835
+ const maximum = maximumComponent === void 0 ? damage ? 0 : 1 : maximumComponent;
1836
+ const damageComponent = component("damage");
1837
+ const value = damage ? damageComponent === void 0 ? 0 : damageComponent : context.count;
1838
+ if (typeof value !== "number" || typeof maximum !== "number" || maximum < 0) throw new Error(`Item-preview ${property} requires numeric components and a nonnegative maximum`);
1839
+ const clamped = Math.max(0, Math.min(maximum, value));
1840
+ return node.normalize === false ? clamped : clamped / maximum;
1841
+ }
1842
+ return fallback;
1843
+ }
1844
+ static selectItemModel(node, key, context) {
1567
1845
  if (!node || typeof node.type !== "string") {
1568
1846
  throw new Error(`Unsupported item model definition for ${key.toNamespacedString()}`);
1569
1847
  }
1570
1848
  switch (node.type.replace(/^minecraft:/, "")) {
1849
+ case "composite":
1850
+ if (Array.isArray(node.models)) return { parts: node.models.map((child) => this.selectItemModel(child, key, context)) };
1851
+ break;
1852
+ case "bundle/selected_item": {
1853
+ const item = context.itemReferences["minecraft:bundle/selected_item"];
1854
+ return item ? { item } : { parts: [] };
1855
+ }
1571
1856
  case "model":
1572
1857
  if (typeof node.model === "string" && node.model) return { model: node.model, tints: node.tints };
1573
1858
  break;
@@ -1575,6 +1860,23 @@ var init_Models = __esm({
1575
1860
  if (!node.base || !node.model || typeof node.model !== "object") break;
1576
1861
  const special = node.model;
1577
1862
  switch (special.type) {
1863
+ case "banner":
1864
+ case "minecraft:banner":
1865
+ if (typeof special.color === "string" && Object.prototype.hasOwnProperty.call(DYE_COLORS, special.color)) return { model: node.base, special };
1866
+ break;
1867
+ case "shield":
1868
+ case "minecraft:shield":
1869
+ case "trident":
1870
+ case "minecraft:trident":
1871
+ case "conduit":
1872
+ case "minecraft:conduit":
1873
+ return { model: node.base, special };
1874
+ case "shulker_box":
1875
+ case "minecraft:shulker_box":
1876
+ 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))) {
1877
+ return { model: node.base, special };
1878
+ }
1879
+ break;
1578
1880
  case "chest":
1579
1881
  case "minecraft:chest":
1580
1882
  case "bed":
@@ -1588,21 +1890,34 @@ var init_Models = __esm({
1588
1890
  }
1589
1891
  throw new Error(`Unsupported special item renderer ${special.type} for ${key.toNamespacedString()}`);
1590
1892
  }
1591
- case "condition":
1592
- return this.defaultItemModel(node.on_false, key);
1893
+ case "condition": {
1894
+ const value = this.propertyValue(node, context);
1895
+ if (typeof value !== "boolean") throw new Error(`Item-preview condition ${node.property} requires a boolean`);
1896
+ return this.selectItemModel(value ? node.on_true : node.on_false, key, context);
1897
+ }
1593
1898
  case "select": {
1594
- 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;
1595
- return this.defaultItemModel(selected ?? node.fallback, key);
1899
+ const value = this.propertyValue(node, context);
1900
+ const component = node.property && this.contextIdentifier(node.property) === "minecraft:component";
1901
+ if (!component && value !== void 0 && typeof value !== "string") throw new Error(`Item-preview selector ${node.property} requires a string`);
1902
+ const matches = (candidate) => component ? JSON.stringify(this.snapshotJson(candidate)) === JSON.stringify(this.snapshotJson(value)) : candidate === value;
1903
+ const selected = value === void 0 ? void 0 : node.cases?.find((entry) => matches(entry.when) || Array.isArray(entry.when) && entry.when.some(matches))?.model;
1904
+ return this.selectItemModel(selected ?? node.fallback, key, context);
1596
1905
  }
1597
1906
  case "range_dispatch": {
1907
+ const value = this.propertyValue(node, context);
1908
+ if (typeof value !== "number") throw new Error(`Item-preview range ${node.property} requires a number`);
1909
+ if (Number.isNaN(value)) return this.selectItemModel(node.fallback, key, context);
1910
+ const scaled = value * (node.scale ?? 1);
1911
+ if (!Number.isFinite(scaled)) throw new Error(`Item-preview range ${node.property} requires a finite value and scale`);
1598
1912
  const selected = node.entries?.reduce((match, entry) => {
1599
- return entry.threshold <= 0 && (!match || entry.threshold >= match.threshold) ? entry : match;
1913
+ return entry.threshold <= scaled && (!match || entry.threshold >= match.threshold) ? entry : match;
1600
1914
  }, void 0);
1601
- return this.defaultItemModel(selected?.model ?? node.fallback, key);
1915
+ return this.selectItemModel(selected?.model ?? node.fallback, key, context);
1602
1916
  }
1603
1917
  }
1604
1918
  throw new Error(`Unsupported item model ${node?.type ?? "definition"} for ${key.toNamespacedString()}`);
1605
1919
  }
1920
+ /** Loads one model file without resolving its parents. Returns `undefined` when the file is missing. */
1606
1921
  static async getRaw(key) {
1607
1922
  if (!key.assetType) {
1608
1923
  key.assetType = "models";
@@ -1615,28 +1930,39 @@ var init_Models = __esm({
1615
1930
  return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
1616
1931
  return AssetLoader.get(key, "model" /* MODEL */);
1617
1932
  });
1618
- }).then((asset) => {
1619
- if (asset) {
1620
- asset.key = key;
1933
+ }).then((asset2) => {
1934
+ if (asset2) {
1935
+ asset2.key = key;
1621
1936
  }
1622
- return asset;
1937
+ return asset2;
1623
1938
  });
1624
1939
  }
1625
- static async getMerged(key) {
1940
+ /**
1941
+ * Loads a model and resolves its parent chain. Returns `undefined` when the model is missing.
1942
+ * Item keys default to GUI context and a stack count of 1. Unresolved conditions are false and numeric properties are zero.
1943
+ * Pass `context` to supply component values, property overrides, and item references for a preview.
1944
+ * Composite items retain independently merged children in `ItemModel.parts`.
1945
+ *
1946
+ * @param key - Model key, for example `AssetKey.parse("models", "minecraft:item/diamond_sword")`.
1947
+ */
1948
+ static async getMerged(key, context = {}) {
1626
1949
  if (!key.assetType) {
1627
1950
  key.assetType = "models";
1628
1951
  }
1629
1952
  if (!key.extension) {
1630
1953
  key.extension = ".json";
1631
1954
  }
1632
- const keyStr = key.serialize();
1955
+ const preview = this.snapshotContext(key, context);
1956
+ const keyStr = key.serialize() + (key.type === "item" ? this.contextKey(preview) : "");
1633
1957
  return Caching.mergedModelCache.get(keyStr, (k) => {
1634
- return _Models.loadAndMerge(key);
1958
+ return _Models.loadAndMerge(key, preview);
1635
1959
  });
1636
1960
  }
1637
- static async get(key) {
1638
- return this.getMerged(key);
1961
+ /** Alias for {@link getMerged}. */
1962
+ static async get(key, context = {}) {
1963
+ return this.getMerged(key, context);
1639
1964
  }
1965
+ /** Clears persisted models. Use {@link Caching.clear} to also discard in-memory assets. */
1640
1966
  static async clearCache() {
1641
1967
  return this.PERSISTENT_CACHE.clear();
1642
1968
  }
@@ -1659,6 +1985,7 @@ var init_BlockStates = __esm({
1659
1985
  return this._persistentCache ??= PersistentCache.open("minerender-blockstates");
1660
1986
  }
1661
1987
  // BlockState names are hardcoded
1988
+ /** Returns blockstate filenames from the selected version's `_list.json`, or an empty list. */
1662
1989
  static async getList() {
1663
1990
  const key = new AssetKey(
1664
1991
  DEFAULT_NAMESPACE,
@@ -1679,6 +2006,7 @@ var init_BlockStates = __esm({
1679
2006
  return AssetLoader.get(key, "json" /* JSON */);
1680
2007
  });
1681
2008
  }
2009
+ /** Looks up vanilla property definitions for the key's block path, or returns `undefined`. */
1682
2010
  static async getDefaultState(key) {
1683
2011
  const defaultStates = await this.getDefaultStates();
1684
2012
  if (!defaultStates) {
@@ -1686,6 +2014,7 @@ var init_BlockStates = __esm({
1686
2014
  }
1687
2015
  return defaultStates["minecraft:" + key.path];
1688
2016
  }
2017
+ /** Loads a cached blockstate definition for {@link MineRenderScene.addBlock}, or returns `undefined`. */
1689
2018
  static async get(key) {
1690
2019
  if (!key.assetType) {
1691
2020
  key.assetType = "blockstates";
@@ -1698,11 +2027,11 @@ var init_BlockStates = __esm({
1698
2027
  return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
1699
2028
  return AssetLoader.get(key, "blockstate" /* BLOCKSTATE */);
1700
2029
  });
1701
- }).then((asset) => {
1702
- if (asset) {
1703
- asset.key = key;
2030
+ }).then((asset2) => {
2031
+ if (asset2) {
2032
+ asset2.key = key;
1704
2033
  }
1705
- return asset;
2034
+ return asset2;
1706
2035
  });
1707
2036
  }
1708
2037
  static getAll(keys) {
@@ -1712,6 +2041,7 @@ var init_BlockStates = __esm({
1712
2041
  }
1713
2042
  return Promise.all(promises);
1714
2043
  }
2044
+ /** Clears persisted blockstates. Use {@link Caching.clear} to also discard in-memory assets. */
1715
2045
  static async clearCache() {
1716
2046
  await this.PERSISTENT_CACHE.clear();
1717
2047
  }
@@ -1737,9 +2067,11 @@ var init_Assets = __esm({
1737
2067
  static parseAssetKey(assetType, str, origin) {
1738
2068
  return AssetKey.parse(assetType, str, origin);
1739
2069
  }
2070
+ /** Loads a model with its parent chain resolved. See {@link Models.getMerged}. */
1740
2071
  static async getModel(key) {
1741
2072
  return Models.getMerged(key);
1742
2073
  }
2074
+ /** Loads a blockstate definition. See {@link BlockStates.get}. */
1743
2075
  static async getBlockState(key) {
1744
2076
  return BlockStates.get(key);
1745
2077
  }
@@ -1754,6 +2086,10 @@ var init_AssetSource = __esm({
1754
2086
  AssetSource = class {
1755
2087
  constructor() {
1756
2088
  }
2089
+ /** Whether this source prevents lower-priority sources from supplying the key. Its own assets remain available. */
2090
+ blocks(key) {
2091
+ return false;
2092
+ }
1757
2093
  /**
1758
2094
  * Identifies this source's content across sessions for persistent caching. Sources that
1759
2095
  * cannot tell whether their content changed (an unnamed archive, for example) return
@@ -1782,6 +2118,10 @@ var init_BatchedExecutor = __esm({
1782
2118
  "src/util/BatchedExecutor.ts"() {
1783
2119
  import_jobqu2 = require("jobqu");
1784
2120
  BatchedExecutor = class {
2121
+ /**
2122
+ * @param interval - Delay between queue runs, in milliseconds.
2123
+ * @param batch - Maximum tasks started per run and maximum tasks active at once.
2124
+ */
1785
2125
  constructor(interval = 1, batch = 30) {
1786
2126
  if (!Number.isFinite(interval) || interval < 0) throw new RangeError("Batch interval must be finite and nonnegative");
1787
2127
  if (!Number.isInteger(batch) || batch < 1) throw new RangeError("Batch size must be a positive integer");
@@ -1789,9 +2129,11 @@ var init_BatchedExecutor = __esm({
1789
2129
  this.batch = batch;
1790
2130
  this.queue = new import_jobqu2.JobQueue(async (task) => task(), { interval, maxPerRun: batch, maxActive: batch });
1791
2131
  }
2132
+ /** Queues a task and resolves with its result, or rejects with its error. */
1792
2133
  submit(task) {
1793
2134
  return this.queue.add(() => task());
1794
2135
  }
2136
+ /** Rejects queued and future tasks. Tasks already running are allowed to finish. */
1795
2137
  stop() {
1796
2138
  this.queue.end();
1797
2139
  }
@@ -1838,29 +2180,29 @@ function applyGenericRotation(axis, rotation, obj) {
1838
2180
  break;
1839
2181
  }
1840
2182
  }
1841
- function addBox3WireframeToObject(box, obj, color = 16777215, w = 2) {
2183
+ function addBox3WireframeToObject(box, obj, color2 = 16777215, w = 2) {
1842
2184
  let size = new import_three8.Vector3();
1843
2185
  box.getSize(size);
1844
2186
  let boxGeo = new import_three8.BoxGeometry(size.x, size.y, size.z, 1, 1, 1);
1845
2187
  let wireGeo = new import_three8.EdgesGeometry(boxGeo);
1846
- let wireMat = new import_three8.LineBasicMaterial({ color, linewidth: w });
2188
+ let wireMat = new import_three8.LineBasicMaterial({ color: color2, linewidth: w });
1847
2189
  let wireframe = new import_three8.LineSegments(wireGeo, wireMat);
1848
2190
  obj.add(wireframe);
1849
2191
  let axes = new import_three8.AxesHelper(1);
1850
2192
  obj.add(axes);
1851
2193
  }
1852
- function addWireframeToObject(obj, color = 16777215, w = 2) {
2194
+ function addWireframeToObject(obj, color2 = 16777215, w = 2) {
1853
2195
  let boxGeo = new import_three8.BoxGeometry(16, 16, 16, 1, 1, 1);
1854
2196
  let wireGeo = new import_three8.EdgesGeometry(boxGeo);
1855
- let wireMat = new import_three8.LineBasicMaterial({ color, linewidth: w });
2197
+ let wireMat = new import_three8.LineBasicMaterial({ color: color2, linewidth: w });
1856
2198
  let wireframe = new import_three8.LineSegments(wireGeo, wireMat);
1857
2199
  obj.add(wireframe);
1858
2200
  let axes = new import_three8.AxesHelper(1);
1859
2201
  obj.add(axes);
1860
2202
  }
1861
- function addWireframeToMesh(geo, mesh, color = 16777215, w = 2) {
2203
+ function addWireframeToMesh(geo, mesh, color2 = 16777215, w = 2) {
1862
2204
  let wireGeo = new import_three8.EdgesGeometry(geo);
1863
- let wireMat = new import_three8.LineBasicMaterial({ color, linewidth: w });
2205
+ let wireMat = new import_three8.LineBasicMaterial({ color: color2, linewidth: w });
1864
2206
  let wireframe = new import_three8.LineSegments(wireGeo, wireMat);
1865
2207
  mesh.add(wireframe);
1866
2208
  let axes = new import_three8.AxesHelper(1);
@@ -1934,10 +2276,11 @@ var init_NBTHelper = __esm({
1934
2276
  "src/nbt/NBTHelper.ts"() {
1935
2277
  import_buffer2 = require("buffer");
1936
2278
  NBTHelper = class {
1937
- static async fromBuffer(data, format) {
2279
+ /** Parses NBT bytes and preserves format and compression metadata. Omit `format` for automatic detection. */
2280
+ static async fromBuffer(data, format2) {
1938
2281
  const buffer = import_buffer2.Buffer.from(data instanceof Uint8Array ? data : new Uint8Array(data));
1939
2282
  const prismarineNbt = await import("prismarine-nbt");
1940
- const { parsed, type, metadata } = await prismarineNbt.parse(buffer, format);
2283
+ const { parsed, type, metadata } = await prismarineNbt.parse(buffer, format2);
1941
2284
  return Object.assign(parsed, {
1942
2285
  format: type,
1943
2286
  metadata,
@@ -2078,6 +2421,7 @@ var init_HostedAssetSource = __esm({
2078
2421
  ["json" /* JSON */, _HostedAssetSource.JSON]
2079
2422
  ]);
2080
2423
  }
2424
+ /** Retries missing assets with the `minecraft` namespace and default root. Other failures reject. */
2081
2425
  async loadOrRetryWithDefaults(key, parser) {
2082
2426
  const direct = await this.load(key, parser);
2083
2427
  if (typeof direct !== "undefined") {
@@ -2135,6 +2479,10 @@ var init_HostedAssetSource = __esm({
2135
2479
  assetBasePath(key) {
2136
2480
  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 + "/" : ""}`;
2137
2481
  }
2482
+ /**
2483
+ * Creates a hosted source for {@link AssetLoader.addSource}.
2484
+ * Set `options.retryDefaults` to `false` to restrict lookups to the requested namespace and root.
2485
+ */
2138
2486
  constructor(root, options) {
2139
2487
  super();
2140
2488
  this._root = root;
@@ -2166,6 +2514,11 @@ var init_BrowserArchiveProxy = __esm({
2166
2514
  "src/assets/source/archive/BrowserArchiveProxy.ts"() {
2167
2515
  import_zip = require("@zip.js/zip.js");
2168
2516
  BrowserArchiveProxy = class {
2517
+ /**
2518
+ * Opens a ZIP for asset lookup.
2519
+ * @param id - Cache identity for this content. Change it when the pack changes.
2520
+ * A File supplies an identity from its name, size, and modification time when omitted.
2521
+ */
2169
2522
  constructor(blob, id) {
2170
2523
  this._blob = blob;
2171
2524
  this._reader = new import_zip.ZipReader(new import_zip.BlobReader(this._blob));
@@ -2186,6 +2539,164 @@ var init_BrowserArchiveProxy = __esm({
2186
2539
  }
2187
2540
  });
2188
2541
 
2542
+ // src/assets/source/archive/PackMetadata.ts
2543
+ function object(value, field) {
2544
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
2545
+ throw new Error(`Invalid ${field}: expected an object`);
2546
+ }
2547
+ return value;
2548
+ }
2549
+ function integer(value, field) {
2550
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
2551
+ throw new Error(`Invalid ${field}: expected a nonnegative integer`);
2552
+ }
2553
+ return value;
2554
+ }
2555
+ function format(value, field, upper = false) {
2556
+ const parts = Array.isArray(value) ? value : [value];
2557
+ if (parts.length < 1 || parts.length > 2) {
2558
+ throw new Error(`Invalid ${field}: expected a major version or [major, minor]`);
2559
+ }
2560
+ return [integer(parts[0], field), parts.length === 2 ? integer(parts[1], field) : upper ? Infinity : 0];
2561
+ }
2562
+ function compare(a, b) {
2563
+ return a[0] === b[0] ? a[1] - b[1] : a[0] - b[0];
2564
+ }
2565
+ function overlay(value, field) {
2566
+ const entry = object(value, field);
2567
+ const directory = entry.directory;
2568
+ if (typeof directory !== "string" || !directory || /[^-_a-zA-Z0-9.]/.test(directory) || directory === "." || directory === "..") {
2569
+ throw new Error(`Invalid ${field}.directory: expected a directory name without path separators`);
2570
+ }
2571
+ let min, max;
2572
+ if ("min_format" in entry || "max_format" in entry) {
2573
+ min = format(entry.min_format, `${field}.min_format`);
2574
+ max = format(entry.max_format, `${field}.max_format`, true);
2575
+ } else {
2576
+ const range = entry.formats;
2577
+ let lower, upper;
2578
+ if (typeof range === "number") {
2579
+ lower = upper = range;
2580
+ } else if (Array.isArray(range)) {
2581
+ if (range.length !== 2) throw new Error(`Invalid ${field}.formats: expected [min, max]`);
2582
+ [lower, upper] = range;
2583
+ } else {
2584
+ const bounds = object(range, `${field}.formats`);
2585
+ lower = bounds.min_inclusive;
2586
+ upper = bounds.max_inclusive;
2587
+ }
2588
+ min = [integer(lower, `${field}.formats.min_inclusive`), 0];
2589
+ max = [integer(upper, `${field}.formats.max_inclusive`), Infinity];
2590
+ }
2591
+ if (compare(min, max) > 0) throw new Error(`Invalid ${field}: minimum format exceeds maximum format`);
2592
+ return { directory, min, max };
2593
+ }
2594
+ function pattern(value, field) {
2595
+ if (value === void 0) return void 0;
2596
+ if (typeof value !== "string") throw new Error(`Invalid ${field}: expected a regular expression string`);
2597
+ try {
2598
+ return new RegExp(value);
2599
+ } catch (cause) {
2600
+ throw new Error(`Invalid ${field}: ${String(cause)}`);
2601
+ }
2602
+ }
2603
+ var PackMetadata;
2604
+ var init_PackMetadata = __esm({
2605
+ "src/assets/source/archive/PackMetadata.ts"() {
2606
+ PackMetadata = class _PackMetadata {
2607
+ constructor(overlays, filters) {
2608
+ this.overlays = overlays;
2609
+ this.filters = filters;
2610
+ this.hasOverlays = overlays.length > 0;
2611
+ }
2612
+ static parse(value) {
2613
+ const metadata = object(value, "pack.mcmeta");
2614
+ let overlays = [];
2615
+ if ("overlays" in metadata) {
2616
+ const entries = object(metadata.overlays, "overlays").entries;
2617
+ if (!Array.isArray(entries)) throw new Error("Invalid overlays.entries: expected an array");
2618
+ overlays = entries.map((entry, index) => overlay(entry, `overlays.entries[${index}]`));
2619
+ }
2620
+ let filters = [];
2621
+ if ("filter" in metadata) {
2622
+ const entries = object(metadata.filter, "filter").block;
2623
+ if (!Array.isArray(entries)) throw new Error("Invalid filter.block: expected an array");
2624
+ filters = entries.map((value2, index) => {
2625
+ const field = `filter.block[${index}]`;
2626
+ const entry = object(value2, field);
2627
+ return { namespace: pattern(entry.namespace, `${field}.namespace`), path: pattern(entry.path, `${field}.path`) };
2628
+ });
2629
+ }
2630
+ return new _PackMetadata(overlays, filters);
2631
+ }
2632
+ /** Returns applicable directories in lookup order, with the last declared overlay first. */
2633
+ overlayDirectories(target) {
2634
+ const selected = format(target, "target format");
2635
+ return this.overlays.filter((entry) => compare(selected, entry.min) >= 0 && compare(selected, entry.max) <= 0).map((entry) => entry.directory).reverse();
2636
+ }
2637
+ blocks(namespace, path) {
2638
+ return this.filters.some((entry) => entry.namespace?.test(namespace) ?? true) && this.filters.some((entry) => entry.path?.test(path) ?? true);
2639
+ }
2640
+ };
2641
+ }
2642
+ });
2643
+
2644
+ // src/assets/source/archive/PackFormats.ts
2645
+ var PackFormats;
2646
+ var init_PackFormats = __esm({
2647
+ "src/assets/source/archive/PackFormats.ts"() {
2648
+ init_Requests();
2649
+ PackFormats = class {
2650
+ static {
2651
+ this.versions = /* @__PURE__ */ new Map();
2652
+ }
2653
+ /** Resolves pack formats from Mojang's client metadata hosted on assets.mcasset.cloud. */
2654
+ static async get(version2, type) {
2655
+ let pending = this.versions.get(version2);
2656
+ if (!pending || pending.expires <= Date.now()) {
2657
+ pending = {
2658
+ formats: this.load(version2),
2659
+ expires: ["latest", "release", "snapshot"].includes(version2) ? Date.now() + 3e5 : Infinity
2660
+ };
2661
+ this.versions.set(version2, pending);
2662
+ }
2663
+ try {
2664
+ return (await pending.formats)[type];
2665
+ } catch (cause) {
2666
+ if (this.versions.get(version2) === pending) this.versions.delete(version2);
2667
+ const option = type === "assets" ? "resourcePackFormat" : "dataPackFormat";
2668
+ const reason = cause instanceof Error ? cause.message : String(cause);
2669
+ throw new Error(`Could not resolve the pack format for Minecraft ${version2}. Set ArchiveAssetSource ${option} explicitly. ${reason}`);
2670
+ }
2671
+ }
2672
+ static async load(version2) {
2673
+ const response = await Requests.genericRequest({
2674
+ url: `https://assets.mcasset.cloud/${encodeURIComponent(version2)}/game-version.json`,
2675
+ responseType: "json"
2676
+ });
2677
+ const data = response.data;
2678
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
2679
+ throw new Error("Expected version metadata to be an object");
2680
+ }
2681
+ const packs = data.pack_version;
2682
+ if (typeof packs !== "number" && (!packs || typeof packs !== "object" || Array.isArray(packs))) {
2683
+ throw new Error("Expected pack_version to be a number or an object");
2684
+ }
2685
+ const format2 = (type) => {
2686
+ const modern = typeof packs === "object" && ["resource_major", "resource_minor", "data_major", "data_minor"].some((key) => key in packs);
2687
+ const major = typeof packs === "number" ? packs : packs[modern ? `${type}_major` : type];
2688
+ const minor = modern ? packs[`${type}_minor`] : 0;
2689
+ if (typeof major !== "number" || !Number.isSafeInteger(major) || major < 0 || typeof minor !== "number" || !Number.isSafeInteger(minor) || minor < 0) {
2690
+ throw new Error(`Invalid ${type} pack format in version metadata`);
2691
+ }
2692
+ return Object.freeze([major, minor]);
2693
+ };
2694
+ return { assets: format2("resource"), data: format2("data") };
2695
+ }
2696
+ };
2697
+ }
2698
+ });
2699
+
2189
2700
  // src/assets/source/archive/ArchiveAssetSource.ts
2190
2701
  var ArchiveAssetSource;
2191
2702
  var init_ArchiveAssetSource = __esm({
@@ -2194,35 +2705,78 @@ var init_ArchiveAssetSource = __esm({
2194
2705
  init_request();
2195
2706
  init_HostedAssetSource();
2196
2707
  init_BrowserArchiveProxy();
2708
+ init_AssetLoader();
2709
+ init_PackMetadata();
2710
+ init_PackFormats();
2197
2711
  ArchiveAssetSource = class _ArchiveAssetSource extends AssetSource {
2198
- constructor(archiveProxy) {
2712
+ constructor(archiveProxy, options = {}) {
2199
2713
  super();
2200
- console.log(archiveProxy);
2201
2714
  this._archiveProxy = archiveProxy;
2715
+ const copyFormat = (format2) => typeof format2 === "object" ? [format2[0], format2[1]] : format2;
2716
+ this.options = {
2717
+ resourcePackFormat: copyFormat(options.resourcePackFormat),
2718
+ dataPackFormat: copyFormat(options.dataPackFormat)
2719
+ };
2202
2720
  }
2203
- static blob(blob) {
2204
- return new _ArchiveAssetSource(new BrowserArchiveProxy(blob));
2721
+ /** Creates a browser ZIP source from a Blob or File. */
2722
+ static blob(blob, options) {
2723
+ return new _ArchiveAssetSource(new BrowserArchiveProxy(blob), options);
2205
2724
  }
2206
2725
  get cacheId() {
2207
2726
  const id = this._archiveProxy.id;
2208
- return id === void 0 ? void 0 : `archive:${id}`;
2727
+ const formats = [this.options.resourcePackFormat ?? AssetLoader.version, this.options.dataPackFormat ?? AssetLoader.version];
2728
+ return id === void 0 ? void 0 : `archive-metadata:1:${id}:${JSON.stringify(formats)}`;
2209
2729
  }
2210
2730
  async getEntries() {
2211
- return this._archiveProxy.getEntries();
2731
+ return this.entries ??= this._archiveProxy.getEntries().catch((error) => {
2732
+ this.entries = void 0;
2733
+ throw error;
2734
+ });
2212
2735
  }
2213
2736
  async getEntry(path) {
2214
2737
  const entries = await this.getEntries();
2215
2738
  return entries.find((e) => e.filename === path);
2216
2739
  }
2740
+ async getMetadata(key) {
2741
+ try {
2742
+ return await (this.metadata ??= this.getEntry("pack.mcmeta").then(async (entry) => PackMetadata.parse(entry ? JSON.parse(await (await entry.getData()).text()) : {})).catch((error) => {
2743
+ this.metadata = void 0;
2744
+ throw error;
2745
+ }));
2746
+ } catch (cause) {
2747
+ throw new AssetLoadError(this, key, "pack.mcmeta", cause);
2748
+ }
2749
+ }
2750
+ async blocks(key) {
2751
+ if (key.rootType !== "assets" && key.rootType !== "data") return false;
2752
+ const metadata = await this.getMetadata(key);
2753
+ const path = `${key.assetType !== void 0 ? key.assetType + "/" : ""}${key.getFullPath()}${key.extension}`;
2754
+ return metadata.blocks(key.namespace, path);
2755
+ }
2217
2756
  async get(key, parser) {
2218
2757
  const responseParser = HostedAssetSource.PARSER_MAP.get(parser);
2219
2758
  return this.load(key, responseParser);
2220
2759
  }
2221
2760
  async load(key, parser) {
2222
2761
  const path = `${this.assetBasePath(key)}${key.type !== void 0 ? key.type + "/" : ""}${key.path}${key.extension}`;
2762
+ const version2 = AssetLoader.version;
2763
+ let selectedPath = path;
2223
2764
  let url;
2224
2765
  try {
2225
- const entry = await this.getEntry(path);
2766
+ let directories = [];
2767
+ if (key.rootType === "assets" || key.rootType === "data") {
2768
+ const metadata = await this.getMetadata(key);
2769
+ if (metadata.hasOverlays) {
2770
+ const format2 = (key.rootType === "assets" ? this.options.resourcePackFormat : this.options.dataPackFormat) ?? await PackFormats.get(version2, key.rootType);
2771
+ directories = metadata.overlayDirectories(format2);
2772
+ }
2773
+ }
2774
+ let entry;
2775
+ for (const directory of [...directories, ""]) {
2776
+ selectedPath = directory ? `${directory}/${path}` : path;
2777
+ entry = await this.getEntry(selectedPath);
2778
+ if (entry) break;
2779
+ }
2226
2780
  if (!entry) {
2227
2781
  return void 0;
2228
2782
  }
@@ -2235,7 +2789,8 @@ var init_ArchiveAssetSource = __esm({
2235
2789
  }
2236
2790
  return await parser.parse(response);
2237
2791
  } catch (cause) {
2238
- throw new AssetLoadError(this, key, path, cause);
2792
+ if (cause instanceof AssetLoadError) throw cause;
2793
+ throw new AssetLoadError(this, key, selectedPath, cause);
2239
2794
  } finally {
2240
2795
  if (url !== void 0) {
2241
2796
  URL.revokeObjectURL(url);
@@ -2268,6 +2823,8 @@ var init_archive = __esm({
2268
2823
  init_ArchiveEntry();
2269
2824
  init_ArchiveProxy();
2270
2825
  init_BrowserArchiveProxy();
2826
+ init_PackFormats();
2827
+ init_PackMetadata();
2271
2828
  }
2272
2829
  });
2273
2830
 
@@ -2294,6 +2851,7 @@ var init_AssetLoader = __esm({
2294
2851
  FALLBACK_ROOT = "https://raw.githubusercontent.com/InventivetalentDev/minerender-fallback-assets/master";
2295
2852
  AssetLoader = class {
2296
2853
  static {
2854
+ /** Default asset base URL. Use {@link setVersion} to select a Minecraft version. */
2297
2855
  this.ROOT = DEFAULT_ROOT;
2298
2856
  }
2299
2857
  static {
@@ -2306,8 +2864,9 @@ var init_AssetLoader = __esm({
2306
2864
  static get version() {
2307
2865
  return this.ROOT.substring(this.ROOT.lastIndexOf("/") + 1);
2308
2866
  }
2309
- static setVersion(version) {
2310
- this.ROOT = `https://assets.mcasset.cloud/${version}`;
2867
+ /** Selects an mcasset.cloud version, updates the vanilla source, and clears in-memory caches. */
2868
+ static setVersion(version2) {
2869
+ this.ROOT = `https://assets.mcasset.cloud/${version2}`;
2311
2870
  const source = new HostedAssetSource(this.ROOT, { retryDefaults: false });
2312
2871
  const index = this._SOURCES.findIndex((s) => s.key === "mcassets");
2313
2872
  if (index !== -1) {
@@ -2339,6 +2898,13 @@ var init_AssetLoader = __esm({
2339
2898
  return scope ? `${scope}
2340
2899
  ${key}` : key;
2341
2900
  }
2901
+ /**
2902
+ * Registers a source at the highest priority.
2903
+ * Call {@link Caching.clear} after changing sources to discard previously loaded assets.
2904
+ *
2905
+ * @param key - Name used to replace or remove this source.
2906
+ * @param override - Removes the first source with this name before adding the new one.
2907
+ */
2342
2908
  static addSource(key, source, override = true) {
2343
2909
  if (override) {
2344
2910
  const existing = this.removeSource(key);
@@ -2349,6 +2915,7 @@ ${key}` : key;
2349
2915
  this._SOURCES.unshift({ key, source });
2350
2916
  console.log(p4, "Added AssetSource", key);
2351
2917
  }
2918
+ /** Removes and returns the first source with this name. Call {@link Caching.clear} to reload assets. */
2352
2919
  static removeSource(key) {
2353
2920
  const index = this._SOURCES.findIndex((s) => s.key === key);
2354
2921
  if (index != -1) {
@@ -2363,14 +2930,16 @@ ${key}` : key;
2363
2930
  this.addSource("mcassets-fallback", new HostedAssetSource(FALLBACK_ROOT, { retryDefaults: false }));
2364
2931
  this.addSource("mcassets", new HostedAssetSource(this.ROOT, { retryDefaults: false }));
2365
2932
  }
2933
+ /** Loads from all sources and returns defined results in priority order. Any source failure rejects the call. */
2366
2934
  static async getAll(key, parser) {
2367
- let promises = [];
2368
- for (const source of this._SOURCES) {
2369
- promises.push(source.source.get(key, parser));
2935
+ const sources = [...this._SOURCES];
2936
+ const results = [];
2937
+ for (const { source } of sources) {
2938
+ const asset2 = await source.get(key, parser);
2939
+ if (asset2 != void 0) results.push(asset2);
2940
+ if (await source.blocks(key)) break;
2370
2941
  }
2371
- return Promise.all(promises).then((results) => {
2372
- return results.filter((r) => r != void 0);
2373
- });
2942
+ return results;
2374
2943
  }
2375
2944
  /** Returns the first defined result in source-priority order, without merging assets. */
2376
2945
  static async get(key, parser) {
@@ -2391,13 +2960,17 @@ ${key}` : key;
2391
2960
  /** Tries each path within a source before considering lower-priority sources. */
2392
2961
  static async getFirst(keys, parser) {
2393
2962
  const sources = [...this._SOURCES];
2963
+ let remaining = [...keys];
2394
2964
  for (const source of sources) {
2395
- for (const key of keys) {
2965
+ for (const key of remaining) {
2396
2966
  const result = await source.source.get(key, parser);
2397
2967
  if (result !== void 0) {
2398
2968
  return { key, asset: result };
2399
2969
  }
2400
2970
  }
2971
+ const blocked = await Promise.all(remaining.map((key) => source.source.blocks(key)));
2972
+ remaining = remaining.filter((_, index) => !blocked[index]);
2973
+ if (!remaining.length) break;
2401
2974
  }
2402
2975
  return void 0;
2403
2976
  }
@@ -2419,14 +2992,15 @@ var init_ModelTextures = __esm({
2419
2992
  static get PERSISTENT_META_CACHE() {
2420
2993
  return this._persistentMetaCache ??= PersistentCache.open("minerender-texturemeta");
2421
2994
  }
2995
+ /** Loads and decodes a texture into a readable canvas, or returns `undefined` when missing. */
2422
2996
  static async get(key) {
2423
2997
  const keyStr = key.serialize();
2424
2998
  const pending = this.preload(key);
2425
2999
  const cached2 = Caching.textureAssetCache.getIfPresent(keyStr);
2426
3000
  try {
2427
- const asset = await pending;
2428
- if (asset) {
2429
- return await ImageLoader.infoToCanvasData(asset);
3001
+ const asset2 = await pending;
3002
+ if (asset2) {
3003
+ return await ImageLoader.infoToCanvasData(asset2);
2430
3004
  }
2431
3005
  return void 0;
2432
3006
  } catch (err) {
@@ -2436,16 +3010,18 @@ var init_ModelTextures = __esm({
2436
3010
  throw err;
2437
3011
  }
2438
3012
  }
3013
+ /** Fetches and caches encoded texture bytes without decoding the pixels. */
2439
3014
  static async preload(key) {
2440
3015
  const keyStr = key.serialize();
2441
3016
  return Caching.textureAssetCache.get(keyStr, (k) => {
2442
- return AssetLoader.get(key, "image" /* IMAGE */).then((asset) => {
2443
- if (asset)
2444
- asset.key = key;
2445
- return asset;
3017
+ return AssetLoader.get(key, "image" /* IMAGE */).then((asset2) => {
3018
+ if (asset2)
3019
+ asset2.key = key;
3020
+ return asset2;
2446
3021
  });
2447
3022
  });
2448
3023
  }
3024
+ /** Loads a texture's `.mcmeta` file, or returns `undefined` when no metadata file exists. */
2449
3025
  static async getMeta(key) {
2450
3026
  if (!key.extension || !key.extension.endsWith(".mcmeta")) {
2451
3027
  key = Object.assign(new AssetKey("", ""), key);
@@ -2454,14 +3030,15 @@ var init_ModelTextures = __esm({
2454
3030
  const keyStr = key.serialize();
2455
3031
  return Caching.textureMetaCache.get(keyStr, (k) => {
2456
3032
  return this.PERSISTENT_META_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
2457
- return AssetLoader.get(key, "meta" /* META */).then((asset) => {
2458
- if (asset)
2459
- asset.key = key;
2460
- return asset;
3033
+ return AssetLoader.get(key, "meta" /* META */).then((asset2) => {
3034
+ if (asset2)
3035
+ asset2.key = key;
3036
+ return asset2;
2461
3037
  });
2462
3038
  });
2463
3039
  });
2464
3040
  }
3041
+ /** Clears persisted texture metadata. Use {@link Caching.clear} to discard in-memory textures and metadata. */
2465
3042
  static async clearCache() {
2466
3043
  await this.PERSISTENT_META_CACHE.clear();
2467
3044
  }
@@ -2549,6 +3126,7 @@ var init_Ticker = __esm({
2549
3126
  }
2550
3127
  return handle;
2551
3128
  }
3129
+ /** Starts or resumes the shared tick loop. Repeated calls have no effect. */
2552
3130
  static start() {
2553
3131
  if (this.interval) return;
2554
3132
  this.interval = this.schedule(() => {
@@ -2571,6 +3149,7 @@ var init_Ticker = __esm({
2571
3149
  _Ticker.fiveSecondTicks = 0;
2572
3150
  }, 1e3 * 5);
2573
3151
  }
3152
+ /** Pauses the loop while retaining callbacks for a later {@link start}. */
2574
3153
  static stop() {
2575
3154
  if (this.interval) clearInterval(this.interval);
2576
3155
  if (this.oneSecondTicksTracker) clearInterval(this.oneSecondTicksTracker);
@@ -2579,12 +3158,14 @@ var init_Ticker = __esm({
2579
3158
  this.oneSecondTicksTracker = void 0;
2580
3159
  this.fiveSecondTicksTracker = void 0;
2581
3160
  }
3161
+ /** Registers a callback, starts the loop if needed, and returns an ID for {@link remove}. */
2582
3162
  static add(tick) {
2583
3163
  this.start();
2584
3164
  const c = this.counter++;
2585
3165
  this._tickers.set(c, tick);
2586
3166
  return c;
2587
3167
  }
3168
+ /** Removes a callback by ID and stops the timers when no callbacks remain. */
2588
3169
  static remove(c) {
2589
3170
  if (typeof c === "number") this._tickers.delete(c);
2590
3171
  if (!this._tickers.size) this.stop();
@@ -2598,6 +3179,7 @@ var init_Ticker = __esm({
2598
3179
  static get tpsFiveSeconds() {
2599
3180
  return this.fiveSecondTps;
2600
3181
  }
3182
+ /** Stops the loop and removes all registered callbacks. */
2601
3183
  static dispose() {
2602
3184
  this.stop();
2603
3185
  this._tickers.clear();
@@ -2644,6 +3226,7 @@ var init_TextureAtlas = __esm({
2644
3226
  }
2645
3227
  };
2646
3228
  }
3229
+ /** Copies pixels for a texture variable from its current atlas rectangle. */
2647
3230
  getData(texture) {
2648
3231
  const pos = this.positions[texture];
2649
3232
  const size = this.sizes[texture];
@@ -2856,6 +3439,37 @@ var init_UVMapper = __esm({
2856
3439
  static facesToUv(faces, originalTextureSize, actualTextureSize) {
2857
3440
  return new import_three9.Float32BufferAttribute(this.facesToUvArray(faces, originalTextureSize, actualTextureSize), 2);
2858
3441
  }
3442
+ /** Bounds texture sampling to texel centres without changing cropped face UVs. */
3443
+ static setAtlasUvBounds(geometry, faces, atlas) {
3444
+ const uv = geometry.getAttribute("uv");
3445
+ const bounds = new Float32Array(uv.count * 4);
3446
+ for (const [faceIndex, faceName] of CUBE_FACES.entries()) {
3447
+ const texture = faces[faceName]?.texture?.substring(1);
3448
+ const position = texture && atlas.positions[texture];
3449
+ const size = texture && atlas.sizes[texture];
3450
+ let minU = Infinity, minV = Infinity, maxU = -Infinity, maxV = -Infinity;
3451
+ if (position && size) {
3452
+ minU = position[0] / atlas.image.width;
3453
+ maxU = (position[0] + size[0]) / atlas.image.width;
3454
+ minV = 1 - (position[1] + size[1]) / atlas.image.height;
3455
+ maxV = 1 - position[1] / atlas.image.height;
3456
+ } else {
3457
+ for (let corner = 0; corner < 4; corner++) {
3458
+ const index = faceIndex * 4 + corner;
3459
+ minU = Math.min(minU, uv.getX(index));
3460
+ minV = Math.min(minV, uv.getY(index));
3461
+ maxU = Math.max(maxU, uv.getX(index));
3462
+ maxV = Math.max(maxV, uv.getY(index));
3463
+ }
3464
+ }
3465
+ const insetU = Math.min(0.5 / atlas.image.width, (maxU - minU) / 2);
3466
+ const insetV = Math.min(0.5 / atlas.image.height, (maxV - minV) / 2);
3467
+ for (let corner = 0; corner < 4; corner++) {
3468
+ bounds.set([minU + insetU, minV + insetV, maxU - insetU, maxV - insetV], (faceIndex * 4 + corner) * 4);
3469
+ }
3470
+ }
3471
+ geometry.setAttribute("uvBounds", new import_three9.Float32BufferAttribute(bounds, 4));
3472
+ }
2859
3473
  static lockUvs(geometry, faces, atlas, rotation) {
2860
3474
  const uv = geometry.getAttribute("uv");
2861
3475
  for (let faceIndex = 0; faceIndex < CUBE_FACES.length; faceIndex++) {
@@ -2892,6 +3506,7 @@ var init_UVMapper = __esm({
2892
3506
  }
2893
3507
  uv.needsUpdate = true;
2894
3508
  }
3509
+ /** Returns the shared atlas for a keyed, merged model, creating it on first use. */
2895
3510
  static async getAtlas(model) {
2896
3511
  const keyStr = model.key.serialize();
2897
3512
  return Caching.modelTextureAtlasCache.get(keyStr, (k) => {
@@ -2962,6 +3577,11 @@ var init_UVMapper = __esm({
2962
3577
  }
2963
3578
  return [tl, tr, bl, br];
2964
3579
  }
3580
+ /**
3581
+ * Combines model textures and animation metadata into an atlas.
3582
+ * Mapped UVs are stored on copies of the model's elements, available through the returned atlas.
3583
+ * Returns `undefined` for models without a texture map.
3584
+ */
2965
3585
  static async createAtlas(originalModel) {
2966
3586
  const textureMap = {};
2967
3587
  const metaMap = {};
@@ -2982,12 +3602,12 @@ var init_UVMapper = __esm({
2982
3602
  } else {
2983
3603
  uniqueTextureNames.push(textureKey);
2984
3604
  const assetKey = AssetKey.parse("textures", textureValue, model.key);
2985
- promises.push(ModelTextures.get(assetKey).then((asset) => {
2986
- if (!asset) {
3605
+ promises.push(ModelTextures.get(assetKey).then((asset2) => {
3606
+ if (!asset2) {
2987
3607
  console.warn(p5, "Missing texture", assetKey);
2988
3608
  return;
2989
3609
  }
2990
- textureMap[textureKey] = new WrappedImage(asset);
3610
+ textureMap[textureKey] = new WrappedImage(asset2);
2991
3611
  }));
2992
3612
  promises.push(ModelTextures.getMeta(assetKey).then((meta) => {
2993
3613
  metaMap[textureKey] = meta;
@@ -3326,14 +3946,15 @@ var init_Entities = __esm({
3326
3946
  init_MineRenderError();
3327
3947
  init_ModelTextures();
3328
3948
  Entities = class {
3949
+ /** Lists model paths in the `minecraft` namespace, including subdirectories and excluding `.json`. */
3329
3950
  static async getEntityList() {
3330
3951
  const collect = async (path) => {
3331
3952
  const prefix2 = path ? `${path}/` : "";
3332
3953
  const key = new AssetKey(DEFAULT_NAMESPACE, `${prefix2}_list`, void 0, void 0, "entity-models", ".json");
3333
- const list = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get(key, "list" /* LIST */));
3334
- if (!list) return [];
3335
- const files = list.files.filter((file) => file.endsWith(".json") && file !== "_list.json").map((file) => `${prefix2}${file.slice(0, -5)}`);
3336
- const children = await Promise.all(list.directories.map((directory) => collect(`${prefix2}${directory}`)));
3954
+ const list2 = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get(key, "list" /* LIST */));
3955
+ if (!list2) return [];
3956
+ const files = list2.files.filter((file) => file.endsWith(".json") && file !== "_list.json").map((file) => `${prefix2}${file.slice(0, -5)}`);
3957
+ const children = await Promise.all(list2.directories.map((directory) => collect(`${prefix2}${directory}`)));
3337
3958
  return [...files, ...children.flat()];
3338
3959
  };
3339
3960
  return collect("");
@@ -3343,6 +3964,7 @@ var init_Entities = __esm({
3343
3964
  const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models", ".json", isAssetKey(modelKey) ? modelKey.root : void 0);
3344
3965
  return Caching.entityModelCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
3345
3966
  }
3967
+ /** Returns selectable geometry-layer names, or an empty list when the model is missing. */
3346
3968
  static async getLayerList(modelKey) {
3347
3969
  return Object.keys((await this.getModelFile(modelKey))?.layers ?? {});
3348
3970
  }
@@ -3350,6 +3972,15 @@ var init_Entities = __esm({
3350
3972
  static async getPassList(modelKey) {
3351
3973
  return (await this.getModelFile(modelKey))?.passes ?? [];
3352
3974
  }
3975
+ /**
3976
+ * Loads the layers and textures to pass to {@link MineRenderScene.addEntity}.
3977
+ * By default, selects `main`, unconditional passes, and passes enabled by `options.when`.
3978
+ *
3979
+ * @param modelKey - Dataset model, such as `new BasicAssetKey("minecraft", "zombie")`.
3980
+ * @param textureKey - Texture override for the first selected layer.
3981
+ * @param options - Layer selection, conditional passes, and per-layer texture overrides.
3982
+ * @returns The selected model, or `undefined` when its dataset file is missing.
3983
+ */
3353
3984
  static async getEntity(modelKey, textureKey, options) {
3354
3985
  const model = await this.getModelFile(modelKey);
3355
3986
  if (!model) return void 0;
@@ -3395,6 +4026,7 @@ var init_Entities = __esm({
3395
4026
  const file = await Caching.entityAnimationCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
3396
4027
  return file?.animations;
3397
4028
  }
4029
+ /** Finds a fallback texture using the model path, its directory, and vanilla variant data. */
3398
4030
  static async resolveTexture(modelKey) {
3399
4031
  const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
3400
4032
  const root = isAssetKey(modelKey) ? modelKey.root : void 0;
@@ -3406,8 +4038,8 @@ var init_Entities = __esm({
3406
4038
  if (await ModelTextures.get(texture2)) return texture2;
3407
4039
  }
3408
4040
  const listKey = new AssetKey(modelKey.namespace, "_list", `${name}_variant`, void 0, "data", ".json", root);
3409
- const list = await AssetLoader.get(listKey, "list" /* LIST */);
3410
- const files = list?.files.filter((file2) => file2.endsWith(".json") && file2 !== "_list.json");
4041
+ const list2 = await AssetLoader.get(listKey, "list" /* LIST */);
4042
+ const files = list2?.files.filter((file2) => file2.endsWith(".json") && file2 !== "_list.json");
3411
4043
  const file = files?.find((file2) => file2 === "temperate.json") ?? files?.[0];
3412
4044
  if (!file) return void 0;
3413
4045
  const variantKey = new AssetKey(modelKey.namespace, file.slice(0, -5), `${name}_variant`, void 0, "data", ".json", root);
@@ -3512,27 +4144,27 @@ var init_EntityAnimationPlayer = __esm({
3512
4144
  for (const layer of layers) {
3513
4145
  const objects = [];
3514
4146
  if (poses) {
3515
- layer.traverse((object) => {
3516
- if (poses.has(object)) objects.push(object);
4147
+ layer.traverse((object3) => {
4148
+ if (poses.has(object3)) objects.push(object3);
3517
4149
  });
3518
4150
  } else {
3519
4151
  for (const bone of Object.keys(animation.bones)) {
3520
- const object = layer.getObjectByName(`group:${bone}`);
3521
- if (object) objects.push(object);
4152
+ const object3 = layer.getObjectByName(`group:${bone}`);
4153
+ if (object3) objects.push(object3);
3522
4154
  }
3523
4155
  }
3524
- for (const object of objects) {
3525
- const bone = object.name.slice("group:".length);
3526
- const pose = poses?.get(object);
4156
+ for (const object3 of objects) {
4157
+ const bone = object3.name.slice("group:".length);
4158
+ const pose = poses?.get(object3);
3527
4159
  this.parts.push({
3528
4160
  bone,
3529
- object,
3530
- position: object.position.clone(),
3531
- rotation: object.rotation.clone(),
3532
- scale: object.scale.clone(),
3533
- basePosition: pose ? new import_three11.Vector3(...pose.offset) : object.position.clone(),
3534
- baseRotation: pose ? new import_three11.Euler(...pose.rotation, "ZYX") : object.rotation.clone(),
3535
- baseScale: pose ? new import_three11.Vector3(...pose.scale ?? [1, 1, 1]) : object.scale.clone()
4161
+ object: object3,
4162
+ position: object3.position.clone(),
4163
+ rotation: object3.rotation.clone(),
4164
+ scale: object3.scale.clone(),
4165
+ basePosition: pose ? new import_three11.Vector3(...pose.offset) : object3.position.clone(),
4166
+ baseRotation: pose ? new import_three11.Euler(...pose.rotation, "ZYX") : object3.rotation.clone(),
4167
+ baseScale: pose ? new import_three11.Vector3(...pose.scale ?? [1, 1, 1]) : object3.scale.clone()
3536
4168
  });
3537
4169
  }
3538
4170
  }
@@ -3555,12 +4187,14 @@ var init_EntityAnimationPlayer = __esm({
3555
4187
  this._animation = void 0;
3556
4188
  this._time = 0;
3557
4189
  }
4190
+ /** Applies the pose at an absolute playback time. Returns `false` when no clip is active. */
3558
4191
  setTime(seconds) {
3559
4192
  if (!this._animation) return false;
3560
4193
  this._time = seconds;
3561
4194
  this.apply();
3562
4195
  return true;
3563
4196
  }
4197
+ /** Advances playback by elapsed seconds multiplied by the speed passed to {@link play}. */
3564
4198
  advance(deltaSeconds) {
3565
4199
  return this.setTime(this._time + deltaSeconds * this.speed);
3566
4200
  }
@@ -3598,6 +4232,7 @@ var init_InstanceReference = __esm({
3598
4232
  if (!this.instanceable.isInstanceActive(this.index, this)) throw new MineRenderError("Instance has been removed");
3599
4233
  return this.instanceable;
3600
4234
  }
4235
+ /** Releases this placement's slot for reuse. Repeated calls have no effect. */
3601
4236
  removeFromScene() {
3602
4237
  if (this.instanceable.isInstanceActive(this.index, this)) this.instanceable.removeInstanceAt(this.index);
3603
4238
  }
@@ -3610,9 +4245,11 @@ var init_InstanceReference = __esm({
3610
4245
  setMatrix(matrix) {
3611
4246
  this.activeInstanceable.setMatrixAt(this.index, matrix);
3612
4247
  }
4248
+ /** Hides or restores this placement while preserving its transform and allocated slot. */
3613
4249
  setVisible(visible) {
3614
4250
  this.activeInstanceable.setInstanceVisibleAt(this.index, visible);
3615
4251
  }
4252
+ /** Updates the supplied transform components. Positions use scene units and rotations use radians. */
3616
4253
  setPositionRotationScale(position, rotation, scale) {
3617
4254
  this.activeInstanceable.setPositionRotationScaleAt(this.index, position, rotation, scale);
3618
4255
  }
@@ -3689,6 +4326,7 @@ var init_SceneObject = __esm({
3689
4326
  }
3690
4327
  async init() {
3691
4328
  }
4329
+ /** Marks ancestor scenes for redraw and emits a `change` event. */
3692
4330
  notifyDirty() {
3693
4331
  this.traverseAncestors((parent) => {
3694
4332
  const scene = parent;
@@ -3724,15 +4362,17 @@ var init_SceneObject = __esm({
3724
4362
  return obj;
3725
4363
  }
3726
4364
  /**
3727
- * Get a group by its name
4365
+ * Finds a named part, such as `head`, without the internal `group:` prefix.
4366
+ * Returns `undefined` when the group does not exist.
3728
4367
  */
3729
4368
  getGroupByName(name) {
3730
4369
  return this.getObjectByName(`group:${name}`);
3731
4370
  }
3732
4371
  /**
3733
- * Toggle visibility of a group
3734
- * @param name name of the group
3735
- * @param visible set the visibility directly, if not set toggles it
4372
+ * Changes a named group's visibility and requests a redraw.
4373
+ * @param name - Part name without the `group:` prefix.
4374
+ * @param visible - Visibility to assign. Omit it to toggle the current value.
4375
+ * @returns The resulting visibility, or `false` if the group does not exist.
3736
4376
  */
3737
4377
  toggleGroupVisibility(name, visible) {
3738
4378
  return this.toggleObjectVisibility(this.getGroupByName(name), visible);
@@ -3783,15 +4423,16 @@ var init_SceneObject = __esm({
3783
4423
  return mesh;
3784
4424
  }
3785
4425
  /**
3786
- * Get a mesh by its name
4426
+ * Finds a named mesh without the internal `mesh:` prefix, or returns `undefined`.
3787
4427
  */
3788
4428
  getMeshByName(name) {
3789
4429
  return this.getObjectByName(`mesh:${name}`);
3790
4430
  }
3791
4431
  /**
3792
- * Toggle visibility of a mesh
3793
- * @param name name of the mesh
3794
- * @param visible set the visibility directly, if not set toggles it
4432
+ * Changes a named mesh's visibility and requests a redraw.
4433
+ * @param name - Mesh name without the `mesh:` prefix.
4434
+ * @param visible - Visibility to assign. Omit it to toggle the current value.
4435
+ * @returns The resulting visibility, or `false` if the mesh does not exist.
3795
4436
  */
3796
4437
  toggleMeshVisibility(name, visible) {
3797
4438
  return this.toggleObjectVisibility(this.getMeshByName(name), visible);
@@ -3837,12 +4478,14 @@ var init_SceneObject = __esm({
3837
4478
  get isInstanced() {
3838
4479
  return this._isInstanced;
3839
4480
  }
4481
+ /** Number of live placements, excluding released instance slots. */
3840
4482
  get instanceCounter() {
3841
4483
  return this._instanceCounter;
3842
4484
  }
3843
4485
  constructInstanceReference(i) {
3844
4486
  return new InstanceReference(this, i);
3845
4487
  }
4488
+ /** Allocates a placement of this instanced object, reusing a released slot when available. */
3846
4489
  nextInstance() {
3847
4490
  const mesh = this.instanceMesh;
3848
4491
  if (!mesh) throw new MineRenderError("Object is not instanced");
@@ -3887,10 +4530,10 @@ var init_SceneObject = __esm({
3887
4530
  return new import_three12.InstancedBufferAttribute(array, attribute.itemSize, attribute.normalized, attribute.meshPerAttribute).setUsage(attribute.usage);
3888
4531
  };
3889
4532
  const matrix = grow(mesh.instanceMatrix, 0);
3890
- const color = mesh.instanceColor ? grow(mesh.instanceColor, 1) : null;
4533
+ const color2 = mesh.instanceColor ? grow(mesh.instanceColor, 1) : null;
3891
4534
  mesh.dispose();
3892
4535
  mesh.instanceMatrix = matrix;
3893
- mesh.instanceColor = color;
4536
+ mesh.instanceColor = color2;
3894
4537
  }
3895
4538
  *activeInstanceIndices() {
3896
4539
  if (this.instanceMesh) {
@@ -3989,6 +4632,7 @@ var init_SceneObject = __esm({
3989
4632
  vector.setFromMatrixScale(matrix);
3990
4633
  return vector;
3991
4634
  }
4635
+ /** Updates supplied transform components. For a shared model, affects every live instance. */
3992
4636
  setPositionRotationScale(position, rotation, scale) {
3993
4637
  if (this.isInstanced) {
3994
4638
  for (const i of this.activeInstanceIndices()) {
@@ -4007,6 +4651,7 @@ var init_SceneObject = __esm({
4007
4651
  }
4008
4652
  this.notifyDirty();
4009
4653
  }
4654
+ /** Sets position in scene units. For a shared model, updates every live instance. */
4010
4655
  setPosition(position) {
4011
4656
  if (this.isInstanced) {
4012
4657
  for (const i of this.activeInstanceIndices()) {
@@ -4024,6 +4669,7 @@ var init_SceneObject = __esm({
4024
4669
  return this.position;
4025
4670
  }
4026
4671
  }
4672
+ /** Sets rotation in radians. For a shared model, updates every live instance. */
4027
4673
  setRotation(rotation) {
4028
4674
  if (this.isInstanced) {
4029
4675
  for (const i of this.activeInstanceIndices()) {
@@ -4041,6 +4687,7 @@ var init_SceneObject = __esm({
4041
4687
  return this.rotation;
4042
4688
  }
4043
4689
  }
4690
+ /** Sets scale factors. For a shared model, updates every live instance. */
4044
4691
  setScale(scale) {
4045
4692
  if (this.isInstanced) {
4046
4693
  for (const i of this.activeInstanceIndices()) {
@@ -4059,15 +4706,15 @@ var init_SceneObject = __esm({
4059
4706
  }
4060
4707
  }
4061
4708
  //</editor-fold>
4062
- toggleObjectVisibility(object, visible) {
4063
- if (object) {
4709
+ toggleObjectVisibility(object3, visible) {
4710
+ if (object3) {
4064
4711
  if (typeof visible !== "undefined") {
4065
- object.visible = visible;
4712
+ object3.visible = visible;
4066
4713
  } else {
4067
- object.visible = !object.visible;
4714
+ object3.visible = !object3.visible;
4068
4715
  }
4069
4716
  this.notifyDirty();
4070
- return object.visible;
4717
+ return object3.visible;
4071
4718
  }
4072
4719
  this.notifyDirty();
4073
4720
  return false;
@@ -4078,6 +4725,7 @@ var init_SceneObject = __esm({
4078
4725
  this.notifyDirty();
4079
4726
  super.dispose();
4080
4727
  }
4728
+ /** Releases instance slots and removes children, disposing children that implement Disposable. */
4081
4729
  disposeAndRemoveAllChildren() {
4082
4730
  if (this.instanceMesh) {
4083
4731
  if (this._scene) this._scene.stats.instanceCount -= this.instanceReferences.size;
@@ -4100,6 +4748,7 @@ var init_SceneObject = __esm({
4100
4748
  }
4101
4749
  this.notifyDirty();
4102
4750
  }
4751
+ /** Detaches the object from its parent and requests a redraw. */
4103
4752
  removeFromScene() {
4104
4753
  this.notifyDirty();
4105
4754
  this.removeFromParent();
@@ -4251,12 +4900,15 @@ var init_EntityObject = __esm({
4251
4900
  if (this.animationPlayers.length) this.notifyDirty();
4252
4901
  }
4253
4902
  //</editor-fold>
4903
+ /** Finds a selected draw by its layer key, such as `main` or `main#2`, or returns `undefined`. */
4254
4904
  getLayerGroup(name) {
4255
4905
  return super.getGroupByName(`layer:${name}`);
4256
4906
  }
4907
+ /** Finds a part name without `group:`. Supply `layerName` to restrict the search to one draw. */
4257
4908
  getGroupByName(name, layerName) {
4258
4909
  return layerName === void 0 ? super.getGroupByName(name) : this.getLayerGroup(layerName)?.getObjectByName(`group:${name}`);
4259
4910
  }
4911
+ /** Finds a mesh name without `mesh:`. Supply `layerName` to restrict the search to one draw. */
4260
4912
  getMeshByName(name, layerName) {
4261
4913
  return layerName === void 0 ? super.getMeshByName(name) : this.getLayerGroup(layerName)?.getObjectByName(`mesh:${name}`);
4262
4914
  }
@@ -4358,8 +5010,8 @@ var init_EntityObject = __esm({
4358
5010
  this.scrollMaterials.push({ material: mat, offset: mat.map.offset, speed: scroll });
4359
5011
  }
4360
5012
  }
4361
- this.getLayerGroup(name)?.traverse((object) => {
4362
- if (isMesh(object)) object.material = mat;
5013
+ this.getLayerGroup(name)?.traverse((object3) => {
5014
+ if (isMesh(object3)) object3.material = mat;
4363
5015
  });
4364
5016
  }));
4365
5017
  if (!this.children.length) {
@@ -4687,8 +5339,8 @@ function toTrianglesDrawMode(geometry, drawMode) {
4687
5339
  return geometry;
4688
5340
  }
4689
5341
  }
4690
- function computeMorphedAttributes(object) {
4691
- if (object.geometry.isBufferGeometry !== true) {
5342
+ function computeMorphedAttributes(object3) {
5343
+ if (object3.geometry.isBufferGeometry !== true) {
4692
5344
  console.error("THREE.BufferGeometryUtils: Geometry is not of type BufferGeometry.");
4693
5345
  return null;
4694
5346
  }
@@ -4701,11 +5353,11 @@ function computeMorphedAttributes(object) {
4701
5353
  const _morphA = new import_three22.Vector3();
4702
5354
  const _morphB = new import_three22.Vector3();
4703
5355
  const _morphC = new import_three22.Vector3();
4704
- function _calculateMorphedAttributeData(object2, material2, attribute, morphAttribute, morphTargetsRelative2, a2, b2, c2, modifiedAttributeArray) {
5356
+ function _calculateMorphedAttributeData(object4, material2, attribute, morphAttribute, morphTargetsRelative2, a2, b2, c2, modifiedAttributeArray) {
4705
5357
  _vA.fromBufferAttribute(attribute, a2);
4706
5358
  _vB.fromBufferAttribute(attribute, b2);
4707
5359
  _vC.fromBufferAttribute(attribute, c2);
4708
- const morphInfluences = object2.morphTargetInfluences;
5360
+ const morphInfluences = object4.morphTargetInfluences;
4709
5361
  if (material2.morphTargets && morphAttribute && morphInfluences) {
4710
5362
  _morphA.set(0, 0, 0);
4711
5363
  _morphB.set(0, 0, 0);
@@ -4731,10 +5383,10 @@ function computeMorphedAttributes(object) {
4731
5383
  _vB.add(_morphB);
4732
5384
  _vC.add(_morphC);
4733
5385
  }
4734
- if (object2.isSkinnedMesh) {
4735
- object2.boneTransform(a2, _vA);
4736
- object2.boneTransform(b2, _vB);
4737
- object2.boneTransform(c2, _vC);
5386
+ if (object4.isSkinnedMesh) {
5387
+ object4.boneTransform(a2, _vA);
5388
+ object4.boneTransform(b2, _vB);
5389
+ object4.boneTransform(c2, _vC);
4738
5390
  }
4739
5391
  modifiedAttributeArray[a2 * 3 + 0] = _vA.x;
4740
5392
  modifiedAttributeArray[a2 * 3 + 1] = _vA.y;
@@ -4746,8 +5398,8 @@ function computeMorphedAttributes(object) {
4746
5398
  modifiedAttributeArray[c2 * 3 + 1] = _vC.y;
4747
5399
  modifiedAttributeArray[c2 * 3 + 2] = _vC.z;
4748
5400
  }
4749
- const geometry = object.geometry;
4750
- const material = object.material;
5401
+ const geometry = object3.geometry;
5402
+ const material = object3.material;
4751
5403
  let a, b, c;
4752
5404
  const index = geometry.index;
4753
5405
  const positionAttribute = geometry.attributes.position;
@@ -4774,7 +5426,7 @@ function computeMorphedAttributes(object) {
4774
5426
  b = index.getX(j + 1);
4775
5427
  c = index.getX(j + 2);
4776
5428
  _calculateMorphedAttributeData(
4777
- object,
5429
+ object3,
4778
5430
  groupMaterial,
4779
5431
  positionAttribute,
4780
5432
  morphPosition,
@@ -4785,7 +5437,7 @@ function computeMorphedAttributes(object) {
4785
5437
  modifiedPosition
4786
5438
  );
4787
5439
  _calculateMorphedAttributeData(
4788
- object,
5440
+ object3,
4789
5441
  groupMaterial,
4790
5442
  normalAttribute,
4791
5443
  morphNormal,
@@ -4805,7 +5457,7 @@ function computeMorphedAttributes(object) {
4805
5457
  b = index.getX(i + 1);
4806
5458
  c = index.getX(i + 2);
4807
5459
  _calculateMorphedAttributeData(
4808
- object,
5460
+ object3,
4809
5461
  material,
4810
5462
  positionAttribute,
4811
5463
  morphPosition,
@@ -4816,7 +5468,7 @@ function computeMorphedAttributes(object) {
4816
5468
  modifiedPosition
4817
5469
  );
4818
5470
  _calculateMorphedAttributeData(
4819
- object,
5471
+ object3,
4820
5472
  material,
4821
5473
  normalAttribute,
4822
5474
  morphNormal,
@@ -4840,7 +5492,7 @@ function computeMorphedAttributes(object) {
4840
5492
  b = j + 1;
4841
5493
  c = j + 2;
4842
5494
  _calculateMorphedAttributeData(
4843
- object,
5495
+ object3,
4844
5496
  groupMaterial,
4845
5497
  positionAttribute,
4846
5498
  morphPosition,
@@ -4851,7 +5503,7 @@ function computeMorphedAttributes(object) {
4851
5503
  modifiedPosition
4852
5504
  );
4853
5505
  _calculateMorphedAttributeData(
4854
- object,
5506
+ object3,
4855
5507
  groupMaterial,
4856
5508
  normalAttribute,
4857
5509
  morphNormal,
@@ -4871,7 +5523,7 @@ function computeMorphedAttributes(object) {
4871
5523
  b = i + 1;
4872
5524
  c = i + 2;
4873
5525
  _calculateMorphedAttributeData(
4874
- object,
5526
+ object3,
4875
5527
  material,
4876
5528
  positionAttribute,
4877
5529
  morphPosition,
@@ -4882,7 +5534,7 @@ function computeMorphedAttributes(object) {
4882
5534
  modifiedPosition
4883
5535
  );
4884
5536
  _calculateMorphedAttributeData(
4885
- object,
5537
+ object3,
4886
5538
  material,
4887
5539
  normalAttribute,
4888
5540
  morphNormal,
@@ -4918,6 +5570,7 @@ var init_ModelCulling = __esm({
4918
5570
  import_three23 = require("three");
4919
5571
  init_CubeFace();
4920
5572
  ModelCulling = class {
5573
+ /** Rotates a world-direction bit mask into model space. Bits follow {@link CUBE_FACES}. */
4921
5574
  static toLocalMask(worldMask, rotation) {
4922
5575
  let localMask = 0;
4923
5576
  for (const [localFace, offset] of CUBE_FACE_OFFSETS.entries()) {
@@ -4928,6 +5581,7 @@ var init_ModelCulling = __esm({
4928
5581
  }
4929
5582
  return localMask;
4930
5583
  }
5584
+ /** Whether a model fills one block with opaque faces and can hide adjacent block faces. */
4931
5585
  static isOpaqueFullCube(atlas) {
4932
5586
  if (!atlas || atlas.hasTransparency !== false || atlas.model.elements?.length !== 1) return false;
4933
5587
  const element = atlas.model.elements[0];
@@ -4960,9 +5614,11 @@ var init_SpecialItems = __esm({
4960
5614
  import_three24 = require("three");
4961
5615
  init_AssetKey();
4962
5616
  init_Entities();
5617
+ init_BannerPatterns();
5618
+ init_ModelTextures();
4963
5619
  SpecialItems = class {
4964
5620
  /** Resolves the static entity parts drawn by a special item renderer, in model units. */
4965
- static async getParts(special, root) {
5621
+ static async getParts(special, root, components = {}) {
4966
5622
  const texture = (id, directory) => {
4967
5623
  const key = AssetKey.parse("textures", id);
4968
5624
  return new AssetKey(
@@ -4975,13 +5631,77 @@ var init_SpecialItems = __esm({
4975
5631
  root
4976
5632
  );
4977
5633
  };
4978
- const load = async (id, textureKey, transform, rotations = {}) => {
5634
+ const load = async (id, textureKey, transform, rotations = {}, layers = ["main"]) => {
4979
5635
  const key = new AssetKey("minecraft", id, void 0, void 0, "entity-models", ".json", root);
4980
- const model = await Entities.getEntity(key, textureKey, { layer: "main" });
5636
+ const model = await Entities.getEntity(key, textureKey, layers.length === 1 ? { layer: layers[0] } : {
5637
+ layers,
5638
+ textures: textureKey ? Object.fromEntries(layers.map((name) => [name, textureKey])) : void 0
5639
+ });
4981
5640
  if (!model) throw new Error(`Missing entity model minecraft:${id} for special item ${special.type}`);
4982
5641
  return { model, transform, rotations };
4983
5642
  };
4984
5643
  switch (special.type) {
5644
+ case "minecraft:trident":
5645
+ case "trident":
5646
+ case "minecraft:conduit":
5647
+ case "conduit": {
5648
+ const trident = special.type === "trident" || special.type === "minecraft:trident";
5649
+ const part = await load(
5650
+ trident ? "trident" : "conduit",
5651
+ texture(trident ? "trident" : "conduit/base", ""),
5652
+ trident ? new import_three24.Matrix4().makeScale(1, -1, -1) : new import_three24.Matrix4().makeTranslation(8, 8, 8),
5653
+ {},
5654
+ trident ? ["main"] : ["shell"]
5655
+ );
5656
+ part.model = { ...part.model, render: "solid", layers: part.model.layers && Object.fromEntries(
5657
+ Object.entries(part.model.layers).map(([name, layer]) => [name, { ...layer, render: "solid" }])
5658
+ ) };
5659
+ return [part];
5660
+ }
5661
+ case "minecraft:banner":
5662
+ case "banner":
5663
+ case "minecraft:shield":
5664
+ case "shield": {
5665
+ const banner = special.type === "banner" || special.type === "minecraft:banner";
5666
+ const color2 = banner ? BannerPatterns.getColor(special.color) : components["minecraft:base_color"] === void 0 ? void 0 : BannerPatterns.getColor(components["minecraft:base_color"]);
5667
+ const patterns = await BannerPatterns.getLayers(components["minecraft:banner_patterns"], banner ? "banner" : "shield", root);
5668
+ const patterned = banner || color2 !== void 0 || patterns.length > 0;
5669
+ const baseTexture = texture(banner ? "banner_base" : patterned ? "shield_base" : "shield_base_nopattern", "");
5670
+ 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);
5671
+ const part = await load(banner ? "standing_banner" : "shield", baseTexture, transform, {}, banner ? ["main", "flag"] : ["main"]);
5672
+ const layers = Object.fromEntries(Object.entries(part.model.layers ?? { main: part.model }).map(([name, layer]) => [name, { ...layer, render: "solid" }]));
5673
+ let overlay2;
5674
+ if (banner) {
5675
+ const flagLayer = layers.flag;
5676
+ const flag = flagLayer?.layer.root.children.flag;
5677
+ if (!flag) throw new Error("Banner entity model is missing its flag layer");
5678
+ overlay2 = { ...flagLayer.layer, root: { ...flagLayer.layer.root, children: {
5679
+ ...flagLayer.layer.root.children,
5680
+ flag: { ...flag, pose: { ...flag.pose, rotation: [-25e-4 * Math.PI, flag.pose.rotation[1], flag.pose.rotation[2]] } }
5681
+ } } };
5682
+ layers.flag = { ...flagLayer, layer: overlay2 };
5683
+ } else {
5684
+ const main = layers.main.layer;
5685
+ const plate = main.root.children.plate;
5686
+ if (!plate) throw new Error("Shield entity model is missing its plate");
5687
+ overlay2 = { ...main, root: { ...main.root, cubes: [], children: { plate } } };
5688
+ }
5689
+ const tints = {};
5690
+ if (patterned) {
5691
+ const draws = [{ texture: texture(`${banner ? "banner" : "shield"}/base`, ""), color: color2 ?? DYE_COLORS.white }, ...patterns];
5692
+ draws.forEach((draw, index) => {
5693
+ const name = index === 0 ? "pattern_base" : `pattern_${index - 1}`;
5694
+ layers[name] = { key: draw.texture, texture: draw.texture, layer: overlay2, render: "no_outline", tint: name };
5695
+ tints[name] = draw.color;
5696
+ });
5697
+ }
5698
+ await Promise.all([...new Map(Object.values(layers).map((layer) => [layer.texture.serialize(), layer.texture])).values()].map(async (key) => {
5699
+ if (!await ModelTextures.preload(key)) throw new Error(`Missing special item texture ${key.toNamespacedString()}`);
5700
+ }));
5701
+ part.model = { ...part.model, ...layers.main, layers };
5702
+ part.tints = tints;
5703
+ return [part];
5704
+ }
4985
5705
  case "minecraft:chest":
4986
5706
  case "chest": {
4987
5707
  const angle = -(special.openness ?? 0) * Math.PI / 2;
@@ -4992,6 +5712,26 @@ var init_SpecialItems = __esm({
4992
5712
  chest.model = { ...chest.model, render: "solid", layers: void 0 };
4993
5713
  return [chest];
4994
5714
  }
5715
+ case "minecraft:shulker_box":
5716
+ case "shulker_box": {
5717
+ const openness = special.openness === void 0 ? 0 : special.openness;
5718
+ const orientation = special.orientation === void 0 ? "up" : special.orientation;
5719
+ const directions = {
5720
+ down: [Math.PI, 0, 0],
5721
+ up: [0, 0, 0],
5722
+ north: [Math.PI / 2, 0, Math.PI],
5723
+ south: [Math.PI / 2, 0, 0],
5724
+ west: [Math.PI / 2, 0, Math.PI / 2],
5725
+ east: [Math.PI / 2, 0, -Math.PI / 2]
5726
+ };
5727
+ if (!Object.prototype.hasOwnProperty.call(directions, orientation)) throw new Error(`Unsupported shulker-box orientation ${orientation}`);
5728
+ if (typeof openness !== "number" || !Number.isFinite(openness)) throw new Error("Shulker-box openness must be finite");
5729
+ if (typeof special.texture !== "string" || !special.texture) throw new Error("Shulker-box texture is required");
5730
+ 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));
5731
+ const box = await load("shulker_box", texture(special.texture, "shulker"), transform, { lid: [0, openness * Math.PI * 1.5, 0] });
5732
+ box.positions = { lid: [0, 24 - openness * 8, 0] };
5733
+ return [box];
5734
+ }
4995
5735
  case "minecraft:bed":
4996
5736
  case "bed": {
4997
5737
  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));
@@ -5106,6 +5846,7 @@ var init_ItemTints = __esm({
5106
5846
  static async get(model, tints) {
5107
5847
  const sources = model.tints;
5108
5848
  if (!sources?.length) return tints;
5849
+ const components = model.components ?? {};
5109
5850
  const colors = { ...tints };
5110
5851
  await Promise.all(sources.map(async (source, index) => {
5111
5852
  if (colors[index] !== void 0) return;
@@ -5120,27 +5861,71 @@ var init_ItemTints = __esm({
5120
5861
  break;
5121
5862
  case "dye":
5122
5863
  case "minecraft:dye":
5864
+ colors[index] = this.colorOrDefault(components["minecraft:dyed_color"], source.default, true);
5865
+ break;
5123
5866
  case "potion":
5124
- case "minecraft:potion":
5867
+ case "minecraft:potion": {
5868
+ const value = typeof components["minecraft:potion_contents"] === "string" ? void 0 : this.componentField(components, "potion_contents", "custom_color");
5869
+ colors[index] = this.colorOrDefault(value, source.default);
5870
+ break;
5871
+ }
5125
5872
  case "map_color":
5126
5873
  case "minecraft:map_color":
5874
+ colors[index] = this.colorOrDefault(components["minecraft:map_color"], source.default);
5875
+ break;
5127
5876
  case "firework":
5128
- case "minecraft:firework":
5877
+ case "minecraft:firework": {
5878
+ const values = this.componentField(components, "firework_explosion", "colors");
5879
+ if (values !== void 0 && !Array.isArray(values)) throw new MineRenderError("Item tint firework_explosion.colors must be an array");
5880
+ if (!values?.length) {
5881
+ colors[index] = this.rgb(source.default);
5882
+ break;
5883
+ }
5884
+ const rgb = values.map((value) => this.componentColor(value));
5885
+ const red = Math.floor(rgb.reduce((sum, color2) => sum + (color2 >> 16 & 255), 0) / rgb.length);
5886
+ const green = Math.floor(rgb.reduce((sum, color2) => sum + (color2 >> 8 & 255), 0) / rgb.length);
5887
+ const blue = Math.floor(rgb.reduce((sum, color2) => sum + (color2 & 255), 0) / rgb.length);
5888
+ colors[index] = red << 16 | green << 8 | blue;
5889
+ break;
5890
+ }
5129
5891
  case "team":
5130
5892
  case "minecraft:team":
5131
- case "custom_model_data":
5132
- case "minecraft:custom_model_data":
5133
5893
  colors[index] = this.rgb(source.default);
5134
5894
  break;
5895
+ case "custom_model_data":
5896
+ case "minecraft:custom_model_data": {
5897
+ const colorIndex = source.index ?? 0;
5898
+ if (!Number.isSafeInteger(colorIndex) || colorIndex < 0) throw new MineRenderError("Item tint custom_model_data index must be a nonnegative integer");
5899
+ const values = this.componentField(components, "custom_model_data", "colors");
5900
+ if (values !== void 0 && !Array.isArray(values)) throw new MineRenderError("Item tint custom_model_data.colors must be an array");
5901
+ colors[index] = this.colorOrDefault(values?.[colorIndex], source.default, true);
5902
+ break;
5903
+ }
5135
5904
  default:
5136
5905
  throw new MineRenderError(`Unsupported item tint source ${source.type}`);
5137
5906
  }
5138
5907
  }));
5139
5908
  return colors;
5140
5909
  }
5141
- static rgb(color) {
5142
- if (!Array.isArray(color)) return color & 16777215;
5143
- const [red, green, blue] = color.map((channel) => Math.floor(Math.fround(Math.fround(channel) * 255)));
5910
+ static componentField(components, id, field) {
5911
+ const value = components[`minecraft:${id}`];
5912
+ if (value === void 0) return void 0;
5913
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new MineRenderError(`Item tint ${id} must be an object`);
5914
+ return value[field];
5915
+ }
5916
+ static colorOrDefault(value, fallback, allowTriple = false) {
5917
+ return value === void 0 ? this.rgb(fallback) : this.componentColor(value, allowTriple);
5918
+ }
5919
+ static componentColor(value, allowTriple = false) {
5920
+ if (typeof value === "number" && Number.isInteger(value) && value >= -2147483648 && value <= 2147483647) return this.rgb(value);
5921
+ if (allowTriple && Array.isArray(value) && value.length === 3 && value.every((channel) => typeof channel === "number" && Number.isFinite(channel))) {
5922
+ return this.rgb(value) & 16777215;
5923
+ }
5924
+ throw new MineRenderError(`Item tint component color must be a packed integer${allowTriple ? " or RGB triple" : ""}`);
5925
+ }
5926
+ static rgb(color2) {
5927
+ if (!Array.isArray(color2)) return color2 & 16777215;
5928
+ const [red, green, blue] = color2.map((channel) => Math.floor(Math.fround(Math.fround(channel) * 255)) & 255);
5144
5929
  return red << 16 | green << 8 | blue;
5145
5930
  }
5146
5931
  };
@@ -5177,9 +5962,10 @@ var init_ModelObject = __esm({
5177
5962
  this.originalModel = originalModel;
5178
5963
  this.isModelObject = true;
5179
5964
  this.specialMaterials = /* @__PURE__ */ new Map();
5965
+ this.geometries = /* @__PURE__ */ new Set();
5180
5966
  this.meshesCreated = false;
5181
5967
  this.options = (0, import_ts_deepmerge5.default)({}, _ModelObject.DEFAULT_OPTIONS, options ?? {});
5182
- if (originalModel.special) this.options.instanceMeshes = false;
5968
+ if (originalModel.special || originalModel.parts) this.options.instanceMeshes = false;
5183
5969
  if (this.options.tints) this.options.tints = { ...this.options.tints };
5184
5970
  this.addEventListener("added", () => this.updateAnimationSubscription());
5185
5971
  this.addEventListener("removed", () => this.updateAnimationSubscription());
@@ -5188,21 +5974,43 @@ var init_ModelObject = __esm({
5188
5974
  this.DEFAULT_OPTIONS = (0, import_ts_deepmerge5.default)({}, SceneObject.DEFAULT_OPTIONS, {});
5189
5975
  }
5190
5976
  async init() {
5977
+ const parts = this.originalModel.parts;
5978
+ if (parts) {
5979
+ try {
5980
+ for (const part of parts) {
5981
+ const object3 = new _ModelObject(part, { ...this.options, instanceMeshes: false });
5982
+ this.add(object3);
5983
+ await object3.init();
5984
+ }
5985
+ let order = 0;
5986
+ this.iterateAllMeshes((mesh) => {
5987
+ mesh.renderOrder = order++;
5988
+ for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
5989
+ material.transparent = true;
5990
+ }
5991
+ });
5992
+ } catch (error) {
5993
+ this.disposeAndRemoveAllChildren();
5994
+ throw error;
5995
+ }
5996
+ this.notifyDirty();
5997
+ return;
5998
+ }
5191
5999
  this.options.tints = await ItemTints.get(this.originalModel, this.options.tints);
5192
6000
  const special = this.originalModel.special;
5193
6001
  if (special) {
5194
- const parts = await SpecialItems.getParts(special, this.originalModel.key?.root);
6002
+ const parts2 = await SpecialItems.getParts(special, this.originalModel.key?.root, this.originalModel.components);
5195
6003
  const transform = this.options.displayPosition ? DisplayTransforms.getMatrix(this.originalModel.display, this.options.displayPosition) : new import_three25.Matrix4();
5196
6004
  transform.multiply(new import_three25.Matrix4().makeTranslation(-8, -8, -8));
5197
6005
  try {
5198
- for (const part of parts) {
5199
- const object = new EntityObject(part.model, { flip: false, wireframe: this.options.wireframe });
5200
- object.matrix.copy(transform).multiply(part.transform);
5201
- object.matrixWorldNeedsUpdate = true;
5202
- object.matrixAutoUpdate = false;
5203
- this.add(object);
5204
- await object.init();
5205
- object.iterateAllMeshes((mesh) => {
6006
+ for (const part of parts2) {
6007
+ const object3 = new EntityObject(part.model, { flip: false, wireframe: this.options.wireframe, tints: part.tints });
6008
+ object3.matrix.copy(transform).multiply(part.transform);
6009
+ object3.matrixWorldNeedsUpdate = true;
6010
+ object3.matrixAutoUpdate = false;
6011
+ this.add(object3);
6012
+ await object3.init();
6013
+ object3.iterateAllMeshes((mesh) => {
5206
6014
  const source = mesh.material;
5207
6015
  let material = this.specialMaterials.get(source);
5208
6016
  if (!material) {
@@ -5213,7 +6021,10 @@ var init_ModelObject = __esm({
5213
6021
  mesh.material = material;
5214
6022
  });
5215
6023
  for (const [name, rotation] of Object.entries(part.rotations)) {
5216
- object.getGroupByName(name)?.rotation.set(...rotation, "ZYX");
6024
+ object3.getGroupByName(name)?.rotation.set(...rotation, "ZYX");
6025
+ }
6026
+ for (const [name, position] of Object.entries(part.positions ?? {})) {
6027
+ object3.getGroupByName(name)?.position.set(...position);
5217
6028
  }
5218
6029
  }
5219
6030
  } catch (error) {
@@ -5246,6 +6057,7 @@ var init_ModelObject = __esm({
5246
6057
  if (this.atlas.model.elements) {
5247
6058
  this.atlas.model.elements?.forEach((el) => {
5248
6059
  const elGeo = this._getBoxGeometryFromElement(el).clone();
6060
+ this.geometries.add(elGeo);
5249
6061
  if (this.options.cullMask) {
5250
6062
  const indices = Array.from(elGeo.getIndex().array);
5251
6063
  elGeo.setIndex(indices.filter((_, index) => {
@@ -5258,6 +6070,7 @@ var init_ModelObject = __esm({
5258
6070
  if (this.options.uvLockRotation) {
5259
6071
  UVMapper.lockUvs(elGeo, el.faces, this.atlas, new import_three25.Euler(...this.options.uvLockRotation));
5260
6072
  }
6073
+ UVMapper.setAtlasUvBounds(elGeo, el.faces, this.atlas);
5261
6074
  if (this.options.tints) {
5262
6075
  const colors = new Float32Array(elGeo.getAttribute("position").count * 3).fill(1);
5263
6076
  for (const [faceIndex, faceName] of CUBE_FACES.entries()) {
@@ -5265,9 +6078,9 @@ var init_ModelObject = __esm({
5265
6078
  if (tintIndex === void 0 || tintIndex < 0) continue;
5266
6079
  const tint = this.options.tints[tintIndex];
5267
6080
  if (tint === void 0) continue;
5268
- const color = new import_three25.Color(tint);
6081
+ const color2 = new import_three25.Color(tint);
5269
6082
  for (let vertex = 0; vertex < 4; vertex++) {
5270
- color.toArray(colors, (faceIndex * 4 + vertex) * 3);
6083
+ color2.toArray(colors, (faceIndex * 4 + vertex) * 3);
5271
6084
  }
5272
6085
  }
5273
6086
  elGeo.setAttribute("color", new import_three25.BufferAttribute(colors, 3));
@@ -5298,10 +6111,14 @@ var init_ModelObject = __esm({
5298
6111
  let combinedGeo;
5299
6112
  if (allGeos.length > 0) {
5300
6113
  combinedGeo = mergeBufferGeometries(allGeos);
6114
+ for (const geometry of allGeos) {
6115
+ this.geometries.delete(geometry);
6116
+ }
5301
6117
  } else {
5302
6118
  combinedGeo = new import_three25.BoxGeometry(16, 16, 16);
5303
6119
  if (displayTransform) DisplayTransforms.apply(combinedGeo, displayTransform);
5304
6120
  }
6121
+ this.geometries.add(combinedGeo);
5305
6122
  combinedGeo.computeBoundingBox();
5306
6123
  let mesh;
5307
6124
  if (this.options.instanceMeshes) {
@@ -5323,9 +6140,12 @@ var init_ModelObject = __esm({
5323
6140
  }
5324
6141
  applyTextures() {
5325
6142
  if (this.atlas) {
5326
- const mat = Materials.createShadedCanvasMaterial(this.atlas.image.canvas, this.atlas.hasTransparency, false);
6143
+ const mat = Materials.createShadedCanvasMaterial(this.atlas.image.canvas, this.atlas.hasTransparency, false, true);
5327
6144
  this.atlasMaterial = mat;
5328
6145
  this.atlasTexture = mat.uniforms?.map?.value ?? mat.map;
6146
+ if (this.options.displayPosition === "gui" /* GUI */ && this.originalModel.gui_light === "front" /* FRONT */ && mat.uniforms?.SHADE) {
6147
+ mat.uniforms.SHADE.value = false;
6148
+ }
5329
6149
  this.iterateAllMeshes((mesh) => {
5330
6150
  if (mesh.geometry.hasAttribute("color")) mat.vertexColors = true;
5331
6151
  mesh.material = mat;
@@ -5334,7 +6154,9 @@ var init_ModelObject = __esm({
5334
6154
  }
5335
6155
  }
5336
6156
  updateAnimationSubscription() {
5337
- const active = !!this.parent && (!this.isInstanced || this.instanceCounter > 0);
6157
+ let root = this;
6158
+ while (root.parent && isModelObject(root.parent)) root = root.parent;
6159
+ const active = !!root.parent && (!this.isInstanced || this.instanceCounter > 0);
5338
6160
  if (active && this.atlas?.hasAnimation && this.atlasTexture) {
5339
6161
  if (!this.unsubscribeAtlas) {
5340
6162
  this.atlasTexture.needsUpdate = true;
@@ -5350,6 +6172,9 @@ var init_ModelObject = __esm({
5350
6172
  this.unsubscribeAtlas?.();
5351
6173
  this.unsubscribeAtlas = void 0;
5352
6174
  }
6175
+ for (const child of this.children) {
6176
+ if (isModelObject(child)) child.updateAnimationSubscription();
6177
+ }
5353
6178
  }
5354
6179
  nextInstance() {
5355
6180
  const reference = super.nextInstance();
@@ -5369,6 +6194,8 @@ var init_ModelObject = __esm({
5369
6194
  this.atlasMaterial = void 0;
5370
6195
  for (const material of this.specialMaterials.values()) material.dispose();
5371
6196
  this.specialMaterials.clear();
6197
+ for (const geometry of this.geometries) geometry.dispose();
6198
+ this.geometries.clear();
5372
6199
  super.disposeAndRemoveAllChildren();
5373
6200
  }
5374
6201
  };
@@ -5582,9 +6409,9 @@ var init_FluidModelObject = __esm({
5582
6409
  }
5583
6410
  }
5584
6411
  geometry.clearGroups();
5585
- const color = new import_three31.Color(this.options.tints?.[0] ?? (this.kind === "water" ? 4159204 : 16777215));
6412
+ const color2 = new import_three31.Color(this.options.tints?.[0] ?? (this.kind === "water" ? 4159204 : 16777215));
5586
6413
  const colors = new Float32Array(geometry.getAttribute("position").count * 3);
5587
- for (let i = 0; i < colors.length; i += 3) color.toArray(colors, i);
6414
+ for (let i = 0; i < colors.length; i += 3) color2.toArray(colors, i);
5588
6415
  geometry.setAttribute("color", new import_three31.BufferAttribute(colors, 3));
5589
6416
  geometry.computeBoundingBox();
5590
6417
  geometry.computeBoundingSphere();
@@ -5628,6 +6455,7 @@ __export(index_node_exports, {
5628
6455
  Axis: () => Axis,
5629
6456
  BUILTIN_ENTITY: () => BUILTIN_ENTITY,
5630
6457
  BUILTIN_GENERATED: () => BUILTIN_GENERATED,
6458
+ BannerPatterns: () => BannerPatterns,
5631
6459
  BasicAssetKey: () => BasicAssetKey,
5632
6460
  BatchedExecutor: () => BatchedExecutor,
5633
6461
  BlockEntities: () => BlockEntities,
@@ -5650,6 +6478,7 @@ __export(index_node_exports, {
5650
6478
  DEFAULT_ROOT: () => DEFAULT_ROOT,
5651
6479
  DEFAULT_UV: () => DEFAULT_UV,
5652
6480
  DISPLAY_POSITIONS: () => DISPLAY_POSITIONS,
6481
+ DYE_COLORS: () => DYE_COLORS,
5653
6482
  DefaultBlockStates: () => DefaultBlockStates,
5654
6483
  DisplayPosition: () => DisplayPosition,
5655
6484
  DisplayTransforms: () => DisplayTransforms,
@@ -5660,6 +6489,7 @@ __export(index_node_exports, {
5660
6489
  Env: () => Env,
5661
6490
  FluidModelObject: () => FluidModelObject,
5662
6491
  Fonts: () => Fonts,
6492
+ GUI_CONTAINER_LAYOUTS: () => GUI_CONTAINER_LAYOUTS,
5663
6493
  Geometries: () => Geometries,
5664
6494
  GuiHelper: () => GuiHelper,
5665
6495
  GuiLight: () => GuiLight,
@@ -5688,11 +6518,14 @@ __export(index_node_exports, {
5688
6518
  NodeCache: () => NodeCache,
5689
6519
  NodeEnv: () => NodeEnv,
5690
6520
  OrbitControls: () => OrbitControls,
6521
+ PackFormats: () => PackFormats,
6522
+ PackMetadata: () => PackMetadata,
5691
6523
  PersistentCache: () => PersistentCache,
5692
6524
  Renderer: () => Renderer,
5693
6525
  RequestError: () => RequestError,
5694
6526
  Requests: () => Requests,
5695
6527
  SKIN_PARTS: () => SKIN_PARTS,
6528
+ SceneDocumentLoader: () => SceneDocumentLoader,
5696
6529
  SceneExporter: () => SceneExporter,
5697
6530
  SceneInspector: () => SceneInspector,
5698
6531
  SceneObject: () => SceneObject,
@@ -5768,6 +6601,7 @@ __export(index_node_exports, {
5768
6601
  mergeBufferAttributes: () => mergeBufferAttributes,
5769
6602
  mergeBufferGeometries: () => mergeBufferGeometries,
5770
6603
  mergeVertices: () => mergeVertices,
6604
+ parseSceneDocument: () => parseSceneDocument,
5771
6605
  prefix: () => prefix,
5772
6606
  registerNodeEnv: () => registerNodeEnv,
5773
6607
  sampleEntityAnimation: () => sampleEntityAnimation,
@@ -5858,9 +6692,9 @@ var NodeEnv = class {
5858
6692
  imageSize(data) {
5859
6693
  return (0, import_image_size.default)(Buffer.isBuffer(data) ? data : Buffer.from(data));
5860
6694
  }
5861
- openCache(name, version) {
6695
+ openCache(name, version2) {
5862
6696
  return new NodeCache(import_node_persist.default.create({
5863
- dir: name + version,
6697
+ dir: name + version2,
5864
6698
  encoding: "utf8"
5865
6699
  }));
5866
6700
  }
@@ -5892,6 +6726,7 @@ var Meshes = class _Meshes {
5892
6726
  static createBox(key) {
5893
6727
  return new import_three6.Mesh(Geometries.getBox(key.geometry), Materials.getImage(key.material));
5894
6728
  }
6729
+ /** Returns a shared Mesh object. Use {@link createBox} for a mesh with its own transform and parent. */
5895
6730
  static getBox(key) {
5896
6731
  const keyStr = serializeMeshKey(key);
5897
6732
  return Caching.boxMeshCache.get(keyStr, (k) => {
@@ -5941,6 +6776,7 @@ function shutdown() {
5941
6776
  init_AssetKey();
5942
6777
  init_AssetLoader();
5943
6778
  init_Assets();
6779
+ init_BannerPatterns();
5944
6780
 
5945
6781
  // src/assets/BlockEntities.ts
5946
6782
  var import_three10 = require("three");
@@ -6004,6 +6840,7 @@ init_AssetParsers();
6004
6840
  init_ModelTextures();
6005
6841
  init_Caching();
6006
6842
  var Fonts = class {
6843
+ /** Combines font definitions in source-priority order. Unsupported Unihex and TrueType providers are skipped. */
6007
6844
  static async get(key = "minecraft:default") {
6008
6845
  const fontKey = typeof key === "string" ? AssetKey.parse("font", key) : key;
6009
6846
  return await Caching.fontCache.get(fontKey.serialize(), () => this.load(fontKey, []));
@@ -6019,8 +6856,8 @@ var Fonts = class {
6019
6856
  const stack = await AssetLoader.getAll(key, "json" /* JSON */);
6020
6857
  if (stack.length === 0) throw new Error(`Could not load font ${key.toNamespacedString()}`);
6021
6858
  const glyphs = /* @__PURE__ */ new Map();
6022
- for (const asset of stack) {
6023
- for (const provider of asset.providers) {
6859
+ for (const asset2 of stack) {
6860
+ for (const provider of asset2.providers) {
6024
6861
  if (provider.filter?.uniform || provider.filter?.jp) continue;
6025
6862
  let provided;
6026
6863
  switch (provider.type) {
@@ -6159,13 +6996,13 @@ var OBJExporter = class {
6159
6996
  * @param {Object3D} object - The 3D object to export.
6160
6997
  * @return {string} The exported OBJ.
6161
6998
  */
6162
- parse(object) {
6999
+ parse(object3) {
6163
7000
  let output = "";
6164
7001
  let indexVertex = 0;
6165
7002
  let indexVertexUvs = 0;
6166
7003
  let indexNormals = 0;
6167
7004
  const vertex = new import_three16.Vector3();
6168
- const color = new import_three16.Color();
7005
+ const color2 = new import_three16.Color();
6169
7006
  const normal = new import_three16.Vector3();
6170
7007
  const uv = new import_three16.Vector2();
6171
7008
  const face = [];
@@ -6264,9 +7101,9 @@ var OBJExporter = class {
6264
7101
  vertex.applyMatrix4(points.matrixWorld);
6265
7102
  output += "v " + vertex.x + " " + vertex.y + " " + vertex.z;
6266
7103
  if (colors !== void 0) {
6267
- color.fromBufferAttribute(colors, i);
6268
- import_three16.ColorManagement.workingToColorSpace(color, import_three16.SRGBColorSpace);
6269
- output += " " + color.r + " " + color.g + " " + color.b;
7104
+ color2.fromBufferAttribute(colors, i);
7105
+ import_three16.ColorManagement.workingToColorSpace(color2, import_three16.SRGBColorSpace);
7106
+ output += " " + color2.r + " " + color2.g + " " + color2.b;
6270
7107
  }
6271
7108
  output += "\n";
6272
7109
  }
@@ -6278,7 +7115,7 @@ var OBJExporter = class {
6278
7115
  }
6279
7116
  indexVertex += nbVertex;
6280
7117
  }
6281
- object.traverse(function(child) {
7118
+ object3.traverse(function(child) {
6282
7119
  if (child.isMesh === true) {
6283
7120
  parseMesh(child);
6284
7121
  }
@@ -6306,9 +7143,9 @@ var PLYExporter = class {
6306
7143
  * @param {PLYExporter~Options} options - The export options.
6307
7144
  * @return {?(string|ArrayBuffer)} The exported PLY.
6308
7145
  */
6309
- parse(object, onDone, options = {}) {
7146
+ parse(object3, onDone, options = {}) {
6310
7147
  function traverseMeshes(cb) {
6311
- object.traverse(function(child) {
7148
+ object3.traverse(function(child) {
6312
7149
  if (child.isMesh === true || child.isPoints) {
6313
7150
  const mesh = child;
6314
7151
  const geometry = mesh.geometry;
@@ -6341,7 +7178,7 @@ var PLYExporter = class {
6341
7178
  for (const name of customAttributeNames) customTypes[name] = "float";
6342
7179
  let vertexCount = 0;
6343
7180
  let faceCount = 0;
6344
- object.traverse(function(child) {
7181
+ object3.traverse(function(child) {
6345
7182
  if (child.isMesh === true) {
6346
7183
  const mesh = child;
6347
7184
  const geometry = mesh.geometry;
@@ -6935,8 +7772,8 @@ function equalArray(array1, array2) {
6935
7772
  return element === array2[index];
6936
7773
  });
6937
7774
  }
6938
- function stringToArrayBuffer(text) {
6939
- return new TextEncoder().encode(text).buffer;
7775
+ function stringToArrayBuffer(text2) {
7776
+ return new TextEncoder().encode(text2).buffer;
6940
7777
  }
6941
7778
  function isIdentityMatrix(matrix) {
6942
7779
  return equalArray(matrix.elements, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
@@ -7129,12 +7966,12 @@ var GLTFWriter = class {
7129
7966
  * @param {THREE.Object3D|THREE.Material|THREE.BufferGeometry|THREE.AnimationClip} object
7130
7967
  * @param {Object} objectDef
7131
7968
  */
7132
- serializeUserData(object, objectDef) {
7133
- if (Object.keys(object.userData).length === 0) return;
7969
+ serializeUserData(object3, objectDef) {
7970
+ if (Object.keys(object3.userData).length === 0) return;
7134
7971
  const options = this.options;
7135
7972
  const extensionsUsed = this.extensionsUsed;
7136
7973
  try {
7137
- const json = JSON.parse(JSON.stringify(object.userData));
7974
+ const json = JSON.parse(JSON.stringify(object3.userData));
7138
7975
  if (options.includeCustomExtensions && json.gltfExtensions) {
7139
7976
  if (objectDef.extensions === void 0) objectDef.extensions = {};
7140
7977
  for (const extensionName in json.gltfExtensions) {
@@ -7145,7 +7982,7 @@ var GLTFWriter = class {
7145
7982
  }
7146
7983
  if (Object.keys(json).length > 0) objectDef.extras = json;
7147
7984
  } catch (error) {
7148
- console.warn("THREE.GLTFExporter: userData of '" + object.name + "' won't be serialized because of JSON.stringify error - " + error.message);
7985
+ console.warn("THREE.GLTFExporter: userData of '" + object3.name + "' won't be serialized because of JSON.stringify error - " + error.message);
7149
7986
  }
7150
7987
  }
7151
7988
  /**
@@ -7518,7 +8355,7 @@ var GLTFWriter = class {
7518
8355
  * @param {string} mimeType export format
7519
8356
  * @return {number} Index of the processed texture in the "images" array
7520
8357
  */
7521
- processImage(image, format, flipY, mimeType = "image/png") {
8358
+ processImage(image, format2, flipY, mimeType = "image/png") {
7522
8359
  if (image !== null) {
7523
8360
  const writer = this;
7524
8361
  const cache = writer.cache;
@@ -7542,8 +8379,8 @@ var GLTFWriter = class {
7542
8379
  ctx.scale(1, -1);
7543
8380
  }
7544
8381
  if (image.data !== void 0) {
7545
- if (format !== import_three18.RGBAFormat) {
7546
- console.error("GLTFExporter: Only RGBAFormat is supported.", format);
8382
+ if (format2 !== import_three18.RGBAFormat) {
8383
+ console.error("GLTFExporter: Only RGBAFormat is supported.", format2);
7547
8384
  }
7548
8385
  if (image.width > options.maxTextureSize || image.height > options.maxTextureSize) {
7549
8386
  console.warn("GLTFExporter: Image size is bigger than maxTextureSize", image);
@@ -7654,9 +8491,9 @@ var GLTFWriter = class {
7654
8491
  if (material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true) {
7655
8492
  console.warn("GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.");
7656
8493
  }
7657
- const color = material.color.toArray().concat([material.opacity]);
7658
- if (!equalArray(color, [1, 1, 1, 1])) {
7659
- materialDef.pbrMetallicRoughness.baseColorFactor = color;
8494
+ const color2 = material.color.toArray().concat([material.opacity]);
8495
+ if (!equalArray(color2, [1, 1, 1, 1])) {
8496
+ materialDef.pbrMetallicRoughness.baseColorFactor = color2;
7660
8497
  }
7661
8498
  if (material.isMeshStandardMaterial) {
7662
8499
  materialDef.pbrMetallicRoughness.metallicFactor = material.metalness;
@@ -8071,13 +8908,13 @@ var GLTFWriter = class {
8071
8908
  * @param {THREE.Object3D} object
8072
8909
  * @return {?number}
8073
8910
  */
8074
- processSkin(object) {
8911
+ processSkin(object3) {
8075
8912
  const json = this.json;
8076
8913
  const nodeMap = this.nodeMap;
8077
- const node = json.nodes[nodeMap.get(object)];
8078
- const skeleton = object.skeleton;
8914
+ const node = json.nodes[nodeMap.get(object3)];
8915
+ const skeleton = object3.skeleton;
8079
8916
  if (skeleton === void 0) return null;
8080
- const rootJoint = object.skeleton.bones[0];
8917
+ const rootJoint = object3.skeleton.bones[0];
8081
8918
  if (rootJoint === void 0) return null;
8082
8919
  const joints = [];
8083
8920
  const inverseBindMatrices = new Float32Array(skeleton.bones.length * 16);
@@ -8085,7 +8922,7 @@ var GLTFWriter = class {
8085
8922
  for (let i = 0; i < skeleton.bones.length; ++i) {
8086
8923
  joints.push(nodeMap.get(skeleton.bones[i]));
8087
8924
  temporaryBoneInverse.copy(skeleton.boneInverses[i]);
8088
- temporaryBoneInverse.multiply(object.bindMatrix).toArray(inverseBindMatrices, i * 16);
8925
+ temporaryBoneInverse.multiply(object3.bindMatrix).toArray(inverseBindMatrices, i * 16);
8089
8926
  }
8090
8927
  if (json.skins === void 0) json.skins = [];
8091
8928
  json.skins.push({
@@ -8101,19 +8938,19 @@ var GLTFWriter = class {
8101
8938
  * @param {THREE.Object3D} object Object3D to processNodeAsync
8102
8939
  * @return {Promise<number>} Index of the node in the nodes list
8103
8940
  */
8104
- async processNodeAsync(object) {
8941
+ async processNodeAsync(object3) {
8105
8942
  const json = this.json;
8106
8943
  const options = this.options;
8107
8944
  const nodeMap = this.nodeMap;
8108
8945
  if (!json.nodes) json.nodes = [];
8109
- if (object.pivot !== null) {
8110
- return await this._processNodeWithPivotAsync(object);
8946
+ if (object3.pivot !== null) {
8947
+ return await this._processNodeWithPivotAsync(object3);
8111
8948
  }
8112
8949
  const nodeDef = {};
8113
8950
  if (options.trs) {
8114
- const rotation = object.quaternion.toArray();
8115
- const position = object.position.toArray();
8116
- const scale = object.scale.toArray();
8951
+ const rotation = object3.quaternion.toArray();
8952
+ const position = object3.position.toArray();
8953
+ const scale = object3.scale.toArray();
8117
8954
  if (!equalArray(rotation, [0, 0, 0, 1])) {
8118
8955
  nodeDef.rotation = rotation;
8119
8956
  }
@@ -8124,28 +8961,28 @@ var GLTFWriter = class {
8124
8961
  nodeDef.scale = scale;
8125
8962
  }
8126
8963
  } else {
8127
- if (object.matrixAutoUpdate) {
8128
- object.updateMatrix();
8964
+ if (object3.matrixAutoUpdate) {
8965
+ object3.updateMatrix();
8129
8966
  }
8130
- if (isIdentityMatrix(object.matrix) === false) {
8131
- nodeDef.matrix = object.matrix.elements;
8967
+ if (isIdentityMatrix(object3.matrix) === false) {
8968
+ nodeDef.matrix = object3.matrix.elements;
8132
8969
  }
8133
8970
  }
8134
- if (object.name !== "") nodeDef.name = String(object.name);
8135
- this.serializeUserData(object, nodeDef);
8136
- if (object.isMesh || object.isLine || object.isPoints) {
8137
- const meshIndex = await this.processMeshAsync(object);
8971
+ if (object3.name !== "") nodeDef.name = String(object3.name);
8972
+ this.serializeUserData(object3, nodeDef);
8973
+ if (object3.isMesh || object3.isLine || object3.isPoints) {
8974
+ const meshIndex = await this.processMeshAsync(object3);
8138
8975
  if (meshIndex !== null) nodeDef.mesh = meshIndex;
8139
- } else if (object.isCamera) {
8140
- nodeDef.camera = this.processCamera(object);
8976
+ } else if (object3.isCamera) {
8977
+ nodeDef.camera = this.processCamera(object3);
8141
8978
  }
8142
- if (object.isSkinnedMesh) this.skins.push(object);
8979
+ if (object3.isSkinnedMesh) this.skins.push(object3);
8143
8980
  const nodeIndex = json.nodes.push(nodeDef) - 1;
8144
- nodeMap.set(object, nodeIndex);
8145
- if (object.children.length > 0) {
8981
+ nodeMap.set(object3, nodeIndex);
8982
+ if (object3.children.length > 0) {
8146
8983
  const children = [];
8147
- for (let i = 0, l = object.children.length; i < l; i++) {
8148
- const child = object.children[i];
8984
+ for (let i = 0, l = object3.children.length; i < l; i++) {
8985
+ const child = object3.children[i];
8149
8986
  if (child.visible || options.onlyVisible === false) {
8150
8987
  const childNodeIndex = await this.processNodeAsync(child);
8151
8988
  if (childNodeIndex !== null) children.push(childNodeIndex);
@@ -8154,7 +8991,7 @@ var GLTFWriter = class {
8154
8991
  if (children.length > 0) nodeDef.children = children;
8155
8992
  }
8156
8993
  await this._invokeAllAsync(function(ext) {
8157
- ext.writeNode && ext.writeNode(object, nodeDef);
8994
+ ext.writeNode && ext.writeNode(object3, nodeDef);
8158
8995
  });
8159
8996
  return nodeIndex;
8160
8997
  }
@@ -8163,19 +9000,19 @@ var GLTFWriter = class {
8163
9000
  * @param {THREE.Object3D} object Object3D with pivot
8164
9001
  * @return {Promise<number>} Index of the container node
8165
9002
  */
8166
- async _processNodeWithPivotAsync(object) {
9003
+ async _processNodeWithPivotAsync(object3) {
8167
9004
  const json = this.json;
8168
9005
  const options = this.options;
8169
9006
  const nodeMap = this.nodeMap;
8170
- const pivot = object.pivot;
9007
+ const pivot = object3.pivot;
8171
9008
  const containerDef = {};
8172
- const rotation = object.quaternion.toArray();
9009
+ const rotation = object3.quaternion.toArray();
8173
9010
  const position = [
8174
- object.position.x + pivot.x,
8175
- object.position.y + pivot.y,
8176
- object.position.z + pivot.z
9011
+ object3.position.x + pivot.x,
9012
+ object3.position.y + pivot.y,
9013
+ object3.position.z + pivot.z
8177
9014
  ];
8178
- const scale = object.scale.toArray();
9015
+ const scale = object3.scale.toArray();
8179
9016
  if (!equalArray(rotation, [0, 0, 0, 1])) {
8180
9017
  containerDef.rotation = rotation;
8181
9018
  }
@@ -8186,28 +9023,28 @@ var GLTFWriter = class {
8186
9023
  containerDef.scale = scale;
8187
9024
  }
8188
9025
  containerDef.extras = { pivot: pivot.toArray() };
8189
- if (object.name !== "") containerDef.name = String(object.name);
8190
- this.serializeUserData(object, containerDef);
9026
+ if (object3.name !== "") containerDef.name = String(object3.name);
9027
+ this.serializeUserData(object3, containerDef);
8191
9028
  const containerIndex = json.nodes.push(containerDef) - 1;
8192
- nodeMap.set(object, containerIndex);
9029
+ nodeMap.set(object3, containerIndex);
8193
9030
  const childDef = {};
8194
9031
  const childPosition = [-pivot.x, -pivot.y, -pivot.z];
8195
9032
  if (!equalArray(childPosition, [0, 0, 0])) {
8196
9033
  childDef.translation = childPosition;
8197
9034
  }
8198
- if (object.isMesh || object.isLine || object.isPoints) {
8199
- const meshIndex = await this.processMeshAsync(object);
9035
+ if (object3.isMesh || object3.isLine || object3.isPoints) {
9036
+ const meshIndex = await this.processMeshAsync(object3);
8200
9037
  if (meshIndex !== null) childDef.mesh = meshIndex;
8201
- } else if (object.isCamera) {
8202
- childDef.camera = this.processCamera(object);
9038
+ } else if (object3.isCamera) {
9039
+ childDef.camera = this.processCamera(object3);
8203
9040
  }
8204
- if (object.isSkinnedMesh) this.skins.push(object);
9041
+ if (object3.isSkinnedMesh) this.skins.push(object3);
8205
9042
  const childIndex = json.nodes.push(childDef) - 1;
8206
9043
  const containerChildren = [childIndex];
8207
- if (object.children.length > 0) {
9044
+ if (object3.children.length > 0) {
8208
9045
  const grandchildren = [];
8209
- for (let i = 0, l = object.children.length; i < l; i++) {
8210
- const child = object.children[i];
9046
+ for (let i = 0, l = object3.children.length; i < l; i++) {
9047
+ const child = object3.children[i];
8211
9048
  if (child.visible || options.onlyVisible === false) {
8212
9049
  const childNodeIndex = await this.processNodeAsync(child);
8213
9050
  if (childNodeIndex !== null) grandchildren.push(childNodeIndex);
@@ -8217,7 +9054,7 @@ var GLTFWriter = class {
8217
9054
  }
8218
9055
  containerDef.children = containerChildren;
8219
9056
  await this._invokeAllAsync(function(ext) {
8220
- ext.writeNode && ext.writeNode(object, containerDef);
9057
+ ext.writeNode && ext.writeNode(object3, containerDef);
8221
9058
  });
8222
9059
  return containerIndex;
8223
9060
  }
@@ -8660,10 +9497,10 @@ var GLTFMeshGpuInstancing = class {
8660
9497
  this.writer = writer;
8661
9498
  this.name = "EXT_mesh_gpu_instancing";
8662
9499
  }
8663
- writeNode(object, nodeDef) {
8664
- if (!object.isInstancedMesh) return;
9500
+ writeNode(object3, nodeDef) {
9501
+ if (!object3.isInstancedMesh) return;
8665
9502
  const writer = this.writer;
8666
- const mesh = object;
9503
+ const mesh = object3;
8667
9504
  const translationAttr = new Float32Array(mesh.count * 3);
8668
9505
  const rotationAttr = new Float32Array(mesh.count * 4);
8669
9506
  const scaleAttr = new Float32Array(mesh.count * 3);
@@ -8853,8 +9690,8 @@ var SceneExporter = class {
8853
9690
  }
8854
9691
  }
8855
9692
  };
8856
- function localMatrix(object) {
8857
- return object.matrixAutoUpdate ? new import_three19.Matrix4().compose(object.position, object.quaternion, object.scale) : object.matrix.clone();
9693
+ function localMatrix(object3) {
9694
+ return object3.matrixAutoUpdate ? new import_three19.Matrix4().compose(object3.position, object3.quaternion, object3.scale) : object3.matrix.clone();
8858
9695
  }
8859
9696
  function createSnapshot(root, gltf, bakeColors = false) {
8860
9697
  const scene = new import_three19.Scene();
@@ -8958,24 +9795,24 @@ function createSnapshot(root, gltf, bakeColors = false) {
8958
9795
  scene.add(copy);
8959
9796
  }
8960
9797
  };
8961
- const visit = (object, parentWorld) => {
8962
- if (!object.visible) return;
8963
- const world = parentWorld.clone().multiply(localMatrix(object));
8964
- const mesh = object;
9798
+ const visit = (object3, parentWorld) => {
9799
+ if (!object3.visible) return;
9800
+ const world = parentWorld.clone().multiply(localMatrix(object3));
9801
+ const mesh = object3;
8965
9802
  if (mesh.isMesh) {
8966
9803
  const instanced = mesh;
8967
9804
  if (instanced.isInstancedMesh) {
8968
9805
  for (let i = 0; i < instanced.count; i++) {
8969
9806
  const matrix = new import_three19.Matrix4();
8970
9807
  instanced.getMatrixAt(i, matrix);
8971
- const color = instanced.instanceColor ? new import_three19.Color().fromBufferAttribute(instanced.instanceColor, i) : void 0;
8972
- addMesh(mesh, world.clone().multiply(matrix), color);
9808
+ const color2 = instanced.instanceColor ? new import_three19.Color().fromBufferAttribute(instanced.instanceColor, i) : void 0;
9809
+ addMesh(mesh, world.clone().multiply(matrix), color2);
8973
9810
  }
8974
9811
  } else {
8975
9812
  addMesh(mesh, world);
8976
9813
  }
8977
9814
  }
8978
- for (const child of object.children) visit(child, world);
9815
+ for (const child of object3.children) visit(child, world);
8979
9816
  };
8980
9817
  try {
8981
9818
  const ancestors = [];
@@ -9048,16 +9885,17 @@ function copyDataTexture(source) {
9048
9885
 
9049
9886
  // src/gui/GuiHelper.ts
9050
9887
  init_AssetKey();
9888
+ init_ModelTextures();
9051
9889
 
9052
9890
  // src/gui/GuiText.ts
9053
9891
  var import_three20 = require("three");
9054
- async function layoutGuiText(text, options = {}) {
9892
+ async function layoutGuiText(text2, options = {}) {
9055
9893
  const maxWidth = options.maxWidth ?? Infinity, lineHeight = options.lineHeight ?? 9;
9056
9894
  if (!(maxWidth > 0) || !Number.isFinite(lineHeight) || lineHeight <= 0) {
9057
9895
  throw new Error("Text wrap width and line height must be positive");
9058
9896
  }
9059
9897
  const font = await Fonts.get(options.font);
9060
- const runs = typeof text === "string" ? [{ text }] : text;
9898
+ const runs = typeof text2 === "string" ? [{ text: text2 }] : text2;
9061
9899
  const glyphs = [], lineWidths = [];
9062
9900
  let line = [], width = 0;
9063
9901
  let trailingNewline = false;
@@ -9111,7 +9949,7 @@ function createGuiTextGeometry(layout, shadow = true) {
9111
9949
  batch = { image: glyph?.image, positions: [], uvs: [], colors: [], indices: [] };
9112
9950
  batches.push(batch);
9113
9951
  }
9114
- const color = new import_three20.Color(isShadow ? (style.color & 16579836) >> 2 : style.color);
9952
+ const color2 = new import_three20.Color(isShadow ? (style.color & 16579836) >> 2 : style.color);
9115
9953
  const offset = isShadow ? 1 : 0;
9116
9954
  const top = glyph ? 7 - glyph.ascent : 0;
9117
9955
  const height = glyph ? glyph.height * glyph.scale : 8;
@@ -9139,7 +9977,7 @@ function createGuiTextGeometry(layout, shadow = true) {
9139
9977
  const v0 = glyph ? 1 - glyph.y / glyph.image.height : 1;
9140
9978
  const v1 = glyph ? 1 - (glyph.y + glyph.height) / glyph.image.height : 0;
9141
9979
  batch.uvs.push(u0, v0, u1, v0, u0, v1, u1, v1);
9142
- for (let i = 0; i < 4; i++) batch.colors.push(color.r, color.g, color.b);
9980
+ for (let i = 0; i < 4; i++) batch.colors.push(color2.r, color2.g, color2.b);
9143
9981
  batch.indices.push(base, base + 2, base + 1, base + 2, base + 3, base + 1);
9144
9982
  };
9145
9983
  for (const boldOffset of style.bold ? [0, 1] : [0]) {
@@ -9157,7 +9995,128 @@ function createGuiTextGeometry(layout, shadow = true) {
9157
9995
  }
9158
9996
 
9159
9997
  // src/gui/GuiHelper.ts
9998
+ var GUI_CONTAINER_LAYOUTS = {
9999
+ generic_54: {
10000
+ texture: "minecraft:gui/container/generic_54",
10001
+ crop: [0, 0, 176, 222],
10002
+ slotOrigin: [8, 18],
10003
+ slotOffset: [18, 18],
10004
+ rowSize: 9,
10005
+ slotCount: 54
10006
+ },
10007
+ crafting_table: {
10008
+ texture: "minecraft:gui/container/crafting_table",
10009
+ crop: [0, 0, 176, 166],
10010
+ slotOrigin: [30, 17],
10011
+ slotOffset: [18, 18],
10012
+ rowSize: 3,
10013
+ slotCount: 9,
10014
+ resultPosition: [124, 35]
10015
+ }
10016
+ };
9160
10017
  var GuiHelper = class {
10018
+ /**
10019
+ * Creates a 182×5 boss bar using the active resource pack. Finite progress is clamped to 0–1.
10020
+ * The fill crops the source image; explicit crops do not apply `.mcmeta` sprite scaling.
10021
+ */
10022
+ static async bossBar(options = {}) {
10023
+ const { color: color2 = "pink", progress = 1, position = [0, 0] } = options;
10024
+ this.validatePosition(position);
10025
+ if (!["pink", "blue", "red", "green", "yellow", "purple", "white"].includes(color2)) {
10026
+ throw new Error(`Unsupported boss bar color: ${color2}`);
10027
+ }
10028
+ if (!Number.isFinite(progress)) throw new Error("Boss bar progress must be finite");
10029
+ const value = Math.max(0, Math.min(1, progress));
10030
+ const width = value > 0 ? Math.floor(value * 181) + 1 : 0;
10031
+ const texture = `minecraft:gui/sprites/boss_bar/${color2}`;
10032
+ const layers = [{
10033
+ name: "boss-bar-background",
10034
+ texture: `${texture}_background`,
10035
+ position: [...position],
10036
+ size: [182, 5]
10037
+ }];
10038
+ if (width > 0) {
10039
+ layers.push({
10040
+ ...await this.cropTexture(`${texture}_progress`, [0, 0, width, 5], [182, 5], position),
10041
+ name: "boss-bar-progress"
10042
+ });
10043
+ }
10044
+ return layers;
10045
+ }
10046
+ /**
10047
+ * Creates a 192×192 book page with optional arrows and supplied text. Buttons are hidden by default.
10048
+ * Callers choose the page content and button states; this helper does not paginate or handle clicks.
10049
+ */
10050
+ static async book(options = {}) {
10051
+ const {
10052
+ position = [0, 0],
10053
+ previous = "hidden",
10054
+ next = "hidden",
10055
+ text: text2,
10056
+ color: color2 = 0,
10057
+ shadow = false,
10058
+ maxWidth = 114,
10059
+ lineHeight = 9,
10060
+ ...style
10061
+ } = options;
10062
+ this.validatePosition(position);
10063
+ for (const state of [previous, next]) {
10064
+ if (!["hidden", "normal", "hover"].includes(state)) throw new Error(`Unsupported book button state: ${state}`);
10065
+ }
10066
+ const layers = [{
10067
+ ...await this.cropTexture("minecraft:gui/book", [0, 0, 192, 192], [256, 256], position),
10068
+ name: "book-background"
10069
+ }];
10070
+ for (const [name, direction, state, x] of [
10071
+ ["previous", "backward", previous, 43],
10072
+ ["next", "forward", next, 116]
10073
+ ]) {
10074
+ if (state === "hidden") continue;
10075
+ layers.push({
10076
+ name: `book-${name}`,
10077
+ texture: `minecraft:gui/sprites/widget/page_${direction}${state === "hover" ? "_highlighted" : ""}`,
10078
+ position: [position[0] + x, position[1] + 157],
10079
+ size: [23, 13]
10080
+ });
10081
+ }
10082
+ if (text2 !== void 0) {
10083
+ layers.push({
10084
+ name: "book-text",
10085
+ text: text2,
10086
+ position: [position[0] + 36, position[1] + 30],
10087
+ color: color2,
10088
+ shadow,
10089
+ maxWidth,
10090
+ lineHeight,
10091
+ ...style
10092
+ });
10093
+ }
10094
+ return layers;
10095
+ }
10096
+ /** Creates a container background. Use {@link GUI_CONTAINER_LAYOUTS} with {@link inventorySlot} to place its items. */
10097
+ static async container(type, position = [0, 0]) {
10098
+ this.validatePosition(position);
10099
+ if (type !== "generic_54" && type !== "crafting_table") throw new Error(`Unsupported GUI container: ${type}`);
10100
+ const layout = GUI_CONTAINER_LAYOUTS[type];
10101
+ return [{ ...await this.cropTexture(layout.texture, layout.crop, [256, 256], position), name: "background" }];
10102
+ }
10103
+ static validatePosition(position) {
10104
+ if (!Array.isArray(position) || position.length !== 2 || !position.every(Number.isFinite)) {
10105
+ throw new Error("GUI position must contain two finite coordinates");
10106
+ }
10107
+ }
10108
+ static async cropTexture(texture, crop, referenceSize, position) {
10109
+ const image = await ModelTextures.preload(AssetKey.parse("textures", texture));
10110
+ if (!image) throw new Error(`Could not load GUI texture ${texture}`);
10111
+ const [x, y, width, height] = crop;
10112
+ const scaleX = image.width / referenceSize[0], scaleY = image.height / referenceSize[1];
10113
+ return {
10114
+ texture,
10115
+ crop: [x * scaleX, y * scaleY, width * scaleX, height * scaleY],
10116
+ position: [...position],
10117
+ size: [width, height]
10118
+ };
10119
+ }
9161
10120
  /** Sizes a tooltip around supplied text; the first entry is its title. */
9162
10121
  static async tooltip(lines, options = {}) {
9163
10122
  if (!lines.length) return [];
@@ -9173,20 +10132,28 @@ var GuiHelper = class {
9173
10132
  size: [width + 24, height + 24]
9174
10133
  }));
9175
10134
  let y = position[1];
9176
- lines.forEach((text, index) => {
9177
- layers.push({ ...textOptions, name: `tooltip-line-${index}`, text, position: [position[0], y] });
10135
+ lines.forEach((text2, index) => {
10136
+ layers.push({ ...textOptions, name: `tooltip-line-${index}`, text: text2, position: [position[0], y] });
9178
10137
  y += layouts[index].height + (index === 0 ? titleGap : 0);
9179
10138
  });
9180
10139
  return layers;
9181
10140
  }
10141
+ /**
10142
+ * Calculates a slot's top-left position in GUI pixels.
10143
+ * @param slot - Zero-based slot index, or `[column, row]`.
10144
+ * @param origin - Position of the first slot.
10145
+ * @param offset - Horizontal and vertical spacing between slot origins, in pixels.
10146
+ * @param rowSize - Slots per row when `slot` is an index.
10147
+ */
9182
10148
  static inventorySlot(slot, origin = [0, 0], offset = [18, 18], rowSize = 9) {
9183
10149
  const [column, row] = typeof slot === "number" ? [slot % rowSize, Math.floor(slot / rowSize)] : slot;
9184
10150
  return [origin[0] + column * offset[0], origin[1] + row * offset[1]];
9185
10151
  }
9186
10152
  /** Creates crafting-table layers for ingredients and the result; stack counts are not drawn. */
9187
10153
  static recipe(recipe, options = {}) {
10154
+ const layout = GUI_CONTAINER_LAYOUTS.crafting_table;
9188
10155
  const layers = [
9189
- { name: "background", texture: "minecraft:gui/container/crafting_table", crop: [0, 0, 176, 166] }
10156
+ { name: "background", texture: layout.texture, crop: [...layout.crop] }
9190
10157
  ];
9191
10158
  const itemKey = (ingredient) => {
9192
10159
  const item = typeof ingredient === "string" && !ingredient.startsWith("#") ? ingredient : typeof ingredient === "object" && "item" in ingredient ? ingredient.item : options.resolveIngredient?.(ingredient);
@@ -9201,17 +10168,17 @@ var GuiHelper = class {
9201
10168
  layers.push({
9202
10169
  name: `ingredient-${slot}`,
9203
10170
  item: itemKey(ingredient),
9204
- position: this.inventorySlot(slot, [30, 17], [18, 18], 3)
10171
+ position: this.inventorySlot(slot, layout.slotOrigin, layout.slotOffset, layout.rowSize)
9205
10172
  });
9206
10173
  };
9207
10174
  switch (recipe.type) {
9208
10175
  case "minecraft:crafting_shaped":
9209
10176
  case "crafting_shaped": {
9210
- const pattern = recipe.pattern;
9211
- if (!pattern.length || pattern.length > 3 || !pattern[0].length || pattern[0].length > 3 || pattern.some((row) => row.length !== pattern[0].length)) {
10177
+ const pattern2 = recipe.pattern;
10178
+ if (!pattern2.length || pattern2.length > 3 || !pattern2[0].length || pattern2[0].length > 3 || pattern2.some((row) => row.length !== pattern2[0].length)) {
9212
10179
  throw new Error("Crafting patterns must be rectangular, with 1\u20133 rows and columns");
9213
10180
  }
9214
- const cells = pattern.flatMap((row, y) => row.split("").map((symbol, x) => ({ symbol, x, y }))).filter((cell) => cell.symbol !== " ");
10181
+ const cells = pattern2.flatMap((row, y) => row.split("").map((symbol, x) => ({ symbol, x, y }))).filter((cell) => cell.symbol !== " ");
9215
10182
  if (!cells.length) throw new Error("Crafting patterns must contain an ingredient");
9216
10183
  const minX = Math.min(...cells.map((cell) => cell.x)), maxX = Math.max(...cells.map((cell) => cell.x));
9217
10184
  const minY = Math.min(...cells.map((cell) => cell.y)), maxY = Math.max(...cells.map((cell) => cell.y));
@@ -9236,7 +10203,7 @@ var GuiHelper = class {
9236
10203
  layers.push({
9237
10204
  name: "result",
9238
10205
  item: itemKey("id" in recipe.result ? recipe.result.id : recipe.result.item),
9239
- position: [124, 35]
10206
+ position: [...layout.resultPosition]
9240
10207
  });
9241
10208
  return layers;
9242
10209
  }
@@ -9331,7 +10298,6 @@ init_SceneObject();
9331
10298
  init_Materials();
9332
10299
  init_ModelObject();
9333
10300
  init_DisplayPosition();
9334
- init_GuiLight();
9335
10301
  var GuiObject = class extends SceneObject {
9336
10302
  constructor(textureLayers, options) {
9337
10303
  super({ ...options, instanceMeshes: false, mergeMeshes: false });
@@ -9399,12 +10365,16 @@ var GuiObject = class extends SceneObject {
9399
10365
  mesh2.geometry.computeBoundingBox();
9400
10366
  bounds.union(mesh2.geometry.boundingBox.clone().applyMatrix4(new import_three26.Matrix4().multiplyMatrices(inverse, mesh2.matrixWorld)));
9401
10367
  });
9402
- item.position.z = depth - bounds.min.z;
9403
- depth += bounds.max.z - bounds.min.z + 0.01;
10368
+ item.position.z = depth;
10369
+ if (!bounds.isEmpty()) {
10370
+ item.position.z -= bounds.min.z;
10371
+ depth += bounds.max.z - bounds.min.z;
10372
+ this.bounds.expandByPoint(new import_three26.Vector2(bounds.min.x, -bounds.max.y));
10373
+ this.bounds.expandByPoint(new import_three26.Vector2(bounds.max.x, -bounds.min.y));
10374
+ }
10375
+ depth += 0.01;
9404
10376
  this.bounds.expandByPoint(new import_three26.Vector2(x, y));
9405
10377
  this.bounds.expandByPoint(new import_three26.Vector2(x + width2, y + height2));
9406
- this.bounds.expandByPoint(new import_three26.Vector2(bounds.min.x, -bounds.max.y));
9407
- this.bounds.expandByPoint(new import_three26.Vector2(bounds.max.x, -bounds.min.y));
9408
10378
  continue;
9409
10379
  }
9410
10380
  const key = typeof layer.texture === "string" ? AssetKey.parse("textures", layer.texture) : layer.texture;
@@ -9433,7 +10403,7 @@ var GuiObject = class extends SceneObject {
9433
10403
  }
9434
10404
  async createItem(layer, index) {
9435
10405
  const key = typeof layer.item === "string" ? AssetKey.parse("models", layer.item) : layer.item;
9436
- const model = await Models.getMerged(key);
10406
+ const model = await Models.getMerged(key, { ...layer.context, displayContext: "gui" /* GUI */ });
9437
10407
  if (!model) throw new Error(`Could not load GUI item ${key.toNamespacedString()}`);
9438
10408
  const item = new ModelObject(model, {
9439
10409
  displayPosition: "gui" /* GUI */,
@@ -9444,15 +10414,13 @@ var GuiObject = class extends SceneObject {
9444
10414
  item.name = `group:${layer.name ?? index}`;
9445
10415
  this.add(item);
9446
10416
  await item.init();
9447
- item.iterateAllMeshes((mesh) => {
9448
- if (!model.special) this.geometries.add(mesh.geometry);
10417
+ const meshes = [];
10418
+ item.iterateAllMeshes((mesh) => meshes.push(mesh));
10419
+ meshes.forEach((mesh, partIndex) => {
9449
10420
  mesh.name = `mesh:${layer.name ?? index}`;
9450
- mesh.renderOrder = index;
10421
+ mesh.renderOrder = index + partIndex / (meshes.length + 1) * 0.5;
9451
10422
  for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
9452
10423
  material.transparent = true;
9453
- if (model.gui_light === "front" /* FRONT */ && material.uniforms?.SHADE) {
9454
- material.uniforms.SHADE.value = false;
9455
- }
9456
10424
  }
9457
10425
  });
9458
10426
  return item;
@@ -9623,29 +10591,29 @@ var SceneInspector = class {
9623
10591
  this.objectControlsContainer.innerHTML = "";
9624
10592
  this.addControls(targetObject, targetIntersection, instance);
9625
10593
  }
9626
- addControls(object, intersection, instance) {
10594
+ addControls(object3, intersection, instance) {
9627
10595
  const container = document.createElement("div");
9628
10596
  container.append(this.separator("Select Parent/Child"));
9629
- if (object.parent) {
9630
- container.append(this.buttonControl("Select Parent " + object.parent.constructor.name + " " + object.parent.name, "P", () => {
9631
- this.selectObject(object.parent, intersection, instance);
10597
+ if (object3.parent) {
10598
+ container.append(this.buttonControl("Select Parent " + object3.parent.constructor.name + " " + object3.parent.name, "P", () => {
10599
+ this.selectObject(object3.parent, intersection, instance);
9632
10600
  }));
9633
10601
  }
9634
- if (object.children.length > 0) {
10602
+ if (object3.children.length > 0) {
9635
10603
  let i = 1;
9636
- for (let child of object.children) {
10604
+ for (let child of object3.children) {
9637
10605
  container.append(this.buttonControl("Select Child " + child.constructor.name + " " + child.name, "C" + i++, () => {
9638
10606
  this.selectObject(child, intersection, instance);
9639
10607
  }));
9640
10608
  }
9641
10609
  }
9642
10610
  container.append(this.separator());
9643
- const selectedInstance = instance && (object === instance.instanceable || object === intersection?.object) ? instance : void 0;
10611
+ const selectedInstance = instance && (object3 === instance.instanceable || object3 === intersection?.object) ? instance : void 0;
9644
10612
  if (!selectedInstance) {
9645
- container.append(this.toggleControl("Visibility", "V", object.visible, (v) => object.visible = v));
10613
+ container.append(this.toggleControl("Visibility", "V", object3.visible, (v) => object3.visible = v));
9646
10614
  }
9647
10615
  container.append(this.separator("Mesh"));
9648
- this.addObjectControls(object, intersection, container, selectedInstance);
10616
+ this.addObjectControls(object3, intersection, container, selectedInstance);
9649
10617
  container.append(this.separator());
9650
10618
  this.objectControlsContainer.append(container);
9651
10619
  }
@@ -9836,6 +10804,7 @@ var SceneStatsDisplay = class {
9836
10804
  appendTo(el) {
9837
10805
  el.append(this.statsContainer);
9838
10806
  }
10807
+ /** Stops the refresh timer and removes the panel from the page. */
9839
10808
  dispose() {
9840
10809
  clearInterval(this.interval);
9841
10810
  this.interval = void 0;
@@ -9860,6 +10829,7 @@ var InstanceManager = class {
9860
10829
  this.isInstanceManager = true;
9861
10830
  this.instanceCache = {};
9862
10831
  }
10832
+ /** Allocates a new placement of a cached model, or returns `undefined` if the key is unknown. */
9863
10833
  async get(key) {
9864
10834
  if (key in this.instanceCache) {
9865
10835
  console.debug(p9, "key in cache", key);
@@ -9867,6 +10837,7 @@ var InstanceManager = class {
9867
10837
  }
9868
10838
  return void 0;
9869
10839
  }
10840
+ /** Allocates a placement, calling the supplier once when the shared model is first needed. */
9870
10841
  async getOrCreate(key, supplier) {
9871
10842
  if (key in this.instanceCache) {
9872
10843
  return await this.get(key);
@@ -9883,6 +10854,7 @@ var InstanceManager = class {
9883
10854
  throw error;
9884
10855
  }
9885
10856
  }
10857
+ /** Forgets cached models without disposing them or releasing their existing placements. */
9886
10858
  reset() {
9887
10859
  for (let instanceCacheKey in this.instanceCache) {
9888
10860
  delete this.instanceCache[instanceCacheKey];
@@ -9928,6 +10900,7 @@ function matchesCondition(condition, state) {
9928
10900
  });
9929
10901
  }
9930
10902
  var BlockStateResolver = class {
10903
+ /** Loads vanilla property defaults, falling back to preview values inferred from the blockstate file. */
9931
10904
  static async defaults(blockState) {
9932
10905
  const defaults = blockState.key ? await BlockStates.getDefaultState(blockState.key) : void 0;
9933
10906
  const state = {};
@@ -9947,9 +10920,11 @@ var BlockStateResolver = class {
9947
10920
  }
9948
10921
  return state;
9949
10922
  }
10923
+ /** Selects matching models. Supply `choose` to control selection within each alternatives array. */
9950
10924
  static select(blockState, state, choose = this.choose) {
9951
10925
  return this.matching(blockState, state).map(choose);
9952
10926
  }
10927
+ /** Returns matching model groups before choosing weighted alternatives. */
9953
10928
  static matching(blockState, state) {
9954
10929
  const out = [];
9955
10930
  if (blockState.variants) {
@@ -9973,6 +10948,7 @@ var BlockStateResolver = class {
9973
10948
  }
9974
10949
  return out;
9975
10950
  }
10951
+ /** Selects a random alternative using positive integer weights, each defaulting to 1. */
9976
10952
  static choose(variants) {
9977
10953
  if (!Array.isArray(variants)) return variants;
9978
10954
  if (!variants.length) throw new MineRenderError("Blockstate variant arrays must not be empty");
@@ -9990,6 +10966,7 @@ var BlockStateResolver = class {
9990
10966
  }
9991
10967
  return variants[variants.length - 1];
9992
10968
  }
10969
+ /** Converts blockstate rotations in degrees into the Euler rotation used by model rendering. */
9993
10970
  static rotation(variant) {
9994
10971
  const rotation = new import_three29.Euler();
9995
10972
  if (typeof variant.x !== "undefined") {
@@ -10052,29 +11029,29 @@ var BlockTints = class {
10052
11029
  }
10053
11030
  }
10054
11031
  if (!indices.size) return tints;
10055
- let color;
11032
+ let color2;
10056
11033
  switch (rule.source) {
10057
11034
  case "constant":
10058
- color = rule.color;
11035
+ color2 = rule.color;
10059
11036
  break;
10060
11037
  case "grass":
10061
- color = await Colormaps.grassColor(key);
11038
+ color2 = await Colormaps.grassColor(key);
10062
11039
  break;
10063
11040
  case "redstone": {
10064
11041
  const power = Number(state.power ?? 0) / 15;
10065
11042
  const red = power * 0.6 + (power > 0 ? 0.4 : 0.3);
10066
11043
  const green = Math.max(0, power * power * 0.7 - 0.5);
10067
11044
  const blue = Math.max(0, power * power * 0.6 - 0.7);
10068
- color = Math.floor(red * 255) << 16 | Math.floor(green * 255) << 8 | Math.floor(blue * 255);
11045
+ color2 = Math.floor(red * 255) << 16 | Math.floor(green * 255) << 8 | Math.floor(blue * 255);
10069
11046
  break;
10070
11047
  }
10071
11048
  case "stem": {
10072
11049
  const age = Number(state.age ?? 0);
10073
- color = age * 32 << 16 | 255 - age * 8 << 8 | age * 4;
11050
+ color2 = age * 32 << 16 | 255 - age * 8 << 8 | age * 4;
10074
11051
  break;
10075
11052
  }
10076
11053
  }
10077
- return { ...Object.fromEntries([...indices].map((index) => [index, color])), ...tints };
11054
+ return { ...Object.fromEntries([...indices].map((index) => [index, color2])), ...tints };
10078
11055
  }
10079
11056
  };
10080
11057
 
@@ -10216,8 +11193,8 @@ var BlockObject = class _BlockObject extends SceneObject {
10216
11193
  return false;
10217
11194
  }
10218
11195
  return this._models.some((model) => {
10219
- const object = isInstanceReference(model) ? model.instanceable : model;
10220
- return object.isOpaqueFullCube && this.getModelCullMask(model, 63) === 63;
11196
+ const object3 = isInstanceReference(model) ? model.instanceable : model;
11197
+ return object3.isOpaqueFullCube && this.getModelCullMask(model, 63) === 63;
10221
11198
  });
10222
11199
  }
10223
11200
  getModelMatrix(model) {
@@ -10226,8 +11203,8 @@ var BlockObject = class _BlockObject extends SceneObject {
10226
11203
  return model.matrix.clone();
10227
11204
  }
10228
11205
  getModelCullMask(model, worldMask) {
10229
- const object = isInstanceReference(model) ? model.instanceable : model;
10230
- if (object.options.displayPosition) return 0;
11206
+ const object3 = isInstanceReference(model) ? model.instanceable : model;
11207
+ if (object3.options.displayPosition) return 0;
10231
11208
  const position = new import_three32.Vector3();
10232
11209
  const rotation = new import_three32.Quaternion();
10233
11210
  const scale = new import_three32.Vector3();
@@ -10244,14 +11221,14 @@ var BlockObject = class _BlockObject extends SceneObject {
10244
11221
  replacements.push(model);
10245
11222
  continue;
10246
11223
  }
10247
- const object = isInstanceReference(model) ? model.instanceable : model;
11224
+ const object3 = isInstanceReference(model) ? model.instanceable : model;
10248
11225
  const cullMask = this.getModelCullMask(model, worldMask);
10249
- if (cullMask === (object.options.cullMask ?? 0)) {
11226
+ if (cullMask === (object3.options.cullMask ?? 0)) {
10250
11227
  replacements.push(model);
10251
11228
  continue;
10252
11229
  }
10253
11230
  const matrix = this.getModelMatrix(model);
10254
- const replacement = await this.scene.addModel(object.originalModel, { ...object.options, cullMask });
11231
+ const replacement = await this.scene.addModel(object3.originalModel, { ...object3.options, cullMask });
10255
11232
  replacements.push(replacement);
10256
11233
  if (isInstanceReference(replacement)) {
10257
11234
  replacement.setMatrix(matrix);
@@ -10259,8 +11236,8 @@ var BlockObject = class _BlockObject extends SceneObject {
10259
11236
  replacement.matrix.copy(matrix);
10260
11237
  replacement.matrixWorldNeedsUpdate = true;
10261
11238
  matrix.decompose(replacement.position, replacement.quaternion, replacement.scale);
10262
- replacement.matrixAutoUpdate = object.matrixAutoUpdate;
10263
- replacement.visible = object.visible;
11239
+ replacement.matrixAutoUpdate = object3.matrixAutoUpdate;
11240
+ replacement.visible = object3.visible;
10264
11241
  }
10265
11242
  }
10266
11243
  } catch (error) {
@@ -10276,6 +11253,7 @@ var BlockObject = class _BlockObject extends SceneObject {
10276
11253
  }
10277
11254
  this.notifyDirty();
10278
11255
  }
11256
+ /** Selected property values. Use {@link setState} to change properties and rebuild the rendered models. */
10279
11257
  get state() {
10280
11258
  return this._state;
10281
11259
  }
@@ -10375,6 +11353,7 @@ var BlockObject = class _BlockObject extends SceneObject {
10375
11353
  obj.setPosition(this.position);
10376
11354
  return obj;
10377
11355
  }
11356
+ /** Clears selected properties and rebuilds models without reapplying property defaults. */
10378
11357
  async resetState() {
10379
11358
  this._previousState = this._state;
10380
11359
  this._state = {};
@@ -12178,9 +13157,9 @@ var SchematicParser = class {
12178
13157
  * Parses a legacy schematic using custom mappings before the bundled defaults.
12179
13158
  * Keys are `id:metadata`; values are block states such as `minecraft:oak_log[axis=x]`.
12180
13159
  */
12181
- static async parse(nbt, customMappings = {}) {
13160
+ static async parse(nbt, customMappings = {}, options = {}) {
12182
13161
  const tags = nbt.value;
12183
- if (tags.Materials?.type !== "string" || tags.Materials.value !== "Alpha") {
13162
+ if (!options.lenient && (tags.Materials?.type !== "string" || tags.Materials.value !== "Alpha")) {
12184
13163
  throw new MineRenderError("Schematic Materials must be Alpha");
12185
13164
  }
12186
13165
  const size = ["Width", "Height", "Length"].map((name) => {
@@ -12221,8 +13200,16 @@ var SchematicParser = class {
12221
13200
  const id = high << 8 | ids.value[index] & 255;
12222
13201
  const metadata = data.value[index] & 255;
12223
13202
  const key = `${id}:${metadata}`;
12224
- const mapped = customMappings[key] ?? mapping[key];
12225
- if (!mapped) throw new MineRenderError(`Unsupported legacy block ${id}:${metadata} at schematic index ${index}`);
13203
+ let mapped = customMappings[key] ?? mapping[key];
13204
+ if (!mapped && options.lenient) {
13205
+ mapped = customMappings[`${id}:0`] ?? mapping[`${id}:0`];
13206
+ }
13207
+ if (!mapped) {
13208
+ if (options.lenient) {
13209
+ continue;
13210
+ }
13211
+ throw new MineRenderError(`Unsupported legacy block ${id}:${metadata} at schematic index ${index}`);
13212
+ }
12226
13213
  const [type, state] = mapped.split("[");
12227
13214
  if (type === "minecraft:air") continue;
12228
13215
  const properties = {};
@@ -12266,6 +13253,11 @@ function compoundList(tags, name) {
12266
13253
  // src/model/multiblock/StructureParser.ts
12267
13254
  init_MineRenderError();
12268
13255
  var StructureParser = class {
13256
+ /**
13257
+ * Parses decoded NBT, omitting air blocks.
13258
+ * @param _nbt - Structure data decoded by {@link NBTHelper.fromBuffer}.
13259
+ * @param paletteIndex - Palette to select when the structure has several. Defaults to 0.
13260
+ */
12269
13261
  static async parse(_nbt, paletteIndex) {
12270
13262
  const nbt = _nbt.value;
12271
13263
  let palette;
@@ -13100,6 +14092,7 @@ var SkinImage = class {
13100
14092
  // src/skin/SkinTextures.ts
13101
14093
  init_Textures();
13102
14094
  var SkinTextures = class {
14095
+ /** Loads a skin URL. Pass `legacy` to override layout detection from the image dimensions. */
13103
14096
  static async get(src, legacy) {
13104
14097
  const image = await SkinImage.getData(src);
13105
14098
  const scale = image.width / 64;
@@ -13273,6 +14266,14 @@ var SkinObject = class extends SceneObject {
13273
14266
  }
13274
14267
  }
13275
14268
  }
14269
+ /**
14270
+ * Loads a skin texture, detecting slim arms and legacy layouts by default.
14271
+ * To override detection, set `slim` or `legacy` in the options passed to
14272
+ * `new SkinObject(options)` or `scene.addSkin(src, options)`.
14273
+ * Use {@link setSlim} or {@link setLegacy} to change these settings later.
14274
+ *
14275
+ * @param src - URL of the skin texture.
14276
+ */
13276
14277
  async setSkinTexture(src) {
13277
14278
  if (typeof src === "undefined") return;
13278
14279
  this.skinTextureSrc = src;
@@ -13383,59 +14384,61 @@ var MineRenderScene = class _MineRenderScene extends import_three34.Scene {
13383
14384
  this.DEFAULT_OPTIONS = (0, import_ts_deepmerge8.default)({}, {});
13384
14385
  }
13385
14386
  add(...objects) {
13386
- for (const object of objects) {
14387
+ for (const object3 of objects) {
13387
14388
  this.dirty = true;
13388
14389
  try {
13389
- super.add(object);
14390
+ super.add(object3);
13390
14391
  } finally {
13391
- if (object?.isObject3D && object !== this) {
13392
- this.updateObjectRegistration(object, object.parent === this);
14392
+ if (object3?.isObject3D && object3 !== this) {
14393
+ this.updateObjectRegistration(object3, object3.parent === this);
13393
14394
  }
13394
14395
  }
13395
14396
  }
13396
14397
  return this;
13397
14398
  }
13398
14399
  remove(...objects) {
13399
- for (const object of objects) {
13400
- if (!this.children.includes(object)) continue;
14400
+ for (const object3 of objects) {
14401
+ if (!this.children.includes(object3)) continue;
13401
14402
  this.dirty = true;
13402
- this.updateObjectRegistration(object, false);
14403
+ this.updateObjectRegistration(object3, false);
13403
14404
  try {
13404
- super.remove(object);
14405
+ super.remove(object3);
13405
14406
  } finally {
13406
- this.updateObjectRegistration(object, object.parent === this);
14407
+ this.updateObjectRegistration(object3, object3.parent === this);
13407
14408
  }
13408
14409
  }
13409
14410
  return this;
13410
14411
  }
13411
- updateObjectRegistration(object, attached) {
13412
- if (this.observedObjects.has(object) === attached) return;
13413
- const observable = object;
14412
+ updateObjectRegistration(object3, attached) {
14413
+ if (this.observedObjects.has(object3) === attached) return;
14414
+ const observable = object3;
13414
14415
  if (attached) {
13415
- this.observedObjects.add(object);
14416
+ this.observedObjects.add(object3);
13416
14417
  observable.addEventListener("change", this.onObjectChange);
13417
14418
  } else {
13418
- this.observedObjects.delete(object);
14419
+ this.observedObjects.delete(object3);
13419
14420
  observable.removeEventListener("change", this.onObjectChange);
13420
14421
  }
13421
14422
  const change = attached ? 1 : -1;
13422
14423
  this.stats.objectCount += change;
13423
- if (isSceneObject(object)) {
14424
+ if (isSceneObject(object3)) {
13424
14425
  this.stats.sceneObjectCount += change;
13425
14426
  }
13426
14427
  }
13427
- async initAndAdd(...object) {
14428
+ /** Initializes each object before attaching it to the scene. */
14429
+ async initAndAdd(...object3) {
13428
14430
  this.dirty = true;
13429
- for (let obj of object) {
14431
+ for (let obj of object3) {
13430
14432
  await obj.init();
13431
14433
  }
13432
- return this.add(...object);
14434
+ return this.add(...object3);
13433
14435
  }
13434
- async addSceneObject(asset, objectSupplier, _options, parent = this) {
14436
+ async addSceneObject(asset2, objectSupplier, _options, parent = this) {
13435
14437
  this.dirty = true;
13436
14438
  const obj = await objectSupplier();
13437
- if (obj?.options?.instanceMeshes && asset.key && asset.key?.assetType === "models") {
13438
- let key = asset.key.serialize();
14439
+ if (isModelObject(obj)) obj.options.tints = await ItemTints.get(obj.originalModel, obj.options.tints);
14440
+ if (obj?.options?.instanceMeshes && asset2.key && asset2.key?.assetType === "models") {
14441
+ let key = asset2.key.serialize();
13439
14442
  if (isModelObject(obj)) {
13440
14443
  const { displayPosition, uvLockRotation, tints, cullMask } = obj.options;
13441
14444
  if (displayPosition) key += `|display:${displayPosition}`;
@@ -13459,13 +14462,32 @@ var MineRenderScene = class _MineRenderScene extends import_three34.Scene {
13459
14462
  return obj;
13460
14463
  }
13461
14464
  }
14465
+ /**
14466
+ * Adds a model loaded with {@link Models.getMerged}.
14467
+ * Set `options.instanceMeshes` to `false` to create a separate ModelObject.
14468
+ *
14469
+ * @param parent - Object to attach the model to. Defaults to this scene.
14470
+ * @returns The model, or a reference to one placement of a shared model.
14471
+ */
13462
14472
  async addModel(model, options, parent = this) {
13463
- options = { ...options, tints: await ItemTints.get(model, options?.tints) };
13464
14473
  return this.addSceneObject(model, () => new ModelObject(model, options), options, parent);
13465
14474
  }
14475
+ /**
14476
+ * Adds the models selected by a blockstate loaded with {@link BlockStates.get}.
14477
+ * Set `options.initialState` to choose block properties such as `facing` or `axis`.
14478
+ * Use the returned object's `setPosition` method to move all of its model parts.
14479
+ */
13466
14480
  async addBlock(blockState, options, parent = this) {
13467
14481
  return this.addSceneObject(blockState, () => new BlockObject(blockState, options), options, parent);
13468
14482
  }
14483
+ /**
14484
+ * Loads a skin and adds an initialized player model.
14485
+ *
14486
+ * @param skin - Texture URL, such as one returned by {@link Skins.fromUuidOrUsername}.
14487
+ * Omit it to assign a texture later with {@link SkinObject.setSkinTexture}.
14488
+ * @param options - Player settings, including `slim` and `legacy` detection overrides.
14489
+ * @param parent - Object to attach the player to. Defaults to this scene.
14490
+ */
13469
14491
  async addSkin(skin, options, parent = this) {
13470
14492
  this.dirty = true;
13471
14493
  const obj = new SkinObject(options);
@@ -13477,9 +14499,14 @@ var MineRenderScene = class _MineRenderScene extends import_three34.Scene {
13477
14499
  parent.add(obj);
13478
14500
  return obj;
13479
14501
  }
14502
+ /** Adds an entity loaded with {@link Entities.getEntity}, using its selected layers and textures. */
13480
14503
  async addEntity(entity, options, parent = this) {
13481
14504
  return this.addSceneObject(entity, () => new EntityObject(entity, options), options, parent);
13482
14505
  }
14506
+ /**
14507
+ * Adds GUI layers in draw order. One GUI pixel equals one scene unit.
14508
+ * Layer positions start at the top left, with positive GUI y pointing down.
14509
+ */
13483
14510
  async addGui(layers, options, parent = this) {
13484
14511
  const obj = new GuiObject(layers, options);
13485
14512
  obj.scene = this;
@@ -13508,11 +14535,11 @@ var _changeEvent = { type: "change" };
13508
14535
  var _startEvent = { type: "start" };
13509
14536
  var _endEvent = { type: "end" };
13510
14537
  var OrbitControls = class extends import_three35.EventDispatcher {
13511
- constructor(object, domElement) {
14538
+ constructor(object3, domElement) {
13512
14539
  super();
13513
14540
  if (domElement === void 0) console.warn('THREE.OrbitControls: The second parameter "domElement" is now mandatory.');
13514
14541
  if (domElement === document) console.error('THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.');
13515
- this.object = object;
14542
+ this.object = object3;
13516
14543
  this.domElement = domElement;
13517
14544
  this.domElement.style.touchAction = "none";
13518
14545
  this.enabled = true;
@@ -13569,7 +14596,7 @@ var OrbitControls = class extends import_three35.EventDispatcher {
13569
14596
  };
13570
14597
  this.update = function() {
13571
14598
  const offset = new import_three35.Vector3();
13572
- const quat = new import_three35.Quaternion().setFromUnitVectors(object.up, new import_three35.Vector3(0, 1, 0));
14599
+ const quat = new import_three35.Quaternion().setFromUnitVectors(object3.up, new import_three35.Vector3(0, 1, 0));
13573
14600
  const quatInverse = quat.clone().invert();
13574
14601
  const lastPosition = new import_three35.Vector3();
13575
14602
  const lastQuaternion = new import_three35.Quaternion();
@@ -14134,8 +15161,8 @@ var OrbitControls = class extends import_three35.EventDispatcher {
14134
15161
  }
14135
15162
  };
14136
15163
  var MapControls = class extends OrbitControls {
14137
- constructor(object, domElement) {
14138
- super(object, domElement);
15164
+ constructor(object3, domElement) {
15165
+ super(object3, domElement);
14139
15166
  this.screenSpacePanning = false;
14140
15167
  this.mouseButtons.LEFT = import_three35.MOUSE.PAN;
14141
15168
  this.mouseButtons.RIGHT = import_three35.MOUSE.ROTATE;
@@ -14334,6 +15361,7 @@ var Renderer = class _Renderer {
14334
15361
  get element() {
14335
15362
  return this._element;
14336
15363
  }
15364
+ /** Attaches the canvas to an element and sizes it to that element's dimensions. */
14337
15365
  appendTo(element) {
14338
15366
  if (this._disposed) return;
14339
15367
  this._element = element;
@@ -14361,6 +15389,7 @@ var Renderer = class _Renderer {
14361
15389
  events.add(changeEvent2);
14362
15390
  dispatcher.addEventListener(changeEvent2, this._changeListener);
14363
15391
  }
15392
+ /** Updates the camera and canvas size. Width and height are in CSS pixels. */
14364
15393
  resize(width, height) {
14365
15394
  if (this._disposed) return;
14366
15395
  if (isPerspectiveCamera(this.camera)) {
@@ -14399,12 +15428,14 @@ var Renderer = class _Renderer {
14399
15428
  if (this._frameCallbacks.get(callback) === subscription) this._frameCallbacks.delete(callback);
14400
15429
  };
14401
15430
  }
15431
+ /** Starts or resumes the animation loop and registered frame callbacks. */
14402
15432
  start() {
14403
15433
  if (this._disposed) return;
14404
15434
  this.stop();
14405
15435
  this._running = true;
14406
15436
  if (!this._inAnimationLoop) this.renderer.setAnimationLoop(this._animationLoop);
14407
15437
  }
15438
+ /** Pauses rendering and frame callbacks. Call {@link start} to resume. */
14408
15439
  stop() {
14409
15440
  if (this._disposed) return;
14410
15441
  this._running = false;
@@ -14513,12 +15544,15 @@ var Renderer = class _Renderer {
14513
15544
  const canvas = trim ? trimCanvas(this.renderer.domElement) : this.renderer.domElement;
14514
15545
  return canvas.toDataURL(mime, quality);
14515
15546
  }
15547
+ /** Exports visible scene meshes as OBJ text, without texture images. */
14516
15548
  toObj() {
14517
15549
  return SceneExporter.toObj(this.scene);
14518
15550
  }
15551
+ /** Exports visible scene meshes as PLY. Set `options.binary` for an ArrayBuffer. */
14519
15552
  toPLY(options) {
14520
15553
  return SceneExporter.toPLY(this.scene, options);
14521
15554
  }
15555
+ /** Exports a static textured scene in the browser. Set `options.binary` for GLB output. */
14522
15556
  toGLTF(options) {
14523
15557
  return SceneExporter.toGLTF(this.scene, options);
14524
15558
  }
@@ -14557,16 +15591,244 @@ init_SceneObject();
14557
15591
  // src/index.ts
14558
15592
  init_request();
14559
15593
 
15594
+ // src/scene/SceneDocument.ts
15595
+ init_DisplayPosition();
15596
+ function fail(path, message) {
15597
+ throw new Error(`${path}: ${message}`);
15598
+ }
15599
+ function object2(value, path) {
15600
+ if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail(path, "expected a JSON object");
15601
+ for (const key of Object.keys(value)) {
15602
+ if (["__proto__", "prototype", "constructor"].includes(key)) fail(`${path}.${key}`, "unsupported property");
15603
+ }
15604
+ return value;
15605
+ }
15606
+ function fields(value, path, allowed, required = []) {
15607
+ const input = object2(value, path);
15608
+ for (const key of required) if (input[key] === void 0) fail(`${path}.${key}`, "required");
15609
+ for (const [key, entry] of Object.entries(input)) {
15610
+ if (!Object.prototype.hasOwnProperty.call(allowed, key)) fail(`${path}.${key}`, "unsupported property");
15611
+ if (entry !== void 0) allowed[key](entry, `${path}.${key}`);
15612
+ }
15613
+ return input;
15614
+ }
15615
+ var text = (value, path) => {
15616
+ if (typeof value !== "string" || !value.trim()) fail(path, "expected a nonempty string");
15617
+ };
15618
+ var literalText = (value, path) => {
15619
+ if (typeof value !== "string") fail(path, "expected a string");
15620
+ };
15621
+ var boolean = (value, path) => {
15622
+ if (typeof value !== "boolean") fail(path, "expected a boolean");
15623
+ };
15624
+ var number = (value, path) => {
15625
+ if (typeof value !== "number" || !Number.isFinite(value)) fail(path, "expected a finite number");
15626
+ };
15627
+ var positive = (value, path) => {
15628
+ number(value, path);
15629
+ if (value <= 0) fail(path, "expected a positive number");
15630
+ };
15631
+ var nonnegative = (value, path) => {
15632
+ number(value, path);
15633
+ if (value < 0) fail(path, "expected a nonnegative number");
15634
+ };
15635
+ var color = (value, path) => {
15636
+ number(value, path);
15637
+ if (!Number.isInteger(value) || value < 0 || value > 16777215) {
15638
+ fail(path, "expected an RGB integer from 0 to 16777215");
15639
+ }
15640
+ };
15641
+ var asset = (value, path) => {
15642
+ text(value, path);
15643
+ if (!/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(value) || value.split(/[:/]/).some((part) => !part || part === "." || part === "..")) {
15644
+ fail(path, "expected a resource ID such as minecraft:stone");
15645
+ }
15646
+ };
15647
+ var version = (value, path) => {
15648
+ text(value, path);
15649
+ if (!/^[a-zA-Z0-9._-]+$/.test(value)) fail(path, "expected a Minecraft version without slashes");
15650
+ };
15651
+ var tuple = (size, item = number) => (value, path) => {
15652
+ if (!Array.isArray(value) || value.length !== size) fail(path, `expected ${size} numbers`);
15653
+ for (let index = 0; index < size; index++) item(value[index], `${path}[${index}]`);
15654
+ };
15655
+ var list = (item) => (value, path) => {
15656
+ if (!Array.isArray(value)) fail(path, "expected an array");
15657
+ for (let index = 0; index < value.length; index++) item(value[index], `${path}[${index}]`);
15658
+ };
15659
+ var dictionary = (item, numericKeys = false) => (value, path) => {
15660
+ for (const [key, entry] of Object.entries(object2(value, path))) {
15661
+ if (numericKeys && !/^\d+$/.test(key)) fail(`${path}.${key}`, "expected a nonnegative tint index");
15662
+ item(entry, `${path}.${key}`);
15663
+ }
15664
+ };
15665
+ var enumeration = (values) => (value, path) => {
15666
+ if (!values.includes(value)) fail(path, `expected one of: ${values.join(", ")}`);
15667
+ };
15668
+ var textStyle = { color, bold: boolean, italic: boolean };
15669
+ function guiLayer(value, path) {
15670
+ const layer = object2(value, path);
15671
+ if (["texture", "item", "text"].filter((key) => layer[key] !== void 0).length !== 1) {
15672
+ fail(path, "expected exactly one of texture, item, or text");
15673
+ }
15674
+ const layout = { name: text, position: tuple(2), size: tuple(2, positive) };
15675
+ if (layer.texture !== void 0) {
15676
+ fields(value, path, { ...layout, texture: asset, crop: (value2, path2) => {
15677
+ tuple(4)(value2, path2);
15678
+ const [x, y, width, height] = value2;
15679
+ nonnegative(x, `${path2}[0]`);
15680
+ nonnegative(y, `${path2}[1]`);
15681
+ positive(width, `${path2}[2]`);
15682
+ positive(height, `${path2}[3]`);
15683
+ } });
15684
+ } else if (layer.item !== void 0) {
15685
+ fields(value, path, { ...layout, item: asset, tints: dictionary(color, true) });
15686
+ } else {
15687
+ fields(value, path, {
15688
+ ...layout,
15689
+ ...textStyle,
15690
+ font: asset,
15691
+ shadow: boolean,
15692
+ maxWidth: positive,
15693
+ lineHeight: positive,
15694
+ text: (value2, path2) => {
15695
+ if (typeof value2 === "string") return;
15696
+ list((run, path3) => {
15697
+ fields(run, path3, { text: literalText, ...textStyle }, ["text"]);
15698
+ })(value2, path2);
15699
+ }
15700
+ });
15701
+ }
15702
+ }
15703
+ function validateObject(value, path) {
15704
+ const input = object2(value, path);
15705
+ const common = { id: text, name: text, type: text, position: tuple(3), rotation: tuple(3), scale: tuple(3), visible: boolean };
15706
+ const required = ["id", "type"];
15707
+ const renderOptions = { wireframe: boolean };
15708
+ switch (input.type) {
15709
+ case "skin":
15710
+ fields(value, path, {
15711
+ ...common,
15712
+ skin: text,
15713
+ cape: (value2, path2) => {
15714
+ fields(value2, path2, { texture: text, layout: enumeration(["minecraft", "optifine", "labymod"]) }, ["texture"]);
15715
+ },
15716
+ hiddenParts: list(enumeration(SKIN_PARTS)),
15717
+ pose: (value2, path2) => {
15718
+ fields(value2, path2, {
15719
+ head: tuple(3),
15720
+ body: tuple(3),
15721
+ rightArm: tuple(3),
15722
+ leftArm: tuple(3),
15723
+ rightLeg: tuple(3),
15724
+ leftLeg: tuple(3),
15725
+ cape: tuple(3)
15726
+ });
15727
+ },
15728
+ options: (value2, path2) => {
15729
+ fields(value2, path2, { slim: boolean, legacy: boolean });
15730
+ }
15731
+ }, required);
15732
+ break;
15733
+ case "block":
15734
+ fields(value, path, {
15735
+ ...common,
15736
+ asset,
15737
+ state: dictionary(text),
15738
+ options: (value2, path2) => {
15739
+ fields(value2, path2, { ...renderOptions, tints: dictionary(color, true) });
15740
+ }
15741
+ }, [...required, "asset"]);
15742
+ break;
15743
+ case "item":
15744
+ case "model":
15745
+ fields(value, path, {
15746
+ ...common,
15747
+ asset,
15748
+ options: (value2, path2) => {
15749
+ fields(value2, path2, { ...renderOptions, displayPosition: enumeration(DISPLAY_POSITIONS), tints: dictionary(color, true) });
15750
+ }
15751
+ }, [...required, "asset"]);
15752
+ break;
15753
+ case "entity":
15754
+ fields(value, path, {
15755
+ ...common,
15756
+ asset,
15757
+ texture: asset,
15758
+ layers: list(text),
15759
+ when: list(text),
15760
+ textures: dictionary(asset),
15761
+ options: (value2, path2) => {
15762
+ fields(value2, path2, {
15763
+ ...renderOptions,
15764
+ flip: boolean,
15765
+ tints: dictionary((value3, path3) => {
15766
+ typeof value3 === "string" ? text(value3, path3) : color(value3, path3);
15767
+ })
15768
+ });
15769
+ },
15770
+ animation: (value2, path2) => {
15771
+ fields(value2, path2, { name: text, loop: boolean, speed: nonnegative, time: nonnegative, paused: boolean }, ["name"]);
15772
+ }
15773
+ }, [...required, "asset"]);
15774
+ if (Array.isArray(input.layers) && !input.layers.length) fail(`${path}.layers`, "expected at least one layer");
15775
+ break;
15776
+ case "gui":
15777
+ fields(value, path, { ...common, layers: list(guiLayer) }, [...required, "layers"]);
15778
+ break;
15779
+ default:
15780
+ fail(`${path}.type`, "expected skin, block, item, entity, model, or gui");
15781
+ }
15782
+ }
15783
+ function parseSceneDocument(value) {
15784
+ if (typeof value === "string") {
15785
+ try {
15786
+ value = JSON.parse(value);
15787
+ } catch {
15788
+ fail("scene", "invalid JSON");
15789
+ }
15790
+ }
15791
+ const input = fields(value, "scene", {
15792
+ format: enumeration(["minerender-scene"]),
15793
+ version: (value2, path) => {
15794
+ if (value2 !== 1) fail(path, "only scene document version 1 is supported");
15795
+ },
15796
+ minecraftVersion: version,
15797
+ camera: (value2, path) => {
15798
+ fields(value2, path, { position: tuple(3), target: tuple(3) }, ["position", "target"]);
15799
+ },
15800
+ objects: list(validateObject)
15801
+ }, ["format", "version", "objects"]);
15802
+ const ids = /* @__PURE__ */ new Set();
15803
+ for (const [index, entry] of input.objects.entries()) {
15804
+ if (ids.has(entry.id)) fail(`scene.objects[${index}].id`, `duplicate object ID "${entry.id}"`);
15805
+ ids.add(entry.id);
15806
+ }
15807
+ return JSON.parse(JSON.stringify(input));
15808
+ }
15809
+
15810
+ // src/scene/SceneDocumentLoader.ts
15811
+ var import_three38 = require("three");
15812
+ init_AssetKey();
15813
+ init_AssetLoader();
15814
+ init_BlockStates();
15815
+ init_Entities();
15816
+ init_Models();
15817
+ init_DisplayPosition();
15818
+ init_SceneObject();
15819
+
14560
15820
  // src/skin/Skins.ts
14561
15821
  init_Requests();
14562
15822
  var Skins = class {
14563
15823
  //<editor-fold desc="Skins">
15824
+ /** Returns a skin URL for {@link MineRenderScene.addSkin}, or `undefined` if name lookup fails. */
14564
15825
  static async fromUuidOrUsername(uuidOrUsername) {
14565
15826
  if (uuidOrUsername.length === 32 || uuidOrUsername.length === 36) {
14566
15827
  return this.fromUuid(uuidOrUsername);
14567
15828
  }
14568
15829
  return this.fromUsername(uuidOrUsername);
14569
15830
  }
15831
+ /** Builds a proxy URL for a UUID without downloading or checking the skin. */
14570
15832
  static async fromUuid(uuid) {
14571
15833
  return this.getMcProxySkinUrl(uuid);
14572
15834
  }
@@ -14578,6 +15840,7 @@ var Skins = class {
14578
15840
  return void 0;
14579
15841
  });
14580
15842
  }
15843
+ /** Looks up a saved MineSkin skin and returns its texture URL, or `undefined` on lookup failure. */
14581
15844
  static async fromMineSkin(mineskinId) {
14582
15845
  return this.getMineSkin(mineskinId).then((mineskin) => {
14583
15846
  if (mineskin) {
@@ -14588,12 +15851,14 @@ var Skins = class {
14588
15851
  }
14589
15852
  //</editor-fold>
14590
15853
  //<editor-fold desc="Capes">
15854
+ /** Resolves a player's Minecraft cape URL for {@link SkinObject.setCapeTexture}. */
14591
15855
  static async capeFromUuidOrUsername(uuidOrUsername) {
14592
15856
  if (uuidOrUsername.length === 32 || uuidOrUsername.length === 36) {
14593
15857
  return this.capeFromUuid(uuidOrUsername);
14594
15858
  }
14595
15859
  return this.capeFromUsername(uuidOrUsername);
14596
15860
  }
15861
+ /** Builds a proxy URL without checking whether the player has a cape. */
14597
15862
  static async capeFromUuid(uuid) {
14598
15863
  return this.getMcProxyCapeUrl(uuid);
14599
15864
  }
@@ -14722,6 +15987,195 @@ var Skins = class {
14722
15987
  //</editor-fold>
14723
15988
  };
14724
15989
 
15990
+ // src/scene/SceneDocumentLoader.ts
15991
+ var DocumentObjectScene = class extends MineRenderScene {
15992
+ async addSceneObject(_asset, supplier, _options, parent = this) {
15993
+ const object3 = await supplier();
15994
+ return this.initialize(object3, parent);
15995
+ }
15996
+ async initialize(object3, parent = this) {
15997
+ object3.scene = this;
15998
+ try {
15999
+ await object3.init();
16000
+ parent.add(object3);
16001
+ return object3;
16002
+ } catch (error) {
16003
+ object3.dispose();
16004
+ object3.removeFromParent();
16005
+ throw error;
16006
+ }
16007
+ }
16008
+ dispose() {
16009
+ for (const child of [...this.children]) {
16010
+ if (child.parent !== this) continue;
16011
+ if (isSceneObject(child)) child.dispose();
16012
+ child.removeFromParent();
16013
+ }
16014
+ }
16015
+ };
16016
+ async function skinTexture(value, cape = false) {
16017
+ if (!/^[a-zA-Z0-9_-]{1,36}$/.test(value)) return value;
16018
+ const texture = await (cape ? Skins.capeFromUuidOrUsername(value) : Skins.fromUuidOrUsername(value));
16019
+ if (!texture) throw new Error(`Could not find ${cape ? "cape" : "skin"} for "${value}"`);
16020
+ return texture;
16021
+ }
16022
+ var SceneDocumentLoader = class {
16023
+ static parse(value) {
16024
+ return parseSceneDocument(value);
16025
+ }
16026
+ /**
16027
+ * Loads objects concurrently and attaches them in document order after every load succeeds.
16028
+ * A failure waits for all loads, disposes staged objects, and reports the first error in document order.
16029
+ */
16030
+ static async load(scene, value, parent = scene) {
16031
+ const definition = this.parse(value);
16032
+ if (definition.minecraftVersion !== void 0 && definition.minecraftVersion !== AssetLoader.version) {
16033
+ throw new Error(`Scene requires Minecraft ${definition.minecraftVersion}; call AssetLoader.setVersion("${definition.minecraftVersion}") before loading it`);
16034
+ }
16035
+ const root = new import_three38.Group();
16036
+ root.name = "MineRender scene document";
16037
+ const objects = [];
16038
+ let disposed = false;
16039
+ const loaded = {
16040
+ root,
16041
+ objects,
16042
+ definition,
16043
+ advanceAnimations: (delta) => {
16044
+ if (!disposed) for (const object3 of objects) object3.advanceAnimation(delta);
16045
+ },
16046
+ dispose: () => {
16047
+ if (disposed) return;
16048
+ disposed = true;
16049
+ root.removeFromParent();
16050
+ for (const object3 of objects) object3.dispose();
16051
+ root.clear();
16052
+ scene.dirty = true;
16053
+ }
16054
+ };
16055
+ try {
16056
+ const results = await Promise.allSettled(definition.objects.map((object3) => this.loadValidatedObject(scene, object3)));
16057
+ for (const result of results) if (result.status === "fulfilled") objects.push(result.value);
16058
+ const failure = results.find((result) => result.status === "rejected");
16059
+ if (failure) throw failure.reason;
16060
+ for (const object3 of objects) root.add(object3.root);
16061
+ parent.add(root);
16062
+ scene.dirty = true;
16063
+ return loaded;
16064
+ } catch (error) {
16065
+ loaded.dispose();
16066
+ throw error;
16067
+ }
16068
+ }
16069
+ /** Loads an independently editable object. Its root owns the document's position, rotation, and scale. */
16070
+ static async loadObject(scene, value, parent = scene) {
16071
+ const definition = this.parse({ format: "minerender-scene", version: 1, objects: [value] }).objects[0];
16072
+ return this.loadValidatedObject(scene, definition, parent);
16073
+ }
16074
+ static async loadValidatedObject(scene, definition, parent) {
16075
+ const root = new import_three38.Group();
16076
+ root.name = definition.name ?? definition.id;
16077
+ root.userData.sceneObjectId = definition.id;
16078
+ root.position.fromArray(definition.position ?? [0, 0, 0]);
16079
+ root.rotation.set(...definition.rotation ?? [0, 0, 0]);
16080
+ root.scale.fromArray(definition.scale ?? [1, 1, 1]);
16081
+ root.visible = definition.visible ?? true;
16082
+ const content = new DocumentObjectScene();
16083
+ root.add(content);
16084
+ let object3;
16085
+ try {
16086
+ switch (definition.type) {
16087
+ case "skin": {
16088
+ const skin = new SkinObject({ ...definition.options, instanceMeshes: false });
16089
+ object3 = skin;
16090
+ const texture = definition.skin ? await skinTexture(definition.skin) : `${AssetLoader.ROOT}/assets/minecraft/textures/entity/player/${definition.options?.slim ? "slim/alex" : "wide/steve"}.png`;
16091
+ await skin.setSkinTexture(texture);
16092
+ await content.initialize(skin);
16093
+ if (definition.cape) await skin.setCapeTexture(await skinTexture(definition.cape.texture, true), definition.cape.layout);
16094
+ for (const [part, rotation] of Object.entries(definition.pose ?? {})) {
16095
+ skin.getGroupByName(part)?.rotation.set(...rotation, "XYZ");
16096
+ }
16097
+ for (const part of definition.hiddenParts ?? []) {
16098
+ const mesh = skin.getMeshByName(part);
16099
+ if (mesh) mesh.visible = false;
16100
+ }
16101
+ break;
16102
+ }
16103
+ case "block": {
16104
+ const state = await BlockStates.get(AssetKey.parse("blockstates", definition.asset));
16105
+ if (!state) throw new Error(`Could not load block ${definition.asset}`);
16106
+ object3 = await content.addBlock(state, { ...definition.options, initialState: definition.state, instanceMeshes: false });
16107
+ break;
16108
+ }
16109
+ case "item":
16110
+ case "model": {
16111
+ let key = AssetKey.parse("models", definition.asset);
16112
+ if (definition.type === "item" && key.type !== "item") {
16113
+ key = new AssetKey(key.namespace, key.getFullPath(), "models", "item");
16114
+ }
16115
+ const model = await Models.getMerged(key);
16116
+ if (!model) throw new Error(`Could not load ${definition.type} ${definition.asset}`);
16117
+ object3 = await content.addModel(model, {
16118
+ ...definition.type === "item" ? { displayPosition: "gui" /* GUI */ } : {},
16119
+ ...definition.options,
16120
+ instanceMeshes: false
16121
+ });
16122
+ break;
16123
+ }
16124
+ case "entity": {
16125
+ const key = AssetKey.parse("entity-models", definition.asset);
16126
+ const model = await Entities.getEntity(key, definition.texture ? AssetKey.parse("textures", definition.texture) : void 0, {
16127
+ layers: definition.layers,
16128
+ when: definition.when,
16129
+ textures: definition.textures && Object.fromEntries(Object.entries(definition.textures).map(([name, texture]) => [name, AssetKey.parse("textures", texture)]))
16130
+ });
16131
+ if (!model) throw new Error(`Could not load entity ${definition.asset}`);
16132
+ const entity = await content.addEntity(model, { ...definition.options, instanceMeshes: false });
16133
+ object3 = entity;
16134
+ if (definition.animation) {
16135
+ const animations = await Entities.getAnimations(key);
16136
+ const clip = animations?.[definition.animation.name];
16137
+ if (!clip) throw new Error(`Entity ${definition.asset} has no animation "${definition.animation.name}"`);
16138
+ entity.playAnimation(clip, definition.animation);
16139
+ }
16140
+ break;
16141
+ }
16142
+ case "gui":
16143
+ object3 = await content.initialize(new GuiObject(definition.layers));
16144
+ break;
16145
+ }
16146
+ let disposed = false;
16147
+ const loaded = {
16148
+ root,
16149
+ object: object3,
16150
+ definition,
16151
+ advanceAnimation: (delta) => {
16152
+ if (!disposed && definition.type === "entity" && !definition.animation?.paused) object3.advanceAnimation(delta);
16153
+ },
16154
+ dispose: () => {
16155
+ if (disposed) return;
16156
+ disposed = true;
16157
+ root.removeFromParent();
16158
+ object3.dispose();
16159
+ object3.removeFromParent();
16160
+ content.dispose();
16161
+ root.clear();
16162
+ scene.dirty = true;
16163
+ }
16164
+ };
16165
+ parent?.add(root);
16166
+ scene.dirty = true;
16167
+ return loaded;
16168
+ } catch (error) {
16169
+ object3?.dispose();
16170
+ object3?.removeFromParent();
16171
+ content.dispose();
16172
+ root.removeFromParent();
16173
+ root.clear();
16174
+ throw new Error(`Scene object "${definition.name ?? definition.id}": ${error instanceof Error ? error.message : String(error)}`);
16175
+ }
16176
+ }
16177
+ };
16178
+
14725
16179
  // src/texture/index.ts
14726
16180
  init_Colormaps();
14727
16181
  init_TextureAtlas();
@@ -14748,16 +16202,19 @@ var ChunkData = class _ChunkData {
14748
16202
  this.freeIds = [];
14749
16203
  this.nbt = /* @__PURE__ */ new Map();
14750
16204
  }
16205
+ /** Returns a detached copy of a cell, or `undefined` for air. Indices range from 0 to 4095. */
14751
16206
  get(index) {
14752
16207
  _ChunkData.validateIndex(index);
14753
16208
  const entry = this.palette[this.indices[index]];
14754
16209
  return entry ? _ChunkData.copyBlock(entry.state, this.nbt.get(index)) : void 0;
14755
16210
  }
16211
+ /** Captures a cell's data and returns a copy-producing reader that survives later cell changes. */
14756
16212
  snapshot(index) {
14757
16213
  _ChunkData.validateIndex(index);
14758
16214
  const entry = this.palette[this.indices[index]];
14759
16215
  return entry ? _ChunkData.copyBlock.bind(void 0, entry.state, this.nbt.get(index)) : void 0;
14760
16216
  }
16217
+ /** Copies block data into a cell. An omitted block or an air block clears the cell. */
14761
16218
  set(index, block) {
14762
16219
  _ChunkData.validateIndex(index);
14763
16220
  let state;
@@ -14822,15 +16279,23 @@ var ChunkData = class _ChunkData {
14822
16279
 
14823
16280
  // src/world/AnvilParser.ts
14824
16281
  var AnvilParser = class {
16282
+ /** Lists occupied chunk positions within the region, using local coordinates from 0 to 31. */
14825
16283
  static getChunkList(data) {
14826
16284
  return this.locations(this.bytes(data)).map(({ x, z }) => ({ x, z }));
14827
16285
  }
16286
+ /** Decodes all stored chunks. Gzip, zlib, and uncompressed payloads are supported. */
14828
16287
  static async parse(data) {
14829
16288
  const bytes = this.bytes(data);
14830
16289
  const chunks = [];
14831
16290
  for (const location of this.locations(bytes)) chunks.push(await this.readChunk(bytes, location));
14832
16291
  return { chunks };
14833
16292
  }
16293
+ /**
16294
+ * Decodes one chunk for {@link MineRenderWorld.placeChunk}.
16295
+ * @param localX - Chunk x within the region, from 0 to 31.
16296
+ * @param localZ - Chunk z within the region, from 0 to 31.
16297
+ * @returns The chunk with absolute coordinates, or `undefined` if the region has no chunk there.
16298
+ */
14834
16299
  static async parseChunk(data, localX, localZ) {
14835
16300
  if (![localX, localZ].every((value) => Number.isInteger(value) && value >= 0 && value < 32)) {
14836
16301
  throw new RangeError("Region-local chunk coordinates must be integers from 0 to 31");
@@ -14992,12 +16457,12 @@ var AnvilParser = class {
14992
16457
  };
14993
16458
 
14994
16459
  // src/world/Chunk.ts
14995
- var import_three41 = require("three");
16460
+ var import_three42 = require("three");
14996
16461
  init_BlockStates();
14997
16462
  init_AssetKey();
14998
16463
 
14999
16464
  // src/world/MineRenderWorld.ts
15000
- var import_three39 = require("three");
16465
+ var import_three40 = require("three");
15001
16466
  init_Model();
15002
16467
  init_BatchedExecutor();
15003
16468
  init_AssetKey();
@@ -15005,7 +16470,7 @@ init_BlockStates();
15005
16470
  init_CubeFace();
15006
16471
 
15007
16472
  // src/world/SectionModels.ts
15008
- var import_three38 = require("three");
16473
+ var import_three39 = require("three");
15009
16474
  init_AssetKey();
15010
16475
  init_Models();
15011
16476
  init_CubeFace();
@@ -15035,6 +16500,7 @@ var SectionModels = class {
15035
16500
  this.templates = /* @__PURE__ */ new Set();
15036
16501
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("maxAtlasSize must be a positive integer");
15037
16502
  }
16503
+ /** Returns a cube template, or `undefined` when the block requires an individual render object. */
15038
16504
  async get(blockState, properties = {}) {
15039
16505
  if (!blockState.variants || blockState.multipart) return void 0;
15040
16506
  let states = this.states.get(blockState);
@@ -15063,20 +16529,20 @@ var SectionModels = class {
15063
16529
  if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
15064
16530
  const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
15065
16531
  const template = await cached(models, key, async () => {
15066
- const object = new SectionModel(model, {
16532
+ const object3 = new SectionModel(model, {
15067
16533
  instanceMeshes: false,
15068
16534
  mergeMeshes: false,
15069
16535
  tints,
15070
16536
  uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
15071
16537
  });
15072
- await object.init();
15073
- const geometry = object.children[0].geometry;
15074
- geometry.applyMatrix4(new import_three38.Matrix4().makeRotationFromEuler(rotation));
15075
- object.clear();
16538
+ await object3.init();
16539
+ const geometry = object3.children[0].geometry;
16540
+ geometry.applyMatrix4(new import_three39.Matrix4().makeRotationFromEuler(rotation));
16541
+ object3.clear();
15076
16542
  const cullFaces = CUBE_FACES.map((face) => {
15077
16543
  const local = CUBE_FACES.findIndex((name) => name === atlas.model.elements[0].faces[face]?.cullface);
15078
16544
  if (local < 0) return 0;
15079
- const normal = new import_three38.Vector3(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);
16545
+ const normal = new import_three39.Vector3(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);
15080
16546
  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);
15081
16547
  return 1 << world;
15082
16548
  });
@@ -15088,6 +16554,7 @@ var SectionModels = class {
15088
16554
  }
15089
16555
  return { variants, templates };
15090
16556
  }
16557
+ /** Disposes prepared geometry and clears this world's template caches. */
15091
16558
  clear() {
15092
16559
  for (const template of this.templates) template.geometry.dispose();
15093
16560
  this.templates.clear();
@@ -15106,20 +16573,20 @@ var MineRenderWorld = class {
15106
16573
  }
15107
16574
  getBlockAt(posOrX, y, z) {
15108
16575
  if (typeof posOrX == "number") {
15109
- return this.getBlockAt(new import_three39.Vector3(posOrX, y, z));
16576
+ return this.getBlockAt(new import_three40.Vector3(posOrX, y, z));
15110
16577
  }
15111
16578
  if (isTripleArray(posOrX)) {
15112
- return this.getBlockAt(new import_three39.Vector3(posOrX[0], posOrX[1], posOrX[2]));
16579
+ return this.getBlockAt(new import_three40.Vector3(posOrX[0], posOrX[1], posOrX[2]));
15113
16580
  }
15114
16581
  this.validatePosBounds(posOrX);
15115
16582
  return this.getChunkAt(posOrX)?.getBlockAt(posOrX);
15116
16583
  }
15117
16584
  async setBlockAt(posOrX, yOrBlock, z, block) {
15118
16585
  if (typeof posOrX == "number") {
15119
- return this.setBlockAt(new import_three39.Vector3(posOrX, yOrBlock, z), block);
16586
+ return this.setBlockAt(new import_three40.Vector3(posOrX, yOrBlock, z), block);
15120
16587
  }
15121
16588
  if (isTripleArray(posOrX)) {
15122
- return this.setBlockAt(new import_three39.Vector3(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
16589
+ return this.setBlockAt(new import_three40.Vector3(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
15123
16590
  }
15124
16591
  return this.placeBlock(posOrX, yOrBlock);
15125
16592
  }
@@ -15130,14 +16597,21 @@ var MineRenderWorld = class {
15130
16597
  }
15131
16598
  async setBlockVisibleAt(posOrX, yOrVisible, z, visible) {
15132
16599
  if (typeof posOrX === "number") {
15133
- return this.setBlockVisibleAt(new import_three39.Vector3(posOrX, yOrVisible, z), visible);
16600
+ return this.setBlockVisibleAt(new import_three40.Vector3(posOrX, yOrVisible, z), visible);
15134
16601
  }
15135
16602
  if (isTripleArray(posOrX)) {
15136
- return this.setBlockVisibleAt(new import_three39.Vector3(...posOrX), yOrVisible);
16603
+ return this.setBlockVisibleAt(new import_three40.Vector3(...posOrX), yOrVisible);
15137
16604
  }
15138
16605
  this.validatePosBounds(posOrX);
15139
16606
  await this.getChunkAt(posOrX)?.setBlockVisibleAt(posOrX, yOrVisible);
15140
16607
  }
16608
+ /**
16609
+ * Places a structure's blocks at their stored positions, then refreshes neighboring faces.
16610
+ * Entity NBT is retained by the structure but is not rendered.
16611
+ *
16612
+ * @param useBatches - Uses bounded batches by default. Set to `false` for sequential placement.
16613
+ * @param executor - Optional queue for batched placement. The caller remains responsible for stopping it.
16614
+ */
15141
16615
  async placeMultiBlock(multiblock, useBatches = true, executor) {
15142
16616
  const changes = /* @__PURE__ */ new Map();
15143
16617
  try {
@@ -15150,7 +16624,7 @@ var MineRenderWorld = class {
15150
16624
  const collect = async (positions2) => {
15151
16625
  for (const pos of positions2) changes.set(pos.toArray().join(","), pos);
15152
16626
  };
15153
- const place = (block) => this.placeBlock(new import_three39.Vector3(...block.position), block, collect);
16627
+ const place = (block) => this.placeBlock(new import_three40.Vector3(...block.position), block, collect);
15154
16628
  const keys = /* @__PURE__ */ new Map();
15155
16629
  for (const block of multiblock.blocks) {
15156
16630
  if (Chunk.isAir(block)) continue;
@@ -15189,13 +16663,7 @@ var MineRenderWorld = class {
15189
16663
  async placeChunk(chunk, executor) {
15190
16664
  const changes = /* @__PURE__ */ new Map();
15191
16665
  try {
15192
- const previous = [...this._chunks.entries()].filter(([, section]) => section.x === chunk.x && section.z === chunk.z);
15193
- for (const [key, section] of previous) {
15194
- this._chunks.delete(key);
15195
- await section.clear(async (positions) => {
15196
- for (const pos of positions) changes.set(pos.toArray().join(","), pos);
15197
- });
15198
- }
16666
+ await this.clearChunkColumn(chunk.x, chunk.z, changes);
15199
16667
  for (const section of chunk.sections) {
15200
16668
  const blocks = [];
15201
16669
  for (let index = 0; index < 4096; index++) {
@@ -15215,6 +16683,32 @@ var MineRenderWorld = class {
15215
16683
  await this.updateCulling([...changes.values()]);
15216
16684
  }
15217
16685
  }
16686
+ /**
16687
+ * Removes all sections at integer chunk-column coordinates, then refreshes neighboring faces and fluids.
16688
+ * Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
16689
+ */
16690
+ async unloadChunkColumn(x, z) {
16691
+ if (!Number.isInteger(x) || !Number.isInteger(z)) {
16692
+ throw new RangeError("Chunk column coordinates must be integers");
16693
+ }
16694
+ await this.culling;
16695
+ const changes = /* @__PURE__ */ new Map();
16696
+ try {
16697
+ await this.clearChunkColumn(x, z, changes);
16698
+ } finally {
16699
+ await this.updateCulling([...changes.values()]);
16700
+ }
16701
+ }
16702
+ async clearChunkColumn(x, z, changes) {
16703
+ const previous = [...this._chunks.entries()].filter(([, section]) => section.x === x && section.z === z);
16704
+ for (const [key, section] of previous) {
16705
+ this._chunks.delete(key);
16706
+ await section.clear(async (positions) => {
16707
+ for (const pos of positions) changes.set(pos.toArray().join(","), pos);
16708
+ });
16709
+ }
16710
+ }
16711
+ /** Removes all chunks and releases their block placements and section meshes. */
15218
16712
  async clear() {
15219
16713
  await this.culling;
15220
16714
  const chunks = [...this._chunks.values()];
@@ -15228,13 +16722,13 @@ var MineRenderWorld = class {
15228
16722
  for (const pos of positions) {
15229
16723
  this.pendingCulling.set(pos.toArray().join(","), pos.clone());
15230
16724
  for (const offset of CUBE_FACE_OFFSETS) {
15231
- const neighbor = pos.clone().add(new import_three39.Vector3(...offset));
16725
+ const neighbor = pos.clone().add(new import_three40.Vector3(...offset));
15232
16726
  this.pendingCulling.set(neighbor.toArray().join(","), neighbor);
15233
16727
  }
15234
16728
  for (let y = -1; y <= 1; y++) {
15235
16729
  for (let z = -1; z <= 1; z++) {
15236
16730
  for (let x = -1; x <= 1; x++) {
15237
- const neighbor = pos.clone().add(new import_three39.Vector3(x, y, z));
16731
+ const neighbor = pos.clone().add(new import_three40.Vector3(x, y, z));
15238
16732
  if (this.getBlockAt(neighbor)?.object?.fluidKind) {
15239
16733
  this.pendingCulling.set(neighbor.toArray().join(","), neighbor);
15240
16734
  }
@@ -15258,19 +16752,19 @@ var MineRenderWorld = class {
15258
16752
  }
15259
16753
  if (block.object?.fluidKind) {
15260
16754
  await block.object.updateFluid((x, y, z) => {
15261
- const neighbor = pos.clone().add(new import_three39.Vector3(x, y, z));
16755
+ const neighbor = pos.clone().add(new import_three40.Vector3(x, y, z));
15262
16756
  const section = this.getChunkAt(neighbor);
15263
- const object = section?.isBlockVisibleAt(neighbor) ? section.getBlockAt(neighbor)?.object : void 0;
16757
+ const object3 = section?.isBlockVisibleAt(neighbor) ? section.getBlockAt(neighbor)?.object : void 0;
15264
16758
  return {
15265
- fluid: object?.fluidKind,
15266
- level: object?.fluidLevel,
16759
+ fluid: object3?.fluidKind,
16760
+ level: object3?.fluidLevel,
15267
16761
  solid: section?.isOccludingAt(neighbor) ?? false
15268
16762
  };
15269
16763
  });
15270
16764
  }
15271
16765
  let mask = 0;
15272
16766
  for (const [face, offset] of CUBE_FACE_OFFSETS.entries()) {
15273
- const neighbor = pos.clone().add(new import_three39.Vector3(...offset));
16767
+ const neighbor = pos.clone().add(new import_three40.Vector3(...offset));
15274
16768
  if (this.getChunkAt(neighbor)?.isOccludingAt(neighbor)) {
15275
16769
  mask |= 1 << face;
15276
16770
  }
@@ -15300,18 +16794,21 @@ var MineRenderWorld = class {
15300
16794
  }
15301
16795
  return chunk;
15302
16796
  }
16797
+ /** Returns the loaded chunk containing a world block position, without creating a chunk. */
15303
16798
  getChunkAt(pos) {
15304
16799
  return this._chunks.get(this.worldPosToChunkKey(pos));
15305
16800
  }
16801
+ /** Converts block coordinates to scene units by multiplying each component by 16. */
15306
16802
  static worldToScenePosition(pos) {
15307
- return new import_three39.Vector3(
16803
+ return new import_three40.Vector3(
15308
16804
  pos.x * 16,
15309
16805
  pos.y * 16,
15310
16806
  pos.z * 16
15311
16807
  );
15312
16808
  }
16809
+ /** Converts scene units to block coordinates without rounding to an integer block. */
15313
16810
  static sceneToWorldPosition(pos) {
15314
- return new import_three39.Vector3(
16811
+ return new import_three40.Vector3(
15315
16812
  pos.x / 16,
15316
16813
  pos.y / 16,
15317
16814
  pos.z / 16
@@ -15334,14 +16831,15 @@ var MineRenderWorld = class {
15334
16831
  init_Model();
15335
16832
 
15336
16833
  // src/world/SectionMesh.ts
15337
- var import_three40 = require("three");
16834
+ var import_three41 = require("three");
15338
16835
  init_CanvasCompat();
15339
16836
  init_Materials();
15340
- var SectionMesh = class _SectionMesh extends import_three40.Group {
16837
+ var SectionMesh = class _SectionMesh extends import_three41.Group {
15341
16838
  constructor() {
15342
16839
  super(...arguments);
15343
16840
  this.ownedMeshes = [];
15344
16841
  }
16842
+ /** Builds section-local meshes from visible cube faces. `maxAtlasSize` limits each atlas dimension in pixels. */
15345
16843
  static build(entries, maxAtlasSize = 2048) {
15346
16844
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
15347
16845
  const visible = entries.filter((entry) => entry.template.cullFaces.some((direction) => !(entry.cullMask & direction)));
@@ -15383,14 +16881,17 @@ var SectionMesh = class _SectionMesh extends import_three40.Group {
15383
16881
  for (const { atlas, x, y } of page.placements) {
15384
16882
  context.drawImage(atlas.image.canvas, x, y);
15385
16883
  }
15386
- const positions = [], normals = [], uvs = [], colors = [], indices = [];
16884
+ const positions = [], normals = [], uvs = [], uvBounds = [], colors = [], indices = [];
15387
16885
  for (const { index, template, cullMask } of page.entries) {
15388
16886
  const position = template.geometry.getAttribute("position");
15389
16887
  const normal = template.geometry.getAttribute("normal");
15390
16888
  const uv = template.geometry.getAttribute("uv");
15391
- const color = template.geometry.getAttribute("color");
16889
+ const bounds = template.geometry.getAttribute("uvBounds");
16890
+ const color2 = template.geometry.getAttribute("color");
15392
16891
  const sourceIndices = template.geometry.getIndex();
15393
16892
  const placement = locations.get(template.atlas).placement;
16893
+ const mapU = (u) => (placement.x + u * template.atlas.image.width) / page.width;
16894
+ const mapV = (v) => 1 - (placement.y + (1 - v) * template.atlas.image.height) / page.height;
15394
16895
  const offsetX = index % 16 * 16;
15395
16896
  const offsetY = Math.floor(index / 256) * 16;
15396
16897
  const offsetZ = Math.floor(index / 16) % 16 * 16;
@@ -15401,27 +16902,31 @@ var SectionMesh = class _SectionMesh extends import_three40.Group {
15401
16902
  const vertex = face * 4 + corner;
15402
16903
  positions.push(position.getX(vertex) + offsetX, position.getY(vertex) + offsetY, position.getZ(vertex) + offsetZ);
15403
16904
  normals.push(normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex));
15404
- uvs.push(
15405
- (placement.x + uv.getX(vertex) * template.atlas.image.width) / page.width,
15406
- 1 - (placement.y + (1 - uv.getY(vertex)) * template.atlas.image.height) / page.height
16905
+ uvs.push(mapU(uv.getX(vertex)), mapV(uv.getY(vertex)));
16906
+ uvBounds.push(
16907
+ mapU(bounds?.getX(vertex) ?? 0),
16908
+ mapV(bounds?.getY(vertex) ?? 0),
16909
+ mapU(bounds?.getZ(vertex) ?? 1),
16910
+ mapV(bounds?.getW(vertex) ?? 1)
15407
16911
  );
15408
- colors.push(color?.getX(vertex) ?? 1, color?.getY(vertex) ?? 1, color?.getZ(vertex) ?? 1);
16912
+ colors.push(color2?.getX(vertex) ?? 1, color2?.getY(vertex) ?? 1, color2?.getZ(vertex) ?? 1);
15409
16913
  }
15410
16914
  for (let corner = 0; corner < 6; corner++) indices.push(start + sourceIndices.getX(face * 6 + corner) - face * 4);
15411
16915
  }
15412
16916
  }
15413
- const geometry = new import_three40.BufferGeometry();
15414
- geometry.setAttribute("position", new import_three40.Float32BufferAttribute(positions, 3));
15415
- geometry.setAttribute("normal", new import_three40.Float32BufferAttribute(normals, 3));
15416
- geometry.setAttribute("uv", new import_three40.Float32BufferAttribute(uvs, 2));
15417
- geometry.setAttribute("color", new import_three40.Float32BufferAttribute(colors, 3));
16917
+ const geometry = new import_three41.BufferGeometry();
16918
+ geometry.setAttribute("position", new import_three41.Float32BufferAttribute(positions, 3));
16919
+ geometry.setAttribute("normal", new import_three41.Float32BufferAttribute(normals, 3));
16920
+ geometry.setAttribute("uv", new import_three41.Float32BufferAttribute(uvs, 2));
16921
+ geometry.setAttribute("uvBounds", new import_three41.Float32BufferAttribute(uvBounds, 4));
16922
+ geometry.setAttribute("color", new import_three41.Float32BufferAttribute(colors, 3));
15418
16923
  geometry.setIndex(indices);
15419
16924
  geometry.computeBoundingBox();
15420
16925
  geometry.computeBoundingSphere();
15421
- const material = Materials.createShadedCanvasMaterial(canvas, false);
16926
+ const material = Materials.createShadedCanvasMaterial(canvas, false, false, true);
15422
16927
  material.vertexColors = true;
15423
16928
  const texture = material.uniforms?.map?.value ?? material.map;
15424
- const mesh = new import_three40.Mesh(geometry, material);
16929
+ const mesh = new import_three41.Mesh(geometry, material);
15425
16930
  section.add(mesh);
15426
16931
  section.ownedMeshes.push({ mesh, texture });
15427
16932
  }
@@ -15457,10 +16962,10 @@ var Chunk = class _Chunk {
15457
16962
  }
15458
16963
  getBlockAt(posOrX, y, z) {
15459
16964
  if (typeof posOrX == "number") {
15460
- return this.getBlockAt(new import_three41.Vector3(posOrX, y, z));
16965
+ return this.getBlockAt(new import_three42.Vector3(posOrX, y, z));
15461
16966
  }
15462
16967
  if (isTripleArray(posOrX)) {
15463
- return this.getBlockAt(new import_three41.Vector3(posOrX[0], posOrX[1], posOrX[2]));
16968
+ return this.getBlockAt(new import_three42.Vector3(posOrX[0], posOrX[1], posOrX[2]));
15464
16969
  }
15465
16970
  const pos = this.worldPosToChunkPos(posOrX);
15466
16971
  const index = _Chunk.chunkPosToBlockIndex(pos);
@@ -15468,10 +16973,10 @@ var Chunk = class _Chunk {
15468
16973
  }
15469
16974
  async setBlockAt(posOrX, yOrBlock, z, block) {
15470
16975
  if (typeof posOrX == "number") {
15471
- return this.setBlockAt(new import_three41.Vector3(posOrX, yOrBlock, z), block);
16976
+ return this.setBlockAt(new import_three42.Vector3(posOrX, yOrBlock, z), block);
15472
16977
  }
15473
16978
  if (isTripleArray(posOrX)) {
15474
- return this.setBlockAt(new import_three41.Vector3(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
16979
+ return this.setBlockAt(new import_three42.Vector3(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
15475
16980
  }
15476
16981
  const worldPos = posOrX;
15477
16982
  const pos = this.worldPosToChunkPos(worldPos);
@@ -15479,7 +16984,7 @@ var Chunk = class _Chunk {
15479
16984
  return this.setBlockInChunkAt(pos, block, worldPos);
15480
16985
  }
15481
16986
  /**
15482
- * Set a block at integer chunk-local coordinates from 0 to 15.
16987
+ * Places a block at integer chunk-local coordinates from 0 to 15.
15483
16988
  */
15484
16989
  async setBlockInChunkAt(pos, block, worldPos, onBlocksChanged = this.onBlocksChanged) {
15485
16990
  if (typeof worldPos === "undefined") {
@@ -15492,7 +16997,7 @@ var Chunk = class _Chunk {
15492
16997
  this.renderedBlocks.get(index)?.object?.removeFromScene();
15493
16998
  if (this.sectionBlocks.delete(index)) this.meshDirty = true;
15494
16999
  this.renderedBlocks.delete(index);
15495
- let object;
17000
+ let object3;
15496
17001
  try {
15497
17002
  if (!readBlock) return void 0;
15498
17003
  const stored = readBlock();
@@ -15507,27 +17012,28 @@ var Chunk = class _Chunk {
15507
17012
  this.sectionBlocks.set(index, { index, template, cullMask: 0 });
15508
17013
  this.meshDirty = true;
15509
17014
  } else {
15510
- object = await this.scene.addBlock(blockState, {
17015
+ object3 = await this.scene.addBlock(blockState, {
15511
17016
  mergeMeshes: true,
15512
17017
  instanceMeshes: true,
15513
17018
  maxInstanceCount: 2e3,
15514
17019
  initialState: stored.properties
15515
17020
  });
15516
- object.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
17021
+ object3.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
15517
17022
  }
15518
17023
  const info = { get block() {
15519
17024
  return readBlock();
15520
- }, object };
17025
+ }, object: object3 };
15521
17026
  this.renderedBlocks.set(index, info);
15522
17027
  return info;
15523
17028
  } catch (error) {
15524
17029
  this.data.set(index, void 0);
15525
- object?.removeFromScene();
17030
+ object3?.removeFromScene();
15526
17031
  throw error;
15527
17032
  } finally {
15528
17033
  await onBlocksChanged?.([worldPos]);
15529
17034
  }
15530
17035
  }
17036
+ /** Changes visibility at world block coordinates while preserving the block's data. */
15531
17037
  async setBlockVisibleAt(pos, visible) {
15532
17038
  const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
15533
17039
  const block = this.renderedBlocks.get(index);
@@ -15581,9 +17087,10 @@ var Chunk = class _Chunk {
15581
17087
  this.scene.dirty = true;
15582
17088
  this.meshDirty = false;
15583
17089
  }
17090
+ /** Removes stored blocks and their render objects, then notifies the owning world of changed positions. */
15584
17091
  async clear(onBlocksChanged = this.onBlocksChanged) {
15585
17092
  const positions = [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
15586
- new import_three41.Vector3(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
17093
+ new import_three42.Vector3(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
15587
17094
  ));
15588
17095
  for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
15589
17096
  this.sectionBlocks.clear();
@@ -15601,6 +17108,7 @@ var Chunk = class _Chunk {
15601
17108
  static isAir(block) {
15602
17109
  return ChunkData.isAir(block);
15603
17110
  }
17111
+ /** Maps chunk-local coordinates from 0 to 15 to a storage index, with x varying fastest, then z, then y. */
15604
17112
  static chunkPosToBlockIndex(pos) {
15605
17113
  if ([pos.x, pos.y, pos.z].some((value) => !Number.isInteger(value) || value < 0 || value >= 16)) {
15606
17114
  throw new RangeError("Block coordinates must be integers from 0 to 15 within a chunk");
@@ -15608,14 +17116,14 @@ var Chunk = class _Chunk {
15608
17116
  return pos.y * 16 * 16 + pos.z * 16 + pos.x;
15609
17117
  }
15610
17118
  worldPosToChunkPos(pos) {
15611
- return new import_three41.Vector3(
17119
+ return new import_three42.Vector3(
15612
17120
  pos.x - this.x * 16,
15613
17121
  pos.y - this.y * 16,
15614
17122
  pos.z - this.z * 16
15615
17123
  );
15616
17124
  }
15617
17125
  chunkPosToWorldPos(pos) {
15618
- return new import_three41.Vector3(
17126
+ return new import_three42.Vector3(
15619
17127
  this.x * 16 + pos.x,
15620
17128
  this.y * 16 + pos.y,
15621
17129
  this.z * 16 + pos.z
@@ -15636,6 +17144,7 @@ var Chunk = class _Chunk {
15636
17144
  Axis,
15637
17145
  BUILTIN_ENTITY,
15638
17146
  BUILTIN_GENERATED,
17147
+ BannerPatterns,
15639
17148
  BasicAssetKey,
15640
17149
  BatchedExecutor,
15641
17150
  BlockEntities,
@@ -15658,6 +17167,7 @@ var Chunk = class _Chunk {
15658
17167
  DEFAULT_ROOT,
15659
17168
  DEFAULT_UV,
15660
17169
  DISPLAY_POSITIONS,
17170
+ DYE_COLORS,
15661
17171
  DefaultBlockStates,
15662
17172
  DisplayPosition,
15663
17173
  DisplayTransforms,
@@ -15668,6 +17178,7 @@ var Chunk = class _Chunk {
15668
17178
  Env,
15669
17179
  FluidModelObject,
15670
17180
  Fonts,
17181
+ GUI_CONTAINER_LAYOUTS,
15671
17182
  Geometries,
15672
17183
  GuiHelper,
15673
17184
  GuiLight,
@@ -15696,11 +17207,14 @@ var Chunk = class _Chunk {
15696
17207
  NodeCache,
15697
17208
  NodeEnv,
15698
17209
  OrbitControls,
17210
+ PackFormats,
17211
+ PackMetadata,
15699
17212
  PersistentCache,
15700
17213
  Renderer,
15701
17214
  RequestError,
15702
17215
  Requests,
15703
17216
  SKIN_PARTS,
17217
+ SceneDocumentLoader,
15704
17218
  SceneExporter,
15705
17219
  SceneInspector,
15706
17220
  SceneObject,
@@ -15776,6 +17290,7 @@ var Chunk = class _Chunk {
15776
17290
  mergeBufferAttributes,
15777
17291
  mergeBufferGeometries,
15778
17292
  mergeVertices,
17293
+ parseSceneDocument,
15779
17294
  prefix,
15780
17295
  registerNodeEnv,
15781
17296
  sampleEntityAnimation,