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.
@@ -64,6 +64,7 @@ var init_PersistentCache = __esm({
64
64
  constructor(backing) {
65
65
  this.backing = backing;
66
66
  }
67
+ /** Opens a named store in the active environment using the current cache schema version. */
67
68
  static open(name) {
68
69
  return Env.provider.openCache(name, _PersistentCache.VERSION);
69
70
  }
@@ -259,9 +260,11 @@ var init_Caching = __esm({
259
260
  this.entityTextureCache,
260
261
  this.entityAnimationCache,
261
262
  this.blockEntityIndexCache,
262
- this.fontCache
263
+ this.fontCache,
264
+ this.bannerPatternCache
263
265
  ];
264
266
  }
267
+ /** Invalidates in-memory entries so later lookups reload them. Persisted entries are retained. */
265
268
  static clear() {
266
269
  for (const cache of this.all) {
267
270
  cache.invalidateAll();
@@ -307,6 +310,7 @@ var init_Caching = __esm({
307
310
  _Caching.entityAnimationCache = _Caching.createAssetCache();
308
311
  _Caching.blockEntityIndexCache = _Caching.createAssetCache();
309
312
  _Caching.fontCache = _Caching.createAssetCache();
313
+ _Caching.bannerPatternCache = _Caching.createAssetCache();
310
314
  Caching = _Caching;
311
315
  }
312
316
  });
@@ -327,6 +331,7 @@ var init_Geometries = __esm({
327
331
  }
328
332
  return geometry;
329
333
  }
334
+ /** Returns shared geometry. Use {@link createBox} when the caller needs to modify or dispose it independently. */
330
335
  static getBox(key) {
331
336
  const keyStr = serializeBoxGeometryKey(key);
332
337
  return Caching.boxGeometryCache.get(keyStr, (k) => {
@@ -504,12 +509,15 @@ var init_Requests = __esm({
504
509
  }
505
510
  };
506
511
  Requests = class {
512
+ /** Queues a general request. Set `request.signal` to cancel it and `request.timeout` to limit each attempt. */
507
513
  static genericRequest(request) {
508
514
  return this.genericQueue.request(request);
509
515
  }
516
+ /** Queues a request separately from general traffic, using the configured asset base URL for relative paths. */
510
517
  static mcAssetRequest(request) {
511
518
  return this.mcAssetRequestQueue.request(request);
512
519
  }
520
+ /** Sets the base URL for relative asset requests. Use {@link AssetLoader.setVersion} to change Minecraft versions. */
513
521
  static setMcAssetRoot(root) {
514
522
  this.mcAssetRequestQueue.baseURL = root;
515
523
  }
@@ -564,6 +572,7 @@ var init_WrappedImage = __esm({
564
572
  }
565
573
  return false;
566
574
  }
575
+ /** Whether the dimensions form a vertical strip of square frames, without consulting `.mcmeta`. */
567
576
  get animated() {
568
577
  return this.height > this.width && this.height % this.width === 0;
569
578
  }
@@ -610,6 +619,7 @@ var init_ImageLoader = __esm({
610
619
  static _createImage() {
611
620
  return createImage();
612
621
  }
622
+ /** Resolves when an image URL has decoded, or rejects if decoding fails. */
613
623
  static async loadAsync(src) {
614
624
  return new Promise((resolve, reject) => {
615
625
  const image = this._createImage();
@@ -618,6 +628,7 @@ var init_ImageLoader = __esm({
618
628
  image.src = src;
619
629
  });
620
630
  }
631
+ /** Returns an image immediately. Use the callbacks to observe loading, or await {@link loadAsync}. */
621
632
  static loadElement(src, onload, onerr) {
622
633
  const image = this._createImage();
623
634
  if (onload)
@@ -645,6 +656,7 @@ var init_ImageLoader = __esm({
645
656
  throw err;
646
657
  }
647
658
  }
659
+ /** Decodes the supplied bytes into a canvas without fetching the source URL again. */
648
660
  static async infoToCanvasData(info) {
649
661
  const type = info.type === "jpg" ? "jpeg" : info.type ?? "png";
650
662
  const image = await _ImageLoader.loadAsync(`data:image/${type};base64,${info.data.toString("base64")}`);
@@ -661,12 +673,14 @@ var init_ImageLoader = __esm({
661
673
  const image = await this.infoToCanvasData(info);
662
674
  return image.data.getImageData(0, 0, image.width, image.height);
663
675
  }
676
+ /** Returns cached RGBA pixels for an image URL or data URL. Failed loads can be retried. */
664
677
  static async getData(src) {
665
678
  const keyStr = serializeImageKey({ src });
666
679
  return await Caching.imageDataCache.get(keyStr, (k) => {
667
680
  return _ImageLoader.loadData(src);
668
681
  });
669
682
  }
683
+ /** Returns a cached canvas context and dimensions for an image URL or data URL. */
670
684
  static async getCanvasData(src) {
671
685
  const keyStr = serializeImageKey({ src });
672
686
  return await Caching.canvasImageDataCache.get(keyStr, (k) => {
@@ -749,7 +763,7 @@ var init_TextureLoader = __esm({
749
763
  static createTexture() {
750
764
  return new Texture();
751
765
  }
752
- static loadInBackground(src, format = RGBAFormat, rotation = 0) {
766
+ static loadInBackground(src, format2 = RGBAFormat, rotation = 0) {
753
767
  const texture = this.createTexture();
754
768
  texture.colorSpace = THREE.SRGBColorSpace;
755
769
  const image = ImageLoader.loadElement(src, () => {
@@ -759,11 +773,12 @@ var init_TextureLoader = __esm({
759
773
  console.warn("Failed to load texture", src, error);
760
774
  });
761
775
  texture.image = image;
762
- texture.format = format;
776
+ texture.format = format2;
763
777
  texture.rotation = rotation;
764
778
  return Textures.initTextureProps(texture);
765
779
  }
766
- static load(src, format = RGBAFormat, rotation = 0) {
780
+ /** Loads image pixels in the background. `rotation` is in radians. Check {@link hasFailed} for load failures. */
781
+ static load(src, format2 = RGBAFormat, rotation = 0) {
767
782
  const texture = new Texture();
768
783
  texture.colorSpace = THREE.SRGBColorSpace;
769
784
  ImageLoader.getData(src).then((image) => {
@@ -773,7 +788,7 @@ var init_TextureLoader = __esm({
773
788
  this.failedTextures.add(texture);
774
789
  console.warn("Failed to load texture", src, error);
775
790
  });
776
- texture.format = format;
791
+ texture.format = format2;
777
792
  texture.rotation = rotation;
778
793
  return Textures.initTextureProps(texture);
779
794
  }
@@ -800,8 +815,8 @@ var init_Textures = __esm({
800
815
  for (let y = 0; y < size; y++) {
801
816
  for (let x = 0; x < size; x++) {
802
817
  const offset = (y * size + x) * 4;
803
- const color = (x + y) % 2 === 0 ? 255 : 0;
804
- data[offset] = data[offset + 2] = color;
818
+ const color2 = (x + y) % 2 === 0 ? 255 : 0;
819
+ data[offset] = data[offset + 2] = color2;
805
820
  data[offset + 3] = 255;
806
821
  }
807
822
  }
@@ -812,6 +827,7 @@ var init_Textures = __esm({
812
827
  return this.initTextureProps(texture);
813
828
  });
814
829
  }
830
+ /** Sets nearest-neighbor filters without changing the texture's color space. Returns the same texture. */
815
831
  static initTextureProps(texture) {
816
832
  texture.magFilter = THREE2.NearestFilter;
817
833
  texture.minFilter = THREE2.NearestFilter;
@@ -824,11 +840,13 @@ var init_Textures = __esm({
824
840
  static createAsset(key) {
825
841
  return new Texture2();
826
842
  }
843
+ /** Creates an sRGB color texture from a canvas. */
827
844
  static createCanvasTexture(canvas) {
828
845
  const texture = new CanvasTexture(canvas);
829
846
  texture.colorSpace = THREE2.SRGBColorSpace;
830
847
  return this.initTextureProps(texture);
831
848
  }
849
+ /** Returns a shared texture immediately and loads its image asynchronously. Failed cached loads are retried. */
832
850
  static getImage(key) {
833
851
  const keyStr = serializeTextureKey(key);
834
852
  const cached2 = Caching.textureCache.peek(keyStr);
@@ -853,6 +871,7 @@ var init_Materials = __esm({
853
871
  init_TextureLoader();
854
872
  init_Caching();
855
873
  Materials = class _Materials {
874
+ /** Shared checkerboard material, created on first access. */
856
875
  static get MISSING_TEXTURE() {
857
876
  return Caching.materialCache.get("builtin:missing-texture", () => new MeshBasicMaterial({
858
877
  map: Textures.getMissing(),
@@ -924,6 +943,7 @@ var init_Materials = __esm({
924
943
  material.depthWrite = false;
925
944
  break;
926
945
  case "eyes":
946
+ case "no_outline":
927
947
  material.transparent = true;
928
948
  material.alphaTest = 0;
929
949
  material.depthWrite = false;
@@ -956,6 +976,7 @@ var init_Materials = __esm({
956
976
  if (tint !== void 0) material.color.multiply(new Color(tint));
957
977
  return material;
958
978
  }
979
+ /** Creates an unlit, transparent GUI material without depth writes or tone mapping. */
959
980
  static createGuiCanvasMaterial(canvas) {
960
981
  const material = new MeshBasicMaterial({
961
982
  map: Textures.createCanvasTexture(canvas),
@@ -981,7 +1002,7 @@ var init_Materials = __esm({
981
1002
  };
982
1003
  return material;
983
1004
  }
984
- static createShadedCanvasMaterial(canvas, transparent = false, shade = false) {
1005
+ static createShadedCanvasMaterial(canvas, transparent = false, shade = false, boundUvs = false) {
985
1006
  const vertShader = `
986
1007
  uniform bool SHADE;
987
1008
 
@@ -991,6 +1012,10 @@ var init_Materials = __esm({
991
1012
  #else
992
1013
  varying vec2 vUv;
993
1014
  #endif
1015
+ #ifdef BOUND_UVS
1016
+ attribute vec4 uvBounds;
1017
+ flat out vec4 vUvBounds;
1018
+ #endif
994
1019
  varying vec3 vTint;
995
1020
  varying float light;
996
1021
  varying float lift;
@@ -1026,13 +1051,17 @@ var init_Materials = __esm({
1026
1051
  }
1027
1052
 
1028
1053
  vUv = uv;
1054
+ #ifdef BOUND_UVS
1055
+ vUvBounds = uvBounds;
1056
+ #endif
1029
1057
  vTint = color;
1030
1058
 
1059
+ vec4 mvPosition = vec4(position, 1.0);
1031
1060
  #ifdef USE_INSTANCING
1032
- gl_Position = projectionMatrix * viewMatrix * modelMatrix * instanceMatrix * vec4(position, 1.0);
1033
- #else
1034
- gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
1061
+ mvPosition = instanceMatrix * mvPosition;
1035
1062
  #endif
1063
+ mvPosition = modelViewMatrix * mvPosition;
1064
+ gl_Position = projectionMatrix * mvPosition;
1036
1065
 
1037
1066
  }
1038
1067
  `;
@@ -1052,13 +1081,22 @@ var init_Materials = __esm({
1052
1081
  #else
1053
1082
  varying vec2 vUv;
1054
1083
  #endif
1084
+ #ifdef BOUND_UVS
1085
+ flat in vec4 vUvBounds;
1086
+ #endif
1055
1087
  varying vec3 vTint;
1056
1088
  varying float light;
1057
1089
  varying float lift;
1058
1090
 
1059
1091
  void main(void)
1060
1092
  {
1061
- vec4 color = texture2D(map, vUv);
1093
+ #ifdef BOUND_UVS
1094
+ // Keep edge samples inside their texture even without centroid interpolation.
1095
+ vec2 mapUv = clamp(vUv, vUvBounds.xy, vUvBounds.zw);
1096
+ #else
1097
+ vec2 mapUv = vUv;
1098
+ #endif
1099
+ vec4 color = texture2D(map, mapUv);
1062
1100
  color.rgb *= vTint;
1063
1101
 
1064
1102
  if (color.a < 0.01) discard;
@@ -1079,7 +1117,8 @@ var init_Materials = __esm({
1079
1117
  }
1080
1118
  `;
1081
1119
  try {
1082
- return new ShaderMaterial({
1120
+ const material = new ShaderMaterial({
1121
+ defines: boundUvs ? { BOUND_UVS: true } : {},
1083
1122
  uniforms: {
1084
1123
  SHADE: { value: true },
1085
1124
  BRIGHTNESS: { value: 1 },
@@ -1097,11 +1136,14 @@ var init_Materials = __esm({
1097
1136
  side: transparent ? DoubleSide : FrontSide,
1098
1137
  alphaTest: 0.5
1099
1138
  });
1139
+ Object.assign(material.defaultAttributeValues, { uvBounds: [0, 0, 1, 1] });
1140
+ return material;
1100
1141
  } catch (e) {
1101
1142
  console.warn(e);
1102
1143
  }
1103
1144
  return this.createBasicCanvasMaterial(canvas, transparent, shade);
1104
1145
  }
1146
+ /** Returns a shared image material. Use {@link createImage} for a separate material instance. */
1105
1147
  static getImage(key) {
1106
1148
  const keyStr = serializeMaterialKey(key);
1107
1149
  const map = Caching.materialCache.peek(keyStr)?.map;
@@ -1276,6 +1318,14 @@ var init_AssetKey = __esm({
1276
1318
  }
1277
1319
  };
1278
1320
  AssetKey = class _AssetKey extends BasicAssetKey {
1321
+ /**
1322
+ * Creates a key from separate path components. Use {@link parse} for a combined reference.
1323
+ *
1324
+ * @param assetType - Asset directory, such as `models`, `textures`, or `blockstates`.
1325
+ * @param type - Subdirectory, such as `block` or `item`.
1326
+ * @param rootType - Top-level directory, usually `assets` or `data`.
1327
+ * @param root - Base URL override for hosted asset sources.
1328
+ */
1279
1329
  constructor(namespace, path, assetType, type, rootType = "assets", extension = ".json", root) {
1280
1330
  super(namespace, path);
1281
1331
  this.namespace = namespace;
@@ -1287,6 +1337,16 @@ var init_AssetKey = __esm({
1287
1337
  this.root = root;
1288
1338
  this.isAssetKey = true;
1289
1339
  }
1340
+ /**
1341
+ * Parses an asset reference such as `minecraft:block/stone`.
1342
+ * A missing namespace defaults to the origin's namespace or `minecraft`.
1343
+ *
1344
+ * @param assetType - Asset category, such as `models` or `textures`.
1345
+ * @param str - Path with an optional namespace and file extension.
1346
+ * @param origin - Supplies the asset root, default namespace, and non-texture extension.
1347
+ * @returns A key with the matching extension removed from its path. Textures use `.png`;
1348
+ * other assets use the origin's extension or `.json`.
1349
+ */
1290
1350
  static parse(assetType, str, origin) {
1291
1351
  let namespace = origin?.namespace || DEFAULT_NAMESPACE;
1292
1352
  if (str.includes(":")) {
@@ -1318,6 +1378,7 @@ var init_AssetKey = __esm({
1318
1378
  ];
1319
1379
  return a.join("/");
1320
1380
  }
1381
+ /** Joins the type subdirectory and path without adding a namespace or file extension. */
1321
1382
  getFullPath() {
1322
1383
  let p10 = [];
1323
1384
  if (this.type) {
@@ -1329,6 +1390,7 @@ var init_AssetKey = __esm({
1329
1390
  toNamespacedString() {
1330
1391
  return this.namespace + ":" + this.getFullPath();
1331
1392
  }
1393
+ /** Returns a cache identifier that includes the explicit root or {@link AssetLoader.ROOT}. */
1332
1394
  serialize() {
1333
1395
  return this.toString();
1334
1396
  }
@@ -1340,17 +1402,17 @@ var init_AssetKey = __esm({
1340
1402
  var DisplayPosition, DISPLAY_POSITIONS;
1341
1403
  var init_DisplayPosition = __esm({
1342
1404
  "src/model/DisplayPosition.ts"() {
1343
- DisplayPosition = /* @__PURE__ */ ((DisplayPosition2) => {
1344
- DisplayPosition2["THIRDPERSON_RIGHTHAND"] = "thirdperson_righthand";
1345
- DisplayPosition2["THIRDPERSON_LEFTHAND"] = "thirdperson_lefthand";
1346
- DisplayPosition2["FIRSTPERSON_RIGHTHAND"] = "firstperson_righthand";
1347
- DisplayPosition2["FIRSTPERSON_LEFTHAND"] = "firstperson_lefthand";
1348
- DisplayPosition2["GUI"] = "gui";
1349
- DisplayPosition2["HEAD"] = "head";
1350
- DisplayPosition2["GROUND"] = "ground";
1351
- DisplayPosition2["FIXED"] = "fixed";
1352
- DisplayPosition2["ON_SHELF"] = "on_shelf";
1353
- return DisplayPosition2;
1405
+ DisplayPosition = /* @__PURE__ */ ((DisplayPosition3) => {
1406
+ DisplayPosition3["THIRDPERSON_RIGHTHAND"] = "thirdperson_righthand";
1407
+ DisplayPosition3["THIRDPERSON_LEFTHAND"] = "thirdperson_lefthand";
1408
+ DisplayPosition3["FIRSTPERSON_RIGHTHAND"] = "firstperson_righthand";
1409
+ DisplayPosition3["FIRSTPERSON_LEFTHAND"] = "firstperson_lefthand";
1410
+ DisplayPosition3["GUI"] = "gui";
1411
+ DisplayPosition3["HEAD"] = "head";
1412
+ DisplayPosition3["GROUND"] = "ground";
1413
+ DisplayPosition3["FIXED"] = "fixed";
1414
+ DisplayPosition3["ON_SHELF"] = "on_shelf";
1415
+ return DisplayPosition3;
1354
1416
  })(DisplayPosition || {});
1355
1417
  DISPLAY_POSITIONS = Object.values(DisplayPosition);
1356
1418
  }
@@ -1388,6 +1450,7 @@ var init_DisplayTransforms = __esm({
1388
1450
  const scale = new Vector32(...transform.scale ?? [1, 1, 1]).clampScalar(-4, 4);
1389
1451
  return new Matrix4().compose(translation, rotation, scale);
1390
1452
  }
1453
+ /** Transforms geometry in place and reverses triangle winding when the matrix reflects it. */
1391
1454
  static apply(geometry, matrix) {
1392
1455
  geometry.applyMatrix4(matrix);
1393
1456
  if (matrix.determinant() < 0) {
@@ -1415,7 +1478,10 @@ var init_ModelMerger = __esm({
1415
1478
  init_AssetKey();
1416
1479
  init_DisplayTransforms();
1417
1480
  ModelMerger = class {
1481
+ /** Loads the parent chain and returns a merged model without modifying the supplied definition. */
1418
1482
  static async mergeWithParents(model) {
1483
+ const parts = model.parts;
1484
+ if (parts) return { ...model, parts: await Promise.all(parts.map((part) => this.mergeWithParents(part))) };
1419
1485
  const models = await this.collectAllParents(model);
1420
1486
  let merged = {};
1421
1487
  for (const source of [...models, model]) {
@@ -1427,6 +1493,7 @@ var init_ModelMerger = __esm({
1427
1493
  }
1428
1494
  merged.hierarchy = models.map((m) => m.parent).filter((p10) => `${p10}`);
1429
1495
  merged.hierarchy.push(`${merged.parent}`);
1496
+ if (model.components) merged.components = model.components;
1430
1497
  return merged;
1431
1498
  }
1432
1499
  static async collectAllParents(model) {
@@ -1465,6 +1532,79 @@ var init_AssetParsers = __esm({
1465
1532
  }
1466
1533
  });
1467
1534
 
1535
+ // src/assets/BannerPatterns.ts
1536
+ var DYE_COLORS, BannerPatterns;
1537
+ var init_BannerPatterns = __esm({
1538
+ "src/assets/BannerPatterns.ts"() {
1539
+ init_AssetKey();
1540
+ init_AssetLoader();
1541
+ init_AssetParsers();
1542
+ init_Caching();
1543
+ DYE_COLORS = Object.freeze({
1544
+ white: 16383998,
1545
+ orange: 16351261,
1546
+ magenta: 13061821,
1547
+ light_blue: 3847130,
1548
+ yellow: 16701501,
1549
+ lime: 8439583,
1550
+ pink: 15961002,
1551
+ gray: 4673362,
1552
+ light_gray: 10329495,
1553
+ cyan: 1481884,
1554
+ purple: 8991416,
1555
+ blue: 3949738,
1556
+ brown: 8606770,
1557
+ green: 6192150,
1558
+ red: 11546150,
1559
+ black: 1908001
1560
+ });
1561
+ BannerPatterns = class {
1562
+ /** Lists namespaced pattern IDs from the selected namespace's data directory. */
1563
+ static async getList(namespace = "minecraft", root) {
1564
+ const collect = async (path) => {
1565
+ const prefix2 = path ? `${path}/` : "";
1566
+ const key = new AssetKey(namespace, `${prefix2}_list`, "banner_pattern", void 0, "data", ".json", root);
1567
+ const list2 = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get(key, "list" /* LIST */));
1568
+ if (!list2) return [];
1569
+ const files = list2.files.filter((file) => file.endsWith(".json") && file !== "_list.json").map((file) => `${namespace}:${prefix2}${file.slice(0, -5)}`);
1570
+ const children = await Promise.all(list2.directories.map((directory) => collect(`${prefix2}${directory}`)));
1571
+ return [...files, ...children.flat()];
1572
+ };
1573
+ return collect("");
1574
+ }
1575
+ /** Returns the named dye's sRGB value, rejecting unknown colors. */
1576
+ static getColor(color2) {
1577
+ if (typeof color2 !== "string" || !Object.prototype.hasOwnProperty.call(DYE_COLORS, color2)) throw new Error(`Unsupported dye color ${color2}`);
1578
+ return DYE_COLORS[color2];
1579
+ }
1580
+ /** Resolves the first 16 layers of a `banner_patterns` component, preserving their order. */
1581
+ static async getLayers(value, target, root) {
1582
+ if (value === void 0) return [];
1583
+ if (!Array.isArray(value)) throw new Error("banner_patterns must be an array");
1584
+ return Promise.all(value.slice(0, 16).map(async (layer) => {
1585
+ if (!layer || typeof layer !== "object" || Array.isArray(layer)) throw new Error("Each banner pattern requires a pattern and dye color");
1586
+ const color2 = this.getColor(layer.color);
1587
+ let pattern2 = layer.pattern;
1588
+ if (typeof pattern2 === "string") {
1589
+ const id = this.identifier(pattern2);
1590
+ const key = new AssetKey(id.namespace, id.getFullPath(), "banner_pattern", void 0, "data", ".json", root);
1591
+ pattern2 = await Caching.bannerPatternCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
1592
+ if (!pattern2) throw new Error(`Missing banner pattern ${layer.pattern}`);
1593
+ }
1594
+ if (!pattern2 || typeof pattern2 !== "object" || Array.isArray(pattern2) || typeof pattern2.translation_key !== "string") throw new Error("Banner patterns require asset_id and translation_key");
1595
+ const asset2 = this.identifier(pattern2.asset_id);
1596
+ return { color: color2, texture: new AssetKey(asset2.namespace, `${target}/${asset2.getFullPath()}`, "textures", "entity", "assets", ".png", root) };
1597
+ }));
1598
+ }
1599
+ static identifier(value) {
1600
+ if (typeof value !== "string" || !/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(value)) throw new Error(`Invalid banner-pattern identifier ${value}`);
1601
+ const [namespace, path] = value.includes(":") ? value.split(":") : ["minecraft", value];
1602
+ return new AssetKey(namespace, path);
1603
+ }
1604
+ };
1605
+ }
1606
+ });
1607
+
1468
1608
  // src/assets/Models.ts
1469
1609
  var Models;
1470
1610
  var init_Models = __esm({
@@ -1476,12 +1616,15 @@ var init_Models = __esm({
1476
1616
  init_PersistentCache();
1477
1617
  init_AssetKey();
1478
1618
  init_AssetParsers();
1619
+ init_DisplayPosition();
1620
+ init_BannerPatterns();
1479
1621
  Models = class _Models {
1480
1622
  // opened lazily: touching the store at import time would hit IndexedDB/disk just for loading
1481
1623
  // the library, and would require an EnvProvider before any entry has had a chance to register
1482
1624
  static get PERSISTENT_CACHE() {
1483
1625
  return this._persistentCache ?? (this._persistentCache = PersistentCache.open("minerender-models"));
1484
1626
  }
1627
+ /** Lists item filenames from the asset source's `_list.json`, with a legacy model-directory fallback. */
1485
1628
  static async getItemList() {
1486
1629
  const key = new AssetKey(DEFAULT_NAMESPACE, "_list", "items");
1487
1630
  const legacyKey = new AssetKey(DEFAULT_NAMESPACE, "_list", "models", "item");
@@ -1489,34 +1632,174 @@ var init_Models = __esm({
1489
1632
  return (await AssetLoader.getFirst([key, legacyKey], "list" /* LIST */))?.asset;
1490
1633
  }).then((r) => r?.files ?? []);
1491
1634
  }
1492
- static async loadAndMerge(key) {
1493
- const model = key.type === "item" ? await this.getItemModel(key) : await this.getRaw(key);
1635
+ static async loadAndMerge(key, context = {}) {
1636
+ const model = key.type === "item" ? await this.getItemModel(key, context) : await this.getRaw(key);
1494
1637
  if (!model) {
1495
1638
  return void 0;
1496
1639
  }
1497
1640
  return ModelMerger.mergeWithParents(model);
1498
1641
  }
1499
- static async getItemModel(key) {
1642
+ static async getItemModel(key, context = {}) {
1643
+ const preview = this.snapshotContext(key, context);
1500
1644
  const itemKey = new AssetKey(key.namespace, key.path, "items", void 0, key.rootType, ".json", key.root);
1501
- const model = await this.PERSISTENT_CACHE.getOrLoad(`item-v2:${AssetLoader.persistentKey(itemKey.serialize())}`, async () => {
1645
+ const cacheKey = itemKey.serialize() + this.contextKey(preview);
1646
+ const model = await this.PERSISTENT_CACHE.getOrLoad(`item-v4:${AssetLoader.persistentKey(cacheKey)}`, async () => {
1502
1647
  const result = await AssetLoader.getFirst([itemKey, key], "json" /* JSON */);
1503
1648
  if (!result) return void 0;
1504
- if (result.key.assetType !== "items") return { ...result.asset, key };
1505
- const selected = this.defaultItemModel(result.asset.model, key);
1506
- const modelKey = AssetKey.parse("models", selected.model);
1507
- modelKey.root = key.root;
1508
- const model2 = await this.getRaw(modelKey);
1509
- if (!model2) throw new Error(`Item ${key.toNamespacedString()} references missing model ${selected.model}`);
1510
- return { ...model2, ...selected.special && { special: selected.special }, ...selected.tints && { tints: selected.tints } };
1649
+ if (result.key.assetType !== "items") return { ...result.asset, key, components: preview.components };
1650
+ const load = async (selected) => {
1651
+ if ("parts" in selected) {
1652
+ return { key, parts: await Promise.all(selected.parts.map(load)) };
1653
+ }
1654
+ if ("item" in selected) {
1655
+ const model3 = await this.getItemModel(selected.item, { displayContext: preview.displayContext });
1656
+ if (!model3) throw new Error(`Item ${key.toNamespacedString()} references missing item ${selected.item.toNamespacedString()}`);
1657
+ return model3;
1658
+ }
1659
+ const modelKey = AssetKey.parse("models", selected.model);
1660
+ modelKey.root = key.root;
1661
+ const model2 = await this.getRaw(modelKey);
1662
+ if (!model2) throw new Error(`Item ${key.toNamespacedString()} references missing model ${selected.model}`);
1663
+ return { ...model2, components: preview.components, ...selected.special && { special: selected.special }, ...selected.tints && { tints: selected.tints } };
1664
+ };
1665
+ return load(this.selectItemModel(result.asset.model, key, preview));
1666
+ });
1667
+ const restore = (model2) => ({
1668
+ ...model2,
1669
+ key: Object.assign(new AssetKey("", ""), model2.key),
1670
+ ...model2.parts && { parts: model2.parts.map(restore) }
1511
1671
  });
1512
- return model ? { ...model, key: Object.assign(new AssetKey("", ""), model.key) } : void 0;
1672
+ return model ? restore(model) : void 0;
1673
+ }
1674
+ static contextIdentifier(id) {
1675
+ if (typeof id !== "string" || !/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(id)) {
1676
+ throw new Error(`Invalid item-preview identifier: ${id}`);
1677
+ }
1678
+ return id.includes(":") ? id : `minecraft:${id}`;
1679
+ }
1680
+ static snapshotContext(key, context) {
1681
+ const displayContext = context.displayContext ?? "gui" /* GUI */;
1682
+ if (displayContext !== "none" && !Object.values(DisplayPosition).includes(displayContext)) {
1683
+ throw new Error(`Unsupported item display context: ${displayContext}`);
1684
+ }
1685
+ for (const map of [context.properties, context.components, context.itemReferences]) {
1686
+ if (map !== void 0 && (!map || typeof map !== "object" || Array.isArray(map))) {
1687
+ throw new Error("Item-preview properties, components, and references must be objects keyed by identifier");
1688
+ }
1689
+ }
1690
+ const count = context.count === void 0 ? 1 : context.count;
1691
+ if (!Number.isSafeInteger(count) || count < 0) throw new Error("Item-preview count must be a nonnegative safe integer");
1692
+ const properties = {};
1693
+ for (const [id, value] of Object.entries(context.properties ?? {})) {
1694
+ const property = this.contextIdentifier(id);
1695
+ if (!["boolean", "string", "number"].includes(typeof value) || typeof value === "number" && !Number.isFinite(value)) {
1696
+ throw new Error(`Item-preview property ${property} must be a boolean, string, or finite number`);
1697
+ }
1698
+ if (Object.prototype.hasOwnProperty.call(properties, property)) throw new Error(`Duplicate item-preview property: ${property}`);
1699
+ properties[property] = value;
1700
+ }
1701
+ const components = {};
1702
+ for (const [id, value] of Object.entries(context.components ?? {})) {
1703
+ const component = this.contextIdentifier(id);
1704
+ if (Object.prototype.hasOwnProperty.call(components, component)) throw new Error(`Duplicate item-preview component: ${component}`);
1705
+ components[component] = this.snapshotJson(value);
1706
+ }
1707
+ const itemReferences = {};
1708
+ for (const [id, value] of Object.entries(context.itemReferences ?? {})) {
1709
+ const reference = this.contextIdentifier(id);
1710
+ if (!isAssetKey(value) || value.assetType !== "models" || value.type !== "item") {
1711
+ throw new Error(`Item-preview reference ${reference} must be an item-model AssetKey`);
1712
+ }
1713
+ this.contextIdentifier(`${value.namespace}:${value.path}`);
1714
+ if (Object.prototype.hasOwnProperty.call(itemReferences, reference)) throw new Error(`Duplicate item-preview reference: ${reference}`);
1715
+ itemReferences[reference] = Object.assign(new AssetKey("", ""), value, { root: value.root ?? key.root });
1716
+ }
1717
+ return { displayContext, properties, components, count, itemReferences };
1718
+ }
1719
+ static contextKey(context) {
1720
+ const itemReferences = Object.fromEntries(Object.entries(context.itemReferences).map(([id, key]) => [id, key.serialize()]));
1721
+ return `|item-v4:${JSON.stringify(this.snapshotJson({ ...context, itemReferences }))}`;
1513
1722
  }
1514
- // Item previews use the GUI context, false conditions, and zero numeric properties.
1515
- static defaultItemModel(node, key) {
1723
+ static snapshotJson(value, ancestors = /* @__PURE__ */ new Set()) {
1724
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) return value;
1725
+ if (typeof value !== "object" || ancestors.has(value) || !Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
1726
+ throw new Error("Item-preview components must contain finite JSON values without circular references");
1727
+ }
1728
+ ancestors.add(value);
1729
+ 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)]));
1730
+ ancestors.delete(value);
1731
+ return copy;
1732
+ }
1733
+ static propertyValue(node, context) {
1734
+ const kind = node.type.replace(/^minecraft:/, "");
1735
+ const property = node.property ? this.contextIdentifier(node.property) : void 0;
1736
+ if (kind === "select" && property === "minecraft:display_context") return context.displayContext;
1737
+ if (property && Object.prototype.hasOwnProperty.call(context.properties, property)) return context.properties[property];
1738
+ const fallback = kind === "condition" ? false : kind === "range_dispatch" ? 0 : void 0;
1739
+ const component = (id) => context.components[this.contextIdentifier(id)];
1740
+ if (property === "minecraft:custom_model_data") {
1741
+ const index = node.index ?? 0;
1742
+ if (!Number.isSafeInteger(index) || index < 0) throw new Error("Item-model custom_model_data index must be a nonnegative integer");
1743
+ const data = component("custom_model_data");
1744
+ if (data === void 0) return fallback;
1745
+ if (!data || typeof data !== "object" || Array.isArray(data)) throw new Error("Item-preview custom_model_data must be an object");
1746
+ const field = kind === "condition" ? "flags" : kind === "select" ? "strings" : "floats";
1747
+ const values = data[field];
1748
+ if (values === void 0) return fallback;
1749
+ if (!Array.isArray(values)) throw new Error(`Item-preview custom_model_data.${field} must be an array`);
1750
+ return index < values.length ? values[index] : fallback;
1751
+ }
1752
+ if (kind === "condition" && property === "minecraft:has_component") {
1753
+ if (node.ignore_default) throw new Error("Item-preview has_component with ignore_default requires an explicit properties override; item defaults are unavailable");
1754
+ return Object.prototype.hasOwnProperty.call(context.components, this.contextIdentifier(node.component));
1755
+ }
1756
+ if (kind === "select") {
1757
+ switch (property) {
1758
+ case "minecraft:component":
1759
+ return component(node.component);
1760
+ case "minecraft:block_state": {
1761
+ const state = component("block_state");
1762
+ if (state === void 0) return void 0;
1763
+ if (!state || typeof state !== "object" || Array.isArray(state)) throw new Error("Item-preview block_state must be an object");
1764
+ if (typeof node.block_state_property !== "string") throw new Error("Item-model block_state requires block_state_property");
1765
+ return Object.prototype.hasOwnProperty.call(state, node.block_state_property) ? state[node.block_state_property] : void 0;
1766
+ }
1767
+ case "minecraft:charge_type": {
1768
+ const projectiles = component("charged_projectiles");
1769
+ if (projectiles === void 0) return "none";
1770
+ if (!Array.isArray(projectiles)) throw new Error("Item-preview charged_projectiles must be an array of item stacks");
1771
+ const ids = projectiles.map((stack) => {
1772
+ if (!stack || typeof stack !== "object" || Array.isArray(stack)) throw new Error("Item-preview charged_projectiles must contain item-stack objects");
1773
+ return this.contextIdentifier(stack.id);
1774
+ });
1775
+ return !ids.length ? "none" : ids.includes("minecraft:firework_rocket") ? "rocket" : "arrow";
1776
+ }
1777
+ }
1778
+ }
1779
+ if (kind === "range_dispatch" && (property === "minecraft:damage" || property === "minecraft:count")) {
1780
+ const damage = property === "minecraft:damage";
1781
+ const maximumComponent = component(damage ? "max_damage" : "max_stack_size");
1782
+ const maximum = maximumComponent === void 0 ? damage ? 0 : 1 : maximumComponent;
1783
+ const damageComponent = component("damage");
1784
+ const value = damage ? damageComponent === void 0 ? 0 : damageComponent : context.count;
1785
+ if (typeof value !== "number" || typeof maximum !== "number" || maximum < 0) throw new Error(`Item-preview ${property} requires numeric components and a nonnegative maximum`);
1786
+ const clamped = Math.max(0, Math.min(maximum, value));
1787
+ return node.normalize === false ? clamped : clamped / maximum;
1788
+ }
1789
+ return fallback;
1790
+ }
1791
+ static selectItemModel(node, key, context) {
1516
1792
  if (!node || typeof node.type !== "string") {
1517
1793
  throw new Error(`Unsupported item model definition for ${key.toNamespacedString()}`);
1518
1794
  }
1519
1795
  switch (node.type.replace(/^minecraft:/, "")) {
1796
+ case "composite":
1797
+ if (Array.isArray(node.models)) return { parts: node.models.map((child) => this.selectItemModel(child, key, context)) };
1798
+ break;
1799
+ case "bundle/selected_item": {
1800
+ const item = context.itemReferences["minecraft:bundle/selected_item"];
1801
+ return item ? { item } : { parts: [] };
1802
+ }
1520
1803
  case "model":
1521
1804
  if (typeof node.model === "string" && node.model) return { model: node.model, tints: node.tints };
1522
1805
  break;
@@ -1524,6 +1807,23 @@ var init_Models = __esm({
1524
1807
  if (!node.base || !node.model || typeof node.model !== "object") break;
1525
1808
  const special = node.model;
1526
1809
  switch (special.type) {
1810
+ case "banner":
1811
+ case "minecraft:banner":
1812
+ if (typeof special.color === "string" && Object.prototype.hasOwnProperty.call(DYE_COLORS, special.color)) return { model: node.base, special };
1813
+ break;
1814
+ case "shield":
1815
+ case "minecraft:shield":
1816
+ case "trident":
1817
+ case "minecraft:trident":
1818
+ case "conduit":
1819
+ case "minecraft:conduit":
1820
+ return { model: node.base, special };
1821
+ case "shulker_box":
1822
+ case "minecraft:shulker_box":
1823
+ 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))) {
1824
+ return { model: node.base, special };
1825
+ }
1826
+ break;
1527
1827
  case "chest":
1528
1828
  case "minecraft:chest":
1529
1829
  case "bed":
@@ -1537,21 +1837,34 @@ var init_Models = __esm({
1537
1837
  }
1538
1838
  throw new Error(`Unsupported special item renderer ${special.type} for ${key.toNamespacedString()}`);
1539
1839
  }
1540
- case "condition":
1541
- return this.defaultItemModel(node.on_false, key);
1840
+ case "condition": {
1841
+ const value = this.propertyValue(node, context);
1842
+ if (typeof value !== "boolean") throw new Error(`Item-preview condition ${node.property} requires a boolean`);
1843
+ return this.selectItemModel(value ? node.on_true : node.on_false, key, context);
1844
+ }
1542
1845
  case "select": {
1543
- 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;
1544
- return this.defaultItemModel(selected ?? node.fallback, key);
1846
+ const value = this.propertyValue(node, context);
1847
+ const component = node.property && this.contextIdentifier(node.property) === "minecraft:component";
1848
+ if (!component && value !== void 0 && typeof value !== "string") throw new Error(`Item-preview selector ${node.property} requires a string`);
1849
+ const matches = (candidate) => component ? JSON.stringify(this.snapshotJson(candidate)) === JSON.stringify(this.snapshotJson(value)) : candidate === value;
1850
+ const selected = value === void 0 ? void 0 : node.cases?.find((entry) => matches(entry.when) || Array.isArray(entry.when) && entry.when.some(matches))?.model;
1851
+ return this.selectItemModel(selected ?? node.fallback, key, context);
1545
1852
  }
1546
1853
  case "range_dispatch": {
1854
+ const value = this.propertyValue(node, context);
1855
+ if (typeof value !== "number") throw new Error(`Item-preview range ${node.property} requires a number`);
1856
+ if (Number.isNaN(value)) return this.selectItemModel(node.fallback, key, context);
1857
+ const scaled = value * (node.scale ?? 1);
1858
+ if (!Number.isFinite(scaled)) throw new Error(`Item-preview range ${node.property} requires a finite value and scale`);
1547
1859
  const selected = node.entries?.reduce((match, entry) => {
1548
- return entry.threshold <= 0 && (!match || entry.threshold >= match.threshold) ? entry : match;
1860
+ return entry.threshold <= scaled && (!match || entry.threshold >= match.threshold) ? entry : match;
1549
1861
  }, void 0);
1550
- return this.defaultItemModel(selected?.model ?? node.fallback, key);
1862
+ return this.selectItemModel(selected?.model ?? node.fallback, key, context);
1551
1863
  }
1552
1864
  }
1553
1865
  throw new Error(`Unsupported item model ${node?.type ?? "definition"} for ${key.toNamespacedString()}`);
1554
1866
  }
1867
+ /** Loads one model file without resolving its parents. Returns `undefined` when the file is missing. */
1555
1868
  static async getRaw(key) {
1556
1869
  if (!key.assetType) {
1557
1870
  key.assetType = "models";
@@ -1564,28 +1877,39 @@ var init_Models = __esm({
1564
1877
  return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
1565
1878
  return AssetLoader.get(key, "model" /* MODEL */);
1566
1879
  });
1567
- }).then((asset) => {
1568
- if (asset) {
1569
- asset.key = key;
1880
+ }).then((asset2) => {
1881
+ if (asset2) {
1882
+ asset2.key = key;
1570
1883
  }
1571
- return asset;
1884
+ return asset2;
1572
1885
  });
1573
1886
  }
1574
- static async getMerged(key) {
1887
+ /**
1888
+ * Loads a model and resolves its parent chain. Returns `undefined` when the model is missing.
1889
+ * Item keys default to GUI context and a stack count of 1. Unresolved conditions are false and numeric properties are zero.
1890
+ * Pass `context` to supply component values, property overrides, and item references for a preview.
1891
+ * Composite items retain independently merged children in `ItemModel.parts`.
1892
+ *
1893
+ * @param key - Model key, for example `AssetKey.parse("models", "minecraft:item/diamond_sword")`.
1894
+ */
1895
+ static async getMerged(key, context = {}) {
1575
1896
  if (!key.assetType) {
1576
1897
  key.assetType = "models";
1577
1898
  }
1578
1899
  if (!key.extension) {
1579
1900
  key.extension = ".json";
1580
1901
  }
1581
- const keyStr = key.serialize();
1902
+ const preview = this.snapshotContext(key, context);
1903
+ const keyStr = key.serialize() + (key.type === "item" ? this.contextKey(preview) : "");
1582
1904
  return Caching.mergedModelCache.get(keyStr, (k) => {
1583
- return _Models.loadAndMerge(key);
1905
+ return _Models.loadAndMerge(key, preview);
1584
1906
  });
1585
1907
  }
1586
- static async get(key) {
1587
- return this.getMerged(key);
1908
+ /** Alias for {@link getMerged}. */
1909
+ static async get(key, context = {}) {
1910
+ return this.getMerged(key, context);
1588
1911
  }
1912
+ /** Clears persisted models. Use {@link Caching.clear} to also discard in-memory assets. */
1589
1913
  static async clearCache() {
1590
1914
  return this.PERSISTENT_CACHE.clear();
1591
1915
  }
@@ -1608,6 +1932,7 @@ var init_BlockStates = __esm({
1608
1932
  return this._persistentCache ?? (this._persistentCache = PersistentCache.open("minerender-blockstates"));
1609
1933
  }
1610
1934
  // BlockState names are hardcoded
1935
+ /** Returns blockstate filenames from the selected version's `_list.json`, or an empty list. */
1611
1936
  static async getList() {
1612
1937
  const key = new AssetKey(
1613
1938
  DEFAULT_NAMESPACE,
@@ -1628,6 +1953,7 @@ var init_BlockStates = __esm({
1628
1953
  return AssetLoader.get(key, "json" /* JSON */);
1629
1954
  });
1630
1955
  }
1956
+ /** Looks up vanilla property definitions for the key's block path, or returns `undefined`. */
1631
1957
  static async getDefaultState(key) {
1632
1958
  const defaultStates = await this.getDefaultStates();
1633
1959
  if (!defaultStates) {
@@ -1635,6 +1961,7 @@ var init_BlockStates = __esm({
1635
1961
  }
1636
1962
  return defaultStates["minecraft:" + key.path];
1637
1963
  }
1964
+ /** Loads a cached blockstate definition for {@link MineRenderScene.addBlock}, or returns `undefined`. */
1638
1965
  static async get(key) {
1639
1966
  if (!key.assetType) {
1640
1967
  key.assetType = "blockstates";
@@ -1647,11 +1974,11 @@ var init_BlockStates = __esm({
1647
1974
  return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
1648
1975
  return AssetLoader.get(key, "blockstate" /* BLOCKSTATE */);
1649
1976
  });
1650
- }).then((asset) => {
1651
- if (asset) {
1652
- asset.key = key;
1977
+ }).then((asset2) => {
1978
+ if (asset2) {
1979
+ asset2.key = key;
1653
1980
  }
1654
- return asset;
1981
+ return asset2;
1655
1982
  });
1656
1983
  }
1657
1984
  static getAll(keys) {
@@ -1661,6 +1988,7 @@ var init_BlockStates = __esm({
1661
1988
  }
1662
1989
  return Promise.all(promises);
1663
1990
  }
1991
+ /** Clears persisted blockstates. Use {@link Caching.clear} to also discard in-memory assets. */
1664
1992
  static async clearCache() {
1665
1993
  await this.PERSISTENT_CACHE.clear();
1666
1994
  }
@@ -1686,9 +2014,11 @@ var init_Assets = __esm({
1686
2014
  static parseAssetKey(assetType, str, origin) {
1687
2015
  return AssetKey.parse(assetType, str, origin);
1688
2016
  }
2017
+ /** Loads a model with its parent chain resolved. See {@link Models.getMerged}. */
1689
2018
  static async getModel(key) {
1690
2019
  return Models.getMerged(key);
1691
2020
  }
2021
+ /** Loads a blockstate definition. See {@link BlockStates.get}. */
1692
2022
  static async getBlockState(key) {
1693
2023
  return BlockStates.get(key);
1694
2024
  }
@@ -1703,6 +2033,10 @@ var init_AssetSource = __esm({
1703
2033
  AssetSource = class {
1704
2034
  constructor() {
1705
2035
  }
2036
+ /** Whether this source prevents lower-priority sources from supplying the key. Its own assets remain available. */
2037
+ blocks(key) {
2038
+ return false;
2039
+ }
1706
2040
  /**
1707
2041
  * Identifies this source's content across sessions for persistent caching. Sources that
1708
2042
  * cannot tell whether their content changed (an unnamed archive, for example) return
@@ -1731,6 +2065,10 @@ var BatchedExecutor;
1731
2065
  var init_BatchedExecutor = __esm({
1732
2066
  "src/util/BatchedExecutor.ts"() {
1733
2067
  BatchedExecutor = class {
2068
+ /**
2069
+ * @param interval - Delay between queue runs, in milliseconds.
2070
+ * @param batch - Maximum tasks started per run and maximum tasks active at once.
2071
+ */
1734
2072
  constructor(interval = 1, batch = 30) {
1735
2073
  if (!Number.isFinite(interval) || interval < 0) throw new RangeError("Batch interval must be finite and nonnegative");
1736
2074
  if (!Number.isInteger(batch) || batch < 1) throw new RangeError("Batch size must be a positive integer");
@@ -1738,9 +2076,11 @@ var init_BatchedExecutor = __esm({
1738
2076
  this.batch = batch;
1739
2077
  this.queue = new JobQueue2(async (task) => task(), { interval, maxPerRun: batch, maxActive: batch });
1740
2078
  }
2079
+ /** Queues a task and resolves with its result, or rejects with its error. */
1741
2080
  submit(task) {
1742
2081
  return this.queue.add(() => task());
1743
2082
  }
2083
+ /** Rejects queued and future tasks. Tasks already running are allowed to finish. */
1744
2084
  stop() {
1745
2085
  this.queue.end();
1746
2086
  }
@@ -1788,29 +2128,29 @@ function applyGenericRotation(axis, rotation, obj) {
1788
2128
  break;
1789
2129
  }
1790
2130
  }
1791
- function addBox3WireframeToObject(box, obj, color = 16777215, w = 2) {
2131
+ function addBox3WireframeToObject(box, obj, color2 = 16777215, w = 2) {
1792
2132
  let size = new Vector33();
1793
2133
  box.getSize(size);
1794
2134
  let boxGeo = new BoxGeometry2(size.x, size.y, size.z, 1, 1, 1);
1795
2135
  let wireGeo = new EdgesGeometry(boxGeo);
1796
- let wireMat = new LineBasicMaterial({ color, linewidth: w });
2136
+ let wireMat = new LineBasicMaterial({ color: color2, linewidth: w });
1797
2137
  let wireframe = new LineSegments(wireGeo, wireMat);
1798
2138
  obj.add(wireframe);
1799
2139
  let axes = new AxesHelper(1);
1800
2140
  obj.add(axes);
1801
2141
  }
1802
- function addWireframeToObject(obj, color = 16777215, w = 2) {
2142
+ function addWireframeToObject(obj, color2 = 16777215, w = 2) {
1803
2143
  let boxGeo = new BoxGeometry2(16, 16, 16, 1, 1, 1);
1804
2144
  let wireGeo = new EdgesGeometry(boxGeo);
1805
- let wireMat = new LineBasicMaterial({ color, linewidth: w });
2145
+ let wireMat = new LineBasicMaterial({ color: color2, linewidth: w });
1806
2146
  let wireframe = new LineSegments(wireGeo, wireMat);
1807
2147
  obj.add(wireframe);
1808
2148
  let axes = new AxesHelper(1);
1809
2149
  obj.add(axes);
1810
2150
  }
1811
- function addWireframeToMesh(geo, mesh, color = 16777215, w = 2) {
2151
+ function addWireframeToMesh(geo, mesh, color2 = 16777215, w = 2) {
1812
2152
  let wireGeo = new EdgesGeometry(geo);
1813
- let wireMat = new LineBasicMaterial({ color, linewidth: w });
2153
+ let wireMat = new LineBasicMaterial({ color: color2, linewidth: w });
1814
2154
  let wireframe = new LineSegments(wireGeo, wireMat);
1815
2155
  mesh.add(wireframe);
1816
2156
  let axes = new AxesHelper(1);
@@ -1882,10 +2222,11 @@ var NBTHelper;
1882
2222
  var init_NBTHelper = __esm({
1883
2223
  "src/nbt/NBTHelper.ts"() {
1884
2224
  NBTHelper = class {
1885
- static async fromBuffer(data, format) {
2225
+ /** Parses NBT bytes and preserves format and compression metadata. Omit `format` for automatic detection. */
2226
+ static async fromBuffer(data, format2) {
1886
2227
  const buffer = Buffer3.from(data instanceof Uint8Array ? data : new Uint8Array(data));
1887
2228
  const prismarineNbt = await import("prismarine-nbt");
1888
- const { parsed, type, metadata } = await prismarineNbt.parse(buffer, format);
2229
+ const { parsed, type, metadata } = await prismarineNbt.parse(buffer, format2);
1889
2230
  return Object.assign(parsed, {
1890
2231
  format: type,
1891
2232
  metadata,
@@ -1950,6 +2291,7 @@ var init_HostedAssetSource = __esm({
1950
2291
  retryDefaults: true
1951
2292
  };
1952
2293
  _HostedAssetSource = class _HostedAssetSource extends AssetSource {
2294
+ /** Retries missing assets with the `minecraft` namespace and default root. Other failures reject. */
1953
2295
  async loadOrRetryWithDefaults(key, parser) {
1954
2296
  const direct = await this.load(key, parser);
1955
2297
  if (typeof direct !== "undefined") {
@@ -2007,6 +2349,10 @@ var init_HostedAssetSource = __esm({
2007
2349
  assetBasePath(key) {
2008
2350
  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 + "/" : ""}`;
2009
2351
  }
2352
+ /**
2353
+ * Creates a hosted source for {@link AssetLoader.addSource}.
2354
+ * Set `options.retryDefaults` to `false` to restrict lookups to the requested namespace and root.
2355
+ */
2010
2356
  constructor(root, options) {
2011
2357
  super();
2012
2358
  this._root = root;
@@ -2099,6 +2445,11 @@ var BrowserArchiveProxy;
2099
2445
  var init_BrowserArchiveProxy = __esm({
2100
2446
  "src/assets/source/archive/BrowserArchiveProxy.ts"() {
2101
2447
  BrowserArchiveProxy = class {
2448
+ /**
2449
+ * Opens a ZIP for asset lookup.
2450
+ * @param id - Cache identity for this content. Change it when the pack changes.
2451
+ * A File supplies an identity from its name, size, and modification time when omitted.
2452
+ */
2102
2453
  constructor(blob, id) {
2103
2454
  this._blob = blob;
2104
2455
  this._reader = new ZipReader(new BlobReader(this._blob));
@@ -2119,6 +2470,162 @@ var init_BrowserArchiveProxy = __esm({
2119
2470
  }
2120
2471
  });
2121
2472
 
2473
+ // src/assets/source/archive/PackMetadata.ts
2474
+ function object(value, field) {
2475
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
2476
+ throw new Error(`Invalid ${field}: expected an object`);
2477
+ }
2478
+ return value;
2479
+ }
2480
+ function integer(value, field) {
2481
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
2482
+ throw new Error(`Invalid ${field}: expected a nonnegative integer`);
2483
+ }
2484
+ return value;
2485
+ }
2486
+ function format(value, field, upper = false) {
2487
+ const parts = Array.isArray(value) ? value : [value];
2488
+ if (parts.length < 1 || parts.length > 2) {
2489
+ throw new Error(`Invalid ${field}: expected a major version or [major, minor]`);
2490
+ }
2491
+ return [integer(parts[0], field), parts.length === 2 ? integer(parts[1], field) : upper ? Infinity : 0];
2492
+ }
2493
+ function compare(a, b) {
2494
+ return a[0] === b[0] ? a[1] - b[1] : a[0] - b[0];
2495
+ }
2496
+ function overlay(value, field) {
2497
+ const entry = object(value, field);
2498
+ const directory = entry.directory;
2499
+ if (typeof directory !== "string" || !directory || /[^-_a-zA-Z0-9.]/.test(directory) || directory === "." || directory === "..") {
2500
+ throw new Error(`Invalid ${field}.directory: expected a directory name without path separators`);
2501
+ }
2502
+ let min, max;
2503
+ if ("min_format" in entry || "max_format" in entry) {
2504
+ min = format(entry.min_format, `${field}.min_format`);
2505
+ max = format(entry.max_format, `${field}.max_format`, true);
2506
+ } else {
2507
+ const range = entry.formats;
2508
+ let lower, upper;
2509
+ if (typeof range === "number") {
2510
+ lower = upper = range;
2511
+ } else if (Array.isArray(range)) {
2512
+ if (range.length !== 2) throw new Error(`Invalid ${field}.formats: expected [min, max]`);
2513
+ [lower, upper] = range;
2514
+ } else {
2515
+ const bounds = object(range, `${field}.formats`);
2516
+ lower = bounds.min_inclusive;
2517
+ upper = bounds.max_inclusive;
2518
+ }
2519
+ min = [integer(lower, `${field}.formats.min_inclusive`), 0];
2520
+ max = [integer(upper, `${field}.formats.max_inclusive`), Infinity];
2521
+ }
2522
+ if (compare(min, max) > 0) throw new Error(`Invalid ${field}: minimum format exceeds maximum format`);
2523
+ return { directory, min, max };
2524
+ }
2525
+ function pattern(value, field) {
2526
+ if (value === void 0) return void 0;
2527
+ if (typeof value !== "string") throw new Error(`Invalid ${field}: expected a regular expression string`);
2528
+ try {
2529
+ return new RegExp(value);
2530
+ } catch (cause) {
2531
+ throw new Error(`Invalid ${field}: ${String(cause)}`);
2532
+ }
2533
+ }
2534
+ var PackMetadata;
2535
+ var init_PackMetadata = __esm({
2536
+ "src/assets/source/archive/PackMetadata.ts"() {
2537
+ PackMetadata = class _PackMetadata {
2538
+ constructor(overlays, filters) {
2539
+ this.overlays = overlays;
2540
+ this.filters = filters;
2541
+ this.hasOverlays = overlays.length > 0;
2542
+ }
2543
+ static parse(value) {
2544
+ const metadata = object(value, "pack.mcmeta");
2545
+ let overlays = [];
2546
+ if ("overlays" in metadata) {
2547
+ const entries = object(metadata.overlays, "overlays").entries;
2548
+ if (!Array.isArray(entries)) throw new Error("Invalid overlays.entries: expected an array");
2549
+ overlays = entries.map((entry, index) => overlay(entry, `overlays.entries[${index}]`));
2550
+ }
2551
+ let filters = [];
2552
+ if ("filter" in metadata) {
2553
+ const entries = object(metadata.filter, "filter").block;
2554
+ if (!Array.isArray(entries)) throw new Error("Invalid filter.block: expected an array");
2555
+ filters = entries.map((value2, index) => {
2556
+ const field = `filter.block[${index}]`;
2557
+ const entry = object(value2, field);
2558
+ return { namespace: pattern(entry.namespace, `${field}.namespace`), path: pattern(entry.path, `${field}.path`) };
2559
+ });
2560
+ }
2561
+ return new _PackMetadata(overlays, filters);
2562
+ }
2563
+ /** Returns applicable directories in lookup order, with the last declared overlay first. */
2564
+ overlayDirectories(target) {
2565
+ const selected = format(target, "target format");
2566
+ return this.overlays.filter((entry) => compare(selected, entry.min) >= 0 && compare(selected, entry.max) <= 0).map((entry) => entry.directory).reverse();
2567
+ }
2568
+ blocks(namespace, path) {
2569
+ return this.filters.some((entry) => entry.namespace?.test(namespace) ?? true) && this.filters.some((entry) => entry.path?.test(path) ?? true);
2570
+ }
2571
+ };
2572
+ }
2573
+ });
2574
+
2575
+ // src/assets/source/archive/PackFormats.ts
2576
+ var PackFormats;
2577
+ var init_PackFormats = __esm({
2578
+ "src/assets/source/archive/PackFormats.ts"() {
2579
+ init_Requests();
2580
+ PackFormats = class {
2581
+ /** Resolves pack formats from Mojang's client metadata hosted on assets.mcasset.cloud. */
2582
+ static async get(version2, type) {
2583
+ let pending = this.versions.get(version2);
2584
+ if (!pending || pending.expires <= Date.now()) {
2585
+ pending = {
2586
+ formats: this.load(version2),
2587
+ expires: ["latest", "release", "snapshot"].includes(version2) ? Date.now() + 3e5 : Infinity
2588
+ };
2589
+ this.versions.set(version2, pending);
2590
+ }
2591
+ try {
2592
+ return (await pending.formats)[type];
2593
+ } catch (cause) {
2594
+ if (this.versions.get(version2) === pending) this.versions.delete(version2);
2595
+ const option = type === "assets" ? "resourcePackFormat" : "dataPackFormat";
2596
+ const reason = cause instanceof Error ? cause.message : String(cause);
2597
+ throw new Error(`Could not resolve the pack format for Minecraft ${version2}. Set ArchiveAssetSource ${option} explicitly. ${reason}`);
2598
+ }
2599
+ }
2600
+ static async load(version2) {
2601
+ const response = await Requests.genericRequest({
2602
+ url: `https://assets.mcasset.cloud/${encodeURIComponent(version2)}/game-version.json`,
2603
+ responseType: "json"
2604
+ });
2605
+ const data = response.data;
2606
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
2607
+ throw new Error("Expected version metadata to be an object");
2608
+ }
2609
+ const packs = data.pack_version;
2610
+ if (typeof packs !== "number" && (!packs || typeof packs !== "object" || Array.isArray(packs))) {
2611
+ throw new Error("Expected pack_version to be a number or an object");
2612
+ }
2613
+ const format2 = (type) => {
2614
+ const modern = typeof packs === "object" && ["resource_major", "resource_minor", "data_major", "data_minor"].some((key) => key in packs);
2615
+ const major = typeof packs === "number" ? packs : packs[modern ? `${type}_major` : type];
2616
+ const minor = modern ? packs[`${type}_minor`] : 0;
2617
+ if (typeof major !== "number" || !Number.isSafeInteger(major) || major < 0 || typeof minor !== "number" || !Number.isSafeInteger(minor) || minor < 0) {
2618
+ throw new Error(`Invalid ${type} pack format in version metadata`);
2619
+ }
2620
+ return Object.freeze([major, minor]);
2621
+ };
2622
+ return { assets: format2("resource"), data: format2("data") };
2623
+ }
2624
+ };
2625
+ PackFormats.versions = /* @__PURE__ */ new Map();
2626
+ }
2627
+ });
2628
+
2122
2629
  // src/assets/source/archive/ArchiveAssetSource.ts
2123
2630
  var ArchiveAssetSource;
2124
2631
  var init_ArchiveAssetSource = __esm({
@@ -2127,35 +2634,78 @@ var init_ArchiveAssetSource = __esm({
2127
2634
  init_request();
2128
2635
  init_HostedAssetSource();
2129
2636
  init_BrowserArchiveProxy();
2637
+ init_AssetLoader();
2638
+ init_PackMetadata();
2639
+ init_PackFormats();
2130
2640
  ArchiveAssetSource = class _ArchiveAssetSource extends AssetSource {
2131
- constructor(archiveProxy) {
2641
+ constructor(archiveProxy, options = {}) {
2132
2642
  super();
2133
- console.log(archiveProxy);
2134
2643
  this._archiveProxy = archiveProxy;
2644
+ const copyFormat = (format2) => typeof format2 === "object" ? [format2[0], format2[1]] : format2;
2645
+ this.options = {
2646
+ resourcePackFormat: copyFormat(options.resourcePackFormat),
2647
+ dataPackFormat: copyFormat(options.dataPackFormat)
2648
+ };
2135
2649
  }
2136
- static blob(blob) {
2137
- return new _ArchiveAssetSource(new BrowserArchiveProxy(blob));
2650
+ /** Creates a browser ZIP source from a Blob or File. */
2651
+ static blob(blob, options) {
2652
+ return new _ArchiveAssetSource(new BrowserArchiveProxy(blob), options);
2138
2653
  }
2139
2654
  get cacheId() {
2140
2655
  const id = this._archiveProxy.id;
2141
- return id === void 0 ? void 0 : `archive:${id}`;
2656
+ const formats = [this.options.resourcePackFormat ?? AssetLoader.version, this.options.dataPackFormat ?? AssetLoader.version];
2657
+ return id === void 0 ? void 0 : `archive-metadata:1:${id}:${JSON.stringify(formats)}`;
2142
2658
  }
2143
2659
  async getEntries() {
2144
- return this._archiveProxy.getEntries();
2660
+ return this.entries ?? (this.entries = this._archiveProxy.getEntries().catch((error) => {
2661
+ this.entries = void 0;
2662
+ throw error;
2663
+ }));
2145
2664
  }
2146
2665
  async getEntry(path) {
2147
2666
  const entries = await this.getEntries();
2148
2667
  return entries.find((e) => e.filename === path);
2149
2668
  }
2669
+ async getMetadata(key) {
2670
+ try {
2671
+ return await (this.metadata ?? (this.metadata = this.getEntry("pack.mcmeta").then(async (entry) => PackMetadata.parse(entry ? JSON.parse(await (await entry.getData()).text()) : {})).catch((error) => {
2672
+ this.metadata = void 0;
2673
+ throw error;
2674
+ })));
2675
+ } catch (cause) {
2676
+ throw new AssetLoadError(this, key, "pack.mcmeta", cause);
2677
+ }
2678
+ }
2679
+ async blocks(key) {
2680
+ if (key.rootType !== "assets" && key.rootType !== "data") return false;
2681
+ const metadata = await this.getMetadata(key);
2682
+ const path = `${key.assetType !== void 0 ? key.assetType + "/" : ""}${key.getFullPath()}${key.extension}`;
2683
+ return metadata.blocks(key.namespace, path);
2684
+ }
2150
2685
  async get(key, parser) {
2151
2686
  const responseParser = HostedAssetSource.PARSER_MAP.get(parser);
2152
2687
  return this.load(key, responseParser);
2153
2688
  }
2154
2689
  async load(key, parser) {
2155
2690
  const path = `${this.assetBasePath(key)}${key.type !== void 0 ? key.type + "/" : ""}${key.path}${key.extension}`;
2691
+ const version2 = AssetLoader.version;
2692
+ let selectedPath = path;
2156
2693
  let url;
2157
2694
  try {
2158
- const entry = await this.getEntry(path);
2695
+ let directories = [];
2696
+ if (key.rootType === "assets" || key.rootType === "data") {
2697
+ const metadata = await this.getMetadata(key);
2698
+ if (metadata.hasOverlays) {
2699
+ const format2 = (key.rootType === "assets" ? this.options.resourcePackFormat : this.options.dataPackFormat) ?? await PackFormats.get(version2, key.rootType);
2700
+ directories = metadata.overlayDirectories(format2);
2701
+ }
2702
+ }
2703
+ let entry;
2704
+ for (const directory of [...directories, ""]) {
2705
+ selectedPath = directory ? `${directory}/${path}` : path;
2706
+ entry = await this.getEntry(selectedPath);
2707
+ if (entry) break;
2708
+ }
2159
2709
  if (!entry) {
2160
2710
  return void 0;
2161
2711
  }
@@ -2168,7 +2718,8 @@ var init_ArchiveAssetSource = __esm({
2168
2718
  }
2169
2719
  return await parser.parse(response);
2170
2720
  } catch (cause) {
2171
- throw new AssetLoadError(this, key, path, cause);
2721
+ if (cause instanceof AssetLoadError) throw cause;
2722
+ throw new AssetLoadError(this, key, selectedPath, cause);
2172
2723
  } finally {
2173
2724
  if (url !== void 0) {
2174
2725
  URL.revokeObjectURL(url);
@@ -2201,6 +2752,8 @@ var init_archive = __esm({
2201
2752
  init_ArchiveEntry();
2202
2753
  init_ArchiveProxy();
2203
2754
  init_BrowserArchiveProxy();
2755
+ init_PackFormats();
2756
+ init_PackMetadata();
2204
2757
  }
2205
2758
  });
2206
2759
 
@@ -2229,8 +2782,9 @@ var init_AssetLoader = __esm({
2229
2782
  static get version() {
2230
2783
  return this.ROOT.substring(this.ROOT.lastIndexOf("/") + 1);
2231
2784
  }
2232
- static setVersion(version) {
2233
- this.ROOT = `https://assets.mcasset.cloud/${version}`;
2785
+ /** Selects an mcasset.cloud version, updates the vanilla source, and clears in-memory caches. */
2786
+ static setVersion(version2) {
2787
+ this.ROOT = `https://assets.mcasset.cloud/${version2}`;
2234
2788
  const source = new HostedAssetSource(this.ROOT, { retryDefaults: false });
2235
2789
  const index = this._SOURCES.findIndex((s) => s.key === "mcassets");
2236
2790
  if (index !== -1) {
@@ -2262,6 +2816,13 @@ var init_AssetLoader = __esm({
2262
2816
  return scope ? `${scope}
2263
2817
  ${key}` : key;
2264
2818
  }
2819
+ /**
2820
+ * Registers a source at the highest priority.
2821
+ * Call {@link Caching.clear} after changing sources to discard previously loaded assets.
2822
+ *
2823
+ * @param key - Name used to replace or remove this source.
2824
+ * @param override - Removes the first source with this name before adding the new one.
2825
+ */
2265
2826
  static addSource(key, source, override = true) {
2266
2827
  if (override) {
2267
2828
  const existing = this.removeSource(key);
@@ -2272,6 +2833,7 @@ ${key}` : key;
2272
2833
  this._SOURCES.unshift({ key, source });
2273
2834
  console.log(p4, "Added AssetSource", key);
2274
2835
  }
2836
+ /** Removes and returns the first source with this name. Call {@link Caching.clear} to reload assets. */
2275
2837
  static removeSource(key) {
2276
2838
  const index = this._SOURCES.findIndex((s) => s.key === key);
2277
2839
  if (index != -1) {
@@ -2282,14 +2844,16 @@ ${key}` : key;
2282
2844
  }
2283
2845
  return void 0;
2284
2846
  }
2847
+ /** Loads from all sources and returns defined results in priority order. Any source failure rejects the call. */
2285
2848
  static async getAll(key, parser) {
2286
- let promises = [];
2287
- for (const source of this._SOURCES) {
2288
- promises.push(source.source.get(key, parser));
2849
+ const sources = [...this._SOURCES];
2850
+ const results = [];
2851
+ for (const { source } of sources) {
2852
+ const asset2 = await source.get(key, parser);
2853
+ if (asset2 != void 0) results.push(asset2);
2854
+ if (await source.blocks(key)) break;
2289
2855
  }
2290
- return Promise.all(promises).then((results) => {
2291
- return results.filter((r) => r != void 0);
2292
- });
2856
+ return results;
2293
2857
  }
2294
2858
  /** Returns the first defined result in source-priority order, without merging assets. */
2295
2859
  static async get(key, parser) {
@@ -2310,17 +2874,22 @@ ${key}` : key;
2310
2874
  /** Tries each path within a source before considering lower-priority sources. */
2311
2875
  static async getFirst(keys, parser) {
2312
2876
  const sources = [...this._SOURCES];
2877
+ let remaining = [...keys];
2313
2878
  for (const source of sources) {
2314
- for (const key of keys) {
2879
+ for (const key of remaining) {
2315
2880
  const result = await source.source.get(key, parser);
2316
2881
  if (result !== void 0) {
2317
2882
  return { key, asset: result };
2318
2883
  }
2319
2884
  }
2885
+ const blocked = await Promise.all(remaining.map((key) => source.source.blocks(key)));
2886
+ remaining = remaining.filter((_, index) => !blocked[index]);
2887
+ if (!remaining.length) break;
2320
2888
  }
2321
2889
  return void 0;
2322
2890
  }
2323
2891
  };
2892
+ /** Default asset base URL. Use {@link setVersion} to select a Minecraft version. */
2324
2893
  _AssetLoader.ROOT = DEFAULT_ROOT;
2325
2894
  _AssetLoader._SOURCES = [];
2326
2895
  /** Used when a registered source cannot identify its content, so nothing persists past this session. */
@@ -2345,14 +2914,15 @@ var init_ModelTextures = __esm({
2345
2914
  static get PERSISTENT_META_CACHE() {
2346
2915
  return this._persistentMetaCache ?? (this._persistentMetaCache = PersistentCache.open("minerender-texturemeta"));
2347
2916
  }
2917
+ /** Loads and decodes a texture into a readable canvas, or returns `undefined` when missing. */
2348
2918
  static async get(key) {
2349
2919
  const keyStr = key.serialize();
2350
2920
  const pending = this.preload(key);
2351
2921
  const cached2 = Caching.textureAssetCache.getIfPresent(keyStr);
2352
2922
  try {
2353
- const asset = await pending;
2354
- if (asset) {
2355
- return await ImageLoader.infoToCanvasData(asset);
2923
+ const asset2 = await pending;
2924
+ if (asset2) {
2925
+ return await ImageLoader.infoToCanvasData(asset2);
2356
2926
  }
2357
2927
  return void 0;
2358
2928
  } catch (err) {
@@ -2362,16 +2932,18 @@ var init_ModelTextures = __esm({
2362
2932
  throw err;
2363
2933
  }
2364
2934
  }
2935
+ /** Fetches and caches encoded texture bytes without decoding the pixels. */
2365
2936
  static async preload(key) {
2366
2937
  const keyStr = key.serialize();
2367
2938
  return Caching.textureAssetCache.get(keyStr, (k) => {
2368
- return AssetLoader.get(key, "image" /* IMAGE */).then((asset) => {
2369
- if (asset)
2370
- asset.key = key;
2371
- return asset;
2939
+ return AssetLoader.get(key, "image" /* IMAGE */).then((asset2) => {
2940
+ if (asset2)
2941
+ asset2.key = key;
2942
+ return asset2;
2372
2943
  });
2373
2944
  });
2374
2945
  }
2946
+ /** Loads a texture's `.mcmeta` file, or returns `undefined` when no metadata file exists. */
2375
2947
  static async getMeta(key) {
2376
2948
  if (!key.extension || !key.extension.endsWith(".mcmeta")) {
2377
2949
  key = Object.assign(new AssetKey("", ""), key);
@@ -2380,14 +2952,15 @@ var init_ModelTextures = __esm({
2380
2952
  const keyStr = key.serialize();
2381
2953
  return Caching.textureMetaCache.get(keyStr, (k) => {
2382
2954
  return this.PERSISTENT_META_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
2383
- return AssetLoader.get(key, "meta" /* META */).then((asset) => {
2384
- if (asset)
2385
- asset.key = key;
2386
- return asset;
2955
+ return AssetLoader.get(key, "meta" /* META */).then((asset2) => {
2956
+ if (asset2)
2957
+ asset2.key = key;
2958
+ return asset2;
2387
2959
  });
2388
2960
  });
2389
2961
  });
2390
2962
  }
2963
+ /** Clears persisted texture metadata. Use {@link Caching.clear} to discard in-memory textures and metadata. */
2391
2964
  static async clearCache() {
2392
2965
  await this.PERSISTENT_META_CACHE.clear();
2393
2966
  }
@@ -2454,6 +3027,7 @@ var init_Ticker = __esm({
2454
3027
  }
2455
3028
  return handle;
2456
3029
  }
3030
+ /** Starts or resumes the shared tick loop. Repeated calls have no effect. */
2457
3031
  static start() {
2458
3032
  if (this.interval) return;
2459
3033
  this.interval = this.schedule(() => {
@@ -2476,6 +3050,7 @@ var init_Ticker = __esm({
2476
3050
  _Ticker.fiveSecondTicks = 0;
2477
3051
  }, 1e3 * 5);
2478
3052
  }
3053
+ /** Pauses the loop while retaining callbacks for a later {@link start}. */
2479
3054
  static stop() {
2480
3055
  if (this.interval) clearInterval(this.interval);
2481
3056
  if (this.oneSecondTicksTracker) clearInterval(this.oneSecondTicksTracker);
@@ -2484,12 +3059,14 @@ var init_Ticker = __esm({
2484
3059
  this.oneSecondTicksTracker = void 0;
2485
3060
  this.fiveSecondTicksTracker = void 0;
2486
3061
  }
3062
+ /** Registers a callback, starts the loop if needed, and returns an ID for {@link remove}. */
2487
3063
  static add(tick) {
2488
3064
  this.start();
2489
3065
  const c = this.counter++;
2490
3066
  this._tickers.set(c, tick);
2491
3067
  return c;
2492
3068
  }
3069
+ /** Removes a callback by ID and stops the timers when no callbacks remain. */
2493
3070
  static remove(c) {
2494
3071
  if (typeof c === "number") this._tickers.delete(c);
2495
3072
  if (!this._tickers.size) this.stop();
@@ -2503,6 +3080,7 @@ var init_Ticker = __esm({
2503
3080
  static get tpsFiveSeconds() {
2504
3081
  return this.fiveSecondTps;
2505
3082
  }
3083
+ /** Stops the loop and removes all registered callbacks. */
2506
3084
  static dispose() {
2507
3085
  this.stop();
2508
3086
  this._tickers.clear();
@@ -2557,6 +3135,7 @@ var init_TextureAtlas = __esm({
2557
3135
  }
2558
3136
  };
2559
3137
  }
3138
+ /** Copies pixels for a texture variable from its current atlas rectangle. */
2560
3139
  getData(texture) {
2561
3140
  const pos = this.positions[texture];
2562
3141
  const size = this.sizes[texture];
@@ -2767,6 +3346,37 @@ var init_UVMapper = __esm({
2767
3346
  static facesToUv(faces, originalTextureSize, actualTextureSize) {
2768
3347
  return new Float32BufferAttribute2(this.facesToUvArray(faces, originalTextureSize, actualTextureSize), 2);
2769
3348
  }
3349
+ /** Bounds texture sampling to texel centres without changing cropped face UVs. */
3350
+ static setAtlasUvBounds(geometry, faces, atlas) {
3351
+ const uv = geometry.getAttribute("uv");
3352
+ const bounds = new Float32Array(uv.count * 4);
3353
+ for (const [faceIndex, faceName] of CUBE_FACES.entries()) {
3354
+ const texture = faces[faceName]?.texture?.substring(1);
3355
+ const position = texture && atlas.positions[texture];
3356
+ const size = texture && atlas.sizes[texture];
3357
+ let minU = Infinity, minV = Infinity, maxU = -Infinity, maxV = -Infinity;
3358
+ if (position && size) {
3359
+ minU = position[0] / atlas.image.width;
3360
+ maxU = (position[0] + size[0]) / atlas.image.width;
3361
+ minV = 1 - (position[1] + size[1]) / atlas.image.height;
3362
+ maxV = 1 - position[1] / atlas.image.height;
3363
+ } else {
3364
+ for (let corner = 0; corner < 4; corner++) {
3365
+ const index = faceIndex * 4 + corner;
3366
+ minU = Math.min(minU, uv.getX(index));
3367
+ minV = Math.min(minV, uv.getY(index));
3368
+ maxU = Math.max(maxU, uv.getX(index));
3369
+ maxV = Math.max(maxV, uv.getY(index));
3370
+ }
3371
+ }
3372
+ const insetU = Math.min(0.5 / atlas.image.width, (maxU - minU) / 2);
3373
+ const insetV = Math.min(0.5 / atlas.image.height, (maxV - minV) / 2);
3374
+ for (let corner = 0; corner < 4; corner++) {
3375
+ bounds.set([minU + insetU, minV + insetV, maxU - insetU, maxV - insetV], (faceIndex * 4 + corner) * 4);
3376
+ }
3377
+ }
3378
+ geometry.setAttribute("uvBounds", new Float32BufferAttribute2(bounds, 4));
3379
+ }
2770
3380
  static lockUvs(geometry, faces, atlas, rotation) {
2771
3381
  const uv = geometry.getAttribute("uv");
2772
3382
  for (let faceIndex = 0; faceIndex < CUBE_FACES.length; faceIndex++) {
@@ -2803,6 +3413,7 @@ var init_UVMapper = __esm({
2803
3413
  }
2804
3414
  uv.needsUpdate = true;
2805
3415
  }
3416
+ /** Returns the shared atlas for a keyed, merged model, creating it on first use. */
2806
3417
  static async getAtlas(model) {
2807
3418
  const keyStr = model.key.serialize();
2808
3419
  return Caching.modelTextureAtlasCache.get(keyStr, (k) => {
@@ -2873,6 +3484,11 @@ var init_UVMapper = __esm({
2873
3484
  }
2874
3485
  return [tl, tr, bl, br];
2875
3486
  }
3487
+ /**
3488
+ * Combines model textures and animation metadata into an atlas.
3489
+ * Mapped UVs are stored on copies of the model's elements, available through the returned atlas.
3490
+ * Returns `undefined` for models without a texture map.
3491
+ */
2876
3492
  static async createAtlas(originalModel) {
2877
3493
  const textureMap = {};
2878
3494
  const metaMap = {};
@@ -2893,12 +3509,12 @@ var init_UVMapper = __esm({
2893
3509
  } else {
2894
3510
  uniqueTextureNames.push(textureKey);
2895
3511
  const assetKey = AssetKey.parse("textures", textureValue, model.key);
2896
- promises.push(ModelTextures.get(assetKey).then((asset) => {
2897
- if (!asset) {
3512
+ promises.push(ModelTextures.get(assetKey).then((asset2) => {
3513
+ if (!asset2) {
2898
3514
  console.warn(p5, "Missing texture", assetKey);
2899
3515
  return;
2900
3516
  }
2901
- textureMap[textureKey] = new WrappedImage(asset);
3517
+ textureMap[textureKey] = new WrappedImage(asset2);
2902
3518
  }));
2903
3519
  promises.push(ModelTextures.getMeta(assetKey).then((meta) => {
2904
3520
  metaMap[textureKey] = meta;
@@ -3237,14 +3853,15 @@ var init_Entities = __esm({
3237
3853
  init_MineRenderError();
3238
3854
  init_ModelTextures();
3239
3855
  Entities = class {
3856
+ /** Lists model paths in the `minecraft` namespace, including subdirectories and excluding `.json`. */
3240
3857
  static async getEntityList() {
3241
3858
  const collect = async (path) => {
3242
3859
  const prefix2 = path ? `${path}/` : "";
3243
3860
  const key = new AssetKey(DEFAULT_NAMESPACE, `${prefix2}_list`, void 0, void 0, "entity-models", ".json");
3244
- const list = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get(key, "list" /* LIST */));
3245
- if (!list) return [];
3246
- const files = list.files.filter((file) => file.endsWith(".json") && file !== "_list.json").map((file) => `${prefix2}${file.slice(0, -5)}`);
3247
- const children = await Promise.all(list.directories.map((directory) => collect(`${prefix2}${directory}`)));
3861
+ const list2 = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get(key, "list" /* LIST */));
3862
+ if (!list2) return [];
3863
+ const files = list2.files.filter((file) => file.endsWith(".json") && file !== "_list.json").map((file) => `${prefix2}${file.slice(0, -5)}`);
3864
+ const children = await Promise.all(list2.directories.map((directory) => collect(`${prefix2}${directory}`)));
3248
3865
  return [...files, ...children.flat()];
3249
3866
  };
3250
3867
  return collect("");
@@ -3254,6 +3871,7 @@ var init_Entities = __esm({
3254
3871
  const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models", ".json", isAssetKey(modelKey) ? modelKey.root : void 0);
3255
3872
  return Caching.entityModelCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
3256
3873
  }
3874
+ /** Returns selectable geometry-layer names, or an empty list when the model is missing. */
3257
3875
  static async getLayerList(modelKey) {
3258
3876
  return Object.keys((await this.getModelFile(modelKey))?.layers ?? {});
3259
3877
  }
@@ -3261,6 +3879,15 @@ var init_Entities = __esm({
3261
3879
  static async getPassList(modelKey) {
3262
3880
  return (await this.getModelFile(modelKey))?.passes ?? [];
3263
3881
  }
3882
+ /**
3883
+ * Loads the layers and textures to pass to {@link MineRenderScene.addEntity}.
3884
+ * By default, selects `main`, unconditional passes, and passes enabled by `options.when`.
3885
+ *
3886
+ * @param modelKey - Dataset model, such as `new BasicAssetKey("minecraft", "zombie")`.
3887
+ * @param textureKey - Texture override for the first selected layer.
3888
+ * @param options - Layer selection, conditional passes, and per-layer texture overrides.
3889
+ * @returns The selected model, or `undefined` when its dataset file is missing.
3890
+ */
3264
3891
  static async getEntity(modelKey, textureKey, options) {
3265
3892
  const model = await this.getModelFile(modelKey);
3266
3893
  if (!model) return void 0;
@@ -3306,6 +3933,7 @@ var init_Entities = __esm({
3306
3933
  const file = await Caching.entityAnimationCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
3307
3934
  return file?.animations;
3308
3935
  }
3936
+ /** Finds a fallback texture using the model path, its directory, and vanilla variant data. */
3309
3937
  static async resolveTexture(modelKey) {
3310
3938
  const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
3311
3939
  const root = isAssetKey(modelKey) ? modelKey.root : void 0;
@@ -3317,8 +3945,8 @@ var init_Entities = __esm({
3317
3945
  if (await ModelTextures.get(texture2)) return texture2;
3318
3946
  }
3319
3947
  const listKey = new AssetKey(modelKey.namespace, "_list", `${name}_variant`, void 0, "data", ".json", root);
3320
- const list = await AssetLoader.get(listKey, "list" /* LIST */);
3321
- const files = list?.files.filter((file2) => file2.endsWith(".json") && file2 !== "_list.json");
3948
+ const list2 = await AssetLoader.get(listKey, "list" /* LIST */);
3949
+ const files = list2?.files.filter((file2) => file2.endsWith(".json") && file2 !== "_list.json");
3322
3950
  const file = files?.find((file2) => file2 === "temperate.json") ?? files?.[0];
3323
3951
  if (!file) return void 0;
3324
3952
  const variantKey = new AssetKey(modelKey.namespace, file.slice(0, -5), `${name}_variant`, void 0, "data", ".json", root);
@@ -3423,27 +4051,27 @@ var init_EntityAnimationPlayer = __esm({
3423
4051
  for (const layer of layers) {
3424
4052
  const objects = [];
3425
4053
  if (poses) {
3426
- layer.traverse((object) => {
3427
- if (poses.has(object)) objects.push(object);
4054
+ layer.traverse((object3) => {
4055
+ if (poses.has(object3)) objects.push(object3);
3428
4056
  });
3429
4057
  } else {
3430
4058
  for (const bone of Object.keys(animation.bones)) {
3431
- const object = layer.getObjectByName(`group:${bone}`);
3432
- if (object) objects.push(object);
4059
+ const object3 = layer.getObjectByName(`group:${bone}`);
4060
+ if (object3) objects.push(object3);
3433
4061
  }
3434
4062
  }
3435
- for (const object of objects) {
3436
- const bone = object.name.slice("group:".length);
3437
- const pose = poses?.get(object);
4063
+ for (const object3 of objects) {
4064
+ const bone = object3.name.slice("group:".length);
4065
+ const pose = poses?.get(object3);
3438
4066
  this.parts.push({
3439
4067
  bone,
3440
- object,
3441
- position: object.position.clone(),
3442
- rotation: object.rotation.clone(),
3443
- scale: object.scale.clone(),
3444
- basePosition: pose ? new Vector35(...pose.offset) : object.position.clone(),
3445
- baseRotation: pose ? new Euler3(...pose.rotation, "ZYX") : object.rotation.clone(),
3446
- baseScale: pose ? new Vector35(...pose.scale ?? [1, 1, 1]) : object.scale.clone()
4068
+ object: object3,
4069
+ position: object3.position.clone(),
4070
+ rotation: object3.rotation.clone(),
4071
+ scale: object3.scale.clone(),
4072
+ basePosition: pose ? new Vector35(...pose.offset) : object3.position.clone(),
4073
+ baseRotation: pose ? new Euler3(...pose.rotation, "ZYX") : object3.rotation.clone(),
4074
+ baseScale: pose ? new Vector35(...pose.scale ?? [1, 1, 1]) : object3.scale.clone()
3447
4075
  });
3448
4076
  }
3449
4077
  }
@@ -3466,12 +4094,14 @@ var init_EntityAnimationPlayer = __esm({
3466
4094
  this._animation = void 0;
3467
4095
  this._time = 0;
3468
4096
  }
4097
+ /** Applies the pose at an absolute playback time. Returns `false` when no clip is active. */
3469
4098
  setTime(seconds) {
3470
4099
  if (!this._animation) return false;
3471
4100
  this._time = seconds;
3472
4101
  this.apply();
3473
4102
  return true;
3474
4103
  }
4104
+ /** Advances playback by elapsed seconds multiplied by the speed passed to {@link play}. */
3475
4105
  advance(deltaSeconds) {
3476
4106
  return this.setTime(this._time + deltaSeconds * this.speed);
3477
4107
  }
@@ -3509,6 +4139,7 @@ var init_InstanceReference = __esm({
3509
4139
  if (!this.instanceable.isInstanceActive(this.index, this)) throw new MineRenderError("Instance has been removed");
3510
4140
  return this.instanceable;
3511
4141
  }
4142
+ /** Releases this placement's slot for reuse. Repeated calls have no effect. */
3512
4143
  removeFromScene() {
3513
4144
  if (this.instanceable.isInstanceActive(this.index, this)) this.instanceable.removeInstanceAt(this.index);
3514
4145
  }
@@ -3521,9 +4152,11 @@ var init_InstanceReference = __esm({
3521
4152
  setMatrix(matrix) {
3522
4153
  this.activeInstanceable.setMatrixAt(this.index, matrix);
3523
4154
  }
4155
+ /** Hides or restores this placement while preserving its transform and allocated slot. */
3524
4156
  setVisible(visible) {
3525
4157
  this.activeInstanceable.setInstanceVisibleAt(this.index, visible);
3526
4158
  }
4159
+ /** Updates the supplied transform components. Positions use scene units and rotations use radians. */
3527
4160
  setPositionRotationScale(position, rotation, scale) {
3528
4161
  this.activeInstanceable.setPositionRotationScaleAt(this.index, position, rotation, scale);
3529
4162
  }
@@ -3592,6 +4225,7 @@ var init_SceneObject = __esm({
3592
4225
  }
3593
4226
  async init() {
3594
4227
  }
4228
+ /** Marks ancestor scenes for redraw and emits a `change` event. */
3595
4229
  notifyDirty() {
3596
4230
  this.traverseAncestors((parent) => {
3597
4231
  const scene = parent;
@@ -3627,15 +4261,17 @@ var init_SceneObject = __esm({
3627
4261
  return obj;
3628
4262
  }
3629
4263
  /**
3630
- * Get a group by its name
4264
+ * Finds a named part, such as `head`, without the internal `group:` prefix.
4265
+ * Returns `undefined` when the group does not exist.
3631
4266
  */
3632
4267
  getGroupByName(name) {
3633
4268
  return this.getObjectByName(`group:${name}`);
3634
4269
  }
3635
4270
  /**
3636
- * Toggle visibility of a group
3637
- * @param name name of the group
3638
- * @param visible set the visibility directly, if not set toggles it
4271
+ * Changes a named group's visibility and requests a redraw.
4272
+ * @param name - Part name without the `group:` prefix.
4273
+ * @param visible - Visibility to assign. Omit it to toggle the current value.
4274
+ * @returns The resulting visibility, or `false` if the group does not exist.
3639
4275
  */
3640
4276
  toggleGroupVisibility(name, visible) {
3641
4277
  return this.toggleObjectVisibility(this.getGroupByName(name), visible);
@@ -3686,15 +4322,16 @@ var init_SceneObject = __esm({
3686
4322
  return mesh;
3687
4323
  }
3688
4324
  /**
3689
- * Get a mesh by its name
4325
+ * Finds a named mesh without the internal `mesh:` prefix, or returns `undefined`.
3690
4326
  */
3691
4327
  getMeshByName(name) {
3692
4328
  return this.getObjectByName(`mesh:${name}`);
3693
4329
  }
3694
4330
  /**
3695
- * Toggle visibility of a mesh
3696
- * @param name name of the mesh
3697
- * @param visible set the visibility directly, if not set toggles it
4331
+ * Changes a named mesh's visibility and requests a redraw.
4332
+ * @param name - Mesh name without the `mesh:` prefix.
4333
+ * @param visible - Visibility to assign. Omit it to toggle the current value.
4334
+ * @returns The resulting visibility, or `false` if the mesh does not exist.
3698
4335
  */
3699
4336
  toggleMeshVisibility(name, visible) {
3700
4337
  return this.toggleObjectVisibility(this.getMeshByName(name), visible);
@@ -3740,12 +4377,14 @@ var init_SceneObject = __esm({
3740
4377
  get isInstanced() {
3741
4378
  return this._isInstanced;
3742
4379
  }
4380
+ /** Number of live placements, excluding released instance slots. */
3743
4381
  get instanceCounter() {
3744
4382
  return this._instanceCounter;
3745
4383
  }
3746
4384
  constructInstanceReference(i) {
3747
4385
  return new InstanceReference(this, i);
3748
4386
  }
4387
+ /** Allocates a placement of this instanced object, reusing a released slot when available. */
3749
4388
  nextInstance() {
3750
4389
  const mesh = this.instanceMesh;
3751
4390
  if (!mesh) throw new MineRenderError("Object is not instanced");
@@ -3790,10 +4429,10 @@ var init_SceneObject = __esm({
3790
4429
  return new InstancedBufferAttribute(array, attribute.itemSize, attribute.normalized, attribute.meshPerAttribute).setUsage(attribute.usage);
3791
4430
  };
3792
4431
  const matrix = grow(mesh.instanceMatrix, 0);
3793
- const color = mesh.instanceColor ? grow(mesh.instanceColor, 1) : null;
4432
+ const color2 = mesh.instanceColor ? grow(mesh.instanceColor, 1) : null;
3794
4433
  mesh.dispose();
3795
4434
  mesh.instanceMatrix = matrix;
3796
- mesh.instanceColor = color;
4435
+ mesh.instanceColor = color2;
3797
4436
  }
3798
4437
  *activeInstanceIndices() {
3799
4438
  if (this.instanceMesh) {
@@ -3892,6 +4531,7 @@ var init_SceneObject = __esm({
3892
4531
  vector.setFromMatrixScale(matrix);
3893
4532
  return vector;
3894
4533
  }
4534
+ /** Updates supplied transform components. For a shared model, affects every live instance. */
3895
4535
  setPositionRotationScale(position, rotation, scale) {
3896
4536
  if (this.isInstanced) {
3897
4537
  for (const i of this.activeInstanceIndices()) {
@@ -3910,6 +4550,7 @@ var init_SceneObject = __esm({
3910
4550
  }
3911
4551
  this.notifyDirty();
3912
4552
  }
4553
+ /** Sets position in scene units. For a shared model, updates every live instance. */
3913
4554
  setPosition(position) {
3914
4555
  if (this.isInstanced) {
3915
4556
  for (const i of this.activeInstanceIndices()) {
@@ -3927,6 +4568,7 @@ var init_SceneObject = __esm({
3927
4568
  return this.position;
3928
4569
  }
3929
4570
  }
4571
+ /** Sets rotation in radians. For a shared model, updates every live instance. */
3930
4572
  setRotation(rotation) {
3931
4573
  if (this.isInstanced) {
3932
4574
  for (const i of this.activeInstanceIndices()) {
@@ -3944,6 +4586,7 @@ var init_SceneObject = __esm({
3944
4586
  return this.rotation;
3945
4587
  }
3946
4588
  }
4589
+ /** Sets scale factors. For a shared model, updates every live instance. */
3947
4590
  setScale(scale) {
3948
4591
  if (this.isInstanced) {
3949
4592
  for (const i of this.activeInstanceIndices()) {
@@ -3962,15 +4605,15 @@ var init_SceneObject = __esm({
3962
4605
  }
3963
4606
  }
3964
4607
  //</editor-fold>
3965
- toggleObjectVisibility(object, visible) {
3966
- if (object) {
4608
+ toggleObjectVisibility(object3, visible) {
4609
+ if (object3) {
3967
4610
  if (typeof visible !== "undefined") {
3968
- object.visible = visible;
4611
+ object3.visible = visible;
3969
4612
  } else {
3970
- object.visible = !object.visible;
4613
+ object3.visible = !object3.visible;
3971
4614
  }
3972
4615
  this.notifyDirty();
3973
- return object.visible;
4616
+ return object3.visible;
3974
4617
  }
3975
4618
  this.notifyDirty();
3976
4619
  return false;
@@ -3981,6 +4624,7 @@ var init_SceneObject = __esm({
3981
4624
  this.notifyDirty();
3982
4625
  super.dispose();
3983
4626
  }
4627
+ /** Releases instance slots and removes children, disposing children that implement Disposable. */
3984
4628
  disposeAndRemoveAllChildren() {
3985
4629
  if (this.instanceMesh) {
3986
4630
  if (this._scene) this._scene.stats.instanceCount -= this.instanceReferences.size;
@@ -4003,6 +4647,7 @@ var init_SceneObject = __esm({
4003
4647
  }
4004
4648
  this.notifyDirty();
4005
4649
  }
4650
+ /** Detaches the object from its parent and requests a redraw. */
4006
4651
  removeFromScene() {
4007
4652
  this.notifyDirty();
4008
4653
  this.removeFromParent();
@@ -4158,12 +4803,15 @@ var init_EntityObject = __esm({
4158
4803
  if (this.animationPlayers.length) this.notifyDirty();
4159
4804
  }
4160
4805
  //</editor-fold>
4806
+ /** Finds a selected draw by its layer key, such as `main` or `main#2`, or returns `undefined`. */
4161
4807
  getLayerGroup(name) {
4162
4808
  return super.getGroupByName(`layer:${name}`);
4163
4809
  }
4810
+ /** Finds a part name without `group:`. Supply `layerName` to restrict the search to one draw. */
4164
4811
  getGroupByName(name, layerName) {
4165
4812
  return layerName === void 0 ? super.getGroupByName(name) : this.getLayerGroup(layerName)?.getObjectByName(`group:${name}`);
4166
4813
  }
4814
+ /** Finds a mesh name without `mesh:`. Supply `layerName` to restrict the search to one draw. */
4167
4815
  getMeshByName(name, layerName) {
4168
4816
  return layerName === void 0 ? super.getMeshByName(name) : this.getLayerGroup(layerName)?.getObjectByName(`mesh:${name}`);
4169
4817
  }
@@ -4265,8 +4913,8 @@ var init_EntityObject = __esm({
4265
4913
  this.scrollMaterials.push({ material: mat, offset: mat.map.offset, speed: scroll });
4266
4914
  }
4267
4915
  }
4268
- this.getLayerGroup(name)?.traverse((object) => {
4269
- if (isMesh(object)) object.material = mat;
4916
+ this.getLayerGroup(name)?.traverse((object3) => {
4917
+ if (isMesh(object3)) object3.material = mat;
4270
4918
  });
4271
4919
  }));
4272
4920
  if (!this.children.length) {
@@ -4607,8 +5255,8 @@ function toTrianglesDrawMode(geometry, drawMode) {
4607
5255
  return geometry;
4608
5256
  }
4609
5257
  }
4610
- function computeMorphedAttributes(object) {
4611
- if (object.geometry.isBufferGeometry !== true) {
5258
+ function computeMorphedAttributes(object3) {
5259
+ if (object3.geometry.isBufferGeometry !== true) {
4612
5260
  console.error("THREE.BufferGeometryUtils: Geometry is not of type BufferGeometry.");
4613
5261
  return null;
4614
5262
  }
@@ -4621,11 +5269,11 @@ function computeMorphedAttributes(object) {
4621
5269
  const _morphA = new Vector310();
4622
5270
  const _morphB = new Vector310();
4623
5271
  const _morphC = new Vector310();
4624
- function _calculateMorphedAttributeData(object2, material2, attribute, morphAttribute, morphTargetsRelative2, a2, b2, c2, modifiedAttributeArray) {
5272
+ function _calculateMorphedAttributeData(object4, material2, attribute, morphAttribute, morphTargetsRelative2, a2, b2, c2, modifiedAttributeArray) {
4625
5273
  _vA.fromBufferAttribute(attribute, a2);
4626
5274
  _vB.fromBufferAttribute(attribute, b2);
4627
5275
  _vC.fromBufferAttribute(attribute, c2);
4628
- const morphInfluences = object2.morphTargetInfluences;
5276
+ const morphInfluences = object4.morphTargetInfluences;
4629
5277
  if (material2.morphTargets && morphAttribute && morphInfluences) {
4630
5278
  _morphA.set(0, 0, 0);
4631
5279
  _morphB.set(0, 0, 0);
@@ -4651,10 +5299,10 @@ function computeMorphedAttributes(object) {
4651
5299
  _vB.add(_morphB);
4652
5300
  _vC.add(_morphC);
4653
5301
  }
4654
- if (object2.isSkinnedMesh) {
4655
- object2.boneTransform(a2, _vA);
4656
- object2.boneTransform(b2, _vB);
4657
- object2.boneTransform(c2, _vC);
5302
+ if (object4.isSkinnedMesh) {
5303
+ object4.boneTransform(a2, _vA);
5304
+ object4.boneTransform(b2, _vB);
5305
+ object4.boneTransform(c2, _vC);
4658
5306
  }
4659
5307
  modifiedAttributeArray[a2 * 3 + 0] = _vA.x;
4660
5308
  modifiedAttributeArray[a2 * 3 + 1] = _vA.y;
@@ -4666,8 +5314,8 @@ function computeMorphedAttributes(object) {
4666
5314
  modifiedAttributeArray[c2 * 3 + 1] = _vC.y;
4667
5315
  modifiedAttributeArray[c2 * 3 + 2] = _vC.z;
4668
5316
  }
4669
- const geometry = object.geometry;
4670
- const material = object.material;
5317
+ const geometry = object3.geometry;
5318
+ const material = object3.material;
4671
5319
  let a, b, c;
4672
5320
  const index = geometry.index;
4673
5321
  const positionAttribute = geometry.attributes.position;
@@ -4694,7 +5342,7 @@ function computeMorphedAttributes(object) {
4694
5342
  b = index.getX(j + 1);
4695
5343
  c = index.getX(j + 2);
4696
5344
  _calculateMorphedAttributeData(
4697
- object,
5345
+ object3,
4698
5346
  groupMaterial,
4699
5347
  positionAttribute,
4700
5348
  morphPosition,
@@ -4705,7 +5353,7 @@ function computeMorphedAttributes(object) {
4705
5353
  modifiedPosition
4706
5354
  );
4707
5355
  _calculateMorphedAttributeData(
4708
- object,
5356
+ object3,
4709
5357
  groupMaterial,
4710
5358
  normalAttribute,
4711
5359
  morphNormal,
@@ -4725,7 +5373,7 @@ function computeMorphedAttributes(object) {
4725
5373
  b = index.getX(i + 1);
4726
5374
  c = index.getX(i + 2);
4727
5375
  _calculateMorphedAttributeData(
4728
- object,
5376
+ object3,
4729
5377
  material,
4730
5378
  positionAttribute,
4731
5379
  morphPosition,
@@ -4736,7 +5384,7 @@ function computeMorphedAttributes(object) {
4736
5384
  modifiedPosition
4737
5385
  );
4738
5386
  _calculateMorphedAttributeData(
4739
- object,
5387
+ object3,
4740
5388
  material,
4741
5389
  normalAttribute,
4742
5390
  morphNormal,
@@ -4760,7 +5408,7 @@ function computeMorphedAttributes(object) {
4760
5408
  b = j + 1;
4761
5409
  c = j + 2;
4762
5410
  _calculateMorphedAttributeData(
4763
- object,
5411
+ object3,
4764
5412
  groupMaterial,
4765
5413
  positionAttribute,
4766
5414
  morphPosition,
@@ -4771,7 +5419,7 @@ function computeMorphedAttributes(object) {
4771
5419
  modifiedPosition
4772
5420
  );
4773
5421
  _calculateMorphedAttributeData(
4774
- object,
5422
+ object3,
4775
5423
  groupMaterial,
4776
5424
  normalAttribute,
4777
5425
  morphNormal,
@@ -4791,7 +5439,7 @@ function computeMorphedAttributes(object) {
4791
5439
  b = i + 1;
4792
5440
  c = i + 2;
4793
5441
  _calculateMorphedAttributeData(
4794
- object,
5442
+ object3,
4795
5443
  material,
4796
5444
  positionAttribute,
4797
5445
  morphPosition,
@@ -4802,7 +5450,7 @@ function computeMorphedAttributes(object) {
4802
5450
  modifiedPosition
4803
5451
  );
4804
5452
  _calculateMorphedAttributeData(
4805
- object,
5453
+ object3,
4806
5454
  material,
4807
5455
  normalAttribute,
4808
5456
  morphNormal,
@@ -4836,6 +5484,7 @@ var init_ModelCulling = __esm({
4836
5484
  "src/model/ModelCulling.ts"() {
4837
5485
  init_CubeFace();
4838
5486
  ModelCulling = class {
5487
+ /** Rotates a world-direction bit mask into model space. Bits follow {@link CUBE_FACES}. */
4839
5488
  static toLocalMask(worldMask, rotation) {
4840
5489
  let localMask = 0;
4841
5490
  for (const [localFace, offset] of CUBE_FACE_OFFSETS.entries()) {
@@ -4846,6 +5495,7 @@ var init_ModelCulling = __esm({
4846
5495
  }
4847
5496
  return localMask;
4848
5497
  }
5498
+ /** Whether a model fills one block with opaque faces and can hide adjacent block faces. */
4849
5499
  static isOpaqueFullCube(atlas) {
4850
5500
  if (!atlas || atlas.hasTransparency !== false || atlas.model.elements?.length !== 1) return false;
4851
5501
  const element = atlas.model.elements[0];
@@ -4872,15 +5522,17 @@ var init_ModelCulling = __esm({
4872
5522
  });
4873
5523
 
4874
5524
  // src/model/SpecialItems.ts
4875
- import { Matrix4 as Matrix48 } from "three";
5525
+ import { Euler as Euler7, Matrix4 as Matrix48 } from "three";
4876
5526
  var SpecialItems;
4877
5527
  var init_SpecialItems = __esm({
4878
5528
  "src/model/SpecialItems.ts"() {
4879
5529
  init_AssetKey();
4880
5530
  init_Entities();
5531
+ init_BannerPatterns();
5532
+ init_ModelTextures();
4881
5533
  SpecialItems = class {
4882
5534
  /** Resolves the static entity parts drawn by a special item renderer, in model units. */
4883
- static async getParts(special, root) {
5535
+ static async getParts(special, root, components = {}) {
4884
5536
  const texture = (id, directory) => {
4885
5537
  const key = AssetKey.parse("textures", id);
4886
5538
  return new AssetKey(
@@ -4893,13 +5545,77 @@ var init_SpecialItems = __esm({
4893
5545
  root
4894
5546
  );
4895
5547
  };
4896
- const load = async (id, textureKey, transform, rotations = {}) => {
5548
+ const load = async (id, textureKey, transform, rotations = {}, layers = ["main"]) => {
4897
5549
  const key = new AssetKey("minecraft", id, void 0, void 0, "entity-models", ".json", root);
4898
- const model = await Entities.getEntity(key, textureKey, { layer: "main" });
5550
+ const model = await Entities.getEntity(key, textureKey, layers.length === 1 ? { layer: layers[0] } : {
5551
+ layers,
5552
+ textures: textureKey ? Object.fromEntries(layers.map((name) => [name, textureKey])) : void 0
5553
+ });
4899
5554
  if (!model) throw new Error(`Missing entity model minecraft:${id} for special item ${special.type}`);
4900
5555
  return { model, transform, rotations };
4901
5556
  };
4902
5557
  switch (special.type) {
5558
+ case "minecraft:trident":
5559
+ case "trident":
5560
+ case "minecraft:conduit":
5561
+ case "conduit": {
5562
+ const trident = special.type === "trident" || special.type === "minecraft:trident";
5563
+ const part = await load(
5564
+ trident ? "trident" : "conduit",
5565
+ texture(trident ? "trident" : "conduit/base", ""),
5566
+ trident ? new Matrix48().makeScale(1, -1, -1) : new Matrix48().makeTranslation(8, 8, 8),
5567
+ {},
5568
+ trident ? ["main"] : ["shell"]
5569
+ );
5570
+ part.model = { ...part.model, render: "solid", layers: part.model.layers && Object.fromEntries(
5571
+ Object.entries(part.model.layers).map(([name, layer]) => [name, { ...layer, render: "solid" }])
5572
+ ) };
5573
+ return [part];
5574
+ }
5575
+ case "minecraft:banner":
5576
+ case "banner":
5577
+ case "minecraft:shield":
5578
+ case "shield": {
5579
+ const banner = special.type === "banner" || special.type === "minecraft:banner";
5580
+ const color2 = banner ? BannerPatterns.getColor(special.color) : components["minecraft:base_color"] === void 0 ? void 0 : BannerPatterns.getColor(components["minecraft:base_color"]);
5581
+ const patterns = await BannerPatterns.getLayers(components["minecraft:banner_patterns"], banner ? "banner" : "shield", root);
5582
+ const patterned = banner || color2 !== void 0 || patterns.length > 0;
5583
+ const baseTexture = texture(banner ? "banner_base" : patterned ? "shield_base" : "shield_base_nopattern", "");
5584
+ const transform = banner ? new Matrix48().makeTranslation(8, 0, 8).multiply(new Matrix48().makeScale(2 / 3, -2 / 3, -2 / 3)) : new Matrix48().makeScale(1, -1, -1);
5585
+ const part = await load(banner ? "standing_banner" : "shield", baseTexture, transform, {}, banner ? ["main", "flag"] : ["main"]);
5586
+ const layers = Object.fromEntries(Object.entries(part.model.layers ?? { main: part.model }).map(([name, layer]) => [name, { ...layer, render: "solid" }]));
5587
+ let overlay2;
5588
+ if (banner) {
5589
+ const flagLayer = layers.flag;
5590
+ const flag = flagLayer?.layer.root.children.flag;
5591
+ if (!flag) throw new Error("Banner entity model is missing its flag layer");
5592
+ overlay2 = { ...flagLayer.layer, root: { ...flagLayer.layer.root, children: {
5593
+ ...flagLayer.layer.root.children,
5594
+ flag: { ...flag, pose: { ...flag.pose, rotation: [-25e-4 * Math.PI, flag.pose.rotation[1], flag.pose.rotation[2]] } }
5595
+ } } };
5596
+ layers.flag = { ...flagLayer, layer: overlay2 };
5597
+ } else {
5598
+ const main = layers.main.layer;
5599
+ const plate = main.root.children.plate;
5600
+ if (!plate) throw new Error("Shield entity model is missing its plate");
5601
+ overlay2 = { ...main, root: { ...main.root, cubes: [], children: { plate } } };
5602
+ }
5603
+ const tints = {};
5604
+ if (patterned) {
5605
+ const draws = [{ texture: texture(`${banner ? "banner" : "shield"}/base`, ""), color: color2 ?? DYE_COLORS.white }, ...patterns];
5606
+ draws.forEach((draw, index) => {
5607
+ const name = index === 0 ? "pattern_base" : `pattern_${index - 1}`;
5608
+ layers[name] = { key: draw.texture, texture: draw.texture, layer: overlay2, render: "no_outline", tint: name };
5609
+ tints[name] = draw.color;
5610
+ });
5611
+ }
5612
+ await Promise.all([...new Map(Object.values(layers).map((layer) => [layer.texture.serialize(), layer.texture])).values()].map(async (key) => {
5613
+ if (!await ModelTextures.preload(key)) throw new Error(`Missing special item texture ${key.toNamespacedString()}`);
5614
+ }));
5615
+ part.model = { ...part.model, ...layers.main, layers };
5616
+ part.tints = tints;
5617
+ return [part];
5618
+ }
4903
5619
  case "minecraft:chest":
4904
5620
  case "chest": {
4905
5621
  const angle = -(special.openness ?? 0) * Math.PI / 2;
@@ -4910,6 +5626,26 @@ var init_SpecialItems = __esm({
4910
5626
  chest.model = { ...chest.model, render: "solid", layers: void 0 };
4911
5627
  return [chest];
4912
5628
  }
5629
+ case "minecraft:shulker_box":
5630
+ case "shulker_box": {
5631
+ const openness = special.openness === void 0 ? 0 : special.openness;
5632
+ const orientation = special.orientation === void 0 ? "up" : special.orientation;
5633
+ const directions = {
5634
+ down: [Math.PI, 0, 0],
5635
+ up: [0, 0, 0],
5636
+ north: [Math.PI / 2, 0, Math.PI],
5637
+ south: [Math.PI / 2, 0, 0],
5638
+ west: [Math.PI / 2, 0, Math.PI / 2],
5639
+ east: [Math.PI / 2, 0, -Math.PI / 2]
5640
+ };
5641
+ if (!Object.prototype.hasOwnProperty.call(directions, orientation)) throw new Error(`Unsupported shulker-box orientation ${orientation}`);
5642
+ if (typeof openness !== "number" || !Number.isFinite(openness)) throw new Error("Shulker-box openness must be finite");
5643
+ if (typeof special.texture !== "string" || !special.texture) throw new Error("Shulker-box texture is required");
5644
+ const transform = new Matrix48().makeTranslation(8, 8, 8).multiply(new Matrix48().makeScale(0.9995, 0.9995, 0.9995)).multiply(new Matrix48().makeRotationFromEuler(new Euler7(...directions[orientation], "XYZ"))).multiply(new Matrix48().makeScale(1, -1, -1)).multiply(new Matrix48().makeTranslation(0, -16, 0));
5645
+ const box = await load("shulker_box", texture(special.texture, "shulker"), transform, { lid: [0, openness * Math.PI * 1.5, 0] });
5646
+ box.positions = { lid: [0, 24 - openness * 8, 0] };
5647
+ return [box];
5648
+ }
4913
5649
  case "minecraft:bed":
4914
5650
  case "bed": {
4915
5651
  const transform = new Matrix48().makeTranslation(0, 9, 0).multiply(new Matrix48().makeRotationX(Math.PI / 2)).multiply(new Matrix48().makeTranslation(8, 8, 8)).multiply(new Matrix48().makeRotationZ(Math.PI)).multiply(new Matrix48().makeTranslation(-8, -8, -8));
@@ -5024,6 +5760,7 @@ var init_ItemTints = __esm({
5024
5760
  static async get(model, tints) {
5025
5761
  const sources = model.tints;
5026
5762
  if (!sources?.length) return tints;
5763
+ const components = model.components ?? {};
5027
5764
  const colors = { ...tints };
5028
5765
  await Promise.all(sources.map(async (source, index) => {
5029
5766
  if (colors[index] !== void 0) return;
@@ -5038,27 +5775,71 @@ var init_ItemTints = __esm({
5038
5775
  break;
5039
5776
  case "dye":
5040
5777
  case "minecraft:dye":
5778
+ colors[index] = this.colorOrDefault(components["minecraft:dyed_color"], source.default, true);
5779
+ break;
5041
5780
  case "potion":
5042
- case "minecraft:potion":
5781
+ case "minecraft:potion": {
5782
+ const value = typeof components["minecraft:potion_contents"] === "string" ? void 0 : this.componentField(components, "potion_contents", "custom_color");
5783
+ colors[index] = this.colorOrDefault(value, source.default);
5784
+ break;
5785
+ }
5043
5786
  case "map_color":
5044
5787
  case "minecraft:map_color":
5788
+ colors[index] = this.colorOrDefault(components["minecraft:map_color"], source.default);
5789
+ break;
5045
5790
  case "firework":
5046
- case "minecraft:firework":
5791
+ case "minecraft:firework": {
5792
+ const values = this.componentField(components, "firework_explosion", "colors");
5793
+ if (values !== void 0 && !Array.isArray(values)) throw new MineRenderError("Item tint firework_explosion.colors must be an array");
5794
+ if (!values?.length) {
5795
+ colors[index] = this.rgb(source.default);
5796
+ break;
5797
+ }
5798
+ const rgb = values.map((value) => this.componentColor(value));
5799
+ const red = Math.floor(rgb.reduce((sum, color2) => sum + (color2 >> 16 & 255), 0) / rgb.length);
5800
+ const green = Math.floor(rgb.reduce((sum, color2) => sum + (color2 >> 8 & 255), 0) / rgb.length);
5801
+ const blue = Math.floor(rgb.reduce((sum, color2) => sum + (color2 & 255), 0) / rgb.length);
5802
+ colors[index] = red << 16 | green << 8 | blue;
5803
+ break;
5804
+ }
5047
5805
  case "team":
5048
5806
  case "minecraft:team":
5049
- case "custom_model_data":
5050
- case "minecraft:custom_model_data":
5051
5807
  colors[index] = this.rgb(source.default);
5052
5808
  break;
5809
+ case "custom_model_data":
5810
+ case "minecraft:custom_model_data": {
5811
+ const colorIndex = source.index ?? 0;
5812
+ if (!Number.isSafeInteger(colorIndex) || colorIndex < 0) throw new MineRenderError("Item tint custom_model_data index must be a nonnegative integer");
5813
+ const values = this.componentField(components, "custom_model_data", "colors");
5814
+ if (values !== void 0 && !Array.isArray(values)) throw new MineRenderError("Item tint custom_model_data.colors must be an array");
5815
+ colors[index] = this.colorOrDefault(values?.[colorIndex], source.default, true);
5816
+ break;
5817
+ }
5053
5818
  default:
5054
5819
  throw new MineRenderError(`Unsupported item tint source ${source.type}`);
5055
5820
  }
5056
5821
  }));
5057
5822
  return colors;
5058
5823
  }
5059
- static rgb(color) {
5060
- if (!Array.isArray(color)) return color & 16777215;
5061
- const [red, green, blue] = color.map((channel) => Math.floor(Math.fround(Math.fround(channel) * 255)));
5824
+ static componentField(components, id, field) {
5825
+ const value = components[`minecraft:${id}`];
5826
+ if (value === void 0) return void 0;
5827
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new MineRenderError(`Item tint ${id} must be an object`);
5828
+ return value[field];
5829
+ }
5830
+ static colorOrDefault(value, fallback, allowTriple = false) {
5831
+ return value === void 0 ? this.rgb(fallback) : this.componentColor(value, allowTriple);
5832
+ }
5833
+ static componentColor(value, allowTriple = false) {
5834
+ if (typeof value === "number" && Number.isInteger(value) && value >= -2147483648 && value <= 2147483647) return this.rgb(value);
5835
+ if (allowTriple && Array.isArray(value) && value.length === 3 && value.every((channel) => typeof channel === "number" && Number.isFinite(channel))) {
5836
+ return this.rgb(value) & 16777215;
5837
+ }
5838
+ throw new MineRenderError(`Item tint component color must be a packed integer${allowTriple ? " or RGB triple" : ""}`);
5839
+ }
5840
+ static rgb(color2) {
5841
+ if (!Array.isArray(color2)) return color2 & 16777215;
5842
+ const [red, green, blue] = color2.map((channel) => Math.floor(Math.fround(Math.fround(channel) * 255)) & 255);
5062
5843
  return red << 16 | green << 8 | blue;
5063
5844
  }
5064
5845
  };
@@ -5066,7 +5847,7 @@ var init_ItemTints = __esm({
5066
5847
  });
5067
5848
 
5068
5849
  // src/model/scene/ModelObject.ts
5069
- import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute5, Color as Color9, Euler as Euler7, Matrix4 as Matrix49 } from "three";
5850
+ import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute5, Color as Color9, Euler as Euler8, Matrix4 as Matrix49 } from "three";
5070
5851
  function isModelObject(obj) {
5071
5852
  return obj.isModelObject;
5072
5853
  }
@@ -5095,29 +5876,52 @@ var init_ModelObject = __esm({
5095
5876
  this.originalModel = originalModel;
5096
5877
  this.isModelObject = true;
5097
5878
  this.specialMaterials = /* @__PURE__ */ new Map();
5879
+ this.geometries = /* @__PURE__ */ new Set();
5098
5880
  this.meshesCreated = false;
5099
5881
  this.options = (0, import_ts_deepmerge5.default)({}, _ModelObject.DEFAULT_OPTIONS, options ?? {});
5100
- if (originalModel.special) this.options.instanceMeshes = false;
5882
+ if (originalModel.special || originalModel.parts) this.options.instanceMeshes = false;
5101
5883
  if (this.options.tints) this.options.tints = { ...this.options.tints };
5102
5884
  this.addEventListener("added", () => this.updateAnimationSubscription());
5103
5885
  this.addEventListener("removed", () => this.updateAnimationSubscription());
5104
5886
  }
5105
5887
  async init() {
5888
+ const parts = this.originalModel.parts;
5889
+ if (parts) {
5890
+ try {
5891
+ for (const part of parts) {
5892
+ const object3 = new _ModelObject(part, { ...this.options, instanceMeshes: false });
5893
+ this.add(object3);
5894
+ await object3.init();
5895
+ }
5896
+ let order = 0;
5897
+ this.iterateAllMeshes((mesh) => {
5898
+ mesh.renderOrder = order++;
5899
+ for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
5900
+ material.transparent = true;
5901
+ }
5902
+ });
5903
+ } catch (error) {
5904
+ this.disposeAndRemoveAllChildren();
5905
+ throw error;
5906
+ }
5907
+ this.notifyDirty();
5908
+ return;
5909
+ }
5106
5910
  this.options.tints = await ItemTints.get(this.originalModel, this.options.tints);
5107
5911
  const special = this.originalModel.special;
5108
5912
  if (special) {
5109
- const parts = await SpecialItems.getParts(special, this.originalModel.key?.root);
5913
+ const parts2 = await SpecialItems.getParts(special, this.originalModel.key?.root, this.originalModel.components);
5110
5914
  const transform = this.options.displayPosition ? DisplayTransforms.getMatrix(this.originalModel.display, this.options.displayPosition) : new Matrix49();
5111
5915
  transform.multiply(new Matrix49().makeTranslation(-8, -8, -8));
5112
5916
  try {
5113
- for (const part of parts) {
5114
- const object = new EntityObject(part.model, { flip: false, wireframe: this.options.wireframe });
5115
- object.matrix.copy(transform).multiply(part.transform);
5116
- object.matrixWorldNeedsUpdate = true;
5117
- object.matrixAutoUpdate = false;
5118
- this.add(object);
5119
- await object.init();
5120
- object.iterateAllMeshes((mesh) => {
5917
+ for (const part of parts2) {
5918
+ const object3 = new EntityObject(part.model, { flip: false, wireframe: this.options.wireframe, tints: part.tints });
5919
+ object3.matrix.copy(transform).multiply(part.transform);
5920
+ object3.matrixWorldNeedsUpdate = true;
5921
+ object3.matrixAutoUpdate = false;
5922
+ this.add(object3);
5923
+ await object3.init();
5924
+ object3.iterateAllMeshes((mesh) => {
5121
5925
  const source = mesh.material;
5122
5926
  let material = this.specialMaterials.get(source);
5123
5927
  if (!material) {
@@ -5128,7 +5932,10 @@ var init_ModelObject = __esm({
5128
5932
  mesh.material = material;
5129
5933
  });
5130
5934
  for (const [name, rotation] of Object.entries(part.rotations)) {
5131
- object.getGroupByName(name)?.rotation.set(...rotation, "ZYX");
5935
+ object3.getGroupByName(name)?.rotation.set(...rotation, "ZYX");
5936
+ }
5937
+ for (const [name, position] of Object.entries(part.positions ?? {})) {
5938
+ object3.getGroupByName(name)?.position.set(...position);
5132
5939
  }
5133
5940
  }
5134
5941
  } catch (error) {
@@ -5161,6 +5968,7 @@ var init_ModelObject = __esm({
5161
5968
  if (this.atlas.model.elements) {
5162
5969
  this.atlas.model.elements?.forEach((el) => {
5163
5970
  const elGeo = this._getBoxGeometryFromElement(el).clone();
5971
+ this.geometries.add(elGeo);
5164
5972
  if (this.options.cullMask) {
5165
5973
  const indices = Array.from(elGeo.getIndex().array);
5166
5974
  elGeo.setIndex(indices.filter((_, index) => {
@@ -5171,8 +5979,9 @@ var init_ModelObject = __esm({
5171
5979
  elGeo.clearGroups();
5172
5980
  }
5173
5981
  if (this.options.uvLockRotation) {
5174
- UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler7(...this.options.uvLockRotation));
5982
+ UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler8(...this.options.uvLockRotation));
5175
5983
  }
5984
+ UVMapper.setAtlasUvBounds(elGeo, el.faces, this.atlas);
5176
5985
  if (this.options.tints) {
5177
5986
  const colors = new Float32Array(elGeo.getAttribute("position").count * 3).fill(1);
5178
5987
  for (const [faceIndex, faceName] of CUBE_FACES.entries()) {
@@ -5180,9 +5989,9 @@ var init_ModelObject = __esm({
5180
5989
  if (tintIndex === void 0 || tintIndex < 0) continue;
5181
5990
  const tint = this.options.tints[tintIndex];
5182
5991
  if (tint === void 0) continue;
5183
- const color = new Color9(tint);
5992
+ const color2 = new Color9(tint);
5184
5993
  for (let vertex = 0; vertex < 4; vertex++) {
5185
- color.toArray(colors, (faceIndex * 4 + vertex) * 3);
5994
+ color2.toArray(colors, (faceIndex * 4 + vertex) * 3);
5186
5995
  }
5187
5996
  }
5188
5997
  elGeo.setAttribute("color", new BufferAttribute5(colors, 3));
@@ -5213,10 +6022,14 @@ var init_ModelObject = __esm({
5213
6022
  let combinedGeo;
5214
6023
  if (allGeos.length > 0) {
5215
6024
  combinedGeo = mergeBufferGeometries(allGeos);
6025
+ for (const geometry of allGeos) {
6026
+ this.geometries.delete(geometry);
6027
+ }
5216
6028
  } else {
5217
6029
  combinedGeo = new BoxGeometry5(16, 16, 16);
5218
6030
  if (displayTransform) DisplayTransforms.apply(combinedGeo, displayTransform);
5219
6031
  }
6032
+ this.geometries.add(combinedGeo);
5220
6033
  combinedGeo.computeBoundingBox();
5221
6034
  let mesh;
5222
6035
  if (this.options.instanceMeshes) {
@@ -5238,9 +6051,12 @@ var init_ModelObject = __esm({
5238
6051
  }
5239
6052
  applyTextures() {
5240
6053
  if (this.atlas) {
5241
- const mat = Materials.createShadedCanvasMaterial(this.atlas.image.canvas, this.atlas.hasTransparency, false);
6054
+ const mat = Materials.createShadedCanvasMaterial(this.atlas.image.canvas, this.atlas.hasTransparency, false, true);
5242
6055
  this.atlasMaterial = mat;
5243
6056
  this.atlasTexture = mat.uniforms?.map?.value ?? mat.map;
6057
+ if (this.options.displayPosition === "gui" /* GUI */ && this.originalModel.gui_light === "front" /* FRONT */ && mat.uniforms?.SHADE) {
6058
+ mat.uniforms.SHADE.value = false;
6059
+ }
5244
6060
  this.iterateAllMeshes((mesh) => {
5245
6061
  if (mesh.geometry.hasAttribute("color")) mat.vertexColors = true;
5246
6062
  mesh.material = mat;
@@ -5249,7 +6065,9 @@ var init_ModelObject = __esm({
5249
6065
  }
5250
6066
  }
5251
6067
  updateAnimationSubscription() {
5252
- const active = !!this.parent && (!this.isInstanced || this.instanceCounter > 0);
6068
+ let root = this;
6069
+ while (root.parent && isModelObject(root.parent)) root = root.parent;
6070
+ const active = !!root.parent && (!this.isInstanced || this.instanceCounter > 0);
5253
6071
  if (active && this.atlas?.hasAnimation && this.atlasTexture) {
5254
6072
  if (!this.unsubscribeAtlas) {
5255
6073
  this.atlasTexture.needsUpdate = true;
@@ -5265,6 +6083,9 @@ var init_ModelObject = __esm({
5265
6083
  this.unsubscribeAtlas?.();
5266
6084
  this.unsubscribeAtlas = void 0;
5267
6085
  }
6086
+ for (const child of this.children) {
6087
+ if (isModelObject(child)) child.updateAnimationSubscription();
6088
+ }
5268
6089
  }
5269
6090
  nextInstance() {
5270
6091
  const reference = super.nextInstance();
@@ -5284,6 +6105,8 @@ var init_ModelObject = __esm({
5284
6105
  this.atlasMaterial = void 0;
5285
6106
  for (const material of this.specialMaterials.values()) material.dispose();
5286
6107
  this.specialMaterials.clear();
6108
+ for (const geometry of this.geometries) geometry.dispose();
6109
+ this.geometries.clear();
5287
6110
  super.disposeAndRemoveAllChildren();
5288
6111
  }
5289
6112
  };
@@ -5499,9 +6322,9 @@ var init_FluidModelObject = __esm({
5499
6322
  }
5500
6323
  }
5501
6324
  geometry.clearGroups();
5502
- const color = new Color10(this.options.tints?.[0] ?? (this.kind === "water" ? 4159204 : 16777215));
6325
+ const color2 = new Color10(this.options.tints?.[0] ?? (this.kind === "water" ? 4159204 : 16777215));
5503
6326
  const colors = new Float32Array(geometry.getAttribute("position").count * 3);
5504
- for (let i = 0; i < colors.length; i += 3) color.toArray(colors, i);
6327
+ for (let i = 0; i < colors.length; i += 3) color2.toArray(colors, i);
5505
6328
  geometry.setAttribute("color", new BufferAttribute6(colors, 3));
5506
6329
  geometry.computeBoundingBox();
5507
6330
  geometry.computeBoundingSphere();
@@ -5631,11 +6454,11 @@ var BrowserEnv = class {
5631
6454
  imageSize(data) {
5632
6455
  return probeImageSize(data);
5633
6456
  }
5634
- openCache(name, version) {
6457
+ openCache(name, version2) {
5635
6458
  return new BrowserCache(localforage.createInstance({
5636
6459
  // IndexedDB schema upgrades retain records; each cache generation needs its own database.
5637
- name: name + version,
5638
- version
6460
+ name: name + version2,
6461
+ version: version2
5639
6462
  }));
5640
6463
  }
5641
6464
  };
@@ -5666,6 +6489,7 @@ var Meshes = class _Meshes {
5666
6489
  static createBox(key) {
5667
6490
  return new Mesh(Geometries.getBox(key.geometry), Materials.getImage(key.material));
5668
6491
  }
6492
+ /** Returns a shared Mesh object. Use {@link createBox} for a mesh with its own transform and parent. */
5669
6493
  static getBox(key) {
5670
6494
  const keyStr = serializeMeshKey(key);
5671
6495
  return Caching.boxMeshCache.get(keyStr, (k) => {
@@ -5715,6 +6539,7 @@ function shutdown() {
5715
6539
  init_AssetKey();
5716
6540
  init_AssetLoader();
5717
6541
  init_Assets();
6542
+ init_BannerPatterns();
5718
6543
 
5719
6544
  // src/assets/BlockEntities.ts
5720
6545
  init_AssetKey();
@@ -5778,6 +6603,7 @@ init_AssetParsers();
5778
6603
  init_ModelTextures();
5779
6604
  init_Caching();
5780
6605
  var Fonts = class {
6606
+ /** Combines font definitions in source-priority order. Unsupported Unihex and TrueType providers are skipped. */
5781
6607
  static async get(key = "minecraft:default") {
5782
6608
  const fontKey = typeof key === "string" ? AssetKey.parse("font", key) : key;
5783
6609
  return await Caching.fontCache.get(fontKey.serialize(), () => this.load(fontKey, []));
@@ -5793,8 +6619,8 @@ var Fonts = class {
5793
6619
  const stack = await AssetLoader.getAll(key, "json" /* JSON */);
5794
6620
  if (stack.length === 0) throw new Error(`Could not load font ${key.toNamespacedString()}`);
5795
6621
  const glyphs = /* @__PURE__ */ new Map();
5796
- for (const asset of stack) {
5797
- for (const provider of asset.providers) {
6622
+ for (const asset2 of stack) {
6623
+ for (const provider of asset2.providers) {
5798
6624
  if (provider.filter?.uniform || provider.filter?.jp) continue;
5799
6625
  let provided;
5800
6626
  switch (provider.type) {
@@ -5940,13 +6766,13 @@ var OBJExporter = class {
5940
6766
  * @param {Object3D} object - The 3D object to export.
5941
6767
  * @return {string} The exported OBJ.
5942
6768
  */
5943
- parse(object) {
6769
+ parse(object3) {
5944
6770
  let output = "";
5945
6771
  let indexVertex = 0;
5946
6772
  let indexVertexUvs = 0;
5947
6773
  let indexNormals = 0;
5948
6774
  const vertex = new Vector37();
5949
- const color = new Color4();
6775
+ const color2 = new Color4();
5950
6776
  const normal = new Vector37();
5951
6777
  const uv = new Vector22();
5952
6778
  const face = [];
@@ -6045,9 +6871,9 @@ var OBJExporter = class {
6045
6871
  vertex.applyMatrix4(points.matrixWorld);
6046
6872
  output += "v " + vertex.x + " " + vertex.y + " " + vertex.z;
6047
6873
  if (colors !== void 0) {
6048
- color.fromBufferAttribute(colors, i);
6049
- ColorManagement.workingToColorSpace(color, SRGBColorSpace3);
6050
- output += " " + color.r + " " + color.g + " " + color.b;
6874
+ color2.fromBufferAttribute(colors, i);
6875
+ ColorManagement.workingToColorSpace(color2, SRGBColorSpace3);
6876
+ output += " " + color2.r + " " + color2.g + " " + color2.b;
6051
6877
  }
6052
6878
  output += "\n";
6053
6879
  }
@@ -6059,7 +6885,7 @@ var OBJExporter = class {
6059
6885
  }
6060
6886
  indexVertex += nbVertex;
6061
6887
  }
6062
- object.traverse(function(child) {
6888
+ object3.traverse(function(child) {
6063
6889
  if (child.isMesh === true) {
6064
6890
  parseMesh(child);
6065
6891
  }
@@ -6093,9 +6919,9 @@ var PLYExporter = class {
6093
6919
  * @param {PLYExporter~Options} options - The export options.
6094
6920
  * @return {?(string|ArrayBuffer)} The exported PLY.
6095
6921
  */
6096
- parse(object, onDone, options = {}) {
6922
+ parse(object3, onDone, options = {}) {
6097
6923
  function traverseMeshes(cb) {
6098
- object.traverse(function(child) {
6924
+ object3.traverse(function(child) {
6099
6925
  if (child.isMesh === true || child.isPoints) {
6100
6926
  const mesh = child;
6101
6927
  const geometry = mesh.geometry;
@@ -6128,7 +6954,7 @@ var PLYExporter = class {
6128
6954
  for (const name of customAttributeNames) customTypes[name] = "float";
6129
6955
  let vertexCount = 0;
6130
6956
  let faceCount = 0;
6131
- object.traverse(function(child) {
6957
+ object3.traverse(function(child) {
6132
6958
  if (child.isMesh === true) {
6133
6959
  const mesh = child;
6134
6960
  const geometry = mesh.geometry;
@@ -6750,8 +7576,8 @@ function equalArray(array1, array2) {
6750
7576
  return element === array2[index];
6751
7577
  });
6752
7578
  }
6753
- function stringToArrayBuffer(text) {
6754
- return new TextEncoder().encode(text).buffer;
7579
+ function stringToArrayBuffer(text2) {
7580
+ return new TextEncoder().encode(text2).buffer;
6755
7581
  }
6756
7582
  function isIdentityMatrix(matrix) {
6757
7583
  return equalArray(matrix.elements, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
@@ -6944,12 +7770,12 @@ var GLTFWriter = class {
6944
7770
  * @param {THREE.Object3D|THREE.Material|THREE.BufferGeometry|THREE.AnimationClip} object
6945
7771
  * @param {Object} objectDef
6946
7772
  */
6947
- serializeUserData(object, objectDef) {
6948
- if (Object.keys(object.userData).length === 0) return;
7773
+ serializeUserData(object3, objectDef) {
7774
+ if (Object.keys(object3.userData).length === 0) return;
6949
7775
  const options = this.options;
6950
7776
  const extensionsUsed = this.extensionsUsed;
6951
7777
  try {
6952
- const json = JSON.parse(JSON.stringify(object.userData));
7778
+ const json = JSON.parse(JSON.stringify(object3.userData));
6953
7779
  if (options.includeCustomExtensions && json.gltfExtensions) {
6954
7780
  if (objectDef.extensions === void 0) objectDef.extensions = {};
6955
7781
  for (const extensionName in json.gltfExtensions) {
@@ -6960,7 +7786,7 @@ var GLTFWriter = class {
6960
7786
  }
6961
7787
  if (Object.keys(json).length > 0) objectDef.extras = json;
6962
7788
  } catch (error) {
6963
- console.warn("THREE.GLTFExporter: userData of '" + object.name + "' won't be serialized because of JSON.stringify error - " + error.message);
7789
+ console.warn("THREE.GLTFExporter: userData of '" + object3.name + "' won't be serialized because of JSON.stringify error - " + error.message);
6964
7790
  }
6965
7791
  }
6966
7792
  /**
@@ -7333,7 +8159,7 @@ var GLTFWriter = class {
7333
8159
  * @param {string} mimeType export format
7334
8160
  * @return {number} Index of the processed texture in the "images" array
7335
8161
  */
7336
- processImage(image, format, flipY, mimeType = "image/png") {
8162
+ processImage(image, format2, flipY, mimeType = "image/png") {
7337
8163
  if (image !== null) {
7338
8164
  const writer = this;
7339
8165
  const cache = writer.cache;
@@ -7357,8 +8183,8 @@ var GLTFWriter = class {
7357
8183
  ctx.scale(1, -1);
7358
8184
  }
7359
8185
  if (image.data !== void 0) {
7360
- if (format !== RGBAFormat2) {
7361
- console.error("GLTFExporter: Only RGBAFormat is supported.", format);
8186
+ if (format2 !== RGBAFormat2) {
8187
+ console.error("GLTFExporter: Only RGBAFormat is supported.", format2);
7362
8188
  }
7363
8189
  if (image.width > options.maxTextureSize || image.height > options.maxTextureSize) {
7364
8190
  console.warn("GLTFExporter: Image size is bigger than maxTextureSize", image);
@@ -7469,9 +8295,9 @@ var GLTFWriter = class {
7469
8295
  if (material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true) {
7470
8296
  console.warn("GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.");
7471
8297
  }
7472
- const color = material.color.toArray().concat([material.opacity]);
7473
- if (!equalArray(color, [1, 1, 1, 1])) {
7474
- materialDef.pbrMetallicRoughness.baseColorFactor = color;
8298
+ const color2 = material.color.toArray().concat([material.opacity]);
8299
+ if (!equalArray(color2, [1, 1, 1, 1])) {
8300
+ materialDef.pbrMetallicRoughness.baseColorFactor = color2;
7475
8301
  }
7476
8302
  if (material.isMeshStandardMaterial) {
7477
8303
  materialDef.pbrMetallicRoughness.metallicFactor = material.metalness;
@@ -7886,13 +8712,13 @@ var GLTFWriter = class {
7886
8712
  * @param {THREE.Object3D} object
7887
8713
  * @return {?number}
7888
8714
  */
7889
- processSkin(object) {
8715
+ processSkin(object3) {
7890
8716
  const json = this.json;
7891
8717
  const nodeMap = this.nodeMap;
7892
- const node = json.nodes[nodeMap.get(object)];
7893
- const skeleton = object.skeleton;
8718
+ const node = json.nodes[nodeMap.get(object3)];
8719
+ const skeleton = object3.skeleton;
7894
8720
  if (skeleton === void 0) return null;
7895
- const rootJoint = object.skeleton.bones[0];
8721
+ const rootJoint = object3.skeleton.bones[0];
7896
8722
  if (rootJoint === void 0) return null;
7897
8723
  const joints = [];
7898
8724
  const inverseBindMatrices = new Float32Array(skeleton.bones.length * 16);
@@ -7900,7 +8726,7 @@ var GLTFWriter = class {
7900
8726
  for (let i = 0; i < skeleton.bones.length; ++i) {
7901
8727
  joints.push(nodeMap.get(skeleton.bones[i]));
7902
8728
  temporaryBoneInverse.copy(skeleton.boneInverses[i]);
7903
- temporaryBoneInverse.multiply(object.bindMatrix).toArray(inverseBindMatrices, i * 16);
8729
+ temporaryBoneInverse.multiply(object3.bindMatrix).toArray(inverseBindMatrices, i * 16);
7904
8730
  }
7905
8731
  if (json.skins === void 0) json.skins = [];
7906
8732
  json.skins.push({
@@ -7916,19 +8742,19 @@ var GLTFWriter = class {
7916
8742
  * @param {THREE.Object3D} object Object3D to processNodeAsync
7917
8743
  * @return {Promise<number>} Index of the node in the nodes list
7918
8744
  */
7919
- async processNodeAsync(object) {
8745
+ async processNodeAsync(object3) {
7920
8746
  const json = this.json;
7921
8747
  const options = this.options;
7922
8748
  const nodeMap = this.nodeMap;
7923
8749
  if (!json.nodes) json.nodes = [];
7924
- if (object.pivot !== null) {
7925
- return await this._processNodeWithPivotAsync(object);
8750
+ if (object3.pivot !== null) {
8751
+ return await this._processNodeWithPivotAsync(object3);
7926
8752
  }
7927
8753
  const nodeDef = {};
7928
8754
  if (options.trs) {
7929
- const rotation = object.quaternion.toArray();
7930
- const position = object.position.toArray();
7931
- const scale = object.scale.toArray();
8755
+ const rotation = object3.quaternion.toArray();
8756
+ const position = object3.position.toArray();
8757
+ const scale = object3.scale.toArray();
7932
8758
  if (!equalArray(rotation, [0, 0, 0, 1])) {
7933
8759
  nodeDef.rotation = rotation;
7934
8760
  }
@@ -7939,28 +8765,28 @@ var GLTFWriter = class {
7939
8765
  nodeDef.scale = scale;
7940
8766
  }
7941
8767
  } else {
7942
- if (object.matrixAutoUpdate) {
7943
- object.updateMatrix();
8768
+ if (object3.matrixAutoUpdate) {
8769
+ object3.updateMatrix();
7944
8770
  }
7945
- if (isIdentityMatrix(object.matrix) === false) {
7946
- nodeDef.matrix = object.matrix.elements;
8771
+ if (isIdentityMatrix(object3.matrix) === false) {
8772
+ nodeDef.matrix = object3.matrix.elements;
7947
8773
  }
7948
8774
  }
7949
- if (object.name !== "") nodeDef.name = String(object.name);
7950
- this.serializeUserData(object, nodeDef);
7951
- if (object.isMesh || object.isLine || object.isPoints) {
7952
- const meshIndex = await this.processMeshAsync(object);
8775
+ if (object3.name !== "") nodeDef.name = String(object3.name);
8776
+ this.serializeUserData(object3, nodeDef);
8777
+ if (object3.isMesh || object3.isLine || object3.isPoints) {
8778
+ const meshIndex = await this.processMeshAsync(object3);
7953
8779
  if (meshIndex !== null) nodeDef.mesh = meshIndex;
7954
- } else if (object.isCamera) {
7955
- nodeDef.camera = this.processCamera(object);
8780
+ } else if (object3.isCamera) {
8781
+ nodeDef.camera = this.processCamera(object3);
7956
8782
  }
7957
- if (object.isSkinnedMesh) this.skins.push(object);
8783
+ if (object3.isSkinnedMesh) this.skins.push(object3);
7958
8784
  const nodeIndex = json.nodes.push(nodeDef) - 1;
7959
- nodeMap.set(object, nodeIndex);
7960
- if (object.children.length > 0) {
8785
+ nodeMap.set(object3, nodeIndex);
8786
+ if (object3.children.length > 0) {
7961
8787
  const children = [];
7962
- for (let i = 0, l = object.children.length; i < l; i++) {
7963
- const child = object.children[i];
8788
+ for (let i = 0, l = object3.children.length; i < l; i++) {
8789
+ const child = object3.children[i];
7964
8790
  if (child.visible || options.onlyVisible === false) {
7965
8791
  const childNodeIndex = await this.processNodeAsync(child);
7966
8792
  if (childNodeIndex !== null) children.push(childNodeIndex);
@@ -7969,7 +8795,7 @@ var GLTFWriter = class {
7969
8795
  if (children.length > 0) nodeDef.children = children;
7970
8796
  }
7971
8797
  await this._invokeAllAsync(function(ext) {
7972
- ext.writeNode && ext.writeNode(object, nodeDef);
8798
+ ext.writeNode && ext.writeNode(object3, nodeDef);
7973
8799
  });
7974
8800
  return nodeIndex;
7975
8801
  }
@@ -7978,19 +8804,19 @@ var GLTFWriter = class {
7978
8804
  * @param {THREE.Object3D} object Object3D with pivot
7979
8805
  * @return {Promise<number>} Index of the container node
7980
8806
  */
7981
- async _processNodeWithPivotAsync(object) {
8807
+ async _processNodeWithPivotAsync(object3) {
7982
8808
  const json = this.json;
7983
8809
  const options = this.options;
7984
8810
  const nodeMap = this.nodeMap;
7985
- const pivot = object.pivot;
8811
+ const pivot = object3.pivot;
7986
8812
  const containerDef = {};
7987
- const rotation = object.quaternion.toArray();
8813
+ const rotation = object3.quaternion.toArray();
7988
8814
  const position = [
7989
- object.position.x + pivot.x,
7990
- object.position.y + pivot.y,
7991
- object.position.z + pivot.z
8815
+ object3.position.x + pivot.x,
8816
+ object3.position.y + pivot.y,
8817
+ object3.position.z + pivot.z
7992
8818
  ];
7993
- const scale = object.scale.toArray();
8819
+ const scale = object3.scale.toArray();
7994
8820
  if (!equalArray(rotation, [0, 0, 0, 1])) {
7995
8821
  containerDef.rotation = rotation;
7996
8822
  }
@@ -8001,28 +8827,28 @@ var GLTFWriter = class {
8001
8827
  containerDef.scale = scale;
8002
8828
  }
8003
8829
  containerDef.extras = { pivot: pivot.toArray() };
8004
- if (object.name !== "") containerDef.name = String(object.name);
8005
- this.serializeUserData(object, containerDef);
8830
+ if (object3.name !== "") containerDef.name = String(object3.name);
8831
+ this.serializeUserData(object3, containerDef);
8006
8832
  const containerIndex = json.nodes.push(containerDef) - 1;
8007
- nodeMap.set(object, containerIndex);
8833
+ nodeMap.set(object3, containerIndex);
8008
8834
  const childDef = {};
8009
8835
  const childPosition = [-pivot.x, -pivot.y, -pivot.z];
8010
8836
  if (!equalArray(childPosition, [0, 0, 0])) {
8011
8837
  childDef.translation = childPosition;
8012
8838
  }
8013
- if (object.isMesh || object.isLine || object.isPoints) {
8014
- const meshIndex = await this.processMeshAsync(object);
8839
+ if (object3.isMesh || object3.isLine || object3.isPoints) {
8840
+ const meshIndex = await this.processMeshAsync(object3);
8015
8841
  if (meshIndex !== null) childDef.mesh = meshIndex;
8016
- } else if (object.isCamera) {
8017
- childDef.camera = this.processCamera(object);
8842
+ } else if (object3.isCamera) {
8843
+ childDef.camera = this.processCamera(object3);
8018
8844
  }
8019
- if (object.isSkinnedMesh) this.skins.push(object);
8845
+ if (object3.isSkinnedMesh) this.skins.push(object3);
8020
8846
  const childIndex = json.nodes.push(childDef) - 1;
8021
8847
  const containerChildren = [childIndex];
8022
- if (object.children.length > 0) {
8848
+ if (object3.children.length > 0) {
8023
8849
  const grandchildren = [];
8024
- for (let i = 0, l = object.children.length; i < l; i++) {
8025
- const child = object.children[i];
8850
+ for (let i = 0, l = object3.children.length; i < l; i++) {
8851
+ const child = object3.children[i];
8026
8852
  if (child.visible || options.onlyVisible === false) {
8027
8853
  const childNodeIndex = await this.processNodeAsync(child);
8028
8854
  if (childNodeIndex !== null) grandchildren.push(childNodeIndex);
@@ -8032,7 +8858,7 @@ var GLTFWriter = class {
8032
8858
  }
8033
8859
  containerDef.children = containerChildren;
8034
8860
  await this._invokeAllAsync(function(ext) {
8035
- ext.writeNode && ext.writeNode(object, containerDef);
8861
+ ext.writeNode && ext.writeNode(object3, containerDef);
8036
8862
  });
8037
8863
  return containerIndex;
8038
8864
  }
@@ -8475,10 +9301,10 @@ var GLTFMeshGpuInstancing = class {
8475
9301
  this.writer = writer;
8476
9302
  this.name = "EXT_mesh_gpu_instancing";
8477
9303
  }
8478
- writeNode(object, nodeDef) {
8479
- if (!object.isInstancedMesh) return;
9304
+ writeNode(object3, nodeDef) {
9305
+ if (!object3.isInstancedMesh) return;
8480
9306
  const writer = this.writer;
8481
- const mesh = object;
9307
+ const mesh = object3;
8482
9308
  const translationAttr = new Float32Array(mesh.count * 3);
8483
9309
  const rotationAttr = new Float32Array(mesh.count * 4);
8484
9310
  const scaleAttr = new Float32Array(mesh.count * 3);
@@ -8668,8 +9494,8 @@ var SceneExporter = class {
8668
9494
  }
8669
9495
  }
8670
9496
  };
8671
- function localMatrix(object) {
8672
- return object.matrixAutoUpdate ? new Matrix47().compose(object.position, object.quaternion, object.scale) : object.matrix.clone();
9497
+ function localMatrix(object3) {
9498
+ return object3.matrixAutoUpdate ? new Matrix47().compose(object3.position, object3.quaternion, object3.scale) : object3.matrix.clone();
8673
9499
  }
8674
9500
  function createSnapshot(root, gltf, bakeColors = false) {
8675
9501
  const scene = new Scene3();
@@ -8773,24 +9599,24 @@ function createSnapshot(root, gltf, bakeColors = false) {
8773
9599
  scene.add(copy);
8774
9600
  }
8775
9601
  };
8776
- const visit = (object, parentWorld) => {
8777
- if (!object.visible) return;
8778
- const world = parentWorld.clone().multiply(localMatrix(object));
8779
- const mesh = object;
9602
+ const visit = (object3, parentWorld) => {
9603
+ if (!object3.visible) return;
9604
+ const world = parentWorld.clone().multiply(localMatrix(object3));
9605
+ const mesh = object3;
8780
9606
  if (mesh.isMesh) {
8781
9607
  const instanced = mesh;
8782
9608
  if (instanced.isInstancedMesh) {
8783
9609
  for (let i = 0; i < instanced.count; i++) {
8784
9610
  const matrix = new Matrix47();
8785
9611
  instanced.getMatrixAt(i, matrix);
8786
- const color = instanced.instanceColor ? new Color7().fromBufferAttribute(instanced.instanceColor, i) : void 0;
8787
- addMesh(mesh, world.clone().multiply(matrix), color);
9612
+ const color2 = instanced.instanceColor ? new Color7().fromBufferAttribute(instanced.instanceColor, i) : void 0;
9613
+ addMesh(mesh, world.clone().multiply(matrix), color2);
8788
9614
  }
8789
9615
  } else {
8790
9616
  addMesh(mesh, world);
8791
9617
  }
8792
9618
  }
8793
- for (const child of object.children) visit(child, world);
9619
+ for (const child of object3.children) visit(child, world);
8794
9620
  };
8795
9621
  try {
8796
9622
  const ancestors = [];
@@ -8863,16 +9689,17 @@ function copyDataTexture(source) {
8863
9689
 
8864
9690
  // src/gui/GuiHelper.ts
8865
9691
  init_AssetKey();
9692
+ init_ModelTextures();
8866
9693
 
8867
9694
  // src/gui/GuiText.ts
8868
9695
  import { BufferGeometry as BufferGeometry2, Color as Color8, Float32BufferAttribute as Float32BufferAttribute4 } from "three";
8869
- async function layoutGuiText(text, options = {}) {
9696
+ async function layoutGuiText(text2, options = {}) {
8870
9697
  const maxWidth = options.maxWidth ?? Infinity, lineHeight = options.lineHeight ?? 9;
8871
9698
  if (!(maxWidth > 0) || !Number.isFinite(lineHeight) || lineHeight <= 0) {
8872
9699
  throw new Error("Text wrap width and line height must be positive");
8873
9700
  }
8874
9701
  const font = await Fonts.get(options.font);
8875
- const runs = typeof text === "string" ? [{ text }] : text;
9702
+ const runs = typeof text2 === "string" ? [{ text: text2 }] : text2;
8876
9703
  const glyphs = [], lineWidths = [];
8877
9704
  let line = [], width = 0;
8878
9705
  let trailingNewline = false;
@@ -8926,7 +9753,7 @@ function createGuiTextGeometry(layout, shadow = true) {
8926
9753
  batch = { image: glyph?.image, positions: [], uvs: [], colors: [], indices: [] };
8927
9754
  batches.push(batch);
8928
9755
  }
8929
- const color = new Color8(isShadow ? (style.color & 16579836) >> 2 : style.color);
9756
+ const color2 = new Color8(isShadow ? (style.color & 16579836) >> 2 : style.color);
8930
9757
  const offset = isShadow ? 1 : 0;
8931
9758
  const top = glyph ? 7 - glyph.ascent : 0;
8932
9759
  const height = glyph ? glyph.height * glyph.scale : 8;
@@ -8954,7 +9781,7 @@ function createGuiTextGeometry(layout, shadow = true) {
8954
9781
  const v0 = glyph ? 1 - glyph.y / glyph.image.height : 1;
8955
9782
  const v1 = glyph ? 1 - (glyph.y + glyph.height) / glyph.image.height : 0;
8956
9783
  batch.uvs.push(u0, v0, u1, v0, u0, v1, u1, v1);
8957
- for (let i = 0; i < 4; i++) batch.colors.push(color.r, color.g, color.b);
9784
+ for (let i = 0; i < 4; i++) batch.colors.push(color2.r, color2.g, color2.b);
8958
9785
  batch.indices.push(base, base + 2, base + 1, base + 2, base + 3, base + 1);
8959
9786
  };
8960
9787
  for (const boldOffset of style.bold ? [0, 1] : [0]) {
@@ -8972,7 +9799,128 @@ function createGuiTextGeometry(layout, shadow = true) {
8972
9799
  }
8973
9800
 
8974
9801
  // src/gui/GuiHelper.ts
9802
+ var GUI_CONTAINER_LAYOUTS = {
9803
+ generic_54: {
9804
+ texture: "minecraft:gui/container/generic_54",
9805
+ crop: [0, 0, 176, 222],
9806
+ slotOrigin: [8, 18],
9807
+ slotOffset: [18, 18],
9808
+ rowSize: 9,
9809
+ slotCount: 54
9810
+ },
9811
+ crafting_table: {
9812
+ texture: "minecraft:gui/container/crafting_table",
9813
+ crop: [0, 0, 176, 166],
9814
+ slotOrigin: [30, 17],
9815
+ slotOffset: [18, 18],
9816
+ rowSize: 3,
9817
+ slotCount: 9,
9818
+ resultPosition: [124, 35]
9819
+ }
9820
+ };
8975
9821
  var GuiHelper = class {
9822
+ /**
9823
+ * Creates a 182×5 boss bar using the active resource pack. Finite progress is clamped to 0–1.
9824
+ * The fill crops the source image; explicit crops do not apply `.mcmeta` sprite scaling.
9825
+ */
9826
+ static async bossBar(options = {}) {
9827
+ const { color: color2 = "pink", progress = 1, position = [0, 0] } = options;
9828
+ this.validatePosition(position);
9829
+ if (!["pink", "blue", "red", "green", "yellow", "purple", "white"].includes(color2)) {
9830
+ throw new Error(`Unsupported boss bar color: ${color2}`);
9831
+ }
9832
+ if (!Number.isFinite(progress)) throw new Error("Boss bar progress must be finite");
9833
+ const value = Math.max(0, Math.min(1, progress));
9834
+ const width = value > 0 ? Math.floor(value * 181) + 1 : 0;
9835
+ const texture = `minecraft:gui/sprites/boss_bar/${color2}`;
9836
+ const layers = [{
9837
+ name: "boss-bar-background",
9838
+ texture: `${texture}_background`,
9839
+ position: [...position],
9840
+ size: [182, 5]
9841
+ }];
9842
+ if (width > 0) {
9843
+ layers.push({
9844
+ ...await this.cropTexture(`${texture}_progress`, [0, 0, width, 5], [182, 5], position),
9845
+ name: "boss-bar-progress"
9846
+ });
9847
+ }
9848
+ return layers;
9849
+ }
9850
+ /**
9851
+ * Creates a 192×192 book page with optional arrows and supplied text. Buttons are hidden by default.
9852
+ * Callers choose the page content and button states; this helper does not paginate or handle clicks.
9853
+ */
9854
+ static async book(options = {}) {
9855
+ const {
9856
+ position = [0, 0],
9857
+ previous = "hidden",
9858
+ next = "hidden",
9859
+ text: text2,
9860
+ color: color2 = 0,
9861
+ shadow = false,
9862
+ maxWidth = 114,
9863
+ lineHeight = 9,
9864
+ ...style
9865
+ } = options;
9866
+ this.validatePosition(position);
9867
+ for (const state of [previous, next]) {
9868
+ if (!["hidden", "normal", "hover"].includes(state)) throw new Error(`Unsupported book button state: ${state}`);
9869
+ }
9870
+ const layers = [{
9871
+ ...await this.cropTexture("minecraft:gui/book", [0, 0, 192, 192], [256, 256], position),
9872
+ name: "book-background"
9873
+ }];
9874
+ for (const [name, direction, state, x] of [
9875
+ ["previous", "backward", previous, 43],
9876
+ ["next", "forward", next, 116]
9877
+ ]) {
9878
+ if (state === "hidden") continue;
9879
+ layers.push({
9880
+ name: `book-${name}`,
9881
+ texture: `minecraft:gui/sprites/widget/page_${direction}${state === "hover" ? "_highlighted" : ""}`,
9882
+ position: [position[0] + x, position[1] + 157],
9883
+ size: [23, 13]
9884
+ });
9885
+ }
9886
+ if (text2 !== void 0) {
9887
+ layers.push({
9888
+ name: "book-text",
9889
+ text: text2,
9890
+ position: [position[0] + 36, position[1] + 30],
9891
+ color: color2,
9892
+ shadow,
9893
+ maxWidth,
9894
+ lineHeight,
9895
+ ...style
9896
+ });
9897
+ }
9898
+ return layers;
9899
+ }
9900
+ /** Creates a container background. Use {@link GUI_CONTAINER_LAYOUTS} with {@link inventorySlot} to place its items. */
9901
+ static async container(type, position = [0, 0]) {
9902
+ this.validatePosition(position);
9903
+ if (type !== "generic_54" && type !== "crafting_table") throw new Error(`Unsupported GUI container: ${type}`);
9904
+ const layout = GUI_CONTAINER_LAYOUTS[type];
9905
+ return [{ ...await this.cropTexture(layout.texture, layout.crop, [256, 256], position), name: "background" }];
9906
+ }
9907
+ static validatePosition(position) {
9908
+ if (!Array.isArray(position) || position.length !== 2 || !position.every(Number.isFinite)) {
9909
+ throw new Error("GUI position must contain two finite coordinates");
9910
+ }
9911
+ }
9912
+ static async cropTexture(texture, crop, referenceSize, position) {
9913
+ const image = await ModelTextures.preload(AssetKey.parse("textures", texture));
9914
+ if (!image) throw new Error(`Could not load GUI texture ${texture}`);
9915
+ const [x, y, width, height] = crop;
9916
+ const scaleX = image.width / referenceSize[0], scaleY = image.height / referenceSize[1];
9917
+ return {
9918
+ texture,
9919
+ crop: [x * scaleX, y * scaleY, width * scaleX, height * scaleY],
9920
+ position: [...position],
9921
+ size: [width, height]
9922
+ };
9923
+ }
8976
9924
  /** Sizes a tooltip around supplied text; the first entry is its title. */
8977
9925
  static async tooltip(lines, options = {}) {
8978
9926
  if (!lines.length) return [];
@@ -8988,20 +9936,28 @@ var GuiHelper = class {
8988
9936
  size: [width + 24, height + 24]
8989
9937
  }));
8990
9938
  let y = position[1];
8991
- lines.forEach((text, index) => {
8992
- layers.push({ ...textOptions, name: `tooltip-line-${index}`, text, position: [position[0], y] });
9939
+ lines.forEach((text2, index) => {
9940
+ layers.push({ ...textOptions, name: `tooltip-line-${index}`, text: text2, position: [position[0], y] });
8993
9941
  y += layouts[index].height + (index === 0 ? titleGap : 0);
8994
9942
  });
8995
9943
  return layers;
8996
9944
  }
9945
+ /**
9946
+ * Calculates a slot's top-left position in GUI pixels.
9947
+ * @param slot - Zero-based slot index, or `[column, row]`.
9948
+ * @param origin - Position of the first slot.
9949
+ * @param offset - Horizontal and vertical spacing between slot origins, in pixels.
9950
+ * @param rowSize - Slots per row when `slot` is an index.
9951
+ */
8997
9952
  static inventorySlot(slot, origin = [0, 0], offset = [18, 18], rowSize = 9) {
8998
9953
  const [column, row] = typeof slot === "number" ? [slot % rowSize, Math.floor(slot / rowSize)] : slot;
8999
9954
  return [origin[0] + column * offset[0], origin[1] + row * offset[1]];
9000
9955
  }
9001
9956
  /** Creates crafting-table layers for ingredients and the result; stack counts are not drawn. */
9002
9957
  static recipe(recipe, options = {}) {
9958
+ const layout = GUI_CONTAINER_LAYOUTS.crafting_table;
9003
9959
  const layers = [
9004
- { name: "background", texture: "minecraft:gui/container/crafting_table", crop: [0, 0, 176, 166] }
9960
+ { name: "background", texture: layout.texture, crop: [...layout.crop] }
9005
9961
  ];
9006
9962
  const itemKey = (ingredient) => {
9007
9963
  const item = typeof ingredient === "string" && !ingredient.startsWith("#") ? ingredient : typeof ingredient === "object" && "item" in ingredient ? ingredient.item : options.resolveIngredient?.(ingredient);
@@ -9016,17 +9972,17 @@ var GuiHelper = class {
9016
9972
  layers.push({
9017
9973
  name: `ingredient-${slot}`,
9018
9974
  item: itemKey(ingredient),
9019
- position: this.inventorySlot(slot, [30, 17], [18, 18], 3)
9975
+ position: this.inventorySlot(slot, layout.slotOrigin, layout.slotOffset, layout.rowSize)
9020
9976
  });
9021
9977
  };
9022
9978
  switch (recipe.type) {
9023
9979
  case "minecraft:crafting_shaped":
9024
9980
  case "crafting_shaped": {
9025
- const pattern = recipe.pattern;
9026
- if (!pattern.length || pattern.length > 3 || !pattern[0].length || pattern[0].length > 3 || pattern.some((row) => row.length !== pattern[0].length)) {
9981
+ const pattern2 = recipe.pattern;
9982
+ if (!pattern2.length || pattern2.length > 3 || !pattern2[0].length || pattern2[0].length > 3 || pattern2.some((row) => row.length !== pattern2[0].length)) {
9027
9983
  throw new Error("Crafting patterns must be rectangular, with 1\u20133 rows and columns");
9028
9984
  }
9029
- const cells = pattern.flatMap((row, y) => row.split("").map((symbol, x) => ({ symbol, x, y }))).filter((cell) => cell.symbol !== " ");
9985
+ const cells = pattern2.flatMap((row, y) => row.split("").map((symbol, x) => ({ symbol, x, y }))).filter((cell) => cell.symbol !== " ");
9030
9986
  if (!cells.length) throw new Error("Crafting patterns must contain an ingredient");
9031
9987
  const minX = Math.min(...cells.map((cell) => cell.x)), maxX = Math.max(...cells.map((cell) => cell.x));
9032
9988
  const minY = Math.min(...cells.map((cell) => cell.y)), maxY = Math.max(...cells.map((cell) => cell.y));
@@ -9051,7 +10007,7 @@ var GuiHelper = class {
9051
10007
  layers.push({
9052
10008
  name: "result",
9053
10009
  item: itemKey("id" in recipe.result ? recipe.result.id : recipe.result.item),
9054
- position: [124, 35]
10010
+ position: [...layout.resultPosition]
9055
10011
  });
9056
10012
  return layers;
9057
10013
  }
@@ -9145,7 +10101,6 @@ init_SceneObject();
9145
10101
  init_Materials();
9146
10102
  init_ModelObject();
9147
10103
  init_DisplayPosition();
9148
- init_GuiLight();
9149
10104
  import { Box2, Box3 as Box32, DoubleSide as DoubleSide3, Group, Matrix4 as Matrix410, Mesh as Mesh6, MeshBasicMaterial as MeshBasicMaterial5, Vector2 as Vector23 } from "three";
9150
10105
  var GuiObject = class extends SceneObject {
9151
10106
  constructor(textureLayers, options) {
@@ -9214,12 +10169,16 @@ var GuiObject = class extends SceneObject {
9214
10169
  mesh2.geometry.computeBoundingBox();
9215
10170
  bounds.union(mesh2.geometry.boundingBox.clone().applyMatrix4(new Matrix410().multiplyMatrices(inverse, mesh2.matrixWorld)));
9216
10171
  });
9217
- item.position.z = depth - bounds.min.z;
9218
- depth += bounds.max.z - bounds.min.z + 0.01;
10172
+ item.position.z = depth;
10173
+ if (!bounds.isEmpty()) {
10174
+ item.position.z -= bounds.min.z;
10175
+ depth += bounds.max.z - bounds.min.z;
10176
+ this.bounds.expandByPoint(new Vector23(bounds.min.x, -bounds.max.y));
10177
+ this.bounds.expandByPoint(new Vector23(bounds.max.x, -bounds.min.y));
10178
+ }
10179
+ depth += 0.01;
9219
10180
  this.bounds.expandByPoint(new Vector23(x, y));
9220
10181
  this.bounds.expandByPoint(new Vector23(x + width2, y + height2));
9221
- this.bounds.expandByPoint(new Vector23(bounds.min.x, -bounds.max.y));
9222
- this.bounds.expandByPoint(new Vector23(bounds.max.x, -bounds.min.y));
9223
10182
  continue;
9224
10183
  }
9225
10184
  const key = typeof layer.texture === "string" ? AssetKey.parse("textures", layer.texture) : layer.texture;
@@ -9248,7 +10207,7 @@ var GuiObject = class extends SceneObject {
9248
10207
  }
9249
10208
  async createItem(layer, index) {
9250
10209
  const key = typeof layer.item === "string" ? AssetKey.parse("models", layer.item) : layer.item;
9251
- const model = await Models.getMerged(key);
10210
+ const model = await Models.getMerged(key, { ...layer.context, displayContext: "gui" /* GUI */ });
9252
10211
  if (!model) throw new Error(`Could not load GUI item ${key.toNamespacedString()}`);
9253
10212
  const item = new ModelObject(model, {
9254
10213
  displayPosition: "gui" /* GUI */,
@@ -9259,15 +10218,13 @@ var GuiObject = class extends SceneObject {
9259
10218
  item.name = `group:${layer.name ?? index}`;
9260
10219
  this.add(item);
9261
10220
  await item.init();
9262
- item.iterateAllMeshes((mesh) => {
9263
- if (!model.special) this.geometries.add(mesh.geometry);
10221
+ const meshes = [];
10222
+ item.iterateAllMeshes((mesh) => meshes.push(mesh));
10223
+ meshes.forEach((mesh, partIndex) => {
9264
10224
  mesh.name = `mesh:${layer.name ?? index}`;
9265
- mesh.renderOrder = index;
10225
+ mesh.renderOrder = index + partIndex / (meshes.length + 1) * 0.5;
9266
10226
  for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
9267
10227
  material.transparent = true;
9268
- if (model.gui_light === "front" /* FRONT */ && material.uniforms?.SHADE) {
9269
- material.uniforms.SHADE.value = false;
9270
- }
9271
10228
  }
9272
10229
  });
9273
10230
  return item;
@@ -9438,29 +10395,29 @@ var SceneInspector = class {
9438
10395
  this.objectControlsContainer.innerHTML = "";
9439
10396
  this.addControls(targetObject, targetIntersection, instance);
9440
10397
  }
9441
- addControls(object, intersection, instance) {
10398
+ addControls(object3, intersection, instance) {
9442
10399
  const container = document.createElement("div");
9443
10400
  container.append(this.separator("Select Parent/Child"));
9444
- if (object.parent) {
9445
- container.append(this.buttonControl("Select Parent " + object.parent.constructor.name + " " + object.parent.name, "P", () => {
9446
- this.selectObject(object.parent, intersection, instance);
10401
+ if (object3.parent) {
10402
+ container.append(this.buttonControl("Select Parent " + object3.parent.constructor.name + " " + object3.parent.name, "P", () => {
10403
+ this.selectObject(object3.parent, intersection, instance);
9447
10404
  }));
9448
10405
  }
9449
- if (object.children.length > 0) {
10406
+ if (object3.children.length > 0) {
9450
10407
  let i = 1;
9451
- for (let child of object.children) {
10408
+ for (let child of object3.children) {
9452
10409
  container.append(this.buttonControl("Select Child " + child.constructor.name + " " + child.name, "C" + i++, () => {
9453
10410
  this.selectObject(child, intersection, instance);
9454
10411
  }));
9455
10412
  }
9456
10413
  }
9457
10414
  container.append(this.separator());
9458
- const selectedInstance = instance && (object === instance.instanceable || object === intersection?.object) ? instance : void 0;
10415
+ const selectedInstance = instance && (object3 === instance.instanceable || object3 === intersection?.object) ? instance : void 0;
9459
10416
  if (!selectedInstance) {
9460
- container.append(this.toggleControl("Visibility", "V", object.visible, (v) => object.visible = v));
10417
+ container.append(this.toggleControl("Visibility", "V", object3.visible, (v) => object3.visible = v));
9461
10418
  }
9462
10419
  container.append(this.separator("Mesh"));
9463
- this.addObjectControls(object, intersection, container, selectedInstance);
10420
+ this.addObjectControls(object3, intersection, container, selectedInstance);
9464
10421
  container.append(this.separator());
9465
10422
  this.objectControlsContainer.append(container);
9466
10423
  }
@@ -9651,6 +10608,7 @@ var SceneStatsDisplay = class {
9651
10608
  appendTo(el) {
9652
10609
  el.append(this.statsContainer);
9653
10610
  }
10611
+ /** Stops the refresh timer and removes the panel from the page. */
9654
10612
  dispose() {
9655
10613
  clearInterval(this.interval);
9656
10614
  this.interval = void 0;
@@ -9675,6 +10633,7 @@ var InstanceManager = class {
9675
10633
  this.isInstanceManager = true;
9676
10634
  this.instanceCache = {};
9677
10635
  }
10636
+ /** Allocates a new placement of a cached model, or returns `undefined` if the key is unknown. */
9678
10637
  async get(key) {
9679
10638
  if (key in this.instanceCache) {
9680
10639
  console.debug(p9, "key in cache", key);
@@ -9682,6 +10641,7 @@ var InstanceManager = class {
9682
10641
  }
9683
10642
  return void 0;
9684
10643
  }
10644
+ /** Allocates a placement, calling the supplier once when the shared model is first needed. */
9685
10645
  async getOrCreate(key, supplier) {
9686
10646
  if (key in this.instanceCache) {
9687
10647
  return await this.get(key);
@@ -9698,6 +10658,7 @@ var InstanceManager = class {
9698
10658
  throw error;
9699
10659
  }
9700
10660
  }
10661
+ /** Forgets cached models without disposing them or releasing their existing placements. */
9701
10662
  reset() {
9702
10663
  for (let instanceCacheKey in this.instanceCache) {
9703
10664
  delete this.instanceCache[instanceCacheKey];
@@ -9731,7 +10692,7 @@ init_SpecialItems();
9731
10692
  init_BlockStates();
9732
10693
  init_MineRenderError();
9733
10694
  init_util();
9734
- import { Euler as Euler8 } from "three";
10695
+ import { Euler as Euler9 } from "three";
9735
10696
  function matchesCondition(condition, state) {
9736
10697
  return Object.entries(condition).every(([key, value]) => {
9737
10698
  if (Array.isArray(value)) {
@@ -9743,6 +10704,7 @@ function matchesCondition(condition, state) {
9743
10704
  });
9744
10705
  }
9745
10706
  var BlockStateResolver = class {
10707
+ /** Loads vanilla property defaults, falling back to preview values inferred from the blockstate file. */
9746
10708
  static async defaults(blockState) {
9747
10709
  const defaults = blockState.key ? await BlockStates.getDefaultState(blockState.key) : void 0;
9748
10710
  const state = {};
@@ -9762,9 +10724,11 @@ var BlockStateResolver = class {
9762
10724
  }
9763
10725
  return state;
9764
10726
  }
10727
+ /** Selects matching models. Supply `choose` to control selection within each alternatives array. */
9765
10728
  static select(blockState, state, choose = this.choose) {
9766
10729
  return this.matching(blockState, state).map(choose);
9767
10730
  }
10731
+ /** Returns matching model groups before choosing weighted alternatives. */
9768
10732
  static matching(blockState, state) {
9769
10733
  const out = [];
9770
10734
  if (blockState.variants) {
@@ -9788,6 +10752,7 @@ var BlockStateResolver = class {
9788
10752
  }
9789
10753
  return out;
9790
10754
  }
10755
+ /** Selects a random alternative using positive integer weights, each defaulting to 1. */
9791
10756
  static choose(variants) {
9792
10757
  if (!Array.isArray(variants)) return variants;
9793
10758
  if (!variants.length) throw new MineRenderError("Blockstate variant arrays must not be empty");
@@ -9805,8 +10770,9 @@ var BlockStateResolver = class {
9805
10770
  }
9806
10771
  return variants[variants.length - 1];
9807
10772
  }
10773
+ /** Converts blockstate rotations in degrees into the Euler rotation used by model rendering. */
9808
10774
  static rotation(variant) {
9809
- const rotation = new Euler8();
10775
+ const rotation = new Euler9();
9810
10776
  if (typeof variant.x !== "undefined") {
9811
10777
  rotation.x = toRadians(clampRotationDegrees(variant.x));
9812
10778
  }
@@ -9867,29 +10833,29 @@ var BlockTints = class {
9867
10833
  }
9868
10834
  }
9869
10835
  if (!indices.size) return tints;
9870
- let color;
10836
+ let color2;
9871
10837
  switch (rule.source) {
9872
10838
  case "constant":
9873
- color = rule.color;
10839
+ color2 = rule.color;
9874
10840
  break;
9875
10841
  case "grass":
9876
- color = await Colormaps.grassColor(key);
10842
+ color2 = await Colormaps.grassColor(key);
9877
10843
  break;
9878
10844
  case "redstone": {
9879
10845
  const power = Number(state.power ?? 0) / 15;
9880
10846
  const red = power * 0.6 + (power > 0 ? 0.4 : 0.3);
9881
10847
  const green = Math.max(0, power * power * 0.7 - 0.5);
9882
10848
  const blue = Math.max(0, power * power * 0.6 - 0.7);
9883
- color = Math.floor(red * 255) << 16 | Math.floor(green * 255) << 8 | Math.floor(blue * 255);
10849
+ color2 = Math.floor(red * 255) << 16 | Math.floor(green * 255) << 8 | Math.floor(blue * 255);
9884
10850
  break;
9885
10851
  }
9886
10852
  case "stem": {
9887
10853
  const age = Number(state.age ?? 0);
9888
- color = age * 32 << 16 | 255 - age * 8 << 8 | age * 4;
10854
+ color2 = age * 32 << 16 | 255 - age * 8 << 8 | age * 4;
9889
10855
  break;
9890
10856
  }
9891
10857
  }
9892
- return { ...Object.fromEntries([...indices].map((index) => [index, color])), ...tints };
10858
+ return { ...Object.fromEntries([...indices].map((index) => [index, color2])), ...tints };
9893
10859
  }
9894
10860
  };
9895
10861
 
@@ -9901,7 +10867,7 @@ var import_ts_deepmerge6 = __toESM(require_dist());
9901
10867
  init_MineRenderError();
9902
10868
  init_InstanceReference();
9903
10869
  init_AssetKey();
9904
- import { Euler as Euler9, Matrix4 as Matrix411, Quaternion as Quaternion5, Vector3 as Vector312 } from "three";
10870
+ import { Euler as Euler10, Matrix4 as Matrix411, Quaternion as Quaternion5, Vector3 as Vector312 } from "three";
9905
10871
  init_ModelCulling();
9906
10872
  init_FluidGeometry();
9907
10873
  init_Entities();
@@ -10026,8 +10992,8 @@ var _BlockObject = class _BlockObject extends SceneObject {
10026
10992
  return false;
10027
10993
  }
10028
10994
  return this._models.some((model) => {
10029
- const object = isInstanceReference(model) ? model.instanceable : model;
10030
- return object.isOpaqueFullCube && this.getModelCullMask(model, 63) === 63;
10995
+ const object3 = isInstanceReference(model) ? model.instanceable : model;
10996
+ return object3.isOpaqueFullCube && this.getModelCullMask(model, 63) === 63;
10031
10997
  });
10032
10998
  }
10033
10999
  getModelMatrix(model) {
@@ -10036,14 +11002,14 @@ var _BlockObject = class _BlockObject extends SceneObject {
10036
11002
  return model.matrix.clone();
10037
11003
  }
10038
11004
  getModelCullMask(model, worldMask) {
10039
- const object = isInstanceReference(model) ? model.instanceable : model;
10040
- if (object.options.displayPosition) return 0;
11005
+ const object3 = isInstanceReference(model) ? model.instanceable : model;
11006
+ if (object3.options.displayPosition) return 0;
10041
11007
  const position = new Vector312();
10042
11008
  const rotation = new Quaternion5();
10043
11009
  const scale = new Vector312();
10044
11010
  this.getModelMatrix(model).decompose(position, rotation, scale);
10045
11011
  if (position.distanceToSquared(this.position) > 1e-10 || scale.distanceToSquared(new Vector312(1, 1, 1)) > 1e-10) return 0;
10046
- return ModelCulling.toLocalMask(worldMask, new Euler9().setFromQuaternion(rotation));
11012
+ return ModelCulling.toLocalMask(worldMask, new Euler10().setFromQuaternion(rotation));
10047
11013
  }
10048
11014
  async setCullMask(worldMask) {
10049
11015
  worldMask &= 63;
@@ -10054,14 +11020,14 @@ var _BlockObject = class _BlockObject extends SceneObject {
10054
11020
  replacements.push(model);
10055
11021
  continue;
10056
11022
  }
10057
- const object = isInstanceReference(model) ? model.instanceable : model;
11023
+ const object3 = isInstanceReference(model) ? model.instanceable : model;
10058
11024
  const cullMask = this.getModelCullMask(model, worldMask);
10059
- if (cullMask === (object.options.cullMask ?? 0)) {
11025
+ if (cullMask === (object3.options.cullMask ?? 0)) {
10060
11026
  replacements.push(model);
10061
11027
  continue;
10062
11028
  }
10063
11029
  const matrix = this.getModelMatrix(model);
10064
- const replacement = await this.scene.addModel(object.originalModel, { ...object.options, cullMask });
11030
+ const replacement = await this.scene.addModel(object3.originalModel, { ...object3.options, cullMask });
10065
11031
  replacements.push(replacement);
10066
11032
  if (isInstanceReference(replacement)) {
10067
11033
  replacement.setMatrix(matrix);
@@ -10069,8 +11035,8 @@ var _BlockObject = class _BlockObject extends SceneObject {
10069
11035
  replacement.matrix.copy(matrix);
10070
11036
  replacement.matrixWorldNeedsUpdate = true;
10071
11037
  matrix.decompose(replacement.position, replacement.quaternion, replacement.scale);
10072
- replacement.matrixAutoUpdate = object.matrixAutoUpdate;
10073
- replacement.visible = object.visible;
11038
+ replacement.matrixAutoUpdate = object3.matrixAutoUpdate;
11039
+ replacement.visible = object3.visible;
10074
11040
  }
10075
11041
  }
10076
11042
  } catch (error) {
@@ -10086,6 +11052,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
10086
11052
  }
10087
11053
  this.notifyDirty();
10088
11054
  }
11055
+ /** Selected property values. Use {@link setState} to change properties and rebuild the rendered models. */
10089
11056
  get state() {
10090
11057
  return this._state;
10091
11058
  }
@@ -10185,6 +11152,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
10185
11152
  obj.setPosition(this.position);
10186
11153
  return obj;
10187
11154
  }
11155
+ /** Clears selected properties and rebuilds models without reapplying property defaults. */
10188
11156
  async resetState() {
10189
11157
  this._previousState = this._state;
10190
11158
  this._state = {};
@@ -11992,9 +12960,9 @@ var SchematicParser = class {
11992
12960
  * Parses a legacy schematic using custom mappings before the bundled defaults.
11993
12961
  * Keys are `id:metadata`; values are block states such as `minecraft:oak_log[axis=x]`.
11994
12962
  */
11995
- static async parse(nbt, customMappings = {}) {
12963
+ static async parse(nbt, customMappings = {}, options = {}) {
11996
12964
  const tags = nbt.value;
11997
- if (tags.Materials?.type !== "string" || tags.Materials.value !== "Alpha") {
12965
+ if (!options.lenient && (tags.Materials?.type !== "string" || tags.Materials.value !== "Alpha")) {
11998
12966
  throw new MineRenderError("Schematic Materials must be Alpha");
11999
12967
  }
12000
12968
  const size = ["Width", "Height", "Length"].map((name) => {
@@ -12035,8 +13003,16 @@ var SchematicParser = class {
12035
13003
  const id = high << 8 | ids.value[index] & 255;
12036
13004
  const metadata = data.value[index] & 255;
12037
13005
  const key = `${id}:${metadata}`;
12038
- const mapped = customMappings[key] ?? mapping[key];
12039
- if (!mapped) throw new MineRenderError(`Unsupported legacy block ${id}:${metadata} at schematic index ${index}`);
13006
+ let mapped = customMappings[key] ?? mapping[key];
13007
+ if (!mapped && options.lenient) {
13008
+ mapped = customMappings[`${id}:0`] ?? mapping[`${id}:0`];
13009
+ }
13010
+ if (!mapped) {
13011
+ if (options.lenient) {
13012
+ continue;
13013
+ }
13014
+ throw new MineRenderError(`Unsupported legacy block ${id}:${metadata} at schematic index ${index}`);
13015
+ }
12040
13016
  const [type, state] = mapped.split("[");
12041
13017
  if (type === "minecraft:air") continue;
12042
13018
  const properties = {};
@@ -12080,6 +13056,11 @@ function compoundList(tags, name) {
12080
13056
  // src/model/multiblock/StructureParser.ts
12081
13057
  init_MineRenderError();
12082
13058
  var StructureParser = class {
13059
+ /**
13060
+ * Parses decoded NBT, omitting air blocks.
13061
+ * @param _nbt - Structure data decoded by {@link NBTHelper.fromBuffer}.
13062
+ * @param paletteIndex - Palette to select when the structure has several. Defaults to 0.
13063
+ */
12083
13064
  static async parse(_nbt, paletteIndex) {
12084
13065
  const nbt = _nbt.value;
12085
13066
  let palette;
@@ -12914,6 +13895,7 @@ var SkinImage = class {
12914
13895
  // src/skin/SkinTextures.ts
12915
13896
  init_Textures();
12916
13897
  var SkinTextures = class {
13898
+ /** Loads a skin URL. Pass `legacy` to override layout detection from the image dimensions. */
12917
13899
  static async get(src, legacy) {
12918
13900
  const image = await SkinImage.getData(src);
12919
13901
  const scale = image.width / 64;
@@ -13087,6 +14069,14 @@ var SkinObject = class extends SceneObject {
13087
14069
  }
13088
14070
  }
13089
14071
  }
14072
+ /**
14073
+ * Loads a skin texture, detecting slim arms and legacy layouts by default.
14074
+ * To override detection, set `slim` or `legacy` in the options passed to
14075
+ * `new SkinObject(options)` or `scene.addSkin(src, options)`.
14076
+ * Use {@link setSlim} or {@link setLegacy} to change these settings later.
14077
+ *
14078
+ * @param src - URL of the skin texture.
14079
+ */
13090
14080
  async setSkinTexture(src) {
13091
14081
  if (typeof src === "undefined") return;
13092
14082
  this.skinTextureSrc = src;
@@ -13194,59 +14184,61 @@ var _MineRenderScene = class _MineRenderScene extends Scene4 {
13194
14184
  this.options = (0, import_ts_deepmerge8.default)({}, _MineRenderScene.DEFAULT_OPTIONS, options ?? {});
13195
14185
  }
13196
14186
  add(...objects) {
13197
- for (const object of objects) {
14187
+ for (const object3 of objects) {
13198
14188
  this.dirty = true;
13199
14189
  try {
13200
- super.add(object);
14190
+ super.add(object3);
13201
14191
  } finally {
13202
- if (object?.isObject3D && object !== this) {
13203
- this.updateObjectRegistration(object, object.parent === this);
14192
+ if (object3?.isObject3D && object3 !== this) {
14193
+ this.updateObjectRegistration(object3, object3.parent === this);
13204
14194
  }
13205
14195
  }
13206
14196
  }
13207
14197
  return this;
13208
14198
  }
13209
14199
  remove(...objects) {
13210
- for (const object of objects) {
13211
- if (!this.children.includes(object)) continue;
14200
+ for (const object3 of objects) {
14201
+ if (!this.children.includes(object3)) continue;
13212
14202
  this.dirty = true;
13213
- this.updateObjectRegistration(object, false);
14203
+ this.updateObjectRegistration(object3, false);
13214
14204
  try {
13215
- super.remove(object);
14205
+ super.remove(object3);
13216
14206
  } finally {
13217
- this.updateObjectRegistration(object, object.parent === this);
14207
+ this.updateObjectRegistration(object3, object3.parent === this);
13218
14208
  }
13219
14209
  }
13220
14210
  return this;
13221
14211
  }
13222
- updateObjectRegistration(object, attached) {
13223
- if (this.observedObjects.has(object) === attached) return;
13224
- const observable = object;
14212
+ updateObjectRegistration(object3, attached) {
14213
+ if (this.observedObjects.has(object3) === attached) return;
14214
+ const observable = object3;
13225
14215
  if (attached) {
13226
- this.observedObjects.add(object);
14216
+ this.observedObjects.add(object3);
13227
14217
  observable.addEventListener("change", this.onObjectChange);
13228
14218
  } else {
13229
- this.observedObjects.delete(object);
14219
+ this.observedObjects.delete(object3);
13230
14220
  observable.removeEventListener("change", this.onObjectChange);
13231
14221
  }
13232
14222
  const change = attached ? 1 : -1;
13233
14223
  this.stats.objectCount += change;
13234
- if (isSceneObject(object)) {
14224
+ if (isSceneObject(object3)) {
13235
14225
  this.stats.sceneObjectCount += change;
13236
14226
  }
13237
14227
  }
13238
- async initAndAdd(...object) {
14228
+ /** Initializes each object before attaching it to the scene. */
14229
+ async initAndAdd(...object3) {
13239
14230
  this.dirty = true;
13240
- for (let obj of object) {
14231
+ for (let obj of object3) {
13241
14232
  await obj.init();
13242
14233
  }
13243
- return this.add(...object);
14234
+ return this.add(...object3);
13244
14235
  }
13245
- async addSceneObject(asset, objectSupplier, _options, parent = this) {
14236
+ async addSceneObject(asset2, objectSupplier, _options, parent = this) {
13246
14237
  this.dirty = true;
13247
14238
  const obj = await objectSupplier();
13248
- if (obj?.options?.instanceMeshes && asset.key && asset.key?.assetType === "models") {
13249
- let key = asset.key.serialize();
14239
+ if (isModelObject(obj)) obj.options.tints = await ItemTints.get(obj.originalModel, obj.options.tints);
14240
+ if (obj?.options?.instanceMeshes && asset2.key && asset2.key?.assetType === "models") {
14241
+ let key = asset2.key.serialize();
13250
14242
  if (isModelObject(obj)) {
13251
14243
  const { displayPosition, uvLockRotation, tints, cullMask } = obj.options;
13252
14244
  if (displayPosition) key += `|display:${displayPosition}`;
@@ -13270,13 +14262,32 @@ var _MineRenderScene = class _MineRenderScene extends Scene4 {
13270
14262
  return obj;
13271
14263
  }
13272
14264
  }
14265
+ /**
14266
+ * Adds a model loaded with {@link Models.getMerged}.
14267
+ * Set `options.instanceMeshes` to `false` to create a separate ModelObject.
14268
+ *
14269
+ * @param parent - Object to attach the model to. Defaults to this scene.
14270
+ * @returns The model, or a reference to one placement of a shared model.
14271
+ */
13273
14272
  async addModel(model, options, parent = this) {
13274
- options = { ...options, tints: await ItemTints.get(model, options?.tints) };
13275
14273
  return this.addSceneObject(model, () => new ModelObject(model, options), options, parent);
13276
14274
  }
14275
+ /**
14276
+ * Adds the models selected by a blockstate loaded with {@link BlockStates.get}.
14277
+ * Set `options.initialState` to choose block properties such as `facing` or `axis`.
14278
+ * Use the returned object's `setPosition` method to move all of its model parts.
14279
+ */
13277
14280
  async addBlock(blockState, options, parent = this) {
13278
14281
  return this.addSceneObject(blockState, () => new BlockObject(blockState, options), options, parent);
13279
14282
  }
14283
+ /**
14284
+ * Loads a skin and adds an initialized player model.
14285
+ *
14286
+ * @param skin - Texture URL, such as one returned by {@link Skins.fromUuidOrUsername}.
14287
+ * Omit it to assign a texture later with {@link SkinObject.setSkinTexture}.
14288
+ * @param options - Player settings, including `slim` and `legacy` detection overrides.
14289
+ * @param parent - Object to attach the player to. Defaults to this scene.
14290
+ */
13280
14291
  async addSkin(skin, options, parent = this) {
13281
14292
  this.dirty = true;
13282
14293
  const obj = new SkinObject(options);
@@ -13288,9 +14299,14 @@ var _MineRenderScene = class _MineRenderScene extends Scene4 {
13288
14299
  parent.add(obj);
13289
14300
  return obj;
13290
14301
  }
14302
+ /** Adds an entity loaded with {@link Entities.getEntity}, using its selected layers and textures. */
13291
14303
  async addEntity(entity, options, parent = this) {
13292
14304
  return this.addSceneObject(entity, () => new EntityObject(entity, options), options, parent);
13293
14305
  }
14306
+ /**
14307
+ * Adds GUI layers in draw order. One GUI pixel equals one scene unit.
14308
+ * Layer positions start at the top left, with positive GUI y pointing down.
14309
+ */
13294
14310
  async addGui(layers, options, parent = this) {
13295
14311
  const obj = new GuiObject(layers, options);
13296
14312
  obj.scene = this;
@@ -13329,11 +14345,11 @@ var _changeEvent = { type: "change" };
13329
14345
  var _startEvent = { type: "start" };
13330
14346
  var _endEvent = { type: "end" };
13331
14347
  var OrbitControls = class extends EventDispatcher {
13332
- constructor(object, domElement) {
14348
+ constructor(object3, domElement) {
13333
14349
  super();
13334
14350
  if (domElement === void 0) console.warn('THREE.OrbitControls: The second parameter "domElement" is now mandatory.');
13335
14351
  if (domElement === document) console.error('THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.');
13336
- this.object = object;
14352
+ this.object = object3;
13337
14353
  this.domElement = domElement;
13338
14354
  this.domElement.style.touchAction = "none";
13339
14355
  this.enabled = true;
@@ -13390,7 +14406,7 @@ var OrbitControls = class extends EventDispatcher {
13390
14406
  };
13391
14407
  this.update = function() {
13392
14408
  const offset = new Vector313();
13393
- const quat = new Quaternion6().setFromUnitVectors(object.up, new Vector313(0, 1, 0));
14409
+ const quat = new Quaternion6().setFromUnitVectors(object3.up, new Vector313(0, 1, 0));
13394
14410
  const quatInverse = quat.clone().invert();
13395
14411
  const lastPosition = new Vector313();
13396
14412
  const lastQuaternion = new Quaternion6();
@@ -13955,8 +14971,8 @@ var OrbitControls = class extends EventDispatcher {
13955
14971
  }
13956
14972
  };
13957
14973
  var MapControls = class extends OrbitControls {
13958
- constructor(object, domElement) {
13959
- super(object, domElement);
14974
+ constructor(object3, domElement) {
14975
+ super(object3, domElement);
13960
14976
  this.screenSpacePanning = false;
13961
14977
  this.mouseButtons.LEFT = MOUSE.PAN;
13962
14978
  this.mouseButtons.RIGHT = MOUSE.ROTATE;
@@ -14115,6 +15131,7 @@ var _Renderer = class _Renderer {
14115
15131
  get element() {
14116
15132
  return this._element;
14117
15133
  }
15134
+ /** Attaches the canvas to an element and sizes it to that element's dimensions. */
14118
15135
  appendTo(element) {
14119
15136
  if (this._disposed) return;
14120
15137
  this._element = element;
@@ -14142,6 +15159,7 @@ var _Renderer = class _Renderer {
14142
15159
  events.add(changeEvent2);
14143
15160
  dispatcher.addEventListener(changeEvent2, this._changeListener);
14144
15161
  }
15162
+ /** Updates the camera and canvas size. Width and height are in CSS pixels. */
14145
15163
  resize(width, height) {
14146
15164
  if (this._disposed) return;
14147
15165
  if (isPerspectiveCamera(this.camera)) {
@@ -14180,12 +15198,14 @@ var _Renderer = class _Renderer {
14180
15198
  if (this._frameCallbacks.get(callback) === subscription) this._frameCallbacks.delete(callback);
14181
15199
  };
14182
15200
  }
15201
+ /** Starts or resumes the animation loop and registered frame callbacks. */
14183
15202
  start() {
14184
15203
  if (this._disposed) return;
14185
15204
  this.stop();
14186
15205
  this._running = true;
14187
15206
  if (!this._inAnimationLoop) this.renderer.setAnimationLoop(this._animationLoop);
14188
15207
  }
15208
+ /** Pauses rendering and frame callbacks. Call {@link start} to resume. */
14189
15209
  stop() {
14190
15210
  if (this._disposed) return;
14191
15211
  this._running = false;
@@ -14294,12 +15314,15 @@ var _Renderer = class _Renderer {
14294
15314
  const canvas = trim ? trimCanvas(this.renderer.domElement) : this.renderer.domElement;
14295
15315
  return canvas.toDataURL(mime, quality);
14296
15316
  }
15317
+ /** Exports visible scene meshes as OBJ text, without texture images. */
14297
15318
  toObj() {
14298
15319
  return SceneExporter.toObj(this.scene);
14299
15320
  }
15321
+ /** Exports visible scene meshes as PLY. Set `options.binary` for an ArrayBuffer. */
14300
15322
  toPLY(options) {
14301
15323
  return SceneExporter.toPLY(this.scene, options);
14302
15324
  }
15325
+ /** Exports a static textured scene in the browser. Set `options.binary` for GLB output. */
14303
15326
  toGLTF(options) {
14304
15327
  return SceneExporter.toGLTF(this.scene, options);
14305
15328
  }
@@ -14377,16 +15400,244 @@ init_SceneObject();
14377
15400
  // src/index.ts
14378
15401
  init_request();
14379
15402
 
15403
+ // src/scene/SceneDocument.ts
15404
+ init_DisplayPosition();
15405
+ function fail(path, message) {
15406
+ throw new Error(`${path}: ${message}`);
15407
+ }
15408
+ function object2(value, path) {
15409
+ if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail(path, "expected a JSON object");
15410
+ for (const key of Object.keys(value)) {
15411
+ if (["__proto__", "prototype", "constructor"].includes(key)) fail(`${path}.${key}`, "unsupported property");
15412
+ }
15413
+ return value;
15414
+ }
15415
+ function fields(value, path, allowed, required = []) {
15416
+ const input = object2(value, path);
15417
+ for (const key of required) if (input[key] === void 0) fail(`${path}.${key}`, "required");
15418
+ for (const [key, entry] of Object.entries(input)) {
15419
+ if (!Object.prototype.hasOwnProperty.call(allowed, key)) fail(`${path}.${key}`, "unsupported property");
15420
+ if (entry !== void 0) allowed[key](entry, `${path}.${key}`);
15421
+ }
15422
+ return input;
15423
+ }
15424
+ var text = (value, path) => {
15425
+ if (typeof value !== "string" || !value.trim()) fail(path, "expected a nonempty string");
15426
+ };
15427
+ var literalText = (value, path) => {
15428
+ if (typeof value !== "string") fail(path, "expected a string");
15429
+ };
15430
+ var boolean = (value, path) => {
15431
+ if (typeof value !== "boolean") fail(path, "expected a boolean");
15432
+ };
15433
+ var number = (value, path) => {
15434
+ if (typeof value !== "number" || !Number.isFinite(value)) fail(path, "expected a finite number");
15435
+ };
15436
+ var positive = (value, path) => {
15437
+ number(value, path);
15438
+ if (value <= 0) fail(path, "expected a positive number");
15439
+ };
15440
+ var nonnegative = (value, path) => {
15441
+ number(value, path);
15442
+ if (value < 0) fail(path, "expected a nonnegative number");
15443
+ };
15444
+ var color = (value, path) => {
15445
+ number(value, path);
15446
+ if (!Number.isInteger(value) || value < 0 || value > 16777215) {
15447
+ fail(path, "expected an RGB integer from 0 to 16777215");
15448
+ }
15449
+ };
15450
+ var asset = (value, path) => {
15451
+ text(value, path);
15452
+ if (!/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(value) || value.split(/[:/]/).some((part) => !part || part === "." || part === "..")) {
15453
+ fail(path, "expected a resource ID such as minecraft:stone");
15454
+ }
15455
+ };
15456
+ var version = (value, path) => {
15457
+ text(value, path);
15458
+ if (!/^[a-zA-Z0-9._-]+$/.test(value)) fail(path, "expected a Minecraft version without slashes");
15459
+ };
15460
+ var tuple = (size, item = number) => (value, path) => {
15461
+ if (!Array.isArray(value) || value.length !== size) fail(path, `expected ${size} numbers`);
15462
+ for (let index = 0; index < size; index++) item(value[index], `${path}[${index}]`);
15463
+ };
15464
+ var list = (item) => (value, path) => {
15465
+ if (!Array.isArray(value)) fail(path, "expected an array");
15466
+ for (let index = 0; index < value.length; index++) item(value[index], `${path}[${index}]`);
15467
+ };
15468
+ var dictionary = (item, numericKeys = false) => (value, path) => {
15469
+ for (const [key, entry] of Object.entries(object2(value, path))) {
15470
+ if (numericKeys && !/^\d+$/.test(key)) fail(`${path}.${key}`, "expected a nonnegative tint index");
15471
+ item(entry, `${path}.${key}`);
15472
+ }
15473
+ };
15474
+ var enumeration = (values) => (value, path) => {
15475
+ if (!values.includes(value)) fail(path, `expected one of: ${values.join(", ")}`);
15476
+ };
15477
+ var textStyle = { color, bold: boolean, italic: boolean };
15478
+ function guiLayer(value, path) {
15479
+ const layer = object2(value, path);
15480
+ if (["texture", "item", "text"].filter((key) => layer[key] !== void 0).length !== 1) {
15481
+ fail(path, "expected exactly one of texture, item, or text");
15482
+ }
15483
+ const layout = { name: text, position: tuple(2), size: tuple(2, positive) };
15484
+ if (layer.texture !== void 0) {
15485
+ fields(value, path, { ...layout, texture: asset, crop: (value2, path2) => {
15486
+ tuple(4)(value2, path2);
15487
+ const [x, y, width, height] = value2;
15488
+ nonnegative(x, `${path2}[0]`);
15489
+ nonnegative(y, `${path2}[1]`);
15490
+ positive(width, `${path2}[2]`);
15491
+ positive(height, `${path2}[3]`);
15492
+ } });
15493
+ } else if (layer.item !== void 0) {
15494
+ fields(value, path, { ...layout, item: asset, tints: dictionary(color, true) });
15495
+ } else {
15496
+ fields(value, path, {
15497
+ ...layout,
15498
+ ...textStyle,
15499
+ font: asset,
15500
+ shadow: boolean,
15501
+ maxWidth: positive,
15502
+ lineHeight: positive,
15503
+ text: (value2, path2) => {
15504
+ if (typeof value2 === "string") return;
15505
+ list((run, path3) => {
15506
+ fields(run, path3, { text: literalText, ...textStyle }, ["text"]);
15507
+ })(value2, path2);
15508
+ }
15509
+ });
15510
+ }
15511
+ }
15512
+ function validateObject(value, path) {
15513
+ const input = object2(value, path);
15514
+ const common = { id: text, name: text, type: text, position: tuple(3), rotation: tuple(3), scale: tuple(3), visible: boolean };
15515
+ const required = ["id", "type"];
15516
+ const renderOptions = { wireframe: boolean };
15517
+ switch (input.type) {
15518
+ case "skin":
15519
+ fields(value, path, {
15520
+ ...common,
15521
+ skin: text,
15522
+ cape: (value2, path2) => {
15523
+ fields(value2, path2, { texture: text, layout: enumeration(["minecraft", "optifine", "labymod"]) }, ["texture"]);
15524
+ },
15525
+ hiddenParts: list(enumeration(SKIN_PARTS)),
15526
+ pose: (value2, path2) => {
15527
+ fields(value2, path2, {
15528
+ head: tuple(3),
15529
+ body: tuple(3),
15530
+ rightArm: tuple(3),
15531
+ leftArm: tuple(3),
15532
+ rightLeg: tuple(3),
15533
+ leftLeg: tuple(3),
15534
+ cape: tuple(3)
15535
+ });
15536
+ },
15537
+ options: (value2, path2) => {
15538
+ fields(value2, path2, { slim: boolean, legacy: boolean });
15539
+ }
15540
+ }, required);
15541
+ break;
15542
+ case "block":
15543
+ fields(value, path, {
15544
+ ...common,
15545
+ asset,
15546
+ state: dictionary(text),
15547
+ options: (value2, path2) => {
15548
+ fields(value2, path2, { ...renderOptions, tints: dictionary(color, true) });
15549
+ }
15550
+ }, [...required, "asset"]);
15551
+ break;
15552
+ case "item":
15553
+ case "model":
15554
+ fields(value, path, {
15555
+ ...common,
15556
+ asset,
15557
+ options: (value2, path2) => {
15558
+ fields(value2, path2, { ...renderOptions, displayPosition: enumeration(DISPLAY_POSITIONS), tints: dictionary(color, true) });
15559
+ }
15560
+ }, [...required, "asset"]);
15561
+ break;
15562
+ case "entity":
15563
+ fields(value, path, {
15564
+ ...common,
15565
+ asset,
15566
+ texture: asset,
15567
+ layers: list(text),
15568
+ when: list(text),
15569
+ textures: dictionary(asset),
15570
+ options: (value2, path2) => {
15571
+ fields(value2, path2, {
15572
+ ...renderOptions,
15573
+ flip: boolean,
15574
+ tints: dictionary((value3, path3) => {
15575
+ typeof value3 === "string" ? text(value3, path3) : color(value3, path3);
15576
+ })
15577
+ });
15578
+ },
15579
+ animation: (value2, path2) => {
15580
+ fields(value2, path2, { name: text, loop: boolean, speed: nonnegative, time: nonnegative, paused: boolean }, ["name"]);
15581
+ }
15582
+ }, [...required, "asset"]);
15583
+ if (Array.isArray(input.layers) && !input.layers.length) fail(`${path}.layers`, "expected at least one layer");
15584
+ break;
15585
+ case "gui":
15586
+ fields(value, path, { ...common, layers: list(guiLayer) }, [...required, "layers"]);
15587
+ break;
15588
+ default:
15589
+ fail(`${path}.type`, "expected skin, block, item, entity, model, or gui");
15590
+ }
15591
+ }
15592
+ function parseSceneDocument(value) {
15593
+ if (typeof value === "string") {
15594
+ try {
15595
+ value = JSON.parse(value);
15596
+ } catch {
15597
+ fail("scene", "invalid JSON");
15598
+ }
15599
+ }
15600
+ const input = fields(value, "scene", {
15601
+ format: enumeration(["minerender-scene"]),
15602
+ version: (value2, path) => {
15603
+ if (value2 !== 1) fail(path, "only scene document version 1 is supported");
15604
+ },
15605
+ minecraftVersion: version,
15606
+ camera: (value2, path) => {
15607
+ fields(value2, path, { position: tuple(3), target: tuple(3) }, ["position", "target"]);
15608
+ },
15609
+ objects: list(validateObject)
15610
+ }, ["format", "version", "objects"]);
15611
+ const ids = /* @__PURE__ */ new Set();
15612
+ for (const [index, entry] of input.objects.entries()) {
15613
+ if (ids.has(entry.id)) fail(`scene.objects[${index}].id`, `duplicate object ID "${entry.id}"`);
15614
+ ids.add(entry.id);
15615
+ }
15616
+ return JSON.parse(JSON.stringify(input));
15617
+ }
15618
+
15619
+ // src/scene/SceneDocumentLoader.ts
15620
+ init_AssetKey();
15621
+ init_AssetLoader();
15622
+ init_BlockStates();
15623
+ init_Entities();
15624
+ init_Models();
15625
+ init_DisplayPosition();
15626
+ import { Group as Group2 } from "three";
15627
+ init_SceneObject();
15628
+
14380
15629
  // src/skin/Skins.ts
14381
15630
  init_Requests();
14382
15631
  var Skins = class {
14383
15632
  //<editor-fold desc="Skins">
15633
+ /** Returns a skin URL for {@link MineRenderScene.addSkin}, or `undefined` if name lookup fails. */
14384
15634
  static async fromUuidOrUsername(uuidOrUsername) {
14385
15635
  if (uuidOrUsername.length === 32 || uuidOrUsername.length === 36) {
14386
15636
  return this.fromUuid(uuidOrUsername);
14387
15637
  }
14388
15638
  return this.fromUsername(uuidOrUsername);
14389
15639
  }
15640
+ /** Builds a proxy URL for a UUID without downloading or checking the skin. */
14390
15641
  static async fromUuid(uuid) {
14391
15642
  return this.getMcProxySkinUrl(uuid);
14392
15643
  }
@@ -14398,6 +15649,7 @@ var Skins = class {
14398
15649
  return void 0;
14399
15650
  });
14400
15651
  }
15652
+ /** Looks up a saved MineSkin skin and returns its texture URL, or `undefined` on lookup failure. */
14401
15653
  static async fromMineSkin(mineskinId) {
14402
15654
  return this.getMineSkin(mineskinId).then((mineskin) => {
14403
15655
  if (mineskin) {
@@ -14408,12 +15660,14 @@ var Skins = class {
14408
15660
  }
14409
15661
  //</editor-fold>
14410
15662
  //<editor-fold desc="Capes">
15663
+ /** Resolves a player's Minecraft cape URL for {@link SkinObject.setCapeTexture}. */
14411
15664
  static async capeFromUuidOrUsername(uuidOrUsername) {
14412
15665
  if (uuidOrUsername.length === 32 || uuidOrUsername.length === 36) {
14413
15666
  return this.capeFromUuid(uuidOrUsername);
14414
15667
  }
14415
15668
  return this.capeFromUsername(uuidOrUsername);
14416
15669
  }
15670
+ /** Builds a proxy URL without checking whether the player has a cape. */
14417
15671
  static async capeFromUuid(uuid) {
14418
15672
  return this.getMcProxyCapeUrl(uuid);
14419
15673
  }
@@ -14542,6 +15796,195 @@ var Skins = class {
14542
15796
  //</editor-fold>
14543
15797
  };
14544
15798
 
15799
+ // src/scene/SceneDocumentLoader.ts
15800
+ var DocumentObjectScene = class extends MineRenderScene {
15801
+ async addSceneObject(_asset, supplier, _options, parent = this) {
15802
+ const object3 = await supplier();
15803
+ return this.initialize(object3, parent);
15804
+ }
15805
+ async initialize(object3, parent = this) {
15806
+ object3.scene = this;
15807
+ try {
15808
+ await object3.init();
15809
+ parent.add(object3);
15810
+ return object3;
15811
+ } catch (error) {
15812
+ object3.dispose();
15813
+ object3.removeFromParent();
15814
+ throw error;
15815
+ }
15816
+ }
15817
+ dispose() {
15818
+ for (const child of [...this.children]) {
15819
+ if (child.parent !== this) continue;
15820
+ if (isSceneObject(child)) child.dispose();
15821
+ child.removeFromParent();
15822
+ }
15823
+ }
15824
+ };
15825
+ async function skinTexture(value, cape = false) {
15826
+ if (!/^[a-zA-Z0-9_-]{1,36}$/.test(value)) return value;
15827
+ const texture = await (cape ? Skins.capeFromUuidOrUsername(value) : Skins.fromUuidOrUsername(value));
15828
+ if (!texture) throw new Error(`Could not find ${cape ? "cape" : "skin"} for "${value}"`);
15829
+ return texture;
15830
+ }
15831
+ var SceneDocumentLoader = class {
15832
+ static parse(value) {
15833
+ return parseSceneDocument(value);
15834
+ }
15835
+ /**
15836
+ * Loads objects concurrently and attaches them in document order after every load succeeds.
15837
+ * A failure waits for all loads, disposes staged objects, and reports the first error in document order.
15838
+ */
15839
+ static async load(scene, value, parent = scene) {
15840
+ const definition = this.parse(value);
15841
+ if (definition.minecraftVersion !== void 0 && definition.minecraftVersion !== AssetLoader.version) {
15842
+ throw new Error(`Scene requires Minecraft ${definition.minecraftVersion}; call AssetLoader.setVersion("${definition.minecraftVersion}") before loading it`);
15843
+ }
15844
+ const root = new Group2();
15845
+ root.name = "MineRender scene document";
15846
+ const objects = [];
15847
+ let disposed = false;
15848
+ const loaded = {
15849
+ root,
15850
+ objects,
15851
+ definition,
15852
+ advanceAnimations: (delta) => {
15853
+ if (!disposed) for (const object3 of objects) object3.advanceAnimation(delta);
15854
+ },
15855
+ dispose: () => {
15856
+ if (disposed) return;
15857
+ disposed = true;
15858
+ root.removeFromParent();
15859
+ for (const object3 of objects) object3.dispose();
15860
+ root.clear();
15861
+ scene.dirty = true;
15862
+ }
15863
+ };
15864
+ try {
15865
+ const results = await Promise.allSettled(definition.objects.map((object3) => this.loadValidatedObject(scene, object3)));
15866
+ for (const result of results) if (result.status === "fulfilled") objects.push(result.value);
15867
+ const failure = results.find((result) => result.status === "rejected");
15868
+ if (failure) throw failure.reason;
15869
+ for (const object3 of objects) root.add(object3.root);
15870
+ parent.add(root);
15871
+ scene.dirty = true;
15872
+ return loaded;
15873
+ } catch (error) {
15874
+ loaded.dispose();
15875
+ throw error;
15876
+ }
15877
+ }
15878
+ /** Loads an independently editable object. Its root owns the document's position, rotation, and scale. */
15879
+ static async loadObject(scene, value, parent = scene) {
15880
+ const definition = this.parse({ format: "minerender-scene", version: 1, objects: [value] }).objects[0];
15881
+ return this.loadValidatedObject(scene, definition, parent);
15882
+ }
15883
+ static async loadValidatedObject(scene, definition, parent) {
15884
+ const root = new Group2();
15885
+ root.name = definition.name ?? definition.id;
15886
+ root.userData.sceneObjectId = definition.id;
15887
+ root.position.fromArray(definition.position ?? [0, 0, 0]);
15888
+ root.rotation.set(...definition.rotation ?? [0, 0, 0]);
15889
+ root.scale.fromArray(definition.scale ?? [1, 1, 1]);
15890
+ root.visible = definition.visible ?? true;
15891
+ const content = new DocumentObjectScene();
15892
+ root.add(content);
15893
+ let object3;
15894
+ try {
15895
+ switch (definition.type) {
15896
+ case "skin": {
15897
+ const skin = new SkinObject({ ...definition.options, instanceMeshes: false });
15898
+ object3 = skin;
15899
+ const texture = definition.skin ? await skinTexture(definition.skin) : `${AssetLoader.ROOT}/assets/minecraft/textures/entity/player/${definition.options?.slim ? "slim/alex" : "wide/steve"}.png`;
15900
+ await skin.setSkinTexture(texture);
15901
+ await content.initialize(skin);
15902
+ if (definition.cape) await skin.setCapeTexture(await skinTexture(definition.cape.texture, true), definition.cape.layout);
15903
+ for (const [part, rotation] of Object.entries(definition.pose ?? {})) {
15904
+ skin.getGroupByName(part)?.rotation.set(...rotation, "XYZ");
15905
+ }
15906
+ for (const part of definition.hiddenParts ?? []) {
15907
+ const mesh = skin.getMeshByName(part);
15908
+ if (mesh) mesh.visible = false;
15909
+ }
15910
+ break;
15911
+ }
15912
+ case "block": {
15913
+ const state = await BlockStates.get(AssetKey.parse("blockstates", definition.asset));
15914
+ if (!state) throw new Error(`Could not load block ${definition.asset}`);
15915
+ object3 = await content.addBlock(state, { ...definition.options, initialState: definition.state, instanceMeshes: false });
15916
+ break;
15917
+ }
15918
+ case "item":
15919
+ case "model": {
15920
+ let key = AssetKey.parse("models", definition.asset);
15921
+ if (definition.type === "item" && key.type !== "item") {
15922
+ key = new AssetKey(key.namespace, key.getFullPath(), "models", "item");
15923
+ }
15924
+ const model = await Models.getMerged(key);
15925
+ if (!model) throw new Error(`Could not load ${definition.type} ${definition.asset}`);
15926
+ object3 = await content.addModel(model, {
15927
+ ...definition.type === "item" ? { displayPosition: "gui" /* GUI */ } : {},
15928
+ ...definition.options,
15929
+ instanceMeshes: false
15930
+ });
15931
+ break;
15932
+ }
15933
+ case "entity": {
15934
+ const key = AssetKey.parse("entity-models", definition.asset);
15935
+ const model = await Entities.getEntity(key, definition.texture ? AssetKey.parse("textures", definition.texture) : void 0, {
15936
+ layers: definition.layers,
15937
+ when: definition.when,
15938
+ textures: definition.textures && Object.fromEntries(Object.entries(definition.textures).map(([name, texture]) => [name, AssetKey.parse("textures", texture)]))
15939
+ });
15940
+ if (!model) throw new Error(`Could not load entity ${definition.asset}`);
15941
+ const entity = await content.addEntity(model, { ...definition.options, instanceMeshes: false });
15942
+ object3 = entity;
15943
+ if (definition.animation) {
15944
+ const animations = await Entities.getAnimations(key);
15945
+ const clip = animations?.[definition.animation.name];
15946
+ if (!clip) throw new Error(`Entity ${definition.asset} has no animation "${definition.animation.name}"`);
15947
+ entity.playAnimation(clip, definition.animation);
15948
+ }
15949
+ break;
15950
+ }
15951
+ case "gui":
15952
+ object3 = await content.initialize(new GuiObject(definition.layers));
15953
+ break;
15954
+ }
15955
+ let disposed = false;
15956
+ const loaded = {
15957
+ root,
15958
+ object: object3,
15959
+ definition,
15960
+ advanceAnimation: (delta) => {
15961
+ if (!disposed && definition.type === "entity" && !definition.animation?.paused) object3.advanceAnimation(delta);
15962
+ },
15963
+ dispose: () => {
15964
+ if (disposed) return;
15965
+ disposed = true;
15966
+ root.removeFromParent();
15967
+ object3.dispose();
15968
+ object3.removeFromParent();
15969
+ content.dispose();
15970
+ root.clear();
15971
+ scene.dirty = true;
15972
+ }
15973
+ };
15974
+ parent?.add(root);
15975
+ scene.dirty = true;
15976
+ return loaded;
15977
+ } catch (error) {
15978
+ object3?.dispose();
15979
+ object3?.removeFromParent();
15980
+ content.dispose();
15981
+ root.removeFromParent();
15982
+ root.clear();
15983
+ throw new Error(`Scene object "${definition.name ?? definition.id}": ${error instanceof Error ? error.message : String(error)}`);
15984
+ }
15985
+ }
15986
+ };
15987
+
14545
15988
  // src/texture/index.ts
14546
15989
  init_Colormaps();
14547
15990
  init_TextureAtlas();
@@ -14568,16 +16011,19 @@ var ChunkData = class _ChunkData {
14568
16011
  this.freeIds = [];
14569
16012
  this.nbt = /* @__PURE__ */ new Map();
14570
16013
  }
16014
+ /** Returns a detached copy of a cell, or `undefined` for air. Indices range from 0 to 4095. */
14571
16015
  get(index) {
14572
16016
  _ChunkData.validateIndex(index);
14573
16017
  const entry = this.palette[this.indices[index]];
14574
16018
  return entry ? _ChunkData.copyBlock(entry.state, this.nbt.get(index)) : void 0;
14575
16019
  }
16020
+ /** Captures a cell's data and returns a copy-producing reader that survives later cell changes. */
14576
16021
  snapshot(index) {
14577
16022
  _ChunkData.validateIndex(index);
14578
16023
  const entry = this.palette[this.indices[index]];
14579
16024
  return entry ? _ChunkData.copyBlock.bind(void 0, entry.state, this.nbt.get(index)) : void 0;
14580
16025
  }
16026
+ /** Copies block data into a cell. An omitted block or an air block clears the cell. */
14581
16027
  set(index, block) {
14582
16028
  _ChunkData.validateIndex(index);
14583
16029
  let state;
@@ -14642,15 +16088,23 @@ var ChunkData = class _ChunkData {
14642
16088
 
14643
16089
  // src/world/AnvilParser.ts
14644
16090
  var AnvilParser = class {
16091
+ /** Lists occupied chunk positions within the region, using local coordinates from 0 to 31. */
14645
16092
  static getChunkList(data) {
14646
16093
  return this.locations(this.bytes(data)).map(({ x, z }) => ({ x, z }));
14647
16094
  }
16095
+ /** Decodes all stored chunks. Gzip, zlib, and uncompressed payloads are supported. */
14648
16096
  static async parse(data) {
14649
16097
  const bytes = this.bytes(data);
14650
16098
  const chunks = [];
14651
16099
  for (const location of this.locations(bytes)) chunks.push(await this.readChunk(bytes, location));
14652
16100
  return { chunks };
14653
16101
  }
16102
+ /**
16103
+ * Decodes one chunk for {@link MineRenderWorld.placeChunk}.
16104
+ * @param localX - Chunk x within the region, from 0 to 31.
16105
+ * @param localZ - Chunk z within the region, from 0 to 31.
16106
+ * @returns The chunk with absolute coordinates, or `undefined` if the region has no chunk there.
16107
+ */
14654
16108
  static async parseChunk(data, localX, localZ) {
14655
16109
  if (![localX, localZ].every((value) => Number.isInteger(value) && value >= 0 && value < 32)) {
14656
16110
  throw new RangeError("Region-local chunk coordinates must be integers from 0 to 31");
@@ -14855,6 +16309,7 @@ var SectionModels = class {
14855
16309
  this.templates = /* @__PURE__ */ new Set();
14856
16310
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("maxAtlasSize must be a positive integer");
14857
16311
  }
16312
+ /** Returns a cube template, or `undefined` when the block requires an individual render object. */
14858
16313
  async get(blockState, properties = {}) {
14859
16314
  if (!blockState.variants || blockState.multipart) return void 0;
14860
16315
  let states = this.states.get(blockState);
@@ -14883,16 +16338,16 @@ var SectionModels = class {
14883
16338
  if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
14884
16339
  const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
14885
16340
  const template = await cached(models, key, async () => {
14886
- const object = new SectionModel(model, {
16341
+ const object3 = new SectionModel(model, {
14887
16342
  instanceMeshes: false,
14888
16343
  mergeMeshes: false,
14889
16344
  tints,
14890
16345
  uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
14891
16346
  });
14892
- await object.init();
14893
- const geometry = object.children[0].geometry;
16347
+ await object3.init();
16348
+ const geometry = object3.children[0].geometry;
14894
16349
  geometry.applyMatrix4(new Matrix412().makeRotationFromEuler(rotation));
14895
- object.clear();
16350
+ object3.clear();
14896
16351
  const cullFaces = CUBE_FACES.map((face) => {
14897
16352
  const local = CUBE_FACES.findIndex((name) => name === atlas.model.elements[0].faces[face]?.cullface);
14898
16353
  if (local < 0) return 0;
@@ -14908,6 +16363,7 @@ var SectionModels = class {
14908
16363
  }
14909
16364
  return { variants, templates };
14910
16365
  }
16366
+ /** Disposes prepared geometry and clears this world's template caches. */
14911
16367
  clear() {
14912
16368
  for (const template of this.templates) template.geometry.dispose();
14913
16369
  this.templates.clear();
@@ -14958,6 +16414,13 @@ var MineRenderWorld = class {
14958
16414
  this.validatePosBounds(posOrX);
14959
16415
  await this.getChunkAt(posOrX)?.setBlockVisibleAt(posOrX, yOrVisible);
14960
16416
  }
16417
+ /**
16418
+ * Places a structure's blocks at their stored positions, then refreshes neighboring faces.
16419
+ * Entity NBT is retained by the structure but is not rendered.
16420
+ *
16421
+ * @param useBatches - Uses bounded batches by default. Set to `false` for sequential placement.
16422
+ * @param executor - Optional queue for batched placement. The caller remains responsible for stopping it.
16423
+ */
14961
16424
  async placeMultiBlock(multiblock, useBatches = true, executor) {
14962
16425
  const changes = /* @__PURE__ */ new Map();
14963
16426
  try {
@@ -15009,13 +16472,7 @@ var MineRenderWorld = class {
15009
16472
  async placeChunk(chunk, executor) {
15010
16473
  const changes = /* @__PURE__ */ new Map();
15011
16474
  try {
15012
- const previous = [...this._chunks.entries()].filter(([, section]) => section.x === chunk.x && section.z === chunk.z);
15013
- for (const [key, section] of previous) {
15014
- this._chunks.delete(key);
15015
- await section.clear(async (positions) => {
15016
- for (const pos of positions) changes.set(pos.toArray().join(","), pos);
15017
- });
15018
- }
16475
+ await this.clearChunkColumn(chunk.x, chunk.z, changes);
15019
16476
  for (const section of chunk.sections) {
15020
16477
  const blocks = [];
15021
16478
  for (let index = 0; index < 4096; index++) {
@@ -15035,6 +16492,32 @@ var MineRenderWorld = class {
15035
16492
  await this.updateCulling([...changes.values()]);
15036
16493
  }
15037
16494
  }
16495
+ /**
16496
+ * Removes all sections at integer chunk-column coordinates, then refreshes neighboring faces and fluids.
16497
+ * Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
16498
+ */
16499
+ async unloadChunkColumn(x, z) {
16500
+ if (!Number.isInteger(x) || !Number.isInteger(z)) {
16501
+ throw new RangeError("Chunk column coordinates must be integers");
16502
+ }
16503
+ await this.culling;
16504
+ const changes = /* @__PURE__ */ new Map();
16505
+ try {
16506
+ await this.clearChunkColumn(x, z, changes);
16507
+ } finally {
16508
+ await this.updateCulling([...changes.values()]);
16509
+ }
16510
+ }
16511
+ async clearChunkColumn(x, z, changes) {
16512
+ const previous = [...this._chunks.entries()].filter(([, section]) => section.x === x && section.z === z);
16513
+ for (const [key, section] of previous) {
16514
+ this._chunks.delete(key);
16515
+ await section.clear(async (positions) => {
16516
+ for (const pos of positions) changes.set(pos.toArray().join(","), pos);
16517
+ });
16518
+ }
16519
+ }
16520
+ /** Removes all chunks and releases their block placements and section meshes. */
15038
16521
  async clear() {
15039
16522
  await this.culling;
15040
16523
  const chunks = [...this._chunks.values()];
@@ -15080,10 +16563,10 @@ var MineRenderWorld = class {
15080
16563
  await block.object.updateFluid((x, y, z) => {
15081
16564
  const neighbor = pos.clone().add(new Vector316(x, y, z));
15082
16565
  const section = this.getChunkAt(neighbor);
15083
- const object = section?.isBlockVisibleAt(neighbor) ? section.getBlockAt(neighbor)?.object : void 0;
16566
+ const object3 = section?.isBlockVisibleAt(neighbor) ? section.getBlockAt(neighbor)?.object : void 0;
15084
16567
  return {
15085
- fluid: object?.fluidKind,
15086
- level: object?.fluidLevel,
16568
+ fluid: object3?.fluidKind,
16569
+ level: object3?.fluidLevel,
15087
16570
  solid: section?.isOccludingAt(neighbor) ?? false
15088
16571
  };
15089
16572
  });
@@ -15120,9 +16603,11 @@ var MineRenderWorld = class {
15120
16603
  }
15121
16604
  return chunk;
15122
16605
  }
16606
+ /** Returns the loaded chunk containing a world block position, without creating a chunk. */
15123
16607
  getChunkAt(pos) {
15124
16608
  return this._chunks.get(this.worldPosToChunkKey(pos));
15125
16609
  }
16610
+ /** Converts block coordinates to scene units by multiplying each component by 16. */
15126
16611
  static worldToScenePosition(pos) {
15127
16612
  return new Vector316(
15128
16613
  pos.x * 16,
@@ -15130,6 +16615,7 @@ var MineRenderWorld = class {
15130
16615
  pos.z * 16
15131
16616
  );
15132
16617
  }
16618
+ /** Converts scene units to block coordinates without rounding to an integer block. */
15133
16619
  static sceneToWorldPosition(pos) {
15134
16620
  return new Vector316(
15135
16621
  pos.x / 16,
@@ -15156,12 +16642,13 @@ init_Model();
15156
16642
  // src/world/SectionMesh.ts
15157
16643
  init_CanvasCompat();
15158
16644
  init_Materials();
15159
- import { BufferGeometry as BufferGeometry8, Float32BufferAttribute as Float32BufferAttribute8, Group as Group2, Mesh as Mesh9 } from "three";
15160
- var SectionMesh = class _SectionMesh extends Group2 {
16645
+ import { BufferGeometry as BufferGeometry8, Float32BufferAttribute as Float32BufferAttribute8, Group as Group3, Mesh as Mesh9 } from "three";
16646
+ var SectionMesh = class _SectionMesh extends Group3 {
15161
16647
  constructor() {
15162
16648
  super(...arguments);
15163
16649
  this.ownedMeshes = [];
15164
16650
  }
16651
+ /** Builds section-local meshes from visible cube faces. `maxAtlasSize` limits each atlas dimension in pixels. */
15165
16652
  static build(entries, maxAtlasSize = 2048) {
15166
16653
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
15167
16654
  const visible = entries.filter((entry) => entry.template.cullFaces.some((direction) => !(entry.cullMask & direction)));
@@ -15203,14 +16690,17 @@ var SectionMesh = class _SectionMesh extends Group2 {
15203
16690
  for (const { atlas, x, y } of page.placements) {
15204
16691
  context.drawImage(atlas.image.canvas, x, y);
15205
16692
  }
15206
- const positions = [], normals = [], uvs = [], colors = [], indices = [];
16693
+ const positions = [], normals = [], uvs = [], uvBounds = [], colors = [], indices = [];
15207
16694
  for (const { index, template, cullMask } of page.entries) {
15208
16695
  const position = template.geometry.getAttribute("position");
15209
16696
  const normal = template.geometry.getAttribute("normal");
15210
16697
  const uv = template.geometry.getAttribute("uv");
15211
- const color = template.geometry.getAttribute("color");
16698
+ const bounds = template.geometry.getAttribute("uvBounds");
16699
+ const color2 = template.geometry.getAttribute("color");
15212
16700
  const sourceIndices = template.geometry.getIndex();
15213
16701
  const placement = locations.get(template.atlas).placement;
16702
+ const mapU = (u) => (placement.x + u * template.atlas.image.width) / page.width;
16703
+ const mapV = (v) => 1 - (placement.y + (1 - v) * template.atlas.image.height) / page.height;
15214
16704
  const offsetX = index % 16 * 16;
15215
16705
  const offsetY = Math.floor(index / 256) * 16;
15216
16706
  const offsetZ = Math.floor(index / 16) % 16 * 16;
@@ -15221,11 +16711,14 @@ var SectionMesh = class _SectionMesh extends Group2 {
15221
16711
  const vertex = face * 4 + corner;
15222
16712
  positions.push(position.getX(vertex) + offsetX, position.getY(vertex) + offsetY, position.getZ(vertex) + offsetZ);
15223
16713
  normals.push(normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex));
15224
- uvs.push(
15225
- (placement.x + uv.getX(vertex) * template.atlas.image.width) / page.width,
15226
- 1 - (placement.y + (1 - uv.getY(vertex)) * template.atlas.image.height) / page.height
16714
+ uvs.push(mapU(uv.getX(vertex)), mapV(uv.getY(vertex)));
16715
+ uvBounds.push(
16716
+ mapU(bounds?.getX(vertex) ?? 0),
16717
+ mapV(bounds?.getY(vertex) ?? 0),
16718
+ mapU(bounds?.getZ(vertex) ?? 1),
16719
+ mapV(bounds?.getW(vertex) ?? 1)
15227
16720
  );
15228
- colors.push(color?.getX(vertex) ?? 1, color?.getY(vertex) ?? 1, color?.getZ(vertex) ?? 1);
16721
+ colors.push(color2?.getX(vertex) ?? 1, color2?.getY(vertex) ?? 1, color2?.getZ(vertex) ?? 1);
15229
16722
  }
15230
16723
  for (let corner = 0; corner < 6; corner++) indices.push(start + sourceIndices.getX(face * 6 + corner) - face * 4);
15231
16724
  }
@@ -15234,11 +16727,12 @@ var SectionMesh = class _SectionMesh extends Group2 {
15234
16727
  geometry.setAttribute("position", new Float32BufferAttribute8(positions, 3));
15235
16728
  geometry.setAttribute("normal", new Float32BufferAttribute8(normals, 3));
15236
16729
  geometry.setAttribute("uv", new Float32BufferAttribute8(uvs, 2));
16730
+ geometry.setAttribute("uvBounds", new Float32BufferAttribute8(uvBounds, 4));
15237
16731
  geometry.setAttribute("color", new Float32BufferAttribute8(colors, 3));
15238
16732
  geometry.setIndex(indices);
15239
16733
  geometry.computeBoundingBox();
15240
16734
  geometry.computeBoundingSphere();
15241
- const material = Materials.createShadedCanvasMaterial(canvas, false);
16735
+ const material = Materials.createShadedCanvasMaterial(canvas, false, false, true);
15242
16736
  material.vertexColors = true;
15243
16737
  const texture = material.uniforms?.map?.value ?? material.map;
15244
16738
  const mesh = new Mesh9(geometry, material);
@@ -15299,7 +16793,7 @@ var Chunk = class _Chunk {
15299
16793
  return this.setBlockInChunkAt(pos, block, worldPos);
15300
16794
  }
15301
16795
  /**
15302
- * Set a block at integer chunk-local coordinates from 0 to 15.
16796
+ * Places a block at integer chunk-local coordinates from 0 to 15.
15303
16797
  */
15304
16798
  async setBlockInChunkAt(pos, block, worldPos, onBlocksChanged = this.onBlocksChanged) {
15305
16799
  if (typeof worldPos === "undefined") {
@@ -15312,7 +16806,7 @@ var Chunk = class _Chunk {
15312
16806
  this.renderedBlocks.get(index)?.object?.removeFromScene();
15313
16807
  if (this.sectionBlocks.delete(index)) this.meshDirty = true;
15314
16808
  this.renderedBlocks.delete(index);
15315
- let object;
16809
+ let object3;
15316
16810
  try {
15317
16811
  if (!readBlock) return void 0;
15318
16812
  const stored = readBlock();
@@ -15327,27 +16821,28 @@ var Chunk = class _Chunk {
15327
16821
  this.sectionBlocks.set(index, { index, template, cullMask: 0 });
15328
16822
  this.meshDirty = true;
15329
16823
  } else {
15330
- object = await this.scene.addBlock(blockState, {
16824
+ object3 = await this.scene.addBlock(blockState, {
15331
16825
  mergeMeshes: true,
15332
16826
  instanceMeshes: true,
15333
16827
  maxInstanceCount: 2e3,
15334
16828
  initialState: stored.properties
15335
16829
  });
15336
- object.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
16830
+ object3.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
15337
16831
  }
15338
16832
  const info = { get block() {
15339
16833
  return readBlock();
15340
- }, object };
16834
+ }, object: object3 };
15341
16835
  this.renderedBlocks.set(index, info);
15342
16836
  return info;
15343
16837
  } catch (error) {
15344
16838
  this.data.set(index, void 0);
15345
- object?.removeFromScene();
16839
+ object3?.removeFromScene();
15346
16840
  throw error;
15347
16841
  } finally {
15348
16842
  await onBlocksChanged?.([worldPos]);
15349
16843
  }
15350
16844
  }
16845
+ /** Changes visibility at world block coordinates while preserving the block's data. */
15351
16846
  async setBlockVisibleAt(pos, visible) {
15352
16847
  const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
15353
16848
  const block = this.renderedBlocks.get(index);
@@ -15401,6 +16896,7 @@ var Chunk = class _Chunk {
15401
16896
  this.scene.dirty = true;
15402
16897
  this.meshDirty = false;
15403
16898
  }
16899
+ /** Removes stored blocks and their render objects, then notifies the owning world of changed positions. */
15404
16900
  async clear(onBlocksChanged = this.onBlocksChanged) {
15405
16901
  const positions = [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
15406
16902
  new Vector317(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
@@ -15421,6 +16917,7 @@ var Chunk = class _Chunk {
15421
16917
  static isAir(block) {
15422
16918
  return ChunkData.isAir(block);
15423
16919
  }
16920
+ /** Maps chunk-local coordinates from 0 to 15 to a storage index, with x varying fastest, then z, then y. */
15424
16921
  static chunkPosToBlockIndex(pos) {
15425
16922
  if ([pos.x, pos.y, pos.z].some((value) => !Number.isInteger(value) || value < 0 || value >= 16)) {
15426
16923
  throw new RangeError("Block coordinates must be integers from 0 to 15 within a chunk");
@@ -15455,6 +16952,7 @@ export {
15455
16952
  Axis,
15456
16953
  BUILTIN_ENTITY,
15457
16954
  BUILTIN_GENERATED,
16955
+ BannerPatterns,
15458
16956
  BasicAssetKey,
15459
16957
  BatchedExecutor,
15460
16958
  BlockEntities,
@@ -15479,6 +16977,7 @@ export {
15479
16977
  DEFAULT_ROOT,
15480
16978
  DEFAULT_UV,
15481
16979
  DISPLAY_POSITIONS,
16980
+ DYE_COLORS,
15482
16981
  DefaultBlockStates,
15483
16982
  DisplayPosition,
15484
16983
  DisplayTransforms,
@@ -15489,6 +16988,7 @@ export {
15489
16988
  Env,
15490
16989
  FluidModelObject,
15491
16990
  Fonts,
16991
+ GUI_CONTAINER_LAYOUTS,
15492
16992
  Geometries,
15493
16993
  GuiHelper,
15494
16994
  GuiLight,
@@ -15515,11 +17015,14 @@ export {
15515
17015
  Models,
15516
17016
  NBTHelper,
15517
17017
  OrbitControls,
17018
+ PackFormats,
17019
+ PackMetadata,
15518
17020
  PersistentCache,
15519
17021
  Renderer,
15520
17022
  RequestError,
15521
17023
  Requests,
15522
17024
  SKIN_PARTS,
17025
+ SceneDocumentLoader,
15523
17026
  SceneExporter,
15524
17027
  SceneInspector,
15525
17028
  SceneObject,
@@ -15595,6 +17098,7 @@ export {
15595
17098
  mergeBufferAttributes,
15596
17099
  mergeBufferGeometries,
15597
17100
  mergeVertices,
17101
+ parseSceneDocument,
15598
17102
  prefix,
15599
17103
  probeImageSize,
15600
17104
  registerBrowserEnv,