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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +10 -1
  2. package/dist/browser/esm/armor/Armor.mjs +84 -0
  3. package/dist/browser/esm/armor/Armor.mjs.map +1 -0
  4. package/dist/browser/esm/armor/index.mjs +3 -0
  5. package/dist/browser/esm/armor/index.mjs.map +1 -0
  6. package/dist/browser/esm/assets/BlockEntities.mjs +2 -0
  7. package/dist/browser/esm/assets/BlockEntities.mjs.map +1 -1
  8. package/dist/browser/esm/assets/Entities.mjs +39 -11
  9. package/dist/browser/esm/assets/Entities.mjs.map +1 -1
  10. package/dist/browser/esm/assets/MineRenderData.mjs +8 -17
  11. package/dist/browser/esm/assets/MineRenderData.mjs.map +1 -1
  12. package/dist/browser/esm/assets/ModelTextures.mjs +13 -7
  13. package/dist/browser/esm/assets/ModelTextures.mjs.map +1 -1
  14. package/dist/browser/esm/assets/_versions/DatasetVersions.mjs +22 -0
  15. package/dist/browser/esm/assets/_versions/DatasetVersions.mjs.map +1 -0
  16. package/dist/browser/esm/cache/Caching.mjs +8 -0
  17. package/dist/browser/esm/cache/Caching.mjs.map +1 -1
  18. package/dist/browser/esm/entity/scene/EntityObject.mjs +31 -5
  19. package/dist/browser/esm/entity/scene/EntityObject.mjs.map +1 -1
  20. package/dist/browser/esm/env/browser/BrowserEnv.mjs +11 -4
  21. package/dist/browser/esm/env/browser/BrowserEnv.mjs.map +1 -1
  22. package/dist/browser/esm/index.browser.d.mts +129 -13
  23. package/dist/browser/esm/index.browser.mjs.map +1 -1
  24. package/dist/browser/esm/index.mjs +1 -0
  25. package/dist/browser/esm/index.mjs.map +1 -1
  26. package/dist/browser/esm/model/ItemGlint.mjs +7 -6
  27. package/dist/browser/esm/model/ItemGlint.mjs.map +1 -1
  28. package/dist/browser/esm/model/block/scene/BlockObject.mjs +16 -5
  29. package/dist/browser/esm/model/block/scene/BlockObject.mjs.map +1 -1
  30. package/dist/browser/esm/scene/SceneDocument.mjs +35 -5
  31. package/dist/browser/esm/scene/SceneDocument.mjs.map +1 -1
  32. package/dist/browser/esm/scene/SceneDocumentLoader.mjs +14 -3
  33. package/dist/browser/esm/scene/SceneDocumentLoader.mjs.map +1 -1
  34. package/dist/browser/esm/skin/SkinGeometries.mjs +4 -1
  35. package/dist/browser/esm/skin/SkinGeometries.mjs.map +1 -1
  36. package/dist/browser/esm/skin/SkinPart.mjs +2 -0
  37. package/dist/browser/esm/skin/SkinPart.mjs.map +1 -1
  38. package/dist/browser/esm/skin/SkinTextureCoordinates.mjs +11 -1
  39. package/dist/browser/esm/skin/SkinTextureCoordinates.mjs.map +1 -1
  40. package/dist/browser/esm/skin/SkinTextures.mjs +1 -1
  41. package/dist/browser/esm/skin/SkinTextures.mjs.map +1 -1
  42. package/dist/browser/esm/skin/scene/SkinObject.mjs +119 -7
  43. package/dist/browser/esm/skin/scene/SkinObject.mjs.map +1 -1
  44. package/dist/browser/esm/texture/PalettedTextures.mjs +46 -0
  45. package/dist/browser/esm/texture/PalettedTextures.mjs.map +1 -0
  46. package/dist/browser/esm/texture/index.mjs +1 -0
  47. package/dist/browser/esm/texture/index.mjs.map +1 -1
  48. package/dist/browser/esm/util/util.mjs +0 -2
  49. package/dist/browser/esm/util/util.mjs.map +1 -1
  50. package/dist/browser/esm/world/Chunk.mjs +62 -33
  51. package/dist/browser/esm/world/Chunk.mjs.map +1 -1
  52. package/dist/browser/esm/world/MineRenderWorld.mjs +98 -13
  53. package/dist/browser/esm/world/MineRenderWorld.mjs.map +1 -1
  54. package/dist/browser/esm/world/SectionMesh.mjs +65 -9
  55. package/dist/browser/esm/world/SectionMesh.mjs.map +1 -1
  56. package/dist/browser/esm/world/SectionModels.mjs +21 -15
  57. package/dist/browser/esm/world/SectionModels.mjs.map +1 -1
  58. package/dist/browser/esm/world/SectionWorker.mjs +8 -2
  59. package/dist/browser/esm/world/SectionWorker.mjs.map +1 -1
  60. package/dist/browser/esm/world/WorldStreamer.mjs +109 -15
  61. package/dist/browser/esm/world/WorldStreamer.mjs.map +1 -1
  62. package/dist/browser/index.d.ts +129 -13
  63. package/dist/browser/index.js +1592 -894
  64. package/dist/browser/index.js.map +1 -1
  65. package/dist/bundle.js +1330 -619
  66. package/dist/bundle.js.map +1 -1
  67. package/dist/node/index.d.mts +117 -12
  68. package/dist/node/index.d.ts +117 -12
  69. package/dist/node/index.js +1505 -793
  70. package/dist/node/index.js.map +1 -1
  71. package/dist/node/index.mjs +1303 -593
  72. package/dist/node/index.mjs.map +1 -1
  73. package/package.json +2 -1
@@ -184,8 +184,6 @@ async function sleep(timeout) {
184
184
  });
185
185
  }
186
186
  function yieldToEventLoop() {
187
- const scheduler = globalThis.scheduler;
188
- if (scheduler?.yield) return scheduler.yield();
189
187
  return new Promise((resolve) => {
190
188
  if (typeof MessageChannel !== "undefined") {
191
189
  const channel = new MessageChannel();
@@ -242,6 +240,10 @@ var Caching;
242
240
  var init_Caching = __esm({
243
241
  "src/cache/Caching.ts"() {
244
242
  Caching = class _Caching {
243
+ static {
244
+ /** Cached in place of a missing asset. The caches drop `undefined` results, which would request the asset again. */
245
+ this.MISSING = false;
246
+ }
245
247
  static {
246
248
  this._generation = 0;
247
249
  }
@@ -309,6 +311,9 @@ var init_Caching = __esm({
309
311
  static {
310
312
  this.blockEntityIndexCache = _Caching.createAssetCache();
311
313
  }
314
+ static {
315
+ this.entityDatasetVersionCache = _Caching.createAssetCache();
316
+ }
312
317
  static {
313
318
  this.unihexCache = _Caching.createAssetCache();
314
319
  }
@@ -318,6 +323,12 @@ var init_Caching = __esm({
318
323
  static {
319
324
  this.bannerPatternCache = _Caching.createAssetCache();
320
325
  }
326
+ static {
327
+ this.equipmentCache = _Caching.createAssetCache();
328
+ }
329
+ static {
330
+ this.trimCache = _Caching.createAssetCache();
331
+ }
321
332
  static {
322
333
  this.mineRenderDataCache = _Caching.createAssetCache();
323
334
  }
@@ -353,9 +364,12 @@ var init_Caching = __esm({
353
364
  this.entityTextureCache,
354
365
  this.entityAnimationCache,
355
366
  this.blockEntityIndexCache,
367
+ this.entityDatasetVersionCache,
356
368
  this.fontCache,
357
369
  this.unihexCache,
358
370
  this.bannerPatternCache,
371
+ this.equipmentCache,
372
+ this.trimCache,
359
373
  this.mineRenderDataCache,
360
374
  this.usernameUuidCache,
361
375
  this.biomeCache
@@ -2403,15 +2417,21 @@ var init_ModelTextures = __esm({
2403
2417
  key.extension += ".mcmeta";
2404
2418
  }
2405
2419
  const keyStr = key.serialize();
2406
- return Caching.textureMetaCache.get(keyStr, (k) => {
2407
- return this.PERSISTENT_META_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
2408
- return AssetLoader.get(key, "meta" /* META */).then((asset2) => {
2409
- if (asset2)
2410
- asset2.key = key;
2411
- return asset2;
2412
- });
2420
+ const meta = await Caching.textureMetaCache.get(keyStr, (k) => {
2421
+ return this.PERSISTENT_META_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), async (k1) => {
2422
+ if (AssetLoader.persistentScope === "") {
2423
+ const path = key.getFullPath();
2424
+ const slash = path.lastIndexOf("/");
2425
+ const listKey = new AssetKey(key.namespace, `${path.slice(0, slash + 1)}_list`, "textures", void 0, "assets", ".json", key.root);
2426
+ const list2 = await Caching.listAssetCache.get(listKey.serialize(), () => AssetLoader.get(listKey, "list" /* LIST */)).catch(() => void 0);
2427
+ if (list2 && !list2.files.includes(`${path.slice(slash + 1)}${key.extension}`)) return Caching.MISSING;
2428
+ }
2429
+ const asset2 = await AssetLoader.get(key, "meta" /* META */);
2430
+ if (asset2) asset2.key = key;
2431
+ return asset2 ?? Caching.MISSING;
2413
2432
  });
2414
2433
  });
2434
+ return meta === Caching.MISSING ? void 0 : meta;
2415
2435
  }
2416
2436
  /** Clears persisted texture metadata. Use {@link Caching.clear} to discard in-memory textures and metadata. */
2417
2437
  static async clearCache() {
@@ -3331,23 +3351,303 @@ var init_MinecraftCubeTexture = __esm({
3331
3351
  }
3332
3352
  });
3333
3353
 
3354
+ // src/assets/_versions/DatasetVersions.ts
3355
+ var parseRelease, compare2, nearestRelease;
3356
+ var init_DatasetVersions = __esm({
3357
+ "src/assets/_versions/DatasetVersions.ts"() {
3358
+ parseRelease = (value) => /^\d+\.\d+(?:\.\d+)?$/.test(value) ? value.split(".").map(Number) : void 0;
3359
+ compare2 = (left, right) => {
3360
+ for (let i = 0; i < 3; i++) {
3361
+ const difference = (left[i] ?? 0) - (right[i] ?? 0);
3362
+ if (difference) return difference;
3363
+ }
3364
+ return 0;
3365
+ };
3366
+ nearestRelease = (requestedVersion, published) => {
3367
+ const requested = parseRelease(requestedVersion);
3368
+ if (!requested) return void 0;
3369
+ const versions = published.map((name) => ({ name, number: parseRelease(name) })).filter((entry) => !!entry.number).sort((left, right) => compare2(left.number, right.number));
3370
+ const sameLine = versions.filter((entry) => entry.number[0] === requested[0] && entry.number[1] === requested[1]);
3371
+ const selected = sameLine.sort((left, right) => Math.abs((left.number[2] ?? 0) - (requested[2] ?? 0)) - Math.abs((right.number[2] ?? 0) - (requested[2] ?? 0)) || compare2(left.number, right.number))[0] ?? versions.filter((entry) => compare2(entry.number, requested) <= 0).pop() ?? versions[0];
3372
+ return selected?.name;
3373
+ };
3374
+ }
3375
+ });
3376
+
3377
+ // src/error/MineRenderError.ts
3378
+ var MineRenderError;
3379
+ var init_MineRenderError = __esm({
3380
+ "src/error/MineRenderError.ts"() {
3381
+ MineRenderError = class _MineRenderError extends Error {
3382
+ constructor(msg) {
3383
+ super(msg);
3384
+ Object.setPrototypeOf(this, _MineRenderError.prototype);
3385
+ }
3386
+ get name() {
3387
+ return "MineRenderError";
3388
+ }
3389
+ };
3390
+ }
3391
+ });
3392
+
3393
+ // src/assets/Entities.ts
3394
+ var Entities;
3395
+ var init_Entities = __esm({
3396
+ "src/assets/Entities.ts"() {
3397
+ init_AssetKey();
3398
+ init_DatasetVersions();
3399
+ init_AssetLoader();
3400
+ init_source();
3401
+ init_Caching();
3402
+ init_AssetDefaults();
3403
+ init_MineRenderError();
3404
+ init_ModelTextures();
3405
+ Entities = class {
3406
+ /**
3407
+ * Resolves `root` (default: the active version). Uses the exact dataset when published or when the
3408
+ * version is not a release; otherwise selects the nearest published release, like {@link MineRenderData.resolve}.
3409
+ * Textures keep the requested root.
3410
+ */
3411
+ static async getDatasetRoot(root) {
3412
+ const requestRoot = (root ?? AssetLoader.ROOT).replace(/\/+$/, "");
3413
+ const requestedVersion = requestRoot.slice(requestRoot.lastIndexOf("/") + 1);
3414
+ const base = requestRoot.slice(0, requestRoot.lastIndexOf("/"));
3415
+ const selected = await Caching.entityDatasetVersionCache.get(root ?? `sources:${AssetLoader.ROOT}`, async () => {
3416
+ const key = new AssetKey("entity-models", "version", void 0, void 0, void 0, ".json", root);
3417
+ key.rootType = void 0;
3418
+ if (!parseRelease(requestedVersion) || await AssetLoader.get(key, "json" /* JSON */) !== void 0) return requestedVersion;
3419
+ const indexKey = new AssetKey("entity-models", "versions", void 0, void 0, void 0, ".json", base);
3420
+ indexKey.rootType = void 0;
3421
+ const index = await AssetLoader.get(indexKey, "json" /* JSON */);
3422
+ if (index === void 0) return requestedVersion;
3423
+ if (!index || typeof index !== "object" || Array.isArray(index) || !Array.isArray(index.versions) || !index.versions.every((entry) => entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.name === "string")) {
3424
+ throw new MineRenderError(`Invalid entity model version index at ${base}/entity-models/versions.json`);
3425
+ }
3426
+ return nearestRelease(requestedVersion, index.versions.map((entry) => entry.name)) ?? requestedVersion;
3427
+ });
3428
+ return selected === requestedVersion ? root : `${base}/${selected}`;
3429
+ }
3430
+ /** Lists model paths in the `minecraft` namespace, including subdirectories and excluding `.json`. */
3431
+ static async getEntityList() {
3432
+ const root = await this.getDatasetRoot();
3433
+ const collect = async (path) => {
3434
+ const prefix2 = path ? `${path}/` : "";
3435
+ const key = new AssetKey(DEFAULT_NAMESPACE, `${prefix2}_list`, void 0, void 0, "entity-models", ".json", root);
3436
+ const list2 = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get(key, "list" /* LIST */));
3437
+ if (!list2) return [];
3438
+ const files = list2.files.filter((file) => file.endsWith(".json") && file !== "_list.json").map((file) => `${prefix2}${file.slice(0, -5)}`);
3439
+ const children = await Promise.all(list2.directories.map((directory) => collect(`${prefix2}${directory}`)));
3440
+ return [...files, ...children.flat()];
3441
+ };
3442
+ return collect("");
3443
+ }
3444
+ /** @internal */
3445
+ static async getModelFile(modelKey) {
3446
+ const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
3447
+ const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models", ".json", await this.getDatasetRoot(isAssetKey(modelKey) ? modelKey.root : void 0));
3448
+ return Caching.entityModelCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
3449
+ }
3450
+ /** Returns selectable geometry-layer names, or an empty list when the model is missing. */
3451
+ static async getLayerList(modelKey) {
3452
+ return Object.keys((await this.getModelFile(modelKey))?.layers ?? {});
3453
+ }
3454
+ /** The dataset's extra draws on top of `main`; their `when` labels are the states `EntityModelOptions.when` can enable. */
3455
+ static async getPassList(modelKey) {
3456
+ return (await this.getModelFile(modelKey))?.passes ?? [];
3457
+ }
3458
+ /**
3459
+ * Loads the layers and textures to pass to {@link MineRenderScene.addEntity}.
3460
+ * By default, selects `main`, unconditional passes, and passes enabled by `options.when`.
3461
+ *
3462
+ * @param modelKey - Dataset model, such as `new BasicAssetKey("minecraft", "zombie")`.
3463
+ * @param textureKey - Texture override for the first selected layer.
3464
+ * @param options - Layer selection, conditional passes, and per-layer texture overrides.
3465
+ * @returns The selected model, or `undefined` when its dataset file is missing.
3466
+ */
3467
+ static async getEntity(modelKey, textureKey, options) {
3468
+ const model = await this.getModelFile(modelKey);
3469
+ if (!model) return void 0;
3470
+ const passes = options?.layers || options?.layer !== void 0 ? [] : (model.passes ?? []).filter((pass) => pass.when === void 0 || options?.when?.includes(pass.when));
3471
+ const draws = [...new Set(options?.layers ?? [options?.layer ?? "main"])].map((layer) => ({ layer }));
3472
+ if (draws.length === 0) throw new MineRenderError(`Entity ${model.id} requires at least one layer`);
3473
+ draws.push(...passes);
3474
+ const names = [];
3475
+ const selected = draws.map((pass) => {
3476
+ const layer = model.layers[pass.layer];
3477
+ if (!layer) {
3478
+ throw new MineRenderError(`Entity ${model.id} has no layer "${pass.layer}". Available layers: ${Object.keys(model.layers).join(", ")}`);
3479
+ }
3480
+ let name = pass.layer;
3481
+ for (let n = 2; names.includes(name); n++) name = `${pass.layer}#${n}`;
3482
+ names.push(name);
3483
+ return { name, layer, pass };
3484
+ });
3485
+ const layers = Object.fromEntries(await Promise.all(selected.map(async ({ name, layer, pass }, index) => {
3486
+ const override = options?.textures?.[name] ?? (index === 0 ? textureKey : void 0);
3487
+ const textureLocation = pass.textureLocation ?? layer.textureLocation;
3488
+ let texture;
3489
+ if (override) {
3490
+ const path = isAssetKey(override) ? override.getFullPath() : override.path;
3491
+ texture = isAssetKey(override) && override.assetType === "textures" && path.startsWith("entity/") ? override : new AssetKey(override.namespace, path, "textures", "entity", "assets", ".png", isAssetKey(override) ? override.root : void 0);
3492
+ } else if (textureLocation !== void 0) {
3493
+ texture = AssetKey.parse("textures", textureLocation.replace(/^([^:]+:)?textures\//, "$1"));
3494
+ if (isAssetKey(modelKey)) texture.root = modelKey.root;
3495
+ } else {
3496
+ texture = await this.resolveTexture(modelKey);
3497
+ }
3498
+ const render = pass.render ?? layer.render;
3499
+ return [name, { key: override ?? modelKey, texture, layer, ...render && { render }, ...pass.tint && { tint: pass.tint } }];
3500
+ })));
3501
+ return { ...layers[names[0]], id: model.id, layers, ...model.transform && { transform: model.transform } };
3502
+ }
3503
+ /**
3504
+ * Native and sampled vanilla clips by name, or undefined when the model or selected version has none.
3505
+ */
3506
+ static async getAnimations(modelKey) {
3507
+ const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
3508
+ const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models/animations", ".json", await this.getDatasetRoot(isAssetKey(modelKey) ? modelKey.root : void 0));
3509
+ const file = await Caching.entityAnimationCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
3510
+ return file?.animations;
3511
+ }
3512
+ /** Finds a fallback texture using the model path, its directory, and vanilla variant data. */
3513
+ static async resolveTexture(modelKey) {
3514
+ const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
3515
+ const root = isAssetKey(modelKey) ? modelKey.root : void 0;
3516
+ const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models", ".json", root);
3517
+ const texture = await Caching.entityTextureCache.get(key.serialize(), async () => {
3518
+ const name = path.split("/").pop();
3519
+ for (const candidate of [path, `${path}/${name}`]) {
3520
+ const texture3 = new AssetKey(modelKey.namespace, candidate, "textures", "entity", "assets", ".png", root);
3521
+ if (await ModelTextures.get(texture3)) return texture3;
3522
+ }
3523
+ const listKey = new AssetKey(modelKey.namespace, "_list", `${name}_variant`, void 0, "data", ".json", root);
3524
+ const list2 = await AssetLoader.get(listKey, "list" /* LIST */);
3525
+ const files = list2?.files.filter((file2) => file2.endsWith(".json") && file2 !== "_list.json");
3526
+ const file = files?.find((file2) => file2 === "temperate.json") ?? files?.[0];
3527
+ if (!file) return Caching.MISSING;
3528
+ const variantKey = new AssetKey(modelKey.namespace, file.slice(0, -5), `${name}_variant`, void 0, "data", ".json", root);
3529
+ const variant = await AssetLoader.get(variantKey, "json" /* JSON */);
3530
+ const assets = variant?.assets;
3531
+ const textureId = variant?.asset_id ?? (typeof assets?.wild === "string" ? assets.wild : Object.values(assets ?? {}).find((value) => typeof value === "string"));
3532
+ if (typeof textureId !== "string") return Caching.MISSING;
3533
+ const texture2 = AssetKey.parse("textures", textureId);
3534
+ texture2.root = root;
3535
+ return texture2;
3536
+ });
3537
+ return texture === Caching.MISSING ? void 0 : texture;
3538
+ }
3539
+ /** @deprecated Use getEntityList(); block entities share the entity dataset. */
3540
+ static getBlockList() {
3541
+ return this.getEntityList();
3542
+ }
3543
+ /** @deprecated Use getEntity(); block entities share the entity dataset. */
3544
+ static getBlock(modelKey, textureKey, options) {
3545
+ return this.getEntity(modelKey, textureKey, options);
3546
+ }
3547
+ };
3548
+ }
3549
+ });
3550
+
3551
+ // src/armor/Armor.ts
3552
+ var Armor_exports = {};
3553
+ __export(Armor_exports, {
3554
+ Armor: () => Armor
3555
+ });
3556
+ var ARMOR_TRANSFORM, Armor;
3557
+ var init_Armor = __esm({
3558
+ "src/armor/Armor.ts"() {
3559
+ init_AssetKey();
3560
+ init_AssetLoader();
3561
+ init_Entities();
3562
+ init_AssetParsers();
3563
+ init_Caching();
3564
+ init_MineRenderError();
3565
+ ARMOR_TRANSFORM = [
3566
+ { rotate: [0, Math.PI, 0] },
3567
+ { scale: [-1, -1, 1] },
3568
+ { translate: [0, -24, 0] }
3569
+ ];
3570
+ Armor = class {
3571
+ /** Loads 1.21.4+ armor in SkinObject's frame, or returns undefined when no layer draws. */
3572
+ static async getEntity(armor) {
3573
+ const model = await Entities.getModelFile(new BasicAssetKey("minecraft", "player"));
3574
+ const layers = {};
3575
+ const slots = { chest: "chestplate", legs: "leggings", feet: "boots", head: "helmet" };
3576
+ for (const slot of Object.keys(slots)) {
3577
+ const value = armor[slot];
3578
+ if (value === void 0) continue;
3579
+ const piece = typeof value === "string" ? { asset: value } : value;
3580
+ const name = slots[slot], geometry = model?.layers[name];
3581
+ if (!geometry) throw new MineRenderError(`Missing armor geometry ${name}; armor needs Minecraft 1.21.4 or later`);
3582
+ const id = this.identifier(piece.asset);
3583
+ const key = new AssetKey(id.namespace, id.path, "equipment", void 0, "assets", ".json");
3584
+ const equipment = await Caching.equipmentCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
3585
+ if (!equipment) throw new MineRenderError(`Missing equipment asset ${piece.asset}; armor takes equipment asset IDs such as minecraft:diamond and needs Minecraft 1.21.4 or later`);
3586
+ const layerType = slot === "legs" ? "humanoid_leggings" : "humanoid";
3587
+ const equipmentLayers = equipment.layers[layerType];
3588
+ if (!equipmentLayers?.length) continue;
3589
+ let count = 0;
3590
+ const add = (texture, settings) => {
3591
+ layers[++count === 1 ? name : `${name}#${count}`] = { key: texture, texture, layer: geometry, ...settings };
3592
+ };
3593
+ let glint = piece.glint;
3594
+ for (const layer of equipmentLayers) {
3595
+ let color2;
3596
+ if (layer.dyeable) {
3597
+ const undyed = layer.dyeable.color_when_undyed;
3598
+ color2 = piece.color || (Array.isArray(undyed) ? Math.floor(undyed[0] * 255) << 16 | Math.floor(undyed[1] * 255) << 8 | Math.floor(undyed[2] * 255) : undyed === void 0 ? void 0 : undyed & 16777215);
3599
+ if (color2 === void 0) continue;
3600
+ }
3601
+ const texture = this.identifier(layer.texture);
3602
+ add(
3603
+ new AssetKey(texture.namespace, `equipment/${layerType}/${texture.path}`, "textures", "entity", "assets", ".png"),
3604
+ { ...color2 !== void 0 && { color: color2 }, ...glint && { glint: true } }
3605
+ );
3606
+ glint = false;
3607
+ }
3608
+ if (piece.trim) {
3609
+ const patternId = this.identifier(piece.trim.pattern), materialId = this.identifier(piece.trim.material);
3610
+ const patternKey = new AssetKey(patternId.namespace, patternId.path, "trim_pattern", void 0, "data", ".json");
3611
+ const materialKey = new AssetKey(materialId.namespace, materialId.path, "trim_material", void 0, "data", ".json");
3612
+ const pattern2 = await Caching.trimCache.get(patternKey.serialize(), () => AssetLoader.get(patternKey, "json" /* JSON */));
3613
+ if (!pattern2) throw new MineRenderError(`Missing trim pattern ${piece.trim.pattern}`);
3614
+ const material = await Caching.trimCache.get(materialKey.serialize(), () => AssetLoader.get(materialKey, "json" /* JSON */));
3615
+ if (!material) throw new MineRenderError(`Missing trim material ${piece.trim.material}`);
3616
+ const patternAsset = this.identifier(pattern2.asset_id);
3617
+ const suffix = material.override_armor_assets?.[id.toNamespacedString()] ?? material.asset_name;
3618
+ add(new AssetKey(patternAsset.namespace, `entity/${layerType}/${patternAsset.path}`, "textures", "trims", "assets", ".png"), {
3619
+ palette: {
3620
+ key: new AssetKey("minecraft", "color_palettes/trim_palette", "textures", "trims", "assets", ".png"),
3621
+ permutation: new AssetKey("minecraft", `color_palettes/${suffix}`, "textures", "trims", "assets", ".png")
3622
+ },
3623
+ render: pattern2.decal ? "cutout_z_offset" : "cutout"
3624
+ });
3625
+ }
3626
+ }
3627
+ const first = Object.values(layers)[0];
3628
+ return first ? { ...first, id: "minecraft:player", layers, transform: ARMOR_TRANSFORM } : void 0;
3629
+ }
3630
+ static identifier(value) {
3631
+ if (!isResourceLocation(value) || value.split(/[:/]/).some((part) => !part || part === "." || part === "..")) {
3632
+ throw new MineRenderError(`Invalid armor identifier ${value}`);
3633
+ }
3634
+ const [namespace, path] = value.includes(":") ? value.split(":") : ["minecraft", value];
3635
+ return new BasicAssetKey(namespace, path);
3636
+ }
3637
+ };
3638
+ }
3639
+ });
3640
+
3334
3641
  // src/assets/MineRenderData.ts
3335
- var object2, release, compare2, MineRenderData;
3642
+ var object2, MineRenderData;
3336
3643
  var init_MineRenderData = __esm({
3337
3644
  "src/assets/MineRenderData.ts"() {
3645
+ init_DatasetVersions();
3338
3646
  init_AssetKey();
3339
3647
  init_AssetLoader();
3340
3648
  init_AssetParsers();
3341
3649
  init_Caching();
3342
3650
  object2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
3343
- release = (value) => /^\d+\.\d+(?:\.\d+)?$/.test(value) ? value.split(".").map(Number) : void 0;
3344
- compare2 = (left, right) => {
3345
- for (let i = 0; i < 3; i++) {
3346
- const difference = (left[i] ?? 0) - (right[i] ?? 0);
3347
- if (difference) return difference;
3348
- }
3349
- return 0;
3350
- };
3351
3651
  MineRenderData = class {
3352
3652
  /** Loads one registry. Missing files and invalid data reject the request. */
3353
3653
  static async get(dataset, root) {
@@ -3377,9 +3677,9 @@ var init_MineRenderData = __esm({
3377
3677
  const scope = AssetLoader.persistentScope;
3378
3678
  return this.cached(`resolve:${requestRoot === void 0 ? "sources" : "explicit"}:${root}`, scope, defaultRoot, async () => {
3379
3679
  const requestedVersion = root.slice(root.lastIndexOf("/") + 1);
3380
- let manifest = await this.manifest(root, requestRoot !== void 0 && release(requestedVersion) ? requestedVersion : void 0, requestRoot);
3680
+ let manifest = await this.manifest(root, requestRoot !== void 0 && parseRelease(requestedVersion) ? requestedVersion : void 0, requestRoot);
3381
3681
  if (manifest) return { requestedVersion, version: manifest.minecraftVersion, root, manifest };
3382
- const requested = release(requestedVersion);
3682
+ const requested = parseRelease(requestedVersion);
3383
3683
  if (!requested) {
3384
3684
  throw new Error(`No MineRender data is published for ${requestedVersion}; automatic fallback requires a release`);
3385
3685
  }
@@ -3391,14 +3691,12 @@ var init_MineRenderData = __esm({
3391
3691
  }
3392
3692
  return value.versions;
3393
3693
  });
3394
- const versions = index.map((entry) => ({ name: entry.name, number: release(entry.name) })).filter((entry) => !!entry.number).sort((left, right) => compare2(left.number, right.number));
3395
- const sameLine = versions.filter((entry) => entry.number[0] === requested[0] && entry.number[1] === requested[1]);
3396
- const selected = sameLine.sort((left, right) => Math.abs((left.number[2] ?? 0) - (requested[2] ?? 0)) - Math.abs((right.number[2] ?? 0) - (requested[2] ?? 0)) || compare2(left.number, right.number))[0] ?? versions.filter((entry) => compare2(entry.number, requested) <= 0).pop() ?? versions[0];
3694
+ const selected = nearestRelease(requestedVersion, index.map((entry) => entry.name));
3397
3695
  if (!selected) throw new Error(`No compatible MineRender data is published for ${requestedVersion}`);
3398
- const resolvedRoot = `${base}/${selected.name}`;
3399
- manifest = await this.manifest(resolvedRoot, selected.name, resolvedRoot);
3400
- if (!manifest) throw new Error(`Missing MineRender manifest for published version ${selected.name}`);
3401
- return { requestedVersion, version: selected.name, root: resolvedRoot, manifest };
3696
+ const resolvedRoot = `${base}/${selected}`;
3697
+ manifest = await this.manifest(resolvedRoot, selected, resolvedRoot);
3698
+ if (!manifest) throw new Error(`Missing MineRender manifest for published version ${selected}`);
3699
+ return { requestedVersion, version: selected, root: resolvedRoot, manifest };
3402
3700
  });
3403
3701
  }
3404
3702
  static async manifest(root, expectedVersion, requestRoot) {
@@ -3730,22 +4028,6 @@ var init_ItemDefaults = __esm({
3730
4028
  }
3731
4029
  });
3732
4030
 
3733
- // src/error/MineRenderError.ts
3734
- var MineRenderError;
3735
- var init_MineRenderError = __esm({
3736
- "src/error/MineRenderError.ts"() {
3737
- MineRenderError = class _MineRenderError extends Error {
3738
- constructor(msg) {
3739
- super(msg);
3740
- Object.setPrototypeOf(this, _MineRenderError.prototype);
3741
- }
3742
- get name() {
3743
- return "MineRenderError";
3744
- }
3745
- };
3746
- }
3747
- });
3748
-
3749
4031
  // src/texture/Colormaps.ts
3750
4032
  var Colormaps;
3751
4033
  var init_Colormaps = __esm({
@@ -4526,6 +4808,7 @@ var log9, BlockEntities;
4526
4808
  var init_BlockEntities = __esm({
4527
4809
  "src/assets/BlockEntities.ts"() {
4528
4810
  init_AssetKey();
4811
+ init_Entities();
4529
4812
  init_AssetLoader();
4530
4813
  init_source();
4531
4814
  init_Caching();
@@ -4542,6 +4825,7 @@ var init_BlockEntities = __esm({
4542
4825
  key.rootType = void 0;
4543
4826
  const index = await Caching.blockEntityIndexCache.get(key.serialize(), async () => {
4544
4827
  try {
4828
+ key.root = await Entities.getDatasetRoot(root);
4545
4829
  return await AssetLoader.get(key, "json" /* JSON */) ?? {};
4546
4830
  } catch (error) {
4547
4831
  log9.warn("Could not load the block entity index; block entities are drawn as plain block models", error);
@@ -4627,136 +4911,6 @@ var init_DecoratedPots = __esm({
4627
4911
  }
4628
4912
  });
4629
4913
 
4630
- // src/assets/Entities.ts
4631
- var Entities;
4632
- var init_Entities = __esm({
4633
- "src/assets/Entities.ts"() {
4634
- init_AssetKey();
4635
- init_AssetLoader();
4636
- init_source();
4637
- init_Caching();
4638
- init_AssetDefaults();
4639
- init_MineRenderError();
4640
- init_ModelTextures();
4641
- Entities = class {
4642
- /** Lists model paths in the `minecraft` namespace, including subdirectories and excluding `.json`. */
4643
- static async getEntityList() {
4644
- const collect = async (path) => {
4645
- const prefix2 = path ? `${path}/` : "";
4646
- const key = new AssetKey(DEFAULT_NAMESPACE, `${prefix2}_list`, void 0, void 0, "entity-models", ".json");
4647
- const list2 = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get(key, "list" /* LIST */));
4648
- if (!list2) return [];
4649
- const files = list2.files.filter((file) => file.endsWith(".json") && file !== "_list.json").map((file) => `${prefix2}${file.slice(0, -5)}`);
4650
- const children = await Promise.all(list2.directories.map((directory) => collect(`${prefix2}${directory}`)));
4651
- return [...files, ...children.flat()];
4652
- };
4653
- return collect("");
4654
- }
4655
- static async getModelFile(modelKey) {
4656
- const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
4657
- const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models", ".json", isAssetKey(modelKey) ? modelKey.root : void 0);
4658
- return Caching.entityModelCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
4659
- }
4660
- /** Returns selectable geometry-layer names, or an empty list when the model is missing. */
4661
- static async getLayerList(modelKey) {
4662
- return Object.keys((await this.getModelFile(modelKey))?.layers ?? {});
4663
- }
4664
- /** The dataset's extra draws on top of `main`; their `when` labels are the states `EntityModelOptions.when` can enable. */
4665
- static async getPassList(modelKey) {
4666
- return (await this.getModelFile(modelKey))?.passes ?? [];
4667
- }
4668
- /**
4669
- * Loads the layers and textures to pass to {@link MineRenderScene.addEntity}.
4670
- * By default, selects `main`, unconditional passes, and passes enabled by `options.when`.
4671
- *
4672
- * @param modelKey - Dataset model, such as `new BasicAssetKey("minecraft", "zombie")`.
4673
- * @param textureKey - Texture override for the first selected layer.
4674
- * @param options - Layer selection, conditional passes, and per-layer texture overrides.
4675
- * @returns The selected model, or `undefined` when its dataset file is missing.
4676
- */
4677
- static async getEntity(modelKey, textureKey, options) {
4678
- const model = await this.getModelFile(modelKey);
4679
- if (!model) return void 0;
4680
- const passes = options?.layers || options?.layer !== void 0 ? [] : (model.passes ?? []).filter((pass) => pass.when === void 0 || options?.when?.includes(pass.when));
4681
- const draws = [...new Set(options?.layers ?? [options?.layer ?? "main"])].map((layer) => ({ layer }));
4682
- if (draws.length === 0) throw new MineRenderError(`Entity ${model.id} requires at least one layer`);
4683
- draws.push(...passes);
4684
- const names = [];
4685
- const selected = draws.map((pass) => {
4686
- const layer = model.layers[pass.layer];
4687
- if (!layer) {
4688
- throw new MineRenderError(`Entity ${model.id} has no layer "${pass.layer}". Available layers: ${Object.keys(model.layers).join(", ")}`);
4689
- }
4690
- let name = pass.layer;
4691
- for (let n = 2; names.includes(name); n++) name = `${pass.layer}#${n}`;
4692
- names.push(name);
4693
- return { name, layer, pass };
4694
- });
4695
- const layers = Object.fromEntries(await Promise.all(selected.map(async ({ name, layer, pass }, index) => {
4696
- const override = options?.textures?.[name] ?? (index === 0 ? textureKey : void 0);
4697
- const textureLocation = pass.textureLocation ?? layer.textureLocation;
4698
- let texture;
4699
- if (override) {
4700
- const path = isAssetKey(override) ? override.getFullPath() : override.path;
4701
- texture = isAssetKey(override) && override.assetType === "textures" && path.startsWith("entity/") ? override : new AssetKey(override.namespace, path, "textures", "entity", "assets", ".png", isAssetKey(override) ? override.root : void 0);
4702
- } else if (textureLocation !== void 0) {
4703
- texture = AssetKey.parse("textures", textureLocation.replace(/^([^:]+:)?textures\//, "$1"));
4704
- if (isAssetKey(modelKey)) texture.root = modelKey.root;
4705
- } else {
4706
- texture = await this.resolveTexture(modelKey);
4707
- }
4708
- const render = pass.render ?? layer.render;
4709
- return [name, { key: override ?? modelKey, texture, layer, ...render && { render }, ...pass.tint && { tint: pass.tint } }];
4710
- })));
4711
- return { ...layers[names[0]], id: model.id, layers, ...model.transform && { transform: model.transform } };
4712
- }
4713
- /**
4714
- * Native and sampled vanilla clips by name, or undefined when the model or selected version has none.
4715
- */
4716
- static async getAnimations(modelKey) {
4717
- const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
4718
- const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models/animations", ".json", isAssetKey(modelKey) ? modelKey.root : void 0);
4719
- const file = await Caching.entityAnimationCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
4720
- return file?.animations;
4721
- }
4722
- /** Finds a fallback texture using the model path, its directory, and vanilla variant data. */
4723
- static async resolveTexture(modelKey) {
4724
- const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
4725
- const root = isAssetKey(modelKey) ? modelKey.root : void 0;
4726
- const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models", ".json", root);
4727
- return Caching.entityTextureCache.get(key.serialize(), async () => {
4728
- const name = path.split("/").pop();
4729
- for (const candidate of [path, `${path}/${name}`]) {
4730
- const texture2 = new AssetKey(modelKey.namespace, candidate, "textures", "entity", "assets", ".png", root);
4731
- if (await ModelTextures.get(texture2)) return texture2;
4732
- }
4733
- const listKey = new AssetKey(modelKey.namespace, "_list", `${name}_variant`, void 0, "data", ".json", root);
4734
- const list2 = await AssetLoader.get(listKey, "list" /* LIST */);
4735
- const files = list2?.files.filter((file2) => file2.endsWith(".json") && file2 !== "_list.json");
4736
- const file = files?.find((file2) => file2 === "temperate.json") ?? files?.[0];
4737
- if (!file) return void 0;
4738
- const variantKey = new AssetKey(modelKey.namespace, file.slice(0, -5), `${name}_variant`, void 0, "data", ".json", root);
4739
- const variant = await AssetLoader.get(variantKey, "json" /* JSON */);
4740
- const assets = variant?.assets;
4741
- const textureId = variant?.asset_id ?? (typeof assets?.wild === "string" ? assets.wild : Object.values(assets ?? {}).find((value) => typeof value === "string"));
4742
- if (typeof textureId !== "string") return void 0;
4743
- const texture = AssetKey.parse("textures", textureId);
4744
- texture.root = root;
4745
- return texture;
4746
- });
4747
- }
4748
- /** @deprecated Use getEntityList(); block entities share the entity dataset. */
4749
- static getBlockList() {
4750
- return this.getEntityList();
4751
- }
4752
- /** @deprecated Use getEntity(); block entities share the entity dataset. */
4753
- static getBlock(modelKey, textureKey, options) {
4754
- return this.getEntity(modelKey, textureKey, options);
4755
- }
4756
- };
4757
- }
4758
- });
4759
-
4760
4914
  // src/assets/_fonts/Unihex.ts
4761
4915
  var Unihex_exports = {};
4762
4916
  __export(Unihex_exports, {
@@ -5896,6 +6050,266 @@ var init_model = __esm({
5896
6050
  }
5897
6051
  });
5898
6052
 
6053
+ // src/model/ItemGlint.ts
6054
+ var ItemGlint_exports = {};
6055
+ __export(ItemGlint_exports, {
6056
+ ItemGlint: () => ItemGlint
6057
+ });
6058
+ import { ClampToEdgeWrapping, CustomBlending as CustomBlending2, DoubleSide as DoubleSide2, EqualDepth, Float32BufferAttribute as Float32BufferAttribute3, LinearFilter, Mesh as Mesh4, OneFactor as OneFactor2, RepeatWrapping as RepeatWrapping2, ShaderMaterial as ShaderMaterial2, SrcColorFactor, Vector2 as Vector22, ZeroFactor } from "three";
6059
+ var ItemGlint;
6060
+ var init_ItemGlint = __esm({
6061
+ "src/model/ItemGlint.ts"() {
6062
+ init_AssetKey();
6063
+ init_Models();
6064
+ init_ModelTextures();
6065
+ init_Caching();
6066
+ init_Textures();
6067
+ init_Ticker();
6068
+ init_CubeFace();
6069
+ init_DisplayPosition();
6070
+ ItemGlint = class _ItemGlint {
6071
+ constructor(owner, material) {
6072
+ this.owner = owner;
6073
+ this.material = material;
6074
+ this.updateTime();
6075
+ }
6076
+ /** Enables glint for enchantments or a lodestone compass unless a boolean component overrides them. */
6077
+ static enabled(components = {}, itemId) {
6078
+ const override = Models.componentValue(components, "enchantment_glint_override");
6079
+ if (override !== void 0) {
6080
+ if (typeof override !== "boolean") throw new Error("Item enchantment_glint_override must be a boolean");
6081
+ return override;
6082
+ }
6083
+ if (itemId === "minecraft:compass") {
6084
+ Models.componentValue(components, "lodestone_tracker");
6085
+ if (Object.prototype.hasOwnProperty.call(components, "lodestone_tracker") || Object.prototype.hasOwnProperty.call(components, "minecraft:lodestone_tracker")) return true;
6086
+ }
6087
+ const enchantments = Models.componentValue(components, "enchantments");
6088
+ if (enchantments === void 0) return false;
6089
+ if (!enchantments || typeof enchantments !== "object" || Array.isArray(enchantments)) throw new Error("Item enchantments must be an object");
6090
+ return Object.keys(enchantments).length > 0;
6091
+ }
6092
+ /** @internal */
6093
+ static async create(owner, baseMap, root, entityMeshes, kind = "item") {
6094
+ const key = new AssetKey("minecraft", `enchanted_glint_${kind}`, "textures", "misc", "assets", ".png", root);
6095
+ const assetKey = key.serialize(), textureKey = `item-glint:${assetKey}`;
6096
+ let texture = Caching.textureCache.getIfPresent(textureKey);
6097
+ if (!texture) {
6098
+ const pending = ModelTextures.get(key);
6099
+ const cachedAsset = Caching.textureAssetCache.getIfPresent(assetKey);
6100
+ const [image, metadata] = await Promise.all([pending, ModelTextures.getMeta(key)]);
6101
+ if (!image) throw new Error(`Missing ${kind} glint texture ${key.toNamespacedString()}`);
6102
+ if (cachedAsset && Caching.textureAssetCache.getIfPresent(assetKey) !== cachedAsset) return this.create(owner, baseMap, root, entityMeshes, kind);
6103
+ texture = Caching.textureCache.get(textureKey, () => {
6104
+ const texture2 = Textures.createCanvasTexture(image.data.canvas);
6105
+ texture2.wrapS = texture2.wrapT = metadata?.texture?.clamp ? ClampToEdgeWrapping : RepeatWrapping2;
6106
+ if (metadata?.texture?.blur) texture2.magFilter = texture2.minFilter = LinearFilter;
6107
+ return texture2;
6108
+ });
6109
+ }
6110
+ const material = new ShaderMaterial2({
6111
+ name: "item-glint",
6112
+ transparent: true,
6113
+ depthWrite: false,
6114
+ depthFunc: EqualDepth,
6115
+ side: DoubleSide2,
6116
+ forceSinglePass: true,
6117
+ blending: CustomBlending2,
6118
+ blendSrc: SrcColorFactor,
6119
+ blendDst: OneFactor2,
6120
+ blendSrcAlpha: ZeroFactor,
6121
+ blendDstAlpha: OneFactor2,
6122
+ toneMapped: false,
6123
+ // TextureTransform.ARMOR_ENTITY_GLINT_TEXTURING uses a smaller UV scale than item glint.
6124
+ defines: entityMeshes ? { ENTITY_GLINT: true, ENTITY_GLINT_SCALE: kind === "armor" ? "0.16" : "0.5" } : {},
6125
+ uniforms: {
6126
+ ...!entityMeshes && { baseMap: { value: baseMap } },
6127
+ glintMap: { value: texture },
6128
+ glintOffset: { value: new Vector22() },
6129
+ glintAlpha: { value: 0.75 }
6130
+ },
6131
+ vertexShader: `
6132
+ uniform vec2 glintOffset;
6133
+ #ifndef ENTITY_GLINT
6134
+ attribute vec4 uvBounds;
6135
+ attribute vec2 glintUv;
6136
+ flat out vec4 vUvBounds;
6137
+ centroid out vec2 vUv;
6138
+ #endif
6139
+ varying vec2 vGlintUv;
6140
+ void main() {
6141
+ #ifdef ENTITY_GLINT
6142
+ vec2 p = vec2(uv.x, 1.0 - uv.y) * ENTITY_GLINT_SCALE;
6143
+ #else
6144
+ vUv = uv;
6145
+ vUvBounds = uvBounds;
6146
+ vec2 p = glintUv * 8.0;
6147
+ #endif
6148
+ float angle = radians(10.0);
6149
+ vGlintUv = vec2(cos(angle) * p.x - sin(angle) * p.y, sin(angle) * p.x + cos(angle) * p.y) + glintOffset;
6150
+ vec4 mvPosition = vec4(position, 1.0);
6151
+ mvPosition = modelViewMatrix * mvPosition;
6152
+ gl_Position = projectionMatrix * mvPosition;
6153
+ }
6154
+ `,
6155
+ fragmentShader: `
6156
+ #ifndef ENTITY_GLINT
6157
+ uniform sampler2D baseMap;
6158
+ flat in vec4 vUvBounds;
6159
+ centroid in vec2 vUv;
6160
+ #endif
6161
+ uniform sampler2D glintMap;
6162
+ uniform float glintAlpha;
6163
+ varying vec2 vGlintUv;
6164
+ void main() {
6165
+ #ifndef ENTITY_GLINT
6166
+ vec2 mapUv = clamp(vUv, vUvBounds.xy, vUvBounds.zw);
6167
+ if (texture2D(baseMap, mapUv).a < 0.01) discard;
6168
+ #endif
6169
+ vec4 color = texture2D(glintMap, vec2(vGlintUv.x, 1.0 - vGlintUv.y));
6170
+ if (color.a < 0.1) discard;
6171
+ gl_FragColor = color;
6172
+ #include <colorspace_fragment>
6173
+ gl_FragColor.rgb *= glintAlpha;
6174
+ }
6175
+ `
6176
+ });
6177
+ if (!entityMeshes) Object.assign(material.defaultAttributeValues, { uvBounds: [0, 0, 1, 1] });
6178
+ const targets = entityMeshes ? [...entityMeshes] : [];
6179
+ if (!entityMeshes) owner.iterateAllMeshes((mesh) => targets.push({ mesh }));
6180
+ for (const { mesh: base, parent = base } of targets) {
6181
+ const pass = new Mesh4(base.geometry, material);
6182
+ pass.name = `${base.name}:glint`;
6183
+ pass.userData.minerenderItemGlint = true;
6184
+ pass.raycast = () => {
6185
+ };
6186
+ pass.renderOrder = parent.renderOrder + 0.5;
6187
+ parent.add(pass);
6188
+ }
6189
+ return new _ItemGlint(owner, material);
6190
+ }
6191
+ /**
6192
+ * Maps glint before model centering and display transforms. Ordinary sprites approximate Minecraft's shared atlas density.
6193
+ * @internal
6194
+ */
6195
+ static mapUvs(geometry, faces, atlas, itemId, display) {
6196
+ const uv = geometry.getAttribute("uv");
6197
+ const mapped = new Float32Array(uv.count * 2);
6198
+ if (itemId === "minecraft:compass" || itemId === "minecraft:recovery_compass" || itemId === "minecraft:clock") {
6199
+ const positions = geometry.getAttribute("position"), normals = geometry.getAttribute("normal");
6200
+ const scale = display === "gui" /* GUI */ ? 0.5 : display === "firstperson_lefthand" /* FIRSTPERSON_LEFTHAND */ || display === "firstperson_righthand" /* FIRSTPERSON_RIGHTHAND */ ? 0.75 : 1;
6201
+ const factor = 1 / (16 * 128 * scale);
6202
+ for (let index = 0; index < positions.count; index++) {
6203
+ const x = positions.getX(index), y = positions.getY(index), z = positions.getZ(index);
6204
+ const nx = normals.getX(index), ny = normals.getY(index), nz = normals.getZ(index);
6205
+ const ax = Math.abs(nx), ay = Math.abs(ny), az = Math.abs(nz);
6206
+ let u, v;
6207
+ if (ay >= az && ay >= ax) {
6208
+ u = x;
6209
+ v = ny < 0 ? -z : z;
6210
+ } else if (az >= ax) {
6211
+ u = nz < 0 ? -x : x;
6212
+ v = -y;
6213
+ } else {
6214
+ u = nx < 0 ? -z : z;
6215
+ v = -y;
6216
+ }
6217
+ mapped[index * 2] = u * factor;
6218
+ mapped[index * 2 + 1] = v * factor;
6219
+ }
6220
+ geometry.setAttribute("glintUv", new Float32BufferAttribute3(mapped, 2));
6221
+ return;
6222
+ }
6223
+ for (const [faceIndex, face] of CUBE_FACES.entries()) {
6224
+ const texture = faces[face]?.texture?.substring(1);
6225
+ const position = texture && atlas.positions[texture] || [0, 0];
6226
+ const size = texture && atlas.sizes[texture] || [atlas.image.width, atlas.image.height];
6227
+ for (let corner = 0; corner < 4; corner++) {
6228
+ const index = faceIndex * 4 + corner;
6229
+ mapped[index * 2] = (uv.getX(index) * atlas.image.width - position[0]) / size[0] / 32;
6230
+ mapped[index * 2 + 1] = ((1 - uv.getY(index)) * atlas.image.height - position[1]) / size[1] / 32;
6231
+ }
6232
+ }
6233
+ geometry.setAttribute("glintUv", new Float32BufferAttribute3(mapped, 2));
6234
+ }
6235
+ updateTime() {
6236
+ const time = Math.trunc(Date.now() * 4);
6237
+ this.material.uniforms.glintOffset.value.set(-(time % 11e4) / 11e4, time % 3e4 / 3e4);
6238
+ }
6239
+ /** @internal */
6240
+ updateSubscription(active) {
6241
+ if (active && this.ticker === void 0) {
6242
+ this.updateTime();
6243
+ this.ticker = Ticker.add(() => {
6244
+ this.updateTime();
6245
+ this.owner.notifyDirty();
6246
+ });
6247
+ } else if (!active) {
6248
+ Ticker.remove(this.ticker);
6249
+ this.ticker = void 0;
6250
+ }
6251
+ }
6252
+ dispose() {
6253
+ Ticker.remove(this.ticker);
6254
+ this.ticker = void 0;
6255
+ this.material.dispose();
6256
+ }
6257
+ };
6258
+ }
6259
+ });
6260
+
6261
+ // src/texture/PalettedTextures.ts
6262
+ var PalettedTextures_exports = {};
6263
+ __export(PalettedTextures_exports, {
6264
+ PalettedTextures: () => PalettedTextures
6265
+ });
6266
+ var PalettedTextures;
6267
+ var init_PalettedTextures = __esm({
6268
+ "src/texture/PalettedTextures.ts"() {
6269
+ init_ModelTextures();
6270
+ init_Caching();
6271
+ init_CanvasImage();
6272
+ init_MineRenderError();
6273
+ PalettedTextures = class {
6274
+ /** Loads a texture with a palette permutation, sharing the resulting canvas between callers. */
6275
+ static async get(texture, key, permutation2) {
6276
+ return Caching.canvasImageDataCache.get(`palette:${texture.serialize()}:${key.serialize()}:${permutation2.serialize()}`, async () => {
6277
+ const source = await ModelTextures.get(texture);
6278
+ if (!source) return void 0;
6279
+ const [palette, replacement] = await Promise.all([ModelTextures.get(key), ModelTextures.get(permutation2)]);
6280
+ if (!palette) throw new MineRenderError(`Missing palette ${key.toNamespacedString()}`);
6281
+ if (!replacement) throw new MineRenderError(`Missing palette ${permutation2.toNamespacedString()}`);
6282
+ const pixels = source.data.getImageData(0, 0, source.width, source.height);
6283
+ const colors2 = palette.data.getImageData(0, 0, palette.width, palette.height).data;
6284
+ const replacements = replacement.data.getImageData(0, 0, replacement.width, replacement.height).data;
6285
+ const image = new CanvasImage(source.width, source.height);
6286
+ const output = image.context.createImageData(source.width, source.height);
6287
+ output.data.set(this.permute(pixels.data, colors2, replacements));
6288
+ image.putData(output);
6289
+ return { data: image.context, width: image.width, height: image.height };
6290
+ });
6291
+ }
6292
+ /** @internal */
6293
+ static permute(pixels, key, permutation2) {
6294
+ if (key.length !== permutation2.length) throw new MineRenderError("Palette images must have the same pixel count");
6295
+ const mapping = /* @__PURE__ */ new Map();
6296
+ for (let i = 0; i < key.length; i += 4) {
6297
+ if (key[i + 3] !== 0) mapping.set(key[i] << 16 | key[i + 1] << 8 | key[i + 2], i);
6298
+ }
6299
+ const output = new Uint8ClampedArray(pixels);
6300
+ for (let i = 0; i < pixels.length; i += 4) {
6301
+ if (pixels[i + 3] === 0) continue;
6302
+ const mapped = mapping.get(pixels[i] << 16 | pixels[i + 1] << 8 | pixels[i + 2]);
6303
+ if (mapped === void 0) continue;
6304
+ output.set(permutation2.subarray(mapped, mapped + 3), i);
6305
+ output[i + 3] = Math.floor(pixels[i + 3] * permutation2[mapped + 3] / 255);
6306
+ }
6307
+ return output;
6308
+ }
6309
+ };
6310
+ }
6311
+ });
6312
+
5899
6313
  // src/entity/scene/EntityObject.ts
5900
6314
  var EntityObject_exports = {};
5901
6315
  __export(EntityObject_exports, {
@@ -5906,7 +6320,7 @@ import { Color as Color3, Object3D as Object3D3 } from "three";
5906
6320
  function isEntityObject(obj) {
5907
6321
  return obj.isEntityObject;
5908
6322
  }
5909
- var EntityObject;
6323
+ var ItemGlintClass, PalettedTexturesClass, EntityObject;
5910
6324
  var init_EntityObject = __esm({
5911
6325
  "src/entity/scene/EntityObject.ts"() {
5912
6326
  init_SceneObject();
@@ -5936,8 +6350,14 @@ var init_EntityObject = __esm({
5936
6350
  this.animationPlayers = [];
5937
6351
  this.partPoses = /* @__PURE__ */ new Map();
5938
6352
  this.options = (0, import_ts_deepmerge.default)({}, _EntityObject.DEFAULT_OPTIONS, options ?? {});
5939
- this.addEventListener("added", () => this.updateScrollSubscription());
5940
- this.addEventListener("removed", () => this.updateScrollSubscription());
6353
+ this.addEventListener("added", () => {
6354
+ this.updateScrollSubscription();
6355
+ this.glint?.updateSubscription(!!this.parent);
6356
+ });
6357
+ this.addEventListener("removed", () => {
6358
+ this.updateScrollSubscription();
6359
+ this.glint?.updateSubscription(!!this.parent);
6360
+ });
5941
6361
  }
5942
6362
  static {
5943
6363
  this.DEFAULT_OPTIONS = (0, import_ts_deepmerge.default)({}, SceneObject.DEFAULT_OPTIONS);
@@ -5951,12 +6371,26 @@ var init_EntityObject = __esm({
5951
6371
  mesh.material = material;
5952
6372
  });
5953
6373
  this.notifyDirty();
5954
- } else await this.applyTextures();
6374
+ } else {
6375
+ await this.applyTextures();
6376
+ const layers = Object.entries(this.entityLayers).filter(([, layer]) => layer.glint);
6377
+ const meshes = [];
6378
+ for (const [name] of layers) this.getLayerGroup(name)?.traverse((object4) => {
6379
+ if (isMesh(object4)) meshes.push(object4);
6380
+ });
6381
+ if (meshes.length) {
6382
+ const ItemGlint2 = ItemGlintClass ??= (await Promise.resolve().then(() => (init_ItemGlint(), ItemGlint_exports))).ItemGlint;
6383
+ this.glint = await ItemGlint2.create(this, void 0, this.getTextureKey(layers[0][1]).root, meshes.map((mesh) => ({ mesh })), "armor");
6384
+ this.glint.updateSubscription(!!this.parent);
6385
+ }
6386
+ }
5955
6387
  }
5956
6388
  dispose() {
5957
6389
  super.dispose();
5958
6390
  }
5959
6391
  disposeAndRemoveAllChildren() {
6392
+ this.glint?.dispose();
6393
+ this.glint = void 0;
5960
6394
  this.clearScrollMaterials();
5961
6395
  for (const geometry of this.geometries) geometry.dispose();
5962
6396
  this.geometries.clear();
@@ -6115,6 +6549,10 @@ var init_EntityObject = __esm({
6115
6549
  );
6116
6550
  }
6117
6551
  async loadTextures(layer = this.entity) {
6552
+ if (layer.palette) {
6553
+ const PalettedTextures2 = PalettedTexturesClass ??= (await Promise.resolve().then(() => (init_PalettedTextures(), PalettedTextures_exports))).PalettedTextures;
6554
+ return PalettedTextures2.get(this.getTextureKey(layer), layer.palette.key, layer.palette.permutation);
6555
+ }
6118
6556
  return ModelTextures.get(this.getTextureKey(layer));
6119
6557
  }
6120
6558
  createMeshes(force = false) {
@@ -6187,10 +6625,10 @@ var init_EntityObject = __esm({
6187
6625
  this.clearScrollMaterials();
6188
6626
  const results = await Promise.allSettled(Object.entries(this.entityLayers).map(async ([name, layer]) => {
6189
6627
  const mode = layer.render ?? layer.layer.render ?? "cutout";
6190
- const tint = layer.tint === void 0 ? void 0 : this.options.tints?.[layer.tint];
6628
+ const tint = (layer.tint === void 0 ? void 0 : this.options.tints?.[layer.tint]) ?? layer.color;
6191
6629
  const scroll = Materials.entityModeScroll(mode);
6192
6630
  const assetKeyStr = this.getTextureKey(layer).serialize();
6193
- const keyStr = `entity:${mode}:${tint === void 0 ? "" : new Color3(tint).getHexString()}:${assetKeyStr}`;
6631
+ const keyStr = `entity:${mode}:${tint === void 0 ? "" : new Color3(tint).getHexString()}:${assetKeyStr}` + (layer.palette ? `:${layer.palette.key.serialize()}:${layer.palette.permutation.serialize()}` : "");
6194
6632
  let mat = scroll ? void 0 : Caching.materialCache.getIfPresent(keyStr);
6195
6633
  if (!mat) {
6196
6634
  const cachedAsset = Caching.textureAssetCache.getIfPresent(assetKeyStr);
@@ -6228,13 +6666,13 @@ __export(GLTFExporter_exports, {
6228
6666
  });
6229
6667
  import {
6230
6668
  BufferAttribute as BufferAttribute3,
6231
- ClampToEdgeWrapping,
6669
+ ClampToEdgeWrapping as ClampToEdgeWrapping2,
6232
6670
  Color as Color6,
6233
- DoubleSide as DoubleSide2,
6671
+ DoubleSide as DoubleSide3,
6234
6672
  InterpolateDiscrete,
6235
6673
  InterpolateLinear,
6236
6674
  NoColorSpace,
6237
- LinearFilter,
6675
+ LinearFilter as LinearFilter2,
6238
6676
  LinearMipmapLinearFilter,
6239
6677
  LinearMipmapNearestFilter,
6240
6678
  MathUtils,
@@ -6245,7 +6683,7 @@ import {
6245
6683
  NearestMipmapNearestFilter,
6246
6684
  PropertyBinding,
6247
6685
  RGBAFormat as RGBAFormat2,
6248
- RepeatWrapping as RepeatWrapping2,
6686
+ RepeatWrapping as RepeatWrapping3,
6249
6687
  Scene as Scene2,
6250
6688
  SRGBColorSpace as SRGBColorSpace5,
6251
6689
  TextureSource,
@@ -6514,11 +6952,11 @@ var init_GLTFExporter = __esm({
6514
6952
  THREE_TO_WEBGL[NearestFilter2] = WEBGL_CONSTANTS.NEAREST;
6515
6953
  THREE_TO_WEBGL[NearestMipmapNearestFilter] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST;
6516
6954
  THREE_TO_WEBGL[NearestMipmapLinearFilter] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR;
6517
- THREE_TO_WEBGL[LinearFilter] = WEBGL_CONSTANTS.LINEAR;
6955
+ THREE_TO_WEBGL[LinearFilter2] = WEBGL_CONSTANTS.LINEAR;
6518
6956
  THREE_TO_WEBGL[LinearMipmapNearestFilter] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST;
6519
6957
  THREE_TO_WEBGL[LinearMipmapLinearFilter] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR;
6520
- THREE_TO_WEBGL[ClampToEdgeWrapping] = WEBGL_CONSTANTS.CLAMP_TO_EDGE;
6521
- THREE_TO_WEBGL[RepeatWrapping2] = WEBGL_CONSTANTS.REPEAT;
6958
+ THREE_TO_WEBGL[ClampToEdgeWrapping2] = WEBGL_CONSTANTS.CLAMP_TO_EDGE;
6959
+ THREE_TO_WEBGL[RepeatWrapping3] = WEBGL_CONSTANTS.REPEAT;
6522
6960
  THREE_TO_WEBGL[MirroredRepeatWrapping] = WEBGL_CONSTANTS.MIRRORED_REPEAT;
6523
6961
  PATH_PROPERTIES = {
6524
6962
  scale: "scale",
@@ -7268,7 +7706,7 @@ var init_GLTFExporter = __esm({
7268
7706
  materialDef.alphaCutoff = material.alphaTest;
7269
7707
  }
7270
7708
  }
7271
- if (material.side === DoubleSide2) materialDef.doubleSided = true;
7709
+ if (material.side === DoubleSide3) materialDef.doubleSided = true;
7272
7710
  if (material.name !== "") materialDef.name = material.name;
7273
7711
  this.serializeUserData(material, materialDef);
7274
7712
  await this._invokeAllAsync(async function(ext) {
@@ -8337,7 +8775,7 @@ var init_GLTFExporter = __esm({
8337
8775
  });
8338
8776
 
8339
8777
  // src/gui/GuiText.ts
8340
- import { BufferGeometry as BufferGeometry2, Color as Color8, Float32BufferAttribute as Float32BufferAttribute4 } from "three";
8778
+ import { BufferGeometry as BufferGeometry2, Color as Color8, Float32BufferAttribute as Float32BufferAttribute5 } from "three";
8341
8779
  async function layoutGuiText(text2, options = {}) {
8342
8780
  const maxWidth = options.maxWidth ?? Infinity, lineHeight = options.lineHeight ?? 9;
8343
8781
  if (!(maxWidth > 0) || !Number.isFinite(lineHeight) || lineHeight <= 0) {
@@ -8441,7 +8879,7 @@ function createGuiTextGeometry(layout, shadow = true) {
8441
8879
  }
8442
8880
  }
8443
8881
  }
8444
- return batches.map(({ image, positions, uvs, colors: colors2, indices }) => ({ image, geometry: new BufferGeometry2().setAttribute("position", new Float32BufferAttribute4(positions, 3)).setAttribute("uv", new Float32BufferAttribute4(uvs, 2)).setAttribute("color", new Float32BufferAttribute4(colors2, 3)).setIndex(indices) }));
8882
+ return batches.map(({ image, positions, uvs, colors: colors2, indices }) => ({ image, geometry: new BufferGeometry2().setAttribute("position", new Float32BufferAttribute5(positions, 3)).setAttribute("uv", new Float32BufferAttribute5(uvs, 2)).setAttribute("color", new Float32BufferAttribute5(colors2, 3)).setIndex(indices) }));
8445
8883
  }
8446
8884
  var init_GuiText = __esm({
8447
8885
  "src/gui/GuiText.ts"() {
@@ -8450,7 +8888,7 @@ var init_GuiText = __esm({
8450
8888
  });
8451
8889
 
8452
8890
  // src/gui/GuiTextureGeometry.ts
8453
- import { BufferGeometry as BufferGeometry3, Float32BufferAttribute as Float32BufferAttribute5, PlaneGeometry } from "three";
8891
+ import { BufferGeometry as BufferGeometry3, Float32BufferAttribute as Float32BufferAttribute6, PlaneGeometry } from "three";
8454
8892
  function createGuiTextureGeometry(width, height, imageWidth, imageHeight, crop, scaling) {
8455
8893
  if (crop || !scaling || scaling.type === "stretch") {
8456
8894
  const [x, y, w, h] = crop ?? [0, 0, imageWidth, imageHeight];
@@ -8525,7 +8963,7 @@ function createGuiTextureGeometry(width, height, imageWidth, imageHeight, crop,
8525
8963
  }
8526
8964
  }
8527
8965
  }
8528
- return new BufferGeometry3().setAttribute("position", new Float32BufferAttribute5(positions, 3)).setAttribute("normal", new Float32BufferAttribute5(normals, 3)).setAttribute("uv", new Float32BufferAttribute5(uvs, 2)).setIndex(indices);
8966
+ return new BufferGeometry3().setAttribute("position", new Float32BufferAttribute6(positions, 3)).setAttribute("normal", new Float32BufferAttribute6(normals, 3)).setAttribute("uv", new Float32BufferAttribute6(uvs, 2)).setIndex(indices);
8529
8967
  }
8530
8968
  var init_GuiTextureGeometry = __esm({
8531
8969
  "src/gui/GuiTextureGeometry.ts"() {
@@ -8536,7 +8974,7 @@ var init_GuiTextureGeometry = __esm({
8536
8974
  import {
8537
8975
  BufferAttribute as BufferAttribute4,
8538
8976
  BufferGeometry as BufferGeometry4,
8539
- Float32BufferAttribute as Float32BufferAttribute6,
8977
+ Float32BufferAttribute as Float32BufferAttribute7,
8540
8978
  InterleavedBuffer,
8541
8979
  InterleavedBufferAttribute,
8542
8980
  TriangleFanDrawMode,
@@ -9064,8 +9502,8 @@ function computeMorphedAttributes(object4) {
9064
9502
  }
9065
9503
  }
9066
9504
  }
9067
- const morphedPositionAttribute = new Float32BufferAttribute6(modifiedPosition, 3);
9068
- const morphedNormalAttribute = new Float32BufferAttribute6(modifiedNormal, 3);
9505
+ const morphedPositionAttribute = new Float32BufferAttribute7(modifiedPosition, 3);
9506
+ const morphedNormalAttribute = new Float32BufferAttribute7(modifiedNormal, 3);
9069
9507
  return {
9070
9508
  positionAttribute,
9071
9509
  normalAttribute,
@@ -9352,7 +9790,7 @@ var init_SkinImage = __esm({
9352
9790
  });
9353
9791
 
9354
9792
  // src/skin/SkinTextures.ts
9355
- import { DataTexture as DataTexture3, DoubleSide as DoubleSide3, FrontSide as FrontSide2, MeshBasicMaterial as MeshBasicMaterial3, SRGBColorSpace as SRGBColorSpace6 } from "three";
9793
+ import { DataTexture as DataTexture3, DoubleSide as DoubleSide4, FrontSide as FrontSide2, MeshBasicMaterial as MeshBasicMaterial3, SRGBColorSpace as SRGBColorSpace6 } from "three";
9356
9794
  var SkinTextures;
9357
9795
  var init_SkinTextures = __esm({
9358
9796
  "src/skin/SkinTextures.ts"() {
@@ -9397,7 +9835,7 @@ var init_SkinTextures = __esm({
9397
9835
  texture.colorSpace = SRGBColorSpace6;
9398
9836
  texture.flipY = true;
9399
9837
  texture.needsUpdate = true;
9400
- return new MeshBasicMaterial3({ map: texture, transparent, side: transparent ? DoubleSide3 : FrontSide2, alphaTest: transparent ? 0.1 : 0 });
9838
+ return new MeshBasicMaterial3({ map: texture, transparent, side: transparent ? DoubleSide4 : FrontSide2, alphaTest: transparent ? 0.1 : 0 });
9401
9839
  }
9402
9840
  static isSlim(image, scale) {
9403
9841
  for (const [x, y] of [[46, 52], [54, 20]]) {
@@ -9450,7 +9888,7 @@ var init_SkinTextures = __esm({
9450
9888
  return pixels;
9451
9889
  }
9452
9890
  static makeBaseOpaque(pixels, width, scale) {
9453
- for (const [x0, y0, x1, y1] of [[0, 0, 32, 16], [0, 16, 64, 32], [16, 48, 48, 64]]) {
9891
+ for (const [x0, y0, x1, y1] of [[0, 0, 32, 16], [0, 16, 64, 32], [16, 48, 48, 64], [32, 0, 38, 7]]) {
9454
9892
  for (let y = y0 * scale; y < y1 * scale; y++) {
9455
9893
  for (let x = x0 * scale; x < x1 * scale; x++) pixels[(y * width + x) * 4 + 3] = 255;
9456
9894
  }
@@ -9461,6 +9899,10 @@ var init_SkinTextures = __esm({
9461
9899
  });
9462
9900
 
9463
9901
  // src/skin/PlayerHeadTextures.ts
9902
+ var PlayerHeadTextures_exports = {};
9903
+ __export(PlayerHeadTextures_exports, {
9904
+ PlayerHeadTextures: () => PlayerHeadTextures
9905
+ });
9464
9906
  var PlayerHeadTextures;
9465
9907
  var init_PlayerHeadTextures = __esm({
9466
9908
  "src/skin/PlayerHeadTextures.ts"() {
@@ -9779,274 +10221,71 @@ var init_SpecialItems = __esm({
9779
10221
  load("bed_foot", key, new Matrix47().makeTranslation(0, 0, -16).multiply(transform))
9780
10222
  ]);
9781
10223
  }
9782
- case "minecraft:head":
9783
- case "head": {
9784
- const models = {
9785
- skeleton: "skeleton_skull",
9786
- wither_skeleton: "wither_skeleton_skull",
9787
- zombie: "zombie_head",
9788
- creeper: "creeper_head",
9789
- dragon: "dragon_skull",
9790
- piglin: "piglin_head"
9791
- };
9792
- const id = Object.prototype.hasOwnProperty.call(models, special.kind) ? models[special.kind] : void 0;
9793
- if (!id) throw new Error(`Unsupported special item head kind ${special.kind}`);
9794
- const animation = (special.animation ?? 0) * Math.PI * 0.2;
9795
- const rotations = { head: [0, Math.PI, 0] };
9796
- if (special.kind === "dragon") rotations.jaw = [(Math.sin(animation) + 1) * 0.2, 0, 0];
9797
- if (special.kind === "piglin") {
9798
- rotations.left_ear = [0, 0, -(Math.cos(animation * 1.2) + 2.5) * 0.2];
9799
- rotations.right_ear = [0, 0, (Math.cos(animation) + 2.5) * 0.2];
9800
- }
9801
- return [await load(
9802
- id,
9803
- special.texture ? texture(special.texture, "") : void 0,
9804
- new Matrix47().makeTranslation(8, 0, 8).multiply(new Matrix47().makeScale(-1, -1, 1)),
9805
- rotations
9806
- )];
9807
- }
9808
- }
9809
- }
9810
- /** An owned material with model-preview shading; its entity texture remains shared. */
9811
- static createMaterial(source, shade) {
9812
- const material = source.clone();
9813
- if (shade) {
9814
- material.onBeforeCompile = (shader) => {
9815
- shader.vertexShader = `varying float itemLight;
9816
- ${shader.vertexShader}`.replace("#include <begin_vertex>", `
9817
- #include <begin_vertex>
9818
- vec3 N = inverseTransformDirection(normalMatrix * normal, viewMatrix);
9819
- itemLight = (1.0 + N.y) * 0.25 - N.x * N.x * 0.15 + N.z * N.z * 0.05 + 0.5;
9820
- `);
9821
- shader.fragmentShader = `varying float itemLight;
9822
- ${shader.fragmentShader}`.replace(
9823
- "#include <color_fragment>",
9824
- "#include <color_fragment>\ndiffuseColor.rgb *= itemLight;"
9825
- );
9826
- };
9827
- material.customProgramCacheKey = () => "special-item-side";
9828
- }
9829
- return material;
9830
- }
9831
- };
9832
- }
9833
- });
9834
-
9835
- // src/model/GuiLight.ts
9836
- var GuiLight;
9837
- var init_GuiLight = __esm({
9838
- "src/model/GuiLight.ts"() {
9839
- GuiLight = /* @__PURE__ */ ((GuiLight2) => {
9840
- GuiLight2["FRONT"] = "front";
9841
- GuiLight2["SIDE"] = "side";
9842
- return GuiLight2;
9843
- })(GuiLight || {});
9844
- }
9845
- });
9846
-
9847
- // src/model/ItemGlint.ts
9848
- import { ClampToEdgeWrapping as ClampToEdgeWrapping3, CustomBlending as CustomBlending2, DoubleSide as DoubleSide4, EqualDepth, Float32BufferAttribute as Float32BufferAttribute7, LinearFilter as LinearFilter2, Mesh as Mesh5, OneFactor as OneFactor2, RepeatWrapping as RepeatWrapping3, ShaderMaterial as ShaderMaterial3, SrcColorFactor, Vector2 as Vector23, ZeroFactor } from "three";
9849
- var ItemGlint;
9850
- var init_ItemGlint = __esm({
9851
- "src/model/ItemGlint.ts"() {
9852
- init_AssetKey();
9853
- init_Models();
9854
- init_ModelTextures();
9855
- init_Caching();
9856
- init_Textures();
9857
- init_Ticker();
9858
- init_CubeFace();
9859
- init_DisplayPosition();
9860
- ItemGlint = class _ItemGlint {
9861
- constructor(owner, material) {
9862
- this.owner = owner;
9863
- this.material = material;
9864
- this.updateTime();
9865
- }
9866
- /** Enables glint for enchantments or a lodestone compass unless a boolean component overrides them. */
9867
- static enabled(components = {}, itemId) {
9868
- const override = Models.componentValue(components, "enchantment_glint_override");
9869
- if (override !== void 0) {
9870
- if (typeof override !== "boolean") throw new Error("Item enchantment_glint_override must be a boolean");
9871
- return override;
9872
- }
9873
- if (itemId === "minecraft:compass") {
9874
- Models.componentValue(components, "lodestone_tracker");
9875
- if (Object.prototype.hasOwnProperty.call(components, "lodestone_tracker") || Object.prototype.hasOwnProperty.call(components, "minecraft:lodestone_tracker")) return true;
9876
- }
9877
- const enchantments = Models.componentValue(components, "enchantments");
9878
- if (enchantments === void 0) return false;
9879
- if (!enchantments || typeof enchantments !== "object" || Array.isArray(enchantments)) throw new Error("Item enchantments must be an object");
9880
- return Object.keys(enchantments).length > 0;
9881
- }
9882
- /** @internal */
9883
- static async create(owner, baseMap, root, entityMeshes) {
9884
- const key = new AssetKey("minecraft", "enchanted_glint_item", "textures", "misc", "assets", ".png", root);
9885
- const assetKey = key.serialize(), textureKey = `item-glint:${assetKey}`;
9886
- let texture = Caching.textureCache.getIfPresent(textureKey);
9887
- if (!texture) {
9888
- const pending = ModelTextures.get(key);
9889
- const cachedAsset = Caching.textureAssetCache.getIfPresent(assetKey);
9890
- const [image, metadata] = await Promise.all([pending, ModelTextures.getMeta(key)]);
9891
- if (!image) throw new Error(`Missing item glint texture ${key.toNamespacedString()}`);
9892
- if (cachedAsset && Caching.textureAssetCache.getIfPresent(assetKey) !== cachedAsset) return this.create(owner, baseMap, root, entityMeshes);
9893
- texture = Caching.textureCache.get(textureKey, () => {
9894
- const texture2 = Textures.createCanvasTexture(image.data.canvas);
9895
- texture2.wrapS = texture2.wrapT = metadata?.texture?.clamp ? ClampToEdgeWrapping3 : RepeatWrapping3;
9896
- if (metadata?.texture?.blur) texture2.magFilter = texture2.minFilter = LinearFilter2;
9897
- return texture2;
9898
- });
9899
- }
9900
- const material = new ShaderMaterial3({
9901
- name: "item-glint",
9902
- transparent: true,
9903
- depthWrite: false,
9904
- depthFunc: EqualDepth,
9905
- side: DoubleSide4,
9906
- forceSinglePass: true,
9907
- blending: CustomBlending2,
9908
- blendSrc: SrcColorFactor,
9909
- blendDst: OneFactor2,
9910
- blendSrcAlpha: ZeroFactor,
9911
- blendDstAlpha: OneFactor2,
9912
- toneMapped: false,
9913
- defines: entityMeshes ? { ENTITY_GLINT: true } : {},
9914
- uniforms: {
9915
- ...!entityMeshes && { baseMap: { value: baseMap } },
9916
- glintMap: { value: texture },
9917
- glintOffset: { value: new Vector23() },
9918
- glintAlpha: { value: 0.75 }
9919
- },
9920
- vertexShader: `
9921
- uniform vec2 glintOffset;
9922
- #ifndef ENTITY_GLINT
9923
- attribute vec4 uvBounds;
9924
- attribute vec2 glintUv;
9925
- flat out vec4 vUvBounds;
9926
- centroid out vec2 vUv;
9927
- #endif
9928
- varying vec2 vGlintUv;
9929
- void main() {
9930
- #ifdef ENTITY_GLINT
9931
- vec2 p = vec2(uv.x, 1.0 - uv.y) * 0.5;
9932
- #else
9933
- vUv = uv;
9934
- vUvBounds = uvBounds;
9935
- vec2 p = glintUv * 8.0;
9936
- #endif
9937
- float angle = radians(10.0);
9938
- vGlintUv = vec2(cos(angle) * p.x - sin(angle) * p.y, sin(angle) * p.x + cos(angle) * p.y) + glintOffset;
9939
- vec4 mvPosition = vec4(position, 1.0);
9940
- mvPosition = modelViewMatrix * mvPosition;
9941
- gl_Position = projectionMatrix * mvPosition;
9942
- }
9943
- `,
9944
- fragmentShader: `
9945
- #ifndef ENTITY_GLINT
9946
- uniform sampler2D baseMap;
9947
- flat in vec4 vUvBounds;
9948
- centroid in vec2 vUv;
9949
- #endif
9950
- uniform sampler2D glintMap;
9951
- uniform float glintAlpha;
9952
- varying vec2 vGlintUv;
9953
- void main() {
9954
- #ifndef ENTITY_GLINT
9955
- vec2 mapUv = clamp(vUv, vUvBounds.xy, vUvBounds.zw);
9956
- if (texture2D(baseMap, mapUv).a < 0.01) discard;
9957
- #endif
9958
- vec4 color = texture2D(glintMap, vec2(vGlintUv.x, 1.0 - vGlintUv.y));
9959
- if (color.a < 0.1) discard;
9960
- gl_FragColor = color;
9961
- #include <colorspace_fragment>
9962
- gl_FragColor.rgb *= glintAlpha;
9963
- }
9964
- `
9965
- });
9966
- if (!entityMeshes) Object.assign(material.defaultAttributeValues, { uvBounds: [0, 0, 1, 1] });
9967
- const targets = entityMeshes ? [...entityMeshes] : [];
9968
- if (!entityMeshes) owner.iterateAllMeshes((mesh) => targets.push({ mesh }));
9969
- for (const { mesh: base, parent = base } of targets) {
9970
- const pass = new Mesh5(base.geometry, material);
9971
- pass.name = `${base.name}:glint`;
9972
- pass.userData.minerenderItemGlint = true;
9973
- pass.raycast = () => {
9974
- };
9975
- pass.renderOrder = parent.renderOrder + 0.5;
9976
- parent.add(pass);
9977
- }
9978
- return new _ItemGlint(owner, material);
9979
- }
9980
- /**
9981
- * Maps glint before model centering and display transforms. Ordinary sprites approximate Minecraft's shared atlas density.
9982
- * @internal
9983
- */
9984
- static mapUvs(geometry, faces, atlas, itemId, display) {
9985
- const uv = geometry.getAttribute("uv");
9986
- const mapped = new Float32Array(uv.count * 2);
9987
- if (itemId === "minecraft:compass" || itemId === "minecraft:recovery_compass" || itemId === "minecraft:clock") {
9988
- const positions = geometry.getAttribute("position"), normals = geometry.getAttribute("normal");
9989
- const scale = display === "gui" /* GUI */ ? 0.5 : display === "firstperson_lefthand" /* FIRSTPERSON_LEFTHAND */ || display === "firstperson_righthand" /* FIRSTPERSON_RIGHTHAND */ ? 0.75 : 1;
9990
- const factor = 1 / (16 * 128 * scale);
9991
- for (let index = 0; index < positions.count; index++) {
9992
- const x = positions.getX(index), y = positions.getY(index), z = positions.getZ(index);
9993
- const nx = normals.getX(index), ny = normals.getY(index), nz = normals.getZ(index);
9994
- const ax = Math.abs(nx), ay = Math.abs(ny), az = Math.abs(nz);
9995
- let u, v;
9996
- if (ay >= az && ay >= ax) {
9997
- u = x;
9998
- v = ny < 0 ? -z : z;
9999
- } else if (az >= ax) {
10000
- u = nz < 0 ? -x : x;
10001
- v = -y;
10002
- } else {
10003
- u = nx < 0 ? -z : z;
10004
- v = -y;
10005
- }
10006
- mapped[index * 2] = u * factor;
10007
- mapped[index * 2 + 1] = v * factor;
10008
- }
10009
- geometry.setAttribute("glintUv", new Float32BufferAttribute7(mapped, 2));
10010
- return;
10011
- }
10012
- for (const [faceIndex, face] of CUBE_FACES.entries()) {
10013
- const texture = faces[face]?.texture?.substring(1);
10014
- const position = texture && atlas.positions[texture] || [0, 0];
10015
- const size = texture && atlas.sizes[texture] || [atlas.image.width, atlas.image.height];
10016
- for (let corner = 0; corner < 4; corner++) {
10017
- const index = faceIndex * 4 + corner;
10018
- mapped[index * 2] = (uv.getX(index) * atlas.image.width - position[0]) / size[0] / 32;
10019
- mapped[index * 2 + 1] = ((1 - uv.getY(index)) * atlas.image.height - position[1]) / size[1] / 32;
10224
+ case "minecraft:head":
10225
+ case "head": {
10226
+ const models = {
10227
+ skeleton: "skeleton_skull",
10228
+ wither_skeleton: "wither_skeleton_skull",
10229
+ zombie: "zombie_head",
10230
+ creeper: "creeper_head",
10231
+ dragon: "dragon_skull",
10232
+ piglin: "piglin_head"
10233
+ };
10234
+ const id = Object.prototype.hasOwnProperty.call(models, special.kind) ? models[special.kind] : void 0;
10235
+ if (!id) throw new Error(`Unsupported special item head kind ${special.kind}`);
10236
+ const animation = (special.animation ?? 0) * Math.PI * 0.2;
10237
+ const rotations = { head: [0, Math.PI, 0] };
10238
+ if (special.kind === "dragon") rotations.jaw = [(Math.sin(animation) + 1) * 0.2, 0, 0];
10239
+ if (special.kind === "piglin") {
10240
+ rotations.left_ear = [0, 0, -(Math.cos(animation * 1.2) + 2.5) * 0.2];
10241
+ rotations.right_ear = [0, 0, (Math.cos(animation) + 2.5) * 0.2];
10242
+ }
10243
+ return [await load(
10244
+ id,
10245
+ special.texture ? texture(special.texture, "") : void 0,
10246
+ new Matrix47().makeTranslation(8, 0, 8).multiply(new Matrix47().makeScale(-1, -1, 1)),
10247
+ rotations
10248
+ )];
10020
10249
  }
10021
10250
  }
10022
- geometry.setAttribute("glintUv", new Float32BufferAttribute7(mapped, 2));
10023
- }
10024
- updateTime() {
10025
- const time = Math.trunc(Date.now() * 4);
10026
- this.material.uniforms.glintOffset.value.set(-(time % 11e4) / 11e4, time % 3e4 / 3e4);
10027
10251
  }
10028
- /** @internal */
10029
- updateSubscription(active) {
10030
- if (active && this.ticker === void 0) {
10031
- this.updateTime();
10032
- this.ticker = Ticker.add(() => {
10033
- this.updateTime();
10034
- this.owner.notifyDirty();
10035
- });
10036
- } else if (!active) {
10037
- Ticker.remove(this.ticker);
10038
- this.ticker = void 0;
10252
+ /** An owned material with model-preview shading; its entity texture remains shared. */
10253
+ static createMaterial(source, shade) {
10254
+ const material = source.clone();
10255
+ if (shade) {
10256
+ material.onBeforeCompile = (shader) => {
10257
+ shader.vertexShader = `varying float itemLight;
10258
+ ${shader.vertexShader}`.replace("#include <begin_vertex>", `
10259
+ #include <begin_vertex>
10260
+ vec3 N = inverseTransformDirection(normalMatrix * normal, viewMatrix);
10261
+ itemLight = (1.0 + N.y) * 0.25 - N.x * N.x * 0.15 + N.z * N.z * 0.05 + 0.5;
10262
+ `);
10263
+ shader.fragmentShader = `varying float itemLight;
10264
+ ${shader.fragmentShader}`.replace(
10265
+ "#include <color_fragment>",
10266
+ "#include <color_fragment>\ndiffuseColor.rgb *= itemLight;"
10267
+ );
10268
+ };
10269
+ material.customProgramCacheKey = () => "special-item-side";
10039
10270
  }
10040
- }
10041
- dispose() {
10042
- Ticker.remove(this.ticker);
10043
- this.ticker = void 0;
10044
- this.material.dispose();
10271
+ return material;
10045
10272
  }
10046
10273
  };
10047
10274
  }
10048
10275
  });
10049
10276
 
10277
+ // src/model/GuiLight.ts
10278
+ var GuiLight;
10279
+ var init_GuiLight = __esm({
10280
+ "src/model/GuiLight.ts"() {
10281
+ GuiLight = /* @__PURE__ */ ((GuiLight2) => {
10282
+ GuiLight2["FRONT"] = "front";
10283
+ GuiLight2["SIDE"] = "side";
10284
+ return GuiLight2;
10285
+ })(GuiLight || {});
10286
+ }
10287
+ });
10288
+
10050
10289
  // src/model/scene/ModelObject.ts
10051
10290
  var ModelObject_exports = {};
10052
10291
  __export(ModelObject_exports, {
@@ -11191,17 +11430,25 @@ var init_BlockObject = __esm({
11191
11430
  if (!key || this.options.displayPosition) return false;
11192
11431
  const generation = this._entityGeneration;
11193
11432
  const created = [];
11433
+ const placement = new Matrix410();
11194
11434
  let resolved;
11195
11435
  try {
11196
11436
  resolved = BlockEntities.resolve(await BlockEntities.getIndex(key.root), key.toNamespacedString(), this.state);
11197
11437
  if (!resolved) return false;
11438
+ BlockEntities.matrix(resolved, placement);
11198
11439
  for (const part of resolved.parts) {
11199
11440
  const [namespace, path] = part.model.includes(":") ? part.model.split(":") : [key.namespace, part.model];
11200
- const texture = part.textureLocation === void 0 ? void 0 : AssetKey.parse("textures", part.textureLocation.replace(/^([^:]+:)?textures\//, "$1"));
11441
+ let texture = part.textureLocation === void 0 ? void 0 : AssetKey.parse("textures", part.textureLocation.replace(/^([^:]+:)?textures\//, "$1"));
11442
+ if (part.textureLocation === void 0 && namespace === "minecraft" && path === "player_head") {
11443
+ const { PlayerHeadTextures: PlayerHeadTextures2 } = await Promise.resolve().then(() => (init_PlayerHeadTextures(), PlayerHeadTextures_exports));
11444
+ texture = (await PlayerHeadTextures2.get(void 0, key.root)).texture;
11445
+ }
11201
11446
  if (texture) texture.root = key.root;
11202
11447
  const entity = await Entities.getEntity(new BasicAssetKey(namespace, path), texture, { layer: part.layer });
11203
11448
  if (!entity) throw new MineRenderError(`Missing entity model ${part.model}`);
11204
- created.push(await this.scene.addEntity(entity, { wireframe: this.options.wireframe }));
11449
+ const object4 = await this.scene.addEntity(entity, { wireframe: this.options.wireframe });
11450
+ this.placeEntity(object4, placement);
11451
+ created.push(object4);
11205
11452
  }
11206
11453
  } catch (error) {
11207
11454
  log15.warn(`Could not draw block entity ${key.toNamespacedString()}, using its block model`, error);
@@ -11212,18 +11459,21 @@ var init_BlockObject = __esm({
11212
11459
  this.removeEntities(created);
11213
11460
  return true;
11214
11461
  }
11215
- BlockEntities.matrix(resolved, this._entityPlacement);
11462
+ this._entityPlacement.copy(placement);
11216
11463
  this._entities = created;
11217
11464
  this.placeEntities();
11218
11465
  this.setVisible(this.visible);
11219
11466
  return true;
11220
11467
  }
11221
11468
  placeEntities() {
11222
- const matrix = new Matrix410().makeTranslation(this.position.x - 8, this.position.y - 8, this.position.z - 8).multiply(this._entityPlacement);
11223
11469
  for (const entity of this._entities) {
11224
- matrix.decompose(entity.position, entity.quaternion, entity.scale);
11470
+ this.placeEntity(entity, this._entityPlacement);
11225
11471
  }
11226
11472
  }
11473
+ placeEntity(entity, placement) {
11474
+ const matrix = new Matrix410().makeTranslation(this.position.x - 8, this.position.y - 8, this.position.z - 8).multiply(placement);
11475
+ matrix.decompose(entity.position, entity.quaternion, entity.scale);
11476
+ }
11227
11477
  get isOccluding() {
11228
11478
  if (!this.visible) {
11229
11479
  return false;
@@ -11517,6 +11767,7 @@ init_PersistentCache();
11517
11767
  var NodeCache = class extends PersistentCache {
11518
11768
  constructor(backing) {
11519
11769
  super(backing);
11770
+ this.pendingWrites = /* @__PURE__ */ new Map();
11520
11771
  }
11521
11772
  ready() {
11522
11773
  return this.initialized ??= this.backing.init();
@@ -11527,7 +11778,14 @@ var NodeCache = class extends PersistentCache {
11527
11778
  }
11528
11779
  async put(key, value) {
11529
11780
  await this.ready();
11530
- return this.backing.setItem(key, value).then((res) => res.content);
11781
+ const previous = this.pendingWrites.get(key);
11782
+ const write = (previous ? previous.catch(() => void 0) : Promise.resolve()).then(() => this.backing.setItem(key, value)).then(() => value);
11783
+ this.pendingWrites.set(key, write);
11784
+ try {
11785
+ return await write;
11786
+ } finally {
11787
+ if (this.pendingWrites.get(key) === write) this.pendingWrites.delete(key);
11788
+ }
11531
11789
  }
11532
11790
  async delete(key) {
11533
11791
  await this.ready();
@@ -11573,7 +11831,10 @@ var NodeEnv = class {
11573
11831
  openCache(name, version2) {
11574
11832
  return new NodeCache(NodePersist.create({
11575
11833
  dir: name + version2,
11576
- encoding: "utf8"
11834
+ encoding: "utf8",
11835
+ // node-persist's unref'd one-second write queue delays loads and lets Node exit before writes finish.
11836
+ // NodeCache serializes writes to each key while allowing different files to write concurrently.
11837
+ writeQueue: false
11577
11838
  }));
11578
11839
  }
11579
11840
  };
@@ -11650,6 +11911,9 @@ function shutdown() {
11650
11911
  Caching.end();
11651
11912
  }
11652
11913
 
11914
+ // src/armor/index.ts
11915
+ init_Armor();
11916
+
11653
11917
  // src/assets/index.ts
11654
11918
  init_AssetDefaults();
11655
11919
  init_AssetKey();
@@ -11741,7 +12005,7 @@ init_EntityObject();
11741
12005
  init_MineRenderError();
11742
12006
 
11743
12007
  // src/export/SceneExporter.ts
11744
- import { ClampToEdgeWrapping as ClampToEdgeWrapping2, Color as Color7, Float32BufferAttribute as Float32BufferAttribute3, Matrix4 as Matrix46, Mesh as Mesh4, MeshBasicMaterial as MeshBasicMaterial2, NearestFilter as NearestFilter3, RGBAFormat as RGBAFormat3, Scene as Scene3, TextureSource as TextureSource2, Texture as Texture3, UnsignedByteType } from "three";
12008
+ import { ClampToEdgeWrapping as ClampToEdgeWrapping3, Color as Color7, Float32BufferAttribute as Float32BufferAttribute4, Matrix4 as Matrix46, Mesh as Mesh5, MeshBasicMaterial as MeshBasicMaterial2, NearestFilter as NearestFilter3, RGBAFormat as RGBAFormat3, Scene as Scene3, TextureSource as TextureSource2, Texture as Texture3, UnsignedByteType } from "three";
11745
12009
 
11746
12010
  // ../../node_modules/three/examples/jsm/exporters/OBJExporter.js
11747
12011
  import {
@@ -11749,7 +12013,7 @@ import {
11749
12013
  ColorManagement,
11750
12014
  Matrix3,
11751
12015
  SRGBColorSpace as SRGBColorSpace3,
11752
- Vector2 as Vector22,
12016
+ Vector2 as Vector23,
11753
12017
  Vector3 as Vector37
11754
12018
  } from "three";
11755
12019
  var OBJExporter = class {
@@ -11769,7 +12033,7 @@ var OBJExporter = class {
11769
12033
  const vertex = new Vector37();
11770
12034
  const color2 = new Color4();
11771
12035
  const normal = new Vector37();
11772
- const uv = new Vector22();
12036
+ const uv = new Vector23();
11773
12037
  const face = [];
11774
12038
  function parseMesh(mesh) {
11775
12039
  let nbVertex = 0;
@@ -12474,7 +12738,7 @@ function createSnapshot(root, gltf, bakeColors = false) {
12474
12738
  if (instanceColor) tint.multiply(instanceColor);
12475
12739
  if (bakeColors) {
12476
12740
  const original = geometry.getAttribute("color");
12477
- const colors2 = new Float32BufferAttribute3(new Float32Array(geometry.getAttribute("position").count * 3), 3);
12741
+ const colors2 = new Float32BufferAttribute4(new Float32Array(geometry.getAttribute("position").count * 3), 3);
12478
12742
  for (let i = 0; i < colors2.count; i++) {
12479
12743
  colors2.setXYZ(i, (original?.getX(i) ?? 1) * tint.r, (original?.getY(i) ?? 1) * tint.g, (original?.getZ(i) ?? 1) * tint.b);
12480
12744
  }
@@ -12483,7 +12747,7 @@ function createSnapshot(root, gltf, bakeColors = false) {
12483
12747
  material.color.copy(tint);
12484
12748
  }
12485
12749
  }
12486
- const copy = new Mesh4(geometry, material);
12750
+ const copy = new Mesh5(geometry, material);
12487
12751
  copy.name = mesh.name;
12488
12752
  copy.renderOrder = mesh.renderOrder;
12489
12753
  scene.add(copy);
@@ -12525,7 +12789,7 @@ function createSnapshot(root, gltf, bakeColors = false) {
12525
12789
  }
12526
12790
  function selectAlphaMode(material, geometry) {
12527
12791
  const texture = material.map;
12528
- if (material.type !== "MeshBasicMaterial" || !material.transparent || !material.depthWrite || material.opacity !== 1 || material.alphaMap || !texture?.isDataTexture || texture.format !== RGBAFormat3 || texture.type !== UnsignedByteType || texture.wrapS !== ClampToEdgeWrapping2 || texture.wrapT !== ClampToEdgeWrapping2 || texture.magFilter !== NearestFilter3 || texture.minFilter !== NearestFilter3 || texture.anisotropy > 1 || texture.offset.x !== 0 || texture.offset.y !== 0 || texture.repeat.x !== 1 || texture.repeat.y !== 1 || texture.rotation !== 0 || !texture.matrixAutoUpdate && !texture.matrix.elements.every((value, i) => value === (i % 4 === 0 ? 1 : 0))) return;
12792
+ if (material.type !== "MeshBasicMaterial" || !material.transparent || !material.depthWrite || material.opacity !== 1 || material.alphaMap || !texture?.isDataTexture || texture.format !== RGBAFormat3 || texture.type !== UnsignedByteType || texture.wrapS !== ClampToEdgeWrapping3 || texture.wrapT !== ClampToEdgeWrapping3 || texture.magFilter !== NearestFilter3 || texture.minFilter !== NearestFilter3 || texture.anisotropy > 1 || texture.offset.x !== 0 || texture.offset.y !== 0 || texture.repeat.x !== 1 || texture.repeat.y !== 1 || texture.rotation !== 0 || !texture.matrixAutoUpdate && !texture.matrix.elements.every((value, i) => value === (i % 4 === 0 ? 1 : 0))) return;
12529
12793
  const uv = geometry.getAttribute(texture.channel === 0 ? "uv" : `uv${texture.channel}`);
12530
12794
  const colors2 = geometry.getAttribute("color");
12531
12795
  if (!uv || material.vertexColors && colors2?.itemSize === 4) return;
@@ -14190,6 +14454,7 @@ import { Scene as Scene4 } from "three";
14190
14454
 
14191
14455
  // src/skin/scene/SkinObject.ts
14192
14456
  init_SceneObject();
14457
+ import { Object3D as Object3D6 } from "three";
14193
14458
 
14194
14459
  // src/skin/SkinPart.ts
14195
14460
  var SkinPart = /* @__PURE__ */ ((SkinPart2) => {
@@ -14206,11 +14471,21 @@ var SkinPart = /* @__PURE__ */ ((SkinPart2) => {
14206
14471
  SkinPart2["RIGHT_TROUSERS"] = "rightTrousers";
14207
14472
  SkinPart2["LEFT_TROUSERS"] = "leftTrousers";
14208
14473
  SkinPart2["CAPE"] = "cape";
14474
+ SkinPart2["LEFT_EAR"] = "leftEar";
14475
+ SkinPart2["RIGHT_EAR"] = "rightEar";
14209
14476
  return SkinPart2;
14210
14477
  })(SkinPart || {});
14211
14478
  var SKIN_PARTS = Object.values(SkinPart);
14212
14479
 
14213
14480
  // src/skin/SkinTextureCoordinates.ts
14481
+ var earCoordinates = {
14482
+ west: { uv: [24, 1, 25, 7] },
14483
+ east: { uv: [31, 1, 32, 7] },
14484
+ south: { uv: [25, 1, 31, 7] },
14485
+ north: { uv: [32, 1, 38, 7] },
14486
+ up: { uv: [25, 0, 31, 1] },
14487
+ down: { uv: [31, 1, 37, 0] }
14488
+ };
14214
14489
  var baseSkinCoordinates = {
14215
14490
  head: {
14216
14491
  west: {
@@ -14831,7 +15106,9 @@ var baseSkinCoordinates = {
14831
15106
  south: { uv: [12, 1, 22, 17] },
14832
15107
  up: { uv: [11, 1, 1, 0] },
14833
15108
  down: { uv: [21, 0, 11, 1] }
14834
- }
15109
+ },
15110
+ leftEar: earCoordinates,
15111
+ rightEar: earCoordinates
14835
15112
  };
14836
15113
  var classicSkinTextureCoordinates = {
14837
15114
  ...baseSkinCoordinates
@@ -14882,6 +15159,7 @@ var box4_12_4outer = [4.5, 12.5, 4.5];
14882
15159
  var box3_12_4inner = [3, 12, 4];
14883
15160
  var box3_12_4outer = [3.5, 12.5, 4.5];
14884
15161
  var box10_16_1 = [10, 16, 1];
15162
+ var box8_8_3ear = [8, 8, 3];
14885
15163
  var baseSkinGeometries = {
14886
15164
  head: box8inner,
14887
15165
  hat: box8outer,
@@ -14895,7 +15173,9 @@ var baseSkinGeometries = {
14895
15173
  leftTrousers: box4_12_4outer,
14896
15174
  rightLeg: box4_12_4inner,
14897
15175
  rightTrousers: box4_12_4outer,
14898
- cape: box10_16_1
15176
+ cape: box10_16_1,
15177
+ leftEar: box8_8_3ear,
15178
+ rightEar: box8_8_3ear
14899
15179
  };
14900
15180
  var classicSkinGeometries = { ...{}, ...baseSkinGeometries };
14901
15181
  var slimSkinGeometries = {
@@ -14924,6 +15204,12 @@ var capeTextureSizes = {
14924
15204
  };
14925
15205
 
14926
15206
  // src/skin/scene/SkinObject.ts
15207
+ var ArmorClass;
15208
+ var EntityObjectClass;
15209
+ function hasCubes(part) {
15210
+ return part.cubes.length > 0 || Object.values(part.children).some(hasCubes);
15211
+ }
15212
+ var UPSIDE_DOWN_OFFSET = (1.8 + 0.1) * 16 / 0.9375;
14927
15213
  var SkinObject = class extends SceneObject {
14928
15214
  constructor(options) {
14929
15215
  super();
@@ -14931,27 +15217,42 @@ var SkinObject = class extends SceneObject {
14931
15217
  this.detectedSlim = false;
14932
15218
  this.skinLoad = 0;
14933
15219
  this.capeLoad = 0;
15220
+ this.armorLoad = 0;
15221
+ this.armorObjects = [];
14934
15222
  this.options = (0, import_ts_deepmerge.default)({}, SceneObject.DEFAULT_OPTIONS, options ?? {});
14935
15223
  this.slim = this.options.slim ?? false;
14936
15224
  }
14937
15225
  async init() {
14938
15226
  const cape = this.getGroupByName("cape" /* CAPE */);
15227
+ const capeOnBody = cape?.parent === this.getGroupByName("body" /* BODY */);
14939
15228
  this.createMeshes();
14940
- if (cape) this.getGroupByName("body" /* BODY */).attach(cape);
15229
+ if (cape) {
15230
+ const body = this.getGroupByName("body" /* BODY */);
15231
+ if (capeOnBody) body.add(cape);
15232
+ else body.attach(cape);
15233
+ }
15234
+ for (const { part, object: object4 } of this.armorObjects) this.getGroupByName(part).add(object4);
15235
+ this.updateEars();
15236
+ this.updateUpsideDown();
14941
15237
  }
14942
15238
  removeMeshes() {
14943
15239
  }
14944
15240
  createMeshes() {
14945
15241
  const mat = this.skinMaterial ?? Materials.MISSING_TEXTURE;
15242
+ this.model?.removeFromParent();
15243
+ const model = this.model = new Object3D6();
15244
+ this.add(model);
14946
15245
  {
14947
- const headGroup = this.createAndAddGroup("head", 0, 28, 0, "y" /* Y */, -4);
15246
+ const headGroup = this.createGroup("head", 0, 28, 0, "y" /* Y */, -4);
15247
+ model.add(headGroup);
14948
15248
  const headGeo = this.getBoxGeometry("head" /* HEAD */);
14949
15249
  const head = this.createAndAddMesh("head", headGroup, headGeo, mat, "y" /* Y */, 4);
14950
15250
  const hatGeo = this.getBoxGeometry("hat" /* HAT */);
14951
15251
  const hat = this.createAndAddMesh("hat", headGroup, hatGeo, mat, "y" /* Y */, 4);
14952
15252
  }
14953
15253
  {
14954
- const bodyGroup = this.createAndAddGroup("body", 0, 24, 0);
15254
+ const bodyGroup = this.createGroup("body", 0, 24, 0);
15255
+ model.add(bodyGroup);
14955
15256
  const bodyGeo = this.getBoxGeometry("body" /* BODY */);
14956
15257
  const body = this.createAndAddMesh("body", bodyGroup, bodyGeo, mat, "y" /* Y */, -6);
14957
15258
  const jacketGeo = this.getBoxGeometry("jacket" /* JACKET */);
@@ -14959,7 +15260,8 @@ var SkinObject = class extends SceneObject {
14959
15260
  }
14960
15261
  {
14961
15262
  {
14962
- const leftArmGroup = this.createAndAddGroup("leftArm", 5, 22, 0);
15263
+ const leftArmGroup = this.createGroup("leftArm", 5, 22, 0);
15264
+ model.add(leftArmGroup);
14963
15265
  const leftArmGeo = this.getBoxGeometry("leftArm" /* LEFT_ARM */);
14964
15266
  const leftArm = this.createAndAddMesh("leftArm", leftArmGroup, leftArmGeo, mat, "y" /* Y */, -4);
14965
15267
  leftArm.position.x = this.slim ? 0.5 : 1;
@@ -14968,7 +15270,8 @@ var SkinObject = class extends SceneObject {
14968
15270
  leftSleeve.position.x = this.slim ? 0.5 : 1;
14969
15271
  }
14970
15272
  {
14971
- const rightArmGroup = this.createAndAddGroup("rightArm", -5, 22, 0);
15273
+ const rightArmGroup = this.createGroup("rightArm", -5, 22, 0);
15274
+ model.add(rightArmGroup);
14972
15275
  const rightArmGeo = this.getBoxGeometry("rightArm" /* RIGHT_ARM */);
14973
15276
  const rightArm = this.createAndAddMesh("rightArm", rightArmGroup, rightArmGeo, mat, "y" /* Y */, -4);
14974
15277
  rightArm.position.x = this.slim ? -0.5 : -1;
@@ -14979,14 +15282,16 @@ var SkinObject = class extends SceneObject {
14979
15282
  }
14980
15283
  {
14981
15284
  {
14982
- const leftLegGroup = this.createAndAddGroup("leftLeg", 1.9, 12, 0);
15285
+ const leftLegGroup = this.createGroup("leftLeg", 1.9, 12, 0);
15286
+ model.add(leftLegGroup);
14983
15287
  const leftLegGeo = this.getBoxGeometry("leftLeg" /* LEFT_LEG */);
14984
15288
  const leftLeg = this.createAndAddMesh("leftLeg", leftLegGroup, leftLegGeo, mat, "y" /* Y */, -6);
14985
15289
  const leftTrousersGeo = this.getBoxGeometry("leftTrousers" /* LEFT_TROUSERS */);
14986
15290
  const leftTrousers = this.createAndAddMesh("leftTrousers", leftLegGroup, leftTrousersGeo, mat, "y" /* Y */, -6);
14987
15291
  }
14988
15292
  {
14989
- const rightLegGroup = this.createAndAddGroup("rightLeg", -1.9, 12, 0);
15293
+ const rightLegGroup = this.createGroup("rightLeg", -1.9, 12, 0);
15294
+ model.add(rightLegGroup);
14990
15295
  const rightLegGeo = this.getBoxGeometry("rightLeg" /* RIGHT_LEG */);
14991
15296
  const rightLeg = this.createAndAddMesh("rightLeg", rightLegGroup, rightLegGeo, mat, "y" /* Y */, -6);
14992
15297
  const rightTrousersGeo = this.getBoxGeometry("rightTrousers" /* RIGHT_TROUSERS */);
@@ -15053,6 +15358,54 @@ var SkinObject = class extends SceneObject {
15053
15358
  }
15054
15359
  this.notifyDirty();
15055
15360
  }
15361
+ /**
15362
+ * Loads armor from 1.21.4+ equipment assets, or pass undefined to remove it.
15363
+ * Each piece moves with the skin part it covers.
15364
+ */
15365
+ async setArmor(armor) {
15366
+ const load = ++this.armorLoad;
15367
+ const objects = [];
15368
+ if (armor) {
15369
+ const Armor2 = ArmorClass ??= (await Promise.resolve().then(() => (init_Armor(), Armor_exports))).Armor;
15370
+ const EntityObject2 = EntityObjectClass ??= (await Promise.resolve().then(() => (init_EntityObject(), EntityObject_exports))).EntityObject;
15371
+ const model = await Armor2.getEntity(armor);
15372
+ if (model) {
15373
+ const parts = { head: "head", body: "body", left_arm: "leftArm", right_arm: "rightArm", left_leg: "leftLeg", right_leg: "rightLeg" };
15374
+ for (const [name, part] of Object.entries(parts)) {
15375
+ const layers = {};
15376
+ for (const [draw, layer] of Object.entries(model.layers)) {
15377
+ const child = layer.layer.root.children[name];
15378
+ if (!child || !hasCubes(child)) continue;
15379
+ const pose = { offset: [0, 0, 0], rotation: [0, 0, 0] };
15380
+ layers[draw] = { ...layer, layer: { ...layer.layer, root: {
15381
+ pose,
15382
+ cubes: [],
15383
+ children: { [name]: { ...child, pose } }
15384
+ } } };
15385
+ }
15386
+ const first = Object.values(layers)[0];
15387
+ if (!first) continue;
15388
+ const object4 = new EntityObject2({ ...first, id: model.id, layers }, { flip: false });
15389
+ object4.rotation.x = Math.PI;
15390
+ objects.push({ part, object: object4 });
15391
+ }
15392
+ }
15393
+ }
15394
+ const results = await Promise.allSettled(objects.map(({ object: object4 }) => object4.init()));
15395
+ const failure = results.find((result) => result.status === "rejected");
15396
+ if (load !== this.armorLoad || failure) {
15397
+ for (const { object: object4 } of objects) object4.dispose();
15398
+ if (load === this.armorLoad && failure?.status === "rejected") throw failure.reason;
15399
+ return;
15400
+ }
15401
+ for (const { object: object4 } of this.armorObjects) {
15402
+ object4.dispose();
15403
+ object4.removeFromParent();
15404
+ }
15405
+ this.armorObjects = objects;
15406
+ for (const { part, object: object4 } of objects) this.getGroupByName(part)?.add(object4);
15407
+ this.notifyDirty();
15408
+ }
15056
15409
  /** Select the arm model, or pass undefined to use texture detection. */
15057
15410
  setSlim(slim) {
15058
15411
  this.options.slim = slim;
@@ -15071,6 +15424,39 @@ var SkinObject = class extends SceneObject {
15071
15424
  }
15072
15425
  this.notifyDirty();
15073
15426
  }
15427
+ /** Shows or hides deadmau5's ears, which vanilla draws from the skin texture at 24,0 for that player only. */
15428
+ setEars(ears) {
15429
+ if (ears === (this.options.ears ?? false)) return;
15430
+ this.options.ears = ears;
15431
+ this.updateEars();
15432
+ }
15433
+ updateEars() {
15434
+ const head = this.getGroupByName("head" /* HEAD */);
15435
+ if (!head) return;
15436
+ for (const [part, x] of [["leftEar" /* LEFT_EAR */, 6], ["rightEar" /* RIGHT_EAR */, -6]]) {
15437
+ const ear = this.getMeshByName(part);
15438
+ if (!this.options.ears) {
15439
+ ear?.removeFromParent();
15440
+ } else if (!ear) {
15441
+ const mesh = this.createAndAddMesh(part, head, this.getBoxGeometry(part), this.skinMaterial ?? Materials.MISSING_TEXTURE);
15442
+ mesh.position.set(x, 9, 0.5);
15443
+ }
15444
+ }
15445
+ this.notifyDirty();
15446
+ }
15447
+ /** Turns the player upside down, as vanilla does for players named Dinnerbone or Grumm. */
15448
+ setUpsideDown(upsideDown) {
15449
+ if (upsideDown === (this.options.upsideDown ?? false)) return;
15450
+ this.options.upsideDown = upsideDown;
15451
+ this.updateUpsideDown();
15452
+ }
15453
+ updateUpsideDown() {
15454
+ if (!this.model) return;
15455
+ const upsideDown = this.options.upsideDown ?? false;
15456
+ this.model.position.y = upsideDown ? UPSIDE_DOWN_OFFSET : 0;
15457
+ this.model.rotation.z = upsideDown ? Math.PI : 0;
15458
+ this.notifyDirty();
15459
+ }
15074
15460
  /** Select the texture layout, or pass undefined to detect it from image dimensions. */
15075
15461
  async setLegacy(legacy) {
15076
15462
  if (legacy === this.options.legacy) return;
@@ -15080,6 +15466,11 @@ var SkinObject = class extends SceneObject {
15080
15466
  dispose() {
15081
15467
  this.skinLoad++;
15082
15468
  this.capeLoad++;
15469
+ this.armorLoad++;
15470
+ for (const { object: object4 } of this.armorObjects.splice(0)) {
15471
+ object4.dispose();
15472
+ object4.removeFromParent();
15473
+ }
15083
15474
  super.dispose();
15084
15475
  }
15085
15476
  //TODO: layer toggles
@@ -15099,7 +15490,7 @@ function isBlockObject2(obj) {
15099
15490
  }
15100
15491
  var ModelObjectClass;
15101
15492
  var BlockObjectClass;
15102
- var EntityObjectClass;
15493
+ var EntityObjectClass2;
15103
15494
  var GuiObjectClass;
15104
15495
  var ItemTintsClass;
15105
15496
  var MineRenderScene = class _MineRenderScene extends Scene4 {
@@ -15243,7 +15634,7 @@ var MineRenderScene = class _MineRenderScene extends Scene4 {
15243
15634
  }
15244
15635
  /** Adds an entity loaded with {@link Entities.getEntity}, using its selected layers and textures. */
15245
15636
  async addEntity(entity, options, parent = this) {
15246
- const EntityObject2 = EntityObjectClass ??= (await Promise.resolve().then(() => (init_EntityObject(), EntityObject_exports))).EntityObject;
15637
+ const EntityObject2 = EntityObjectClass2 ??= (await Promise.resolve().then(() => (init_EntityObject(), EntityObject_exports))).EntityObject;
15247
15638
  return this.addSceneObject(entity, () => new EntityObject2(entity, options), options, parent);
15248
15639
  }
15249
15640
  /**
@@ -16598,6 +16989,23 @@ var itemContext = (withDisplayContext) => (value, path) => {
16598
16989
  }
16599
16990
  });
16600
16991
  };
16992
+ function entityAnimation(value, path) {
16993
+ const animation = object3(value, path);
16994
+ if (["name", "clips"].filter((key) => animation[key] !== void 0).length !== 1) {
16995
+ fail(path, "expected exactly one of name or clips");
16996
+ }
16997
+ fields(value, path, { name: text, clips: (value2, path2) => {
16998
+ list((value3, path3) => {
16999
+ fields(value3, path3, { name: text, weight: nonnegative }, ["name"]);
17000
+ })(value2, path2);
17001
+ if (!value2.length) fail(path2, "expected at least one clip");
17002
+ const names = /* @__PURE__ */ new Set();
17003
+ for (const [index, clip] of value2.entries()) {
17004
+ if (names.has(clip.name)) fail(`${path2}[${index}].name`, `duplicate clip "${clip.name}"`);
17005
+ names.add(clip.name);
17006
+ }
17007
+ }, loop: boolean, speed: nonnegative, time: nonnegative, paused: boolean });
17008
+ }
16601
17009
  function guiLayer(value, path) {
16602
17010
  const layer = object3(value, path);
16603
17011
  if (["texture", "item", "text"].filter((key) => layer[key] !== void 0).length !== 1) {
@@ -16638,13 +17046,27 @@ function validateObject(value, path) {
16638
17046
  const required = ["id", "type"];
16639
17047
  const renderOptions = { wireframe: boolean };
16640
17048
  switch (input.type) {
16641
- case "skin":
17049
+ case "skin": {
17050
+ const piece = (value2, path2) => {
17051
+ if (typeof value2 === "string") asset(value2, path2);
17052
+ else fields(value2, path2, {
17053
+ asset,
17054
+ color,
17055
+ glint: boolean,
17056
+ trim: (value3, path3) => {
17057
+ fields(value3, path3, { pattern: asset, material: asset }, ["pattern", "material"]);
17058
+ }
17059
+ }, ["asset"]);
17060
+ };
16642
17061
  fields(value, path, {
16643
17062
  ...common,
16644
17063
  skin: text,
16645
17064
  cape: (value2, path2) => {
16646
17065
  fields(value2, path2, { texture: text, layout: enumeration(["minecraft", "optifine", "labymod"]) }, ["texture"]);
16647
17066
  },
17067
+ armor: (value2, path2) => {
17068
+ fields(value2, path2, { head: piece, chest: piece, legs: piece, feet: piece });
17069
+ },
16648
17070
  hiddenParts: list(enumeration(SKIN_PARTS)),
16649
17071
  pose: (value2, path2) => {
16650
17072
  fields(value2, path2, {
@@ -16658,10 +17080,11 @@ function validateObject(value, path) {
16658
17080
  });
16659
17081
  },
16660
17082
  options: (value2, path2) => {
16661
- fields(value2, path2, { slim: boolean, legacy: boolean });
17083
+ fields(value2, path2, { slim: boolean, legacy: boolean, ears: boolean, upsideDown: boolean });
16662
17084
  }
16663
17085
  }, required);
16664
17086
  break;
17087
+ }
16665
17088
  case "block":
16666
17089
  fields(value, path, {
16667
17090
  ...common,
@@ -16700,9 +17123,7 @@ function validateObject(value, path) {
16700
17123
  })
16701
17124
  });
16702
17125
  },
16703
- animation: (value2, path2) => {
16704
- fields(value2, path2, { name: text, loop: boolean, speed: nonnegative, time: nonnegative, paused: boolean }, ["name"]);
16705
- }
17126
+ animation: entityAnimation
16706
17127
  }, [...required, "asset"]);
16707
17128
  if (Array.isArray(input.layers) && !input.layers.length) fail(`${path}.layers`, "expected at least one layer");
16708
17129
  break;
@@ -16931,6 +17352,7 @@ var SceneDocumentLoader = class {
16931
17352
  await skin.setSkinTexture(texture);
16932
17353
  await content.initialize(skin);
16933
17354
  if (definition.cape) await skin.setCapeTexture(await skinTexture(definition.cape.texture, true), definition.cape.layout);
17355
+ if (definition.armor) await skin.setArmor(definition.armor);
16934
17356
  for (const [part, rotation] of Object.entries(definition.pose ?? {})) {
16935
17357
  skin.getGroupByName(part)?.rotation.set(...rotation, "XYZ");
16936
17358
  }
@@ -16969,10 +17391,20 @@ var SceneDocumentLoader = class {
16969
17391
  const entity = await content.addEntity(model, { ...definition.options, instanceMeshes: false });
16970
17392
  object4 = entity;
16971
17393
  if (definition.animation) {
17394
+ const animation = definition.animation;
16972
17395
  const animations = await Entities.getAnimations(key);
16973
- const clip = animations?.[definition.animation.name];
16974
- if (!clip) throw new Error(`Entity ${definition.asset} has no animation "${definition.animation.name}"`);
16975
- entity.playAnimation(clip, definition.animation);
17396
+ if (animation.name !== void 0) {
17397
+ const clip = animations && Object.prototype.hasOwnProperty.call(animations, animation.name) ? animations[animation.name] : void 0;
17398
+ if (!clip) throw new Error(`Entity ${definition.asset} has no animation "${animation.name}"`);
17399
+ entity.playAnimation(clip, animation);
17400
+ } else {
17401
+ const clips = animation.clips.map(({ name }) => {
17402
+ const clip = animations && Object.prototype.hasOwnProperty.call(animations, name) ? animations[name] : void 0;
17403
+ if (!clip) throw new Error(`Entity ${definition.asset} has no animation "${name}"`);
17404
+ return clip;
17405
+ });
17406
+ entity.playAnimations(clips, { ...animation, weights: animation.clips.map((clip) => clip.weight ?? 1) });
17407
+ }
16976
17408
  }
16977
17409
  break;
16978
17410
  }
@@ -17021,6 +17453,7 @@ init_Skins();
17021
17453
 
17022
17454
  // src/texture/index.ts
17023
17455
  init_Colormaps();
17456
+ init_PalettedTextures();
17024
17457
  init_TextureAtlas();
17025
17458
  init_TextureLoader();
17026
17459
  init_Textures();
@@ -17810,7 +18243,7 @@ var AnvilWorldSource = class {
17810
18243
  read.waiters++;
17811
18244
  return new Promise((resolve, reject) => {
17812
18245
  let finished = false;
17813
- const release2 = () => {
18246
+ const release = () => {
17814
18247
  if (finished) return false;
17815
18248
  finished = true;
17816
18249
  signal?.removeEventListener("abort", abort);
@@ -17818,7 +18251,7 @@ var AnvilWorldSource = class {
17818
18251
  return true;
17819
18252
  };
17820
18253
  const abort = () => {
17821
- if (!release2()) return;
18254
+ if (!release()) return;
17822
18255
  if (!read.waiters && !read.settled) {
17823
18256
  if (this.pending.get(key) === read) this.pending.delete(key);
17824
18257
  read.controller.abort(signal.reason);
@@ -17828,10 +18261,10 @@ var AnvilWorldSource = class {
17828
18261
  signal?.addEventListener("abort", abort, { once: true });
17829
18262
  read.promise.then(
17830
18263
  (data) => {
17831
- if (release2()) resolve(data);
18264
+ if (release()) resolve(data);
17832
18265
  },
17833
18266
  (error) => {
17834
- if (release2()) reject(error);
18267
+ if (release()) reject(error);
17835
18268
  }
17836
18269
  );
17837
18270
  if (signal?.aborted) abort();
@@ -17853,7 +18286,8 @@ var AnvilWorldSource = class {
17853
18286
  };
17854
18287
 
17855
18288
  // src/world/Chunk.ts
17856
- import { Color as Color13, Vector3 as Vector320 } from "three";
18289
+ init_BlockObject();
18290
+ import { Color as Color13, Vector3 as Vector321 } from "three";
17857
18291
  init_BlockStates();
17858
18292
  init_AssetKey();
17859
18293
 
@@ -17863,6 +18297,7 @@ init_util();
17863
18297
  import { Vector3 as Vector319 } from "three";
17864
18298
  init_AssetKey();
17865
18299
  init_BlockStates();
18300
+ init_BlockEntities();
17866
18301
  init_CubeFace();
17867
18302
 
17868
18303
  // src/world/SectionModels.ts
@@ -17960,6 +18395,10 @@ var SectionModels = class {
17960
18395
  biomeTint: source ? [...templates.values()].some((template) => template.biomeTint?.some((value) => value === 1)) || void 0 : void 0
17961
18396
  };
17962
18397
  }
18398
+ /** Starts loading the state's models and atlases so a later `prepareState` with the same properties finds them loaded. */
18399
+ async preload(blockState3, properties = {}) {
18400
+ await this.state(blockState3, properties);
18401
+ }
17963
18402
  async state(blockState3, properties) {
17964
18403
  let states = this.states.get(blockState3);
17965
18404
  if (!states) this.states.set(blockState3, states = /* @__PURE__ */ new Map());
@@ -17982,22 +18421,24 @@ var SectionModels = class {
17982
18421
  async prepare(blockState3, properties) {
17983
18422
  const state = { ...await BlockStateResolver.defaults(blockState3), ...properties };
17984
18423
  const groups = BlockStateResolver.matching(blockState3, state);
17985
- if (!groups.length) return void 0;
18424
+ if (!groups.length || groups.some((group) => Array.isArray(group) && !group.length)) return void 0;
18425
+ const variants = groups.flatMap((group) => Array.isArray(group) ? group : [group]);
18426
+ if (variants.some((variant) => !variant.model)) return void 0;
18427
+ const settled = await Promise.allSettled(variants.map(async (variant) => {
18428
+ const modelKey = AssetKey.parse("models", variant.model);
18429
+ modelKey.root = blockState3.key?.root;
18430
+ const model = await Models.getMerged(modelKey);
18431
+ if (!model?.elements?.length) return void 0;
18432
+ const atlas = await UVMapper.getAtlas(model);
18433
+ const rotation = BlockStateResolver.rotation(variant);
18434
+ if (!atlas || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return void 0;
18435
+ return { model, atlas, rotation };
18436
+ }));
17986
18437
  const prepared = /* @__PURE__ */ new Map();
17987
- for (const group of groups) {
17988
- const choices = Array.isArray(group) ? group : [group];
17989
- if (!choices.length) return void 0;
17990
- for (const variant of choices) {
17991
- if (!variant.model) return void 0;
17992
- const modelKey = AssetKey.parse("models", variant.model);
17993
- modelKey.root = blockState3.key?.root;
17994
- const model = await Models.getMerged(modelKey);
17995
- if (!model?.elements?.length) return void 0;
17996
- const atlas = await UVMapper.getAtlas(model);
17997
- const rotation = BlockStateResolver.rotation(variant);
17998
- if (!atlas || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return void 0;
17999
- prepared.set(variant, { model, atlas, rotation });
18000
- }
18438
+ for (const [i, result] of settled.entries()) {
18439
+ if (result.status === "rejected") throw result.reason;
18440
+ if (!result.value) return void 0;
18441
+ prepared.set(variants[i], result.value);
18001
18442
  }
18002
18443
  return { state, groups, choices: prepared };
18003
18444
  }
@@ -18457,6 +18898,7 @@ function skipsBlockFace(data, rule, block, neighbor, face) {
18457
18898
  // src/world/MineRenderWorld.ts
18458
18899
  init_log();
18459
18900
  var log18 = logger("MineRenderWorld");
18901
+ var YIELD_INTERVAL_MS = 8;
18460
18902
  var MineRenderWorld = class {
18461
18903
  constructor(scene, options = {}) {
18462
18904
  this._chunks = /* @__PURE__ */ new Map();
@@ -18570,7 +19012,7 @@ var MineRenderWorld = class {
18570
19012
  failures.set(chunk, error);
18571
19013
  if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingBiomes.set(chunk, void 0);
18572
19014
  }
18573
- if (performance.now() - sliceStart > 8) {
19015
+ if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
18574
19016
  await yieldToEventLoop();
18575
19017
  sliceStart = performance.now();
18576
19018
  }
@@ -18671,15 +19113,18 @@ var MineRenderWorld = class {
18671
19113
  const key = AssetKey.parse("blockstates", block.type);
18672
19114
  keys.set(key.serialize(), key);
18673
19115
  }
18674
- await BlockStates.getAll(keys.values());
19116
+ const loaded = await BlockStates.getAll(keys.values());
19117
+ const states = new Map([...keys.keys()].map((key, i) => [key, loaded[i]]));
18675
19118
  if (!useBatches) {
18676
19119
  for (const block of multiblock.blocks) await place(block);
18677
19120
  return;
18678
19121
  }
18679
19122
  if (!executor) {
18680
19123
  const groups2 = /* @__PURE__ */ new Map();
19124
+ const distinct = /* @__PURE__ */ new Map();
18681
19125
  const position = new Vector319();
18682
19126
  for (const block of multiblock.blocks) {
19127
+ if (this.sectionModels && !Chunk.isAir(block)) distinct.set(ChunkData.paletteKey(block), block);
18683
19128
  const [x, y, z] = block.position;
18684
19129
  this.validatePosBounds(position.set(x, y, z));
18685
19130
  const cx = Math.floor(x / 16), cy = Math.floor(y / 16), cz = Math.floor(z / 16);
@@ -18690,6 +19135,16 @@ var MineRenderWorld = class {
18690
19135
  if (!group) groups2.set(chunk, group = []);
18691
19136
  group.push({ index: (y - cy * 16) * 256 + (z - cz * 16) * 16 + x - cx * 16, block });
18692
19137
  }
19138
+ if (this.sectionModels) {
19139
+ for (const block of distinct.values()) {
19140
+ const state = states.get(AssetKey.parse("blockstates", block.type).serialize());
19141
+ if (!state) continue;
19142
+ void (async () => {
19143
+ if (state.key && BlockEntities.entry(await BlockEntities.getIndex(state.key.root), state.key.toNamespacedString())) return;
19144
+ await this.sectionModels.preload(state, block.properties);
19145
+ })().catch(() => void 0);
19146
+ }
19147
+ }
18693
19148
  await this.placeChunkGroups(groups2, changes);
18694
19149
  return;
18695
19150
  }
@@ -18725,7 +19180,7 @@ var MineRenderWorld = class {
18725
19180
  ));
18726
19181
  for (const pos of positions) changes.set(`${pos.x},${pos.y},${pos.z}`, pos);
18727
19182
  }
18728
- if (performance.now() - sliceStart > 8) {
19183
+ if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
18729
19184
  await yieldToEventLoop();
18730
19185
  sliceStart = performance.now();
18731
19186
  }
@@ -18733,6 +19188,62 @@ var MineRenderWorld = class {
18733
19188
  }
18734
19189
  /** Replaces a chunk column's blocks, biomes, and saved light and, with `renderEntities`, its supported saved mobs. */
18735
19190
  async placeChunk(chunk, executor) {
19191
+ return this.placeColumn(chunk, executor);
19192
+ }
19193
+ /**
19194
+ * Replaces parsed columns in input order, then refreshes neighboring geometry once.
19195
+ * Duplicate coordinates retain their order. A failed column stops placement; queued refreshes finish before rejection.
19196
+ */
19197
+ async placeChunks(chunks) {
19198
+ const columns = [];
19199
+ for (const chunk of chunks) {
19200
+ const { x, z } = chunk ?? {};
19201
+ if (![x, z].every(Number.isSafeInteger)) {
19202
+ throw new RangeError("Chunk column coordinates must be safe integers");
19203
+ }
19204
+ const sections = Array.from(chunk.sections, (section) => {
19205
+ const { y } = section ?? {};
19206
+ if (!Number.isSafeInteger(y)) throw new RangeError("Chunk section coordinates must be safe integers");
19207
+ return { ...section, y };
19208
+ });
19209
+ columns.push({ ...chunk, x, z, sections });
19210
+ }
19211
+ if (!columns.length) return;
19212
+ await this.culling;
19213
+ let failed = false, failure, sliceStart = performance.now();
19214
+ try {
19215
+ for (const column of columns) {
19216
+ await this.placeColumn(column, void 0, true);
19217
+ if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
19218
+ await yieldToEventLoop();
19219
+ sliceStart = performance.now();
19220
+ }
19221
+ }
19222
+ } catch (error) {
19223
+ failed = true;
19224
+ failure = error;
19225
+ } finally {
19226
+ try {
19227
+ const failures = await this.refreshBiomes(true);
19228
+ if (failures.size) log18.warn("Failed to refresh biome colors", failures.values().next().value);
19229
+ } catch (error) {
19230
+ if (!failed) {
19231
+ failed = true;
19232
+ failure = error;
19233
+ }
19234
+ }
19235
+ try {
19236
+ await this.updateCulling([]);
19237
+ } catch (error) {
19238
+ if (!failed) {
19239
+ failed = true;
19240
+ failure = error;
19241
+ }
19242
+ }
19243
+ }
19244
+ if (failed) throw failure;
19245
+ }
19246
+ async placeColumn(chunk, executor, deferCulling = false) {
18736
19247
  if (![chunk.x, chunk.z].every(Number.isSafeInteger)) {
18737
19248
  throw new RangeError("Chunk column coordinates must be safe integers");
18738
19249
  }
@@ -18794,7 +19305,7 @@ var MineRenderWorld = class {
18794
19305
  } finally {
18795
19306
  this.markSectionChanged(target);
18796
19307
  }
18797
- if (performance.now() - sliceStart > 8) {
19308
+ if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
18798
19309
  await yieldToEventLoop();
18799
19310
  sliceStart = performance.now();
18800
19311
  }
@@ -18817,9 +19328,12 @@ var MineRenderWorld = class {
18817
19328
  await placeEntities?.();
18818
19329
  } finally {
18819
19330
  this.markBiomesChanged(chunk.x, chunk.z, void 0, false);
18820
- const failures = await this.refreshBiomes();
18821
- if (failures.size) log18.warn("Failed to refresh biome colors", failures.values().next().value);
18822
- await this.updateCulling([...changes.values()]);
19331
+ if (deferCulling) await this.updateCulling([...changes.values()], true);
19332
+ else {
19333
+ const failures = await this.refreshBiomes();
19334
+ if (failures.size) log18.warn("Failed to refresh biome colors", failures.values().next().value);
19335
+ await this.updateCulling([...changes.values()]);
19336
+ }
18823
19337
  }
18824
19338
  }
18825
19339
  /**
@@ -18849,7 +19363,7 @@ var MineRenderWorld = class {
18849
19363
  for (const { x, z } of columns.values()) {
18850
19364
  this.entities?.clearColumn(x, z);
18851
19365
  await this.clearChunkColumn(x, z);
18852
- if (performance.now() - sliceStart > 8) {
19366
+ if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
18853
19367
  await yieldToEventLoop();
18854
19368
  sliceStart = performance.now();
18855
19369
  }
@@ -18988,15 +19502,25 @@ var MineRenderWorld = class {
18988
19502
  for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
18989
19503
  await chunk.refreshLight(indices === "all" ? void 0 : indices);
18990
19504
  chunk.refreshAmbientOcclusion(this._ambientOcclusion);
18991
- if ((this._ambientOcclusion || this.ambientOcclusionPending) && performance.now() - sliceStart > 8) {
19505
+ if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
18992
19506
  await yieldToEventLoop();
18993
19507
  sliceStart = performance.now();
18994
19508
  }
18995
19509
  }
18996
- const builds = await Promise.allSettled([...batch.keys()].map(async (chunk) => {
18997
- await chunk.rebuildSectionMesh();
18998
- batch.delete(chunk);
18999
- }));
19510
+ const pending = [];
19511
+ for (const chunk of [...batch.keys()]) {
19512
+ const build = (async () => {
19513
+ await chunk.rebuildSectionMesh();
19514
+ batch.delete(chunk);
19515
+ })();
19516
+ void build.catch(() => void 0);
19517
+ pending.push(build);
19518
+ if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
19519
+ await yieldToEventLoop();
19520
+ sliceStart = performance.now();
19521
+ }
19522
+ }
19523
+ const builds = await Promise.allSettled(pending);
19000
19524
  const failure = builds.find((result) => result.status === "rejected");
19001
19525
  if (failure?.status === "rejected") throw failure.reason;
19002
19526
  } catch (error) {
@@ -19113,7 +19637,7 @@ init_BlockEntities();
19113
19637
  // src/world/SectionMesh.ts
19114
19638
  init_CanvasCompat();
19115
19639
  init_Materials();
19116
- import { BufferGeometry as BufferGeometry9, Color as Color12, DoubleSide as DoubleSide8, Float32BufferAttribute as Float32BufferAttribute10, FrontSide as FrontSide4, Group as Group3, Mesh as Mesh12, Uint32BufferAttribute } from "three";
19640
+ import { BufferGeometry as BufferGeometry9, Color as Color12, DoubleSide as DoubleSide8, DynamicDrawUsage, Float32BufferAttribute as Float32BufferAttribute10, FrontSide as FrontSide4, Group as Group3, Matrix4 as Matrix413, Mesh as Mesh12, Uint32BufferAttribute, Vector3 as Vector320 } from "three";
19117
19641
 
19118
19642
  // src/world/SectionGeometry.ts
19119
19643
  init_FluidQuads();
@@ -19388,6 +19912,8 @@ function sectionGeometryTransferables(pages) {
19388
19912
 
19389
19913
  // src/world/SectionWorker.ts
19390
19914
  init_Env();
19915
+ init_log();
19916
+ var log19 = logger("SectionWorker");
19391
19917
  var SectionWorker = class _SectionWorker {
19392
19918
  constructor(worker) {
19393
19919
  this.worker = worker;
@@ -19401,8 +19927,12 @@ var SectionWorker = class _SectionWorker {
19401
19927
  if (reply.type === "pages") pending.resolve(reply.pages);
19402
19928
  else pending.reject(new Error(reply.message));
19403
19929
  });
19404
- worker.addEventListener("error", (event) => this.fail(event.message || "Section worker failed"));
19405
- worker.addEventListener("messageerror", () => this.fail("Section worker message could not be deserialized"));
19930
+ const fallBack = (message) => {
19931
+ log19.warn(`${message}, so sections build on the main thread`);
19932
+ this.fail(message);
19933
+ };
19934
+ worker.addEventListener("error", (event) => fallBack(event.message || "Section worker failed"));
19935
+ worker.addEventListener("messageerror", () => fallBack("Section worker message could not be deserialized"));
19406
19936
  }
19407
19937
  static {
19408
19938
  this.initialized = false;
@@ -19440,6 +19970,9 @@ var SectionWorker = class _SectionWorker {
19440
19970
  };
19441
19971
 
19442
19972
  // src/world/SectionMesh.ts
19973
+ var BLOCK_SIZE = 16;
19974
+ var QUAD_VERTICES = 4;
19975
+ var QUAD_INDICES = 6;
19443
19976
  var templateCache = /* @__PURE__ */ new WeakMap();
19444
19977
  var WATER_TINT = new Color12(4159204).toArray();
19445
19978
  function toInput(entries, maxAtlasSize, fluids, lighting) {
@@ -19476,8 +20009,8 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
19476
20009
  if (!data) {
19477
20010
  const indices = template.geometry.getIndex();
19478
20011
  const position = template.geometry.getAttribute("position");
19479
- const quads = indices.count / 6;
19480
- if (indices.count % 6 !== 0 || position.count !== quads * 4) {
20012
+ const quads = indices.count / QUAD_INDICES;
20013
+ if (indices.count % QUAD_INDICES !== 0 || position.count !== quads * QUAD_VERTICES) {
19481
20014
  throw new RangeError("Section template geometry must hold four vertices and six indices per quad");
19482
20015
  }
19483
20016
  data = {
@@ -19487,8 +20020,8 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
19487
20020
  uvs: new Float32Array(quads * 8),
19488
20021
  uvBounds: new Float32Array(quads * 16),
19489
20022
  colors: new Float32Array(quads * 12),
19490
- shade: new Float32Array(quads * 4),
19491
- indices: new Uint16Array(quads * 6),
20023
+ shade: new Float32Array(quads * QUAD_VERTICES),
20024
+ indices: new Uint16Array(quads * QUAD_INDICES),
19492
20025
  cullFaces: new Uint8Array(template.cullFaces),
19493
20026
  biomeTint: template.biomeTint ? new Uint8Array(template.biomeTint) : void 0,
19494
20027
  atlas: 0,
@@ -19500,7 +20033,7 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
19500
20033
  const bounds = template.geometry.getAttribute("uvBounds");
19501
20034
  const color2 = template.geometry.getAttribute("color");
19502
20035
  const shade = template.geometry.getAttribute("shade");
19503
- for (let vertex = 0; vertex < quads * 4; vertex++) {
20036
+ for (let vertex = 0; vertex < quads * QUAD_VERTICES; vertex++) {
19504
20037
  data.positions.set([position.getX(vertex), position.getY(vertex), position.getZ(vertex)], vertex * 3);
19505
20038
  data.normals.set([normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex)], vertex * 3);
19506
20039
  data.uvs.set([uv.getX(vertex), uv.getY(vertex)], vertex * 2);
@@ -19513,7 +20046,7 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
19513
20046
  data.colors.set([color2?.getX(vertex) ?? 1, color2?.getY(vertex) ?? 1, color2?.getZ(vertex) ?? 1], vertex * 3);
19514
20047
  data.shade[vertex] = shade?.getX(vertex) ?? 1;
19515
20048
  }
19516
- for (let i2 = 0; i2 < data.indices.length; i2++) data.indices[i2] = indices.getX(i2) - 4 * Math.floor(i2 / 6);
20049
+ for (let i2 = 0; i2 < data.indices.length; i2++) data.indices[i2] = indices.getX(i2) - QUAD_VERTICES * Math.floor(i2 / QUAD_INDICES);
19517
20050
  templateCache.set(template, data);
19518
20051
  }
19519
20052
  id = input.templateData.length;
@@ -19546,6 +20079,54 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
19546
20079
  }
19547
20080
  return { input, atlases: atlases2 };
19548
20081
  }
20082
+ function sortTransparentQuads(mesh) {
20083
+ const index = mesh.geometry.getIndex();
20084
+ if (index.count <= QUAD_INDICES) return;
20085
+ index.setUsage(DynamicDrawUsage);
20086
+ const count = index.count / QUAD_INDICES;
20087
+ let source, centers, depths, order;
20088
+ const view = new Matrix413(), localCamera = new Vector320(), key = new Vector320(), previous = new Vector320();
20089
+ let previousOrthographic;
20090
+ mesh.onBeforeRender = (_renderer, _scene, camera) => {
20091
+ localCamera.setFromMatrixPosition(camera.matrixWorld).applyMatrix4(view.copy(mesh.matrixWorld).invert());
20092
+ const orthographic = !!camera.isOrthographicCamera;
20093
+ if (orthographic) {
20094
+ view.multiplyMatrices(camera.matrixWorldInverse, mesh.matrixWorld);
20095
+ key.set(view.elements[2], view.elements[6], view.elements[10]);
20096
+ } else {
20097
+ key.copy(localCamera).divideScalar(BLOCK_SIZE).floor();
20098
+ }
20099
+ if (orthographic === previousOrthographic && key.equals(previous)) return;
20100
+ previous.copy(key);
20101
+ previousOrthographic = orthographic;
20102
+ if (!centers) {
20103
+ source = new Uint32Array(index.array);
20104
+ centers = new Float32Array(count * 3);
20105
+ depths = new Float32Array(count);
20106
+ order = Uint32Array.from({ length: count }, (_, quad) => quad);
20107
+ const positions = mesh.geometry.getAttribute("position");
20108
+ for (let quad = 0; quad < count; quad++) {
20109
+ for (let axis = 0; axis < 3; axis++) {
20110
+ let center = 0;
20111
+ for (let corner = 0; corner < QUAD_VERTICES; corner++) center += positions.getComponent(quad * QUAD_VERTICES + corner, axis);
20112
+ centers[quad * 3 + axis] = center / QUAD_VERTICES;
20113
+ }
20114
+ }
20115
+ }
20116
+ for (let quad = 0; quad < count; quad++) {
20117
+ const x = centers[quad * 3], y = centers[quad * 3 + 1], z = centers[quad * 3 + 2];
20118
+ depths[quad] = orthographic ? key.x * x + key.y * y + key.z * z : -((x - localCamera.x) ** 2 + (y - localCamera.y) ** 2 + (z - localCamera.z) ** 2);
20119
+ }
20120
+ order.sort((a, b) => depths[a] - depths[b] || a - b);
20121
+ if (!order.some((quad, offset) => index.array[offset * QUAD_INDICES] !== source[quad * QUAD_INDICES])) return;
20122
+ for (let offset = 0; offset < count; offset++) {
20123
+ for (let indexOffset = 0; indexOffset < QUAD_INDICES; indexOffset++) {
20124
+ index.array[offset * QUAD_INDICES + indexOffset] = source[order[offset] * QUAD_INDICES + indexOffset];
20125
+ }
20126
+ }
20127
+ index.needsUpdate = true;
20128
+ };
20129
+ }
19549
20130
  var SectionMesh = class _SectionMesh extends Group3 {
19550
20131
  constructor() {
19551
20132
  super(...arguments);
@@ -19555,7 +20136,11 @@ var SectionMesh = class _SectionMesh extends Group3 {
19555
20136
  /** Returns the owning section cell for a triangle hit on this mesh's merged geometry. */
19556
20137
  getBlockIndex(hit) {
19557
20138
  if (hit.faceIndex === void 0 || hit.faceIndex === null) return void 0;
19558
- return this.blockIndices.get(hit.object)?.[Math.floor(hit.faceIndex / 2)];
20139
+ const mesh = hit.object;
20140
+ const owners = this.blockIndices.get(mesh);
20141
+ if (!owners) return void 0;
20142
+ const vertex = mesh.geometry.getIndex().getX(hit.faceIndex * 3);
20143
+ return owners[Math.floor(vertex / QUAD_VERTICES)];
19559
20144
  }
19560
20145
  /** Builds section-local meshes from visible quads. `maxAtlasSize` limits each atlas dimension in pixels. */
19561
20146
  static build(entries, maxAtlasSize = 2048, fluids, lighting) {
@@ -19591,13 +20176,14 @@ var SectionMesh = class _SectionMesh extends Group3 {
19591
20176
  const material = Materials.createShadedCanvasMaterial(canvas, transparent, false, true);
19592
20177
  material.side = FrontSide4;
19593
20178
  if (page.layer === 2) {
20179
+ material.depthWrite = !material.transparent;
19594
20180
  material.side = DoubleSide8;
19595
20181
  material.forceSinglePass = true;
19596
- material.depthWrite = !material.transparent;
19597
20182
  }
19598
20183
  material.vertexColors = true;
19599
20184
  const texture = material.uniforms?.map?.value ?? material.map;
19600
20185
  const mesh = new Mesh12(geometry, material);
20186
+ if (transparent) sortTransparentQuads(mesh);
19601
20187
  section.add(mesh);
19602
20188
  section.blockIndices.set(mesh, page.blockIndices);
19603
20189
  const unsubscribe = [];
@@ -19656,7 +20242,7 @@ var Chunk = class _Chunk {
19656
20242
  this.sectionBlocks = /* @__PURE__ */ new Map();
19657
20243
  this.hiddenBlocks = /* @__PURE__ */ new Set();
19658
20244
  this.fluidCells = new Uint8Array(4096);
19659
- this.blockPosition = new Vector320();
20245
+ this.blockPosition = new Vector321();
19660
20246
  this.meshDirty = false;
19661
20247
  this.meshGeneration = 0;
19662
20248
  this.blockChanges = /* @__PURE__ */ new Set();
@@ -19716,7 +20302,7 @@ var Chunk = class _Chunk {
19716
20302
  const info = owner.chunk.renderedBlocks.get(owner.index);
19717
20303
  if (!info) continue;
19718
20304
  return {
19719
- position: new Vector320(
20305
+ position: new Vector321(
19720
20306
  owner.chunk.x * 16 + owner.index % 16,
19721
20307
  owner.chunk.y * 16 + Math.floor(owner.index / 256),
19722
20308
  owner.chunk.z * 16 + Math.floor(owner.index / 16) % 16
@@ -19731,18 +20317,18 @@ var Chunk = class _Chunk {
19731
20317
  }
19732
20318
  getBlockAt(posOrX, y, z) {
19733
20319
  if (typeof posOrX == "number") {
19734
- return this.getBlockAt(new Vector320(posOrX, y, z));
20320
+ return this.getBlockAt(new Vector321(posOrX, y, z));
19735
20321
  }
19736
20322
  if (isTripleArray(posOrX)) {
19737
- return this.getBlockAt(new Vector320(posOrX[0], posOrX[1], posOrX[2]));
20323
+ return this.getBlockAt(new Vector321(posOrX[0], posOrX[1], posOrX[2]));
19738
20324
  }
19739
20325
  const pos = this.blockPosition.set(posOrX.x - this.x * 16, posOrX.y - this.y * 16, posOrX.z - this.z * 16);
19740
20326
  const index = _Chunk.chunkPosToBlockIndex(pos);
19741
20327
  return this.renderedBlocks.get(index);
19742
20328
  }
19743
20329
  getBiomeAt(posOrX, y, z) {
19744
- if (typeof posOrX === "number") return this.getBiomeAt(new Vector320(posOrX, y, z));
19745
- if (isTripleArray(posOrX)) return this.getBiomeAt(new Vector320(...posOrX));
20330
+ if (typeof posOrX === "number") return this.getBiomeAt(new Vector321(posOrX, y, z));
20331
+ if (isTripleArray(posOrX)) return this.getBiomeAt(new Vector321(...posOrX));
19746
20332
  const pos = this.worldPosToChunkPos(posOrX);
19747
20333
  _Chunk.chunkPosToBlockIndex(pos);
19748
20334
  return this.biomeAt ? this.biomeAt(posOrX.x, posOrX.y, posOrX.z) : this.biomes?.[Math.floor(pos.x / 4) + Math.floor(pos.z / 4) * 4 + Math.floor(pos.y / 4) * 16];
@@ -19820,17 +20406,17 @@ var Chunk = class _Chunk {
19820
20406
  this.hiddenBlocks.add(index);
19821
20407
  this.renderedBlocks.get(index)?.object?.setVisible(false);
19822
20408
  }
19823
- const positions = blocks.map(({ index }) => new Vector320(...this.positionAt(index)));
20409
+ const positions = blocks.map(({ index }) => new Vector321(...this.positionAt(index)));
19824
20410
  if (this.onBlocksChanged) await this.onBlocksChanged(positions, deferCulling);
19825
20411
  else await this.rebuildSectionMesh();
19826
20412
  }
19827
20413
  }
19828
20414
  async setBlockAt(posOrX, yOrBlock, z, block) {
19829
20415
  if (typeof posOrX == "number") {
19830
- return this.setBlockAt(new Vector320(posOrX, yOrBlock, z), block);
20416
+ return this.setBlockAt(new Vector321(posOrX, yOrBlock, z), block);
19831
20417
  }
19832
20418
  if (isTripleArray(posOrX)) {
19833
- return this.setBlockAt(new Vector320(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
20419
+ return this.setBlockAt(new Vector321(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
19834
20420
  }
19835
20421
  const worldPos = posOrX;
19836
20422
  const pos = this.worldPosToChunkPos(worldPos);
@@ -19892,8 +20478,10 @@ var Chunk = class _Chunk {
19892
20478
  if (rejected?.status === "rejected") throw rejected.reason;
19893
20479
  }
19894
20480
  const positions = [];
19895
- let failed = false, failure;
19896
- for (const { index, key, block } of placements) {
20481
+ const jobs = /* @__PURE__ */ new Map();
20482
+ let failureOrder = Infinity, failure;
20483
+ for (const [order, { index, key, block }] of placements.entries()) {
20484
+ jobs.delete(index);
19897
20485
  this.hiddenBlocks.delete(index);
19898
20486
  this.data.assign(index, this.data.intern(block), block?.nbt);
19899
20487
  const readBlock = this.data.snapshot(index);
@@ -19903,13 +20491,12 @@ var Chunk = class _Chunk {
19903
20491
  this.fluidCells[index] = 0;
19904
20492
  this.renderedBlocks.delete(index);
19905
20493
  this.objectBlocks.delete(index);
19906
- const worldPos = new Vector320(
20494
+ const worldPos = new Vector321(
19907
20495
  this.x * 16 + index % 16,
19908
20496
  this.y * 16 + Math.floor(index / 256),
19909
20497
  this.z * 16 + Math.floor(index / 16) % 16
19910
20498
  );
19911
20499
  positions.push(worldPos);
19912
- let object4;
19913
20500
  try {
19914
20501
  if (!readBlock) continue;
19915
20502
  const resolved = resolutions.get(key);
@@ -19931,7 +20518,7 @@ var Chunk = class _Chunk {
19931
20518
  this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0, biomeTint: context?.tint })));
19932
20519
  this.meshDirty = true;
19933
20520
  } else {
19934
- object4 = await this.scene.addBlock(blockState3, {
20521
+ jobs.set(index, { order, readBlock, blockState: blockState3, position: MineRenderWorld.worldToScenePosition(worldPos), options: {
19935
20522
  mergeMeshes: true,
19936
20523
  instanceMeshes: true,
19937
20524
  maxInstanceCount: 2e3,
@@ -19939,13 +20526,12 @@ var Chunk = class _Chunk {
19939
20526
  variantPosition,
19940
20527
  biome: context?.biome,
19941
20528
  biomePosition: this.biomePosition(index, blockState3, stored)
19942
- });
19943
- object4.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
20529
+ } });
20530
+ continue;
19944
20531
  }
19945
20532
  this.renderedBlocks.set(index, { get block() {
19946
20533
  return readBlock();
19947
- }, object: object4 });
19948
- if (object4) this.objectBlocks.add(index);
20534
+ }, object: void 0 });
19949
20535
  } catch (error) {
19950
20536
  if (preserveDataOnFailure && readBlock) {
19951
20537
  this.renderedBlocks.set(index, { get block() {
@@ -19953,12 +20539,35 @@ var Chunk = class _Chunk {
19953
20539
  }, object: void 0 });
19954
20540
  } else this.data.assign(index, 0);
19955
20541
  this.fluidCells[index] = 0;
19956
- object4?.removeFromScene();
19957
- if (!failed) failure = error;
19958
- failed = true;
20542
+ if (order < failureOrder) {
20543
+ failureOrder = order;
20544
+ failure = error;
20545
+ }
19959
20546
  }
19960
20547
  }
19961
- if (failed) throw failure;
20548
+ const pending = [...jobs];
20549
+ const objects = await Promise.allSettled(pending.map(([, job]) => this.createBlockObject(job.blockState, job.position, job.options)));
20550
+ for (const [i, [index, { order, readBlock }]] of pending.entries()) {
20551
+ const result = objects[i];
20552
+ if (result.status === "fulfilled") {
20553
+ this.renderedBlocks.set(index, { get block() {
20554
+ return readBlock();
20555
+ }, object: result.value });
20556
+ this.objectBlocks.add(index);
20557
+ } else {
20558
+ if (preserveDataOnFailure) {
20559
+ this.renderedBlocks.set(index, { get block() {
20560
+ return readBlock();
20561
+ }, object: void 0 });
20562
+ } else this.data.assign(index, 0);
20563
+ this.fluidCells[index] = 0;
20564
+ if (order < failureOrder) {
20565
+ failureOrder = order;
20566
+ failure = result.reason;
20567
+ }
20568
+ }
20569
+ }
20570
+ if (failureOrder < Infinity) throw failure;
19962
20571
  return positions;
19963
20572
  }
19964
20573
  /** Replaces every cell from parsed section data. The caller refreshes neighbor culling. */
@@ -20029,37 +20638,42 @@ var Chunk = class _Chunk {
20029
20638
  failed = true;
20030
20639
  }
20031
20640
  }
20032
- for (const { index, blockState: blockState3, biome } of perBlocks) {
20033
- let object4;
20034
- try {
20035
- object4 = await this.scene.addBlock(blockState3, {
20036
- mergeMeshes: true,
20037
- instanceMeshes: true,
20038
- maxInstanceCount: 2e3,
20039
- initialState: this.data.stateAt(this.data.idAt(index)).properties,
20040
- variantPosition: this.positionAt(index),
20041
- biome,
20042
- biomePosition: this.biomePosition(index, blockState3, this.data.stateAt(this.data.idAt(index)))
20043
- });
20044
- object4.setPosition(new Vector320(
20045
- this.x * 256 + index % 16 * 16,
20046
- this.y * 256 + Math.floor(index / 256) * 16,
20047
- this.z * 256 + Math.floor(index / 16) % 16 * 16
20048
- ));
20049
- this.renderedBlocks.get(index).object = object4;
20641
+ const objects = await Promise.allSettled(perBlocks.map(({ index, blockState: blockState3, biome }) => this.createBlockObject(blockState3, new Vector321(
20642
+ this.x * 256 + index % 16 * 16,
20643
+ this.y * 256 + Math.floor(index / 256) * 16,
20644
+ this.z * 256 + Math.floor(index / 16) % 16 * 16
20645
+ ), {
20646
+ mergeMeshes: true,
20647
+ instanceMeshes: true,
20648
+ maxInstanceCount: 2e3,
20649
+ initialState: this.data.stateAt(this.data.idAt(index)).properties,
20650
+ variantPosition: this.positionAt(index),
20651
+ biome,
20652
+ biomePosition: this.biomePosition(index, blockState3, this.data.stateAt(this.data.idAt(index)))
20653
+ })));
20654
+ for (const [i, { index }] of perBlocks.entries()) {
20655
+ const result = objects[i];
20656
+ if (result.status === "fulfilled") {
20657
+ this.renderedBlocks.get(index).object = result.value;
20050
20658
  this.objectBlocks.add(index);
20051
- } catch (error) {
20659
+ } else {
20052
20660
  this.data.assign(index, 0);
20053
20661
  this.fluidCells[index] = 0;
20054
20662
  this.renderedBlocks.delete(index);
20055
20663
  this.objectBlocks.delete(index);
20056
- object4?.removeFromScene();
20057
- if (!failed) failure = error;
20664
+ if (!failed) failure = result.reason;
20058
20665
  failed = true;
20059
20666
  }
20060
20667
  }
20061
20668
  if (failed) throw failure;
20062
20669
  }
20670
+ createBlockObject(blockState3, position, options) {
20671
+ return this.scene.addSceneObject(blockState3, () => {
20672
+ const object4 = new BlockObject(blockState3, options);
20673
+ object4.position.copy(position);
20674
+ return object4;
20675
+ }, options);
20676
+ }
20063
20677
  positionAt(index) {
20064
20678
  return [this.x * 16 + index % 16, this.y * 16 + Math.floor(index / 256), this.z * 16 + Math.floor(index / 16) % 16];
20065
20679
  }
@@ -20348,7 +20962,7 @@ var Chunk = class _Chunk {
20348
20962
  async clear(notify = true) {
20349
20963
  if (this.blockChanges.size) await Promise.allSettled(this.blockChanges);
20350
20964
  const positions = notify && this.onBlocksChanged ? [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
20351
- new Vector320(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
20965
+ new Vector321(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
20352
20966
  )) : [];
20353
20967
  for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
20354
20968
  this.sectionBlocks.clear();
@@ -20384,14 +20998,14 @@ var Chunk = class _Chunk {
20384
20998
  return pos.y * 16 * 16 + pos.z * 16 + pos.x;
20385
20999
  }
20386
21000
  worldPosToChunkPos(pos) {
20387
- return new Vector320(
21001
+ return new Vector321(
20388
21002
  pos.x - this.x * 16,
20389
21003
  pos.y - this.y * 16,
20390
21004
  pos.z - this.z * 16
20391
21005
  );
20392
21006
  }
20393
21007
  chunkPosToWorldPos(pos) {
20394
- return new Vector320(
21008
+ return new Vector321(
20395
21009
  this.x * 16 + pos.x,
20396
21010
  this.y * 16 + pos.y,
20397
21011
  this.z * 16 + pos.z
@@ -20400,6 +21014,7 @@ var Chunk = class _Chunk {
20400
21014
  };
20401
21015
 
20402
21016
  // src/world/WorldStreamer.ts
21017
+ var MAX_PREFETCHED_COLUMNS = 4;
20403
21018
  var WorldStreamer = class {
20404
21019
  constructor(world, source, options = {}) {
20405
21020
  this.world = world;
@@ -20409,6 +21024,7 @@ var WorldStreamer = class {
20409
21024
  this.missing = /* @__PURE__ */ new Map();
20410
21025
  this.failed = /* @__PURE__ */ new Map();
20411
21026
  this.desired = [];
21027
+ this.reads = /* @__PURE__ */ new Map();
20412
21028
  this.disposed = false;
20413
21029
  this.loadRadius = options.loadRadius ?? 2;
20414
21030
  this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);
@@ -20453,6 +21069,12 @@ var WorldStreamer = class {
20453
21069
  if (this.loading && !this.within(this.loading.position, this.loadRadius)) {
20454
21070
  this.loading.controller.abort();
20455
21071
  }
21072
+ for (const [key, read] of this.reads) {
21073
+ if (!this.within(read.position, this.loadRadius)) {
21074
+ read.controller.abort();
21075
+ if (read.ready) this.reads.delete(key);
21076
+ }
21077
+ }
20456
21078
  }
20457
21079
  return this.running ??= Promise.resolve().then(() => this.drain());
20458
21080
  }
@@ -20473,8 +21095,11 @@ var WorldStreamer = class {
20473
21095
  this.desired = [];
20474
21096
  this.center = void 0;
20475
21097
  this.loading?.controller.abort();
21098
+ for (const read of this.reads.values()) read.controller.abort();
20476
21099
  return this.disposal ??= Promise.resolve().then(async () => {
20477
21100
  await this.running?.catch(() => void 0);
21101
+ await Promise.all([...this.reads.values()].map((read) => read.promise));
21102
+ this.reads.clear();
20478
21103
  try {
20479
21104
  await this.unloadMany([...this.owned.values()]);
20480
21105
  } finally {
@@ -20500,6 +21125,10 @@ var WorldStreamer = class {
20500
21125
  await this.unloadMany(obsolete);
20501
21126
  continue;
20502
21127
  }
21128
+ if (this.world.placeChunks) {
21129
+ if (await this.placeReadyChunks()) continue;
21130
+ return;
21131
+ }
20503
21132
  const position = this.desired.find((position2) => this.needsLoad(position2));
20504
21133
  if (!position) return;
20505
21134
  let chunk;
@@ -20522,26 +21151,105 @@ var WorldStreamer = class {
20522
21151
  continue;
20523
21152
  }
20524
21153
  this.owned.set(this.key(position), position);
21154
+ await this.placeOwnedColumns([position], () => this.world.placeChunk(chunk));
21155
+ }
21156
+ } finally {
21157
+ for (const [key, read] of this.reads) {
21158
+ read.controller.abort();
21159
+ if (read.ready) this.reads.delete(key);
21160
+ }
21161
+ this.running = void 0;
21162
+ }
21163
+ }
21164
+ fillReadSlots() {
21165
+ while (!this.disposed && this.reads.size < MAX_PREFETCHED_COLUMNS) {
21166
+ const position = this.desired.find((position2) => this.needsLoad(position2) && !this.reads.has(this.key(position2)));
21167
+ if (!position) return;
21168
+ const key = this.key(position);
21169
+ const read = {
21170
+ position,
21171
+ controller: new AbortController(),
21172
+ promise: Promise.resolve(),
21173
+ ready: false,
21174
+ failed: false
21175
+ };
21176
+ this.reads.set(key, read);
21177
+ read.promise = (async () => {
20525
21178
  try {
20526
- await this.world.placeChunk(chunk);
20527
- this.loaded.set(this.key(position), position);
21179
+ read.chunk = await this.source.getChunk(position.x, position.z, read.controller.signal);
21180
+ if (read.chunk) this.validateChunk(read.chunk, position);
20528
21181
  } catch (error) {
20529
- try {
20530
- await this.unloadMany([position]);
20531
- } catch (cleanupError) {
20532
- const failure = error instanceof Error ? error : new Error(String(error));
20533
- try {
20534
- Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
20535
- } catch {
20536
- throw Object.assign(new Error(failure.message), { cause: cleanupError });
20537
- }
20538
- throw failure;
21182
+ read.failed = true;
21183
+ read.error = error;
21184
+ } finally {
21185
+ read.ready = true;
21186
+ if (read.controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) {
21187
+ this.reads.delete(key);
20539
21188
  }
20540
- throw error;
21189
+ }
21190
+ })();
21191
+ }
21192
+ }
21193
+ /** Places only the ready prefix, so later source reads cannot delay the nearest ready column. */
21194
+ async placeReadyChunks() {
21195
+ this.fillReadSlots();
21196
+ const position = this.desired.find((position2) => this.needsLoad(position2));
21197
+ if (!position) {
21198
+ if (!this.reads.size) return false;
21199
+ await Promise.all([...this.reads.values()].map((read) => read.promise));
21200
+ return true;
21201
+ }
21202
+ const head = this.reads.get(this.key(position));
21203
+ if (!head) {
21204
+ for (let index = this.desired.length - 1; index >= 0; index--) {
21205
+ const key = this.key(this.desired[index]);
21206
+ if (this.reads.get(key)?.ready) {
21207
+ this.reads.delete(key);
21208
+ return true;
20541
21209
  }
20542
21210
  }
20543
- } finally {
20544
- this.running = void 0;
21211
+ await Promise.race([...this.reads.values()].map((read) => read.promise));
21212
+ return true;
21213
+ }
21214
+ if (!head.ready) {
21215
+ await head.promise;
21216
+ return true;
21217
+ }
21218
+ const chunks = [], positions = [];
21219
+ for (const desired of this.desired) {
21220
+ if (!this.needsLoad(desired)) continue;
21221
+ const key = this.key(desired), read = this.reads.get(key);
21222
+ if (!read?.ready) break;
21223
+ this.reads.delete(key);
21224
+ if (read.failed) this.failed.set(key, { ...desired, error: read.error });
21225
+ else if (!read.chunk) this.missing.set(key, desired);
21226
+ else {
21227
+ chunks.push(read.chunk);
21228
+ positions.push(desired);
21229
+ }
21230
+ }
21231
+ if (!chunks.length) return true;
21232
+ for (const position2 of positions) this.owned.set(this.key(position2), position2);
21233
+ await this.placeOwnedColumns(positions, () => this.world.placeChunks(chunks));
21234
+ return true;
21235
+ }
21236
+ async placeOwnedColumns(positions, place) {
21237
+ try {
21238
+ await place();
21239
+ for (const position of positions) this.loaded.set(this.key(position), position);
21240
+ } catch (error) {
21241
+ try {
21242
+ await this.unloadMany(positions);
21243
+ } catch (cleanupError) {
21244
+ const failure = error instanceof Error ? error : new Error(String(error));
21245
+ try {
21246
+ Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
21247
+ } catch {
21248
+ throw Object.assign(new Error(failure.message), { cause: cleanupError });
21249
+ }
21250
+ throw failure;
21251
+ }
21252
+ throw error;
20545
21253
  }
20546
21254
  }
20547
21255
  needsLoad(position) {
@@ -20742,6 +21450,7 @@ export {
20742
21450
  AnvilParser,
20743
21451
  AnvilWorldSource,
20744
21452
  ArchiveAssetSource,
21453
+ Armor,
20745
21454
  AssetKey,
20746
21455
  AssetLoadError,
20747
21456
  AssetLoader,
@@ -20825,6 +21534,7 @@ export {
20825
21534
  OrbitControls,
20826
21535
  PackFormats,
20827
21536
  PackMetadata,
21537
+ PalettedTextures,
20828
21538
  PersistentCache,
20829
21539
  PlayerHeadTextures,
20830
21540
  Renames,