minerender 2.1.0-alpha.28 → 2.1.0-alpha.29

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.
@@ -624,6 +624,12 @@ declare enum AssetParser {
624
624
  declare abstract class AssetSource {
625
625
  protected constructor();
626
626
  abstract get<T extends MinecraftAsset>(key: AssetKey, parser: AssetParser | string): Promise<Maybe<T>>;
627
+ /**
628
+ * Identifies this source's content across sessions for persistent caching. Sources that
629
+ * cannot tell whether their content changed (an unnamed archive, for example) return
630
+ * undefined, which keeps their results out of long-lived caches.
631
+ */
632
+ get cacheId(): Maybe<string>;
627
633
  }
628
634
  /** A source failed to load or parse an asset; cause retains the original failure. */
629
635
  declare class AssetLoadError extends Error {
@@ -637,8 +643,18 @@ declare class AssetLoadError extends Error {
637
643
  declare class AssetLoader {
638
644
  static ROOT: string;
639
645
  private static _SOURCES;
646
+ /** Used when a registered source cannot identify its content, so nothing persists past this session. */
647
+ private static readonly SESSION_SCOPE;
640
648
  static get version(): string;
641
649
  static setVersion(version: string): void;
650
+ /**
651
+ * Scope for persistent cache keys. Empty with only the default vanilla sources, so their
652
+ * entries stay valid across sessions; otherwise it names every added source (resource packs,
653
+ * mirrors) so their results never masquerade as vanilla assets after a reload.
654
+ */
655
+ static get persistentScope(): string;
656
+ /** Prefixes a persistent cache key with the current source scope. */
657
+ static persistentKey(key: string): string;
642
658
  static addSource(key: string, source: AssetSource, override?: boolean): void;
643
659
  static removeSource(key: string): Maybe<AssetSource>;
644
660
  static getAll<T extends MinecraftAsset>(key: AssetKey, parser: AssetParser | string): Promise<T[]>;
@@ -904,12 +920,14 @@ declare class SkinObject extends SceneObject {
904
920
  private skinTextureSrc?;
905
921
  private skinMaterial?;
906
922
  private skinLoad;
907
- private capeTextureSrc?;
923
+ private capeLoad;
908
924
  constructor(options?: DeepPartial<SkinObjectOptions>);
909
925
  init(): Promise<void>;
910
926
  protected removeMeshes(): void;
911
927
  protected createMeshes(): void;
912
928
  setSkinTexture(src: string): Promise<void>;
929
+ /** Load a vanilla cape texture, or pass undefined to remove the cape. */
930
+ setCapeTexture(src?: string): Promise<void>;
913
931
  /** Select the arm model, or pass undefined to use texture detection. */
914
932
  setSlim(slim?: boolean): void;
915
933
  private updateSlim;
@@ -1187,6 +1205,7 @@ declare class HostedAssetSource extends AssetSource {
1187
1205
  private readonly _options;
1188
1206
  constructor(root: string, options?: Partial<HostedAssetSourceOptions>);
1189
1207
  get root(): string;
1208
+ get cacheId(): string;
1190
1209
  get options(): HostedAssetSourceOptions;
1191
1210
  get<T extends MinecraftAsset>(key: AssetKey, parser: AssetParser): Promise<Maybe<T>>;
1192
1211
  }
@@ -1201,6 +1220,8 @@ interface ArchiveEntry {
1201
1220
  }
1202
1221
 
1203
1222
  interface ArchiveProxy {
1223
+ /** Identifies the archive's content across sessions; undefined when it cannot be identified. */
1224
+ readonly id?: string;
1204
1225
  getEntries(): Promise<ArchiveEntry[]>;
1205
1226
  }
1206
1227
 
@@ -1208,6 +1229,7 @@ declare class ArchiveAssetSource extends AssetSource implements ArchiveProxy {
1208
1229
  readonly _archiveProxy: ArchiveProxy;
1209
1230
  constructor(archiveProxy: ArchiveProxy);
1210
1231
  static blob(blob: Blob): ArchiveAssetSource;
1232
+ get cacheId(): Maybe<string>;
1211
1233
  getEntries(): Promise<ArchiveEntry[]>;
1212
1234
  getEntry(path: string): Promise<Maybe<ArchiveEntry>>;
1213
1235
  get<T extends MinecraftAsset>(key: AssetKey, parser: AssetParser | string): Promise<Maybe<T>>;
@@ -1218,7 +1240,8 @@ declare class ArchiveAssetSource extends AssetSource implements ArchiveProxy {
1218
1240
  declare class BrowserArchiveProxy implements ArchiveProxy {
1219
1241
  readonly _blob: Blob;
1220
1242
  readonly _reader: ZipReader<Blob>;
1221
- constructor(blob: Blob);
1243
+ readonly id?: string;
1244
+ constructor(blob: Blob, id?: string);
1222
1245
  getEntries(): Promise<ArchiveEntry[]>;
1223
1246
  }
1224
1247
 
@@ -1563,6 +1586,8 @@ interface SkinTexture {
1563
1586
  }
1564
1587
  declare class SkinTextures {
1565
1588
  static get(src: string, legacy?: boolean): Promise<SkinTexture>;
1589
+ static getCape(src: string): Promise<MeshBasicMaterial>;
1590
+ private static createMaterial;
1566
1591
  private static isSlim;
1567
1592
  private static normalizeLegacy;
1568
1593
  }
@@ -624,6 +624,12 @@ declare enum AssetParser {
624
624
  declare abstract class AssetSource {
625
625
  protected constructor();
626
626
  abstract get<T extends MinecraftAsset>(key: AssetKey, parser: AssetParser | string): Promise<Maybe<T>>;
627
+ /**
628
+ * Identifies this source's content across sessions for persistent caching. Sources that
629
+ * cannot tell whether their content changed (an unnamed archive, for example) return
630
+ * undefined, which keeps their results out of long-lived caches.
631
+ */
632
+ get cacheId(): Maybe<string>;
627
633
  }
628
634
  /** A source failed to load or parse an asset; cause retains the original failure. */
629
635
  declare class AssetLoadError extends Error {
@@ -637,8 +643,18 @@ declare class AssetLoadError extends Error {
637
643
  declare class AssetLoader {
638
644
  static ROOT: string;
639
645
  private static _SOURCES;
646
+ /** Used when a registered source cannot identify its content, so nothing persists past this session. */
647
+ private static readonly SESSION_SCOPE;
640
648
  static get version(): string;
641
649
  static setVersion(version: string): void;
650
+ /**
651
+ * Scope for persistent cache keys. Empty with only the default vanilla sources, so their
652
+ * entries stay valid across sessions; otherwise it names every added source (resource packs,
653
+ * mirrors) so their results never masquerade as vanilla assets after a reload.
654
+ */
655
+ static get persistentScope(): string;
656
+ /** Prefixes a persistent cache key with the current source scope. */
657
+ static persistentKey(key: string): string;
642
658
  static addSource(key: string, source: AssetSource, override?: boolean): void;
643
659
  static removeSource(key: string): Maybe<AssetSource>;
644
660
  static getAll<T extends MinecraftAsset>(key: AssetKey, parser: AssetParser | string): Promise<T[]>;
@@ -904,12 +920,14 @@ declare class SkinObject extends SceneObject {
904
920
  private skinTextureSrc?;
905
921
  private skinMaterial?;
906
922
  private skinLoad;
907
- private capeTextureSrc?;
923
+ private capeLoad;
908
924
  constructor(options?: DeepPartial<SkinObjectOptions>);
909
925
  init(): Promise<void>;
910
926
  protected removeMeshes(): void;
911
927
  protected createMeshes(): void;
912
928
  setSkinTexture(src: string): Promise<void>;
929
+ /** Load a vanilla cape texture, or pass undefined to remove the cape. */
930
+ setCapeTexture(src?: string): Promise<void>;
913
931
  /** Select the arm model, or pass undefined to use texture detection. */
914
932
  setSlim(slim?: boolean): void;
915
933
  private updateSlim;
@@ -1187,6 +1205,7 @@ declare class HostedAssetSource extends AssetSource {
1187
1205
  private readonly _options;
1188
1206
  constructor(root: string, options?: Partial<HostedAssetSourceOptions>);
1189
1207
  get root(): string;
1208
+ get cacheId(): string;
1190
1209
  get options(): HostedAssetSourceOptions;
1191
1210
  get<T extends MinecraftAsset>(key: AssetKey, parser: AssetParser): Promise<Maybe<T>>;
1192
1211
  }
@@ -1201,6 +1220,8 @@ interface ArchiveEntry {
1201
1220
  }
1202
1221
 
1203
1222
  interface ArchiveProxy {
1223
+ /** Identifies the archive's content across sessions; undefined when it cannot be identified. */
1224
+ readonly id?: string;
1204
1225
  getEntries(): Promise<ArchiveEntry[]>;
1205
1226
  }
1206
1227
 
@@ -1208,6 +1229,7 @@ declare class ArchiveAssetSource extends AssetSource implements ArchiveProxy {
1208
1229
  readonly _archiveProxy: ArchiveProxy;
1209
1230
  constructor(archiveProxy: ArchiveProxy);
1210
1231
  static blob(blob: Blob): ArchiveAssetSource;
1232
+ get cacheId(): Maybe<string>;
1211
1233
  getEntries(): Promise<ArchiveEntry[]>;
1212
1234
  getEntry(path: string): Promise<Maybe<ArchiveEntry>>;
1213
1235
  get<T extends MinecraftAsset>(key: AssetKey, parser: AssetParser | string): Promise<Maybe<T>>;
@@ -1218,7 +1240,8 @@ declare class ArchiveAssetSource extends AssetSource implements ArchiveProxy {
1218
1240
  declare class BrowserArchiveProxy implements ArchiveProxy {
1219
1241
  readonly _blob: Blob;
1220
1242
  readonly _reader: ZipReader<Blob>;
1221
- constructor(blob: Blob);
1243
+ readonly id?: string;
1244
+ constructor(blob: Blob, id?: string);
1222
1245
  getEntries(): Promise<ArchiveEntry[]>;
1223
1246
  }
1224
1247
 
@@ -1563,6 +1586,8 @@ interface SkinTexture {
1563
1586
  }
1564
1587
  declare class SkinTextures {
1565
1588
  static get(src: string, legacy?: boolean): Promise<SkinTexture>;
1589
+ static getCape(src: string): Promise<MeshBasicMaterial>;
1590
+ private static createMaterial;
1566
1591
  private static isSlim;
1567
1592
  private static normalizeLegacy;
1568
1593
  }
@@ -1474,7 +1474,7 @@ var Models = class _Models {
1474
1474
  }
1475
1475
  static async getItemModel(key) {
1476
1476
  const itemKey = new AssetKey(key.namespace, key.path, "items", void 0, key.rootType, ".json", key.root);
1477
- const model = await this.PERSISTENT_CACHE.getOrLoad(itemKey.serialize(), async () => {
1477
+ const model = await this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(itemKey.serialize()), async () => {
1478
1478
  const result = await AssetLoader.getFirst([itemKey, key], "json" /* JSON */);
1479
1479
  if (!result) return void 0;
1480
1480
  if (result.key.assetType !== "items") return { ...result.asset, key };
@@ -1520,7 +1520,7 @@ var Models = class _Models {
1520
1520
  }
1521
1521
  const keyStr = key.serialize();
1522
1522
  return Caching.rawModelCache.get(keyStr, (k) => {
1523
- return this.PERSISTENT_CACHE.getOrLoad(keyStr, (k1) => {
1523
+ return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
1524
1524
  return AssetLoader.get(key, "model" /* MODEL */);
1525
1525
  });
1526
1526
  }).then((asset) => {
@@ -1592,7 +1592,7 @@ var BlockStates = class {
1592
1592
  }
1593
1593
  const keyStr = key.serialize();
1594
1594
  return Caching.blockStateCache.get(keyStr, (k) => {
1595
- return this.PERSISTENT_CACHE.getOrLoad(keyStr, (k1) => {
1595
+ return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
1596
1596
  return AssetLoader.get(key, "blockstate" /* BLOCKSTATE */);
1597
1597
  });
1598
1598
  }).then((asset) => {
@@ -1638,6 +1638,14 @@ var Assets = class {
1638
1638
  var AssetSource = class {
1639
1639
  constructor() {
1640
1640
  }
1641
+ /**
1642
+ * Identifies this source's content across sessions for persistent caching. Sources that
1643
+ * cannot tell whether their content changed (an unnamed archive, for example) return
1644
+ * undefined, which keeps their results out of long-lived caches.
1645
+ */
1646
+ get cacheId() {
1647
+ return void 0;
1648
+ }
1641
1649
  };
1642
1650
  var AssetLoadError = class extends Error {
1643
1651
  constructor(source, key, path, cause) {
@@ -1926,6 +1934,9 @@ var HostedAssetSource = class _HostedAssetSource extends AssetSource {
1926
1934
  get root() {
1927
1935
  return this._root;
1928
1936
  }
1937
+ get cacheId() {
1938
+ return `hosted:${this._root}`;
1939
+ }
1929
1940
  get options() {
1930
1941
  return this._options;
1931
1942
  }
@@ -1941,9 +1952,10 @@ var HostedAssetSource = class _HostedAssetSource extends AssetSource {
1941
1952
  // src/assets/source/archive/BrowserArchiveProxy.ts
1942
1953
  var import_zip = require("@zip.js/zip.js");
1943
1954
  var BrowserArchiveProxy = class {
1944
- constructor(blob) {
1955
+ constructor(blob, id) {
1945
1956
  this._blob = blob;
1946
1957
  this._reader = new import_zip.ZipReader(new import_zip.BlobReader(this._blob));
1958
+ this.id = id ?? (typeof File !== "undefined" && blob instanceof File ? `${blob.name}:${blob.size}:${blob.lastModified}` : void 0);
1947
1959
  }
1948
1960
  async getEntries() {
1949
1961
  return (await this._reader.getEntries()).map((e) => {
@@ -1968,6 +1980,10 @@ var ArchiveAssetSource = class _ArchiveAssetSource extends AssetSource {
1968
1980
  static blob(blob) {
1969
1981
  return new _ArchiveAssetSource(new BrowserArchiveProxy(blob));
1970
1982
  }
1983
+ get cacheId() {
1984
+ const id = this._archiveProxy.id;
1985
+ return id === void 0 ? void 0 : `archive:${id}`;
1986
+ }
1971
1987
  async getEntries() {
1972
1988
  return this._archiveProxy.getEntries();
1973
1989
  }
@@ -2010,6 +2026,7 @@ var ArchiveAssetSource = class _ArchiveAssetSource extends AssetSource {
2010
2026
 
2011
2027
  // src/assets/AssetLoader.ts
2012
2028
  var p4 = prefix("AssetLoader");
2029
+ var FALLBACK_ROOT = "https://raw.githubusercontent.com/InventivetalentDev/minerender-fallback-assets/master";
2013
2030
  var AssetLoader = class {
2014
2031
  static {
2015
2032
  this.ROOT = DEFAULT_ROOT;
@@ -2017,6 +2034,10 @@ var AssetLoader = class {
2017
2034
  static {
2018
2035
  this._SOURCES = [];
2019
2036
  }
2037
+ static {
2038
+ /** Used when a registered source cannot identify its content, so nothing persists past this session. */
2039
+ this.SESSION_SCOPE = `session:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
2040
+ }
2020
2041
  static get version() {
2021
2042
  return this.ROOT.substring(this.ROOT.lastIndexOf("/") + 1);
2022
2043
  }
@@ -2031,6 +2052,28 @@ var AssetLoader = class {
2031
2052
  }
2032
2053
  Caching.clear();
2033
2054
  }
2055
+ /**
2056
+ * Scope for persistent cache keys. Empty with only the default vanilla sources, so their
2057
+ * entries stay valid across sessions; otherwise it names every added source (resource packs,
2058
+ * mirrors) so their results never masquerade as vanilla assets after a reload.
2059
+ */
2060
+ static get persistentScope() {
2061
+ const parts = [];
2062
+ for (const { key, source } of this._SOURCES) {
2063
+ const id = source.cacheId;
2064
+ if (key === "mcassets" && id === `hosted:${this.ROOT}`) continue;
2065
+ if (key === "mcassets-fallback" && id === `hosted:${FALLBACK_ROOT}`) continue;
2066
+ if (id === void 0) return this.SESSION_SCOPE;
2067
+ parts.push(`${key}=${id}`);
2068
+ }
2069
+ return parts.join("|");
2070
+ }
2071
+ /** Prefixes a persistent cache key with the current source scope. */
2072
+ static persistentKey(key) {
2073
+ const scope = this.persistentScope;
2074
+ return scope ? `${scope}
2075
+ ${key}` : key;
2076
+ }
2034
2077
  static addSource(key, source, override = true) {
2035
2078
  if (override) {
2036
2079
  const existing = this.removeSource(key);
@@ -2052,7 +2095,7 @@ var AssetLoader = class {
2052
2095
  return void 0;
2053
2096
  }
2054
2097
  static {
2055
- this.addSource("mcassets-fallback", new HostedAssetSource("https://raw.githubusercontent.com/InventivetalentDev/minerender-fallback-assets/master", { retryDefaults: false }));
2098
+ this.addSource("mcassets-fallback", new HostedAssetSource(FALLBACK_ROOT, { retryDefaults: false }));
2056
2099
  this.addSource("mcassets", new HostedAssetSource(this.ROOT, { retryDefaults: false }));
2057
2100
  }
2058
2101
  static async getAll(key, parser) {
@@ -2134,7 +2177,7 @@ var ModelTextures = class {
2134
2177
  }
2135
2178
  const keyStr = key.serialize();
2136
2179
  return Caching.textureMetaCache.get(keyStr, (k) => {
2137
- return this.PERSISTENT_META_CACHE.getOrLoad(keyStr, (k1) => {
2180
+ return this.PERSISTENT_META_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
2138
2181
  return AssetLoader.get(key, "meta" /* META */).then((asset) => {
2139
2182
  if (asset)
2140
2183
  asset.key = key;
@@ -4804,7 +4847,9 @@ var BlockObject = class _BlockObject extends SceneObject {
4804
4847
  if (typeof variant.y !== "undefined") {
4805
4848
  rotation.y = toRadians(clampRotationDegrees(typeof variant.x !== "undefined" ? variant.y : 360 - variant.y));
4806
4849
  }
4807
- const model = await Models.getMerged(AssetKey.parse("models", variant.model));
4850
+ const modelKey = AssetKey.parse("models", variant.model);
4851
+ modelKey.root = this.blockState.key?.root;
4852
+ const model = await Models.getMerged(modelKey);
4808
4853
  const options = {
4809
4854
  ...this.options,
4810
4855
  tints: model ? await BlockTints.get(this.blockState.key, this.state, model, this.options.tints) : this.options.tints,
@@ -5596,56 +5641,12 @@ var baseSkinCoordinates = {
5596
5641
  }
5597
5642
  },
5598
5643
  cape: {
5599
- //TODO
5600
- east: {
5601
- uv: [
5602
- 0,
5603
- 36,
5604
- 4,
5605
- 48
5606
- ]
5607
- },
5608
- west: {
5609
- uv: [
5610
- 8,
5611
- 36,
5612
- 12,
5613
- 48
5614
- ]
5615
- },
5616
- north: {
5617
- uv: [
5618
- 4,
5619
- 36,
5620
- 8,
5621
- 48
5622
- ]
5623
- },
5624
- south: {
5625
- uv: [
5626
- 12,
5627
- 36,
5628
- 16,
5629
- 48
5630
- ]
5631
- },
5632
- up: {
5633
- uv: [
5634
- 4,
5635
- 32,
5636
- 8,
5637
- 36
5638
- ]
5639
- },
5640
- down: {
5641
- uv: [
5642
- 8,
5643
- 32,
5644
- 12,
5645
- 36
5646
- ],
5647
- rotation: 180
5648
- }
5644
+ east: { uv: [0, 1, 1, 17] },
5645
+ west: { uv: [11, 1, 12, 17] },
5646
+ north: { uv: [1, 1, 11, 17] },
5647
+ south: { uv: [12, 1, 22, 17] },
5648
+ up: { uv: [11, 1, 1, 0] },
5649
+ down: { uv: [21, 0, 11, 1] }
5649
5650
  }
5650
5651
  };
5651
5652
  var classicSkinTextureCoordinates = {
@@ -5744,14 +5745,27 @@ var SkinTextures = class {
5744
5745
  const key = `skin:${serializeImageKey({ src })}:${isLegacy}`;
5745
5746
  const material = Caching.materialCache.get(key, () => {
5746
5747
  const pixels = isLegacy ? this.normalizeLegacy(image, scale) : new Uint8Array(image.data);
5747
- const texture = Textures.initTextureProps(new import_three18.DataTexture(pixels, image.width, image.width));
5748
- texture.colorSpace = import_three18.SRGBColorSpace;
5749
- texture.flipY = true;
5750
- texture.needsUpdate = true;
5751
- return new import_three18.MeshBasicMaterial({ map: texture, transparent: true, side: import_three18.DoubleSide, alphaTest: 0.5 });
5748
+ return this.createMaterial(pixels, image.width, image.width);
5752
5749
  });
5753
5750
  return { material, slim, legacy: isLegacy };
5754
5751
  }
5752
+ static async getCape(src) {
5753
+ const image = await ImageLoader.getData(src);
5754
+ const scale = image.width / 64;
5755
+ if (!Number.isInteger(scale) || scale < 1 || image.height !== image.width / 2) {
5756
+ throw new MineRenderError(`Invalid cape dimensions ${image.width}x${image.height}; expected 64x32 or an integer-scaled equivalent`);
5757
+ }
5758
+ return Caching.materialCache.get(`cape:${serializeImageKey({ src })}`, () => {
5759
+ return this.createMaterial(new Uint8Array(image.data), image.width, image.height, false);
5760
+ });
5761
+ }
5762
+ static createMaterial(pixels, width, height, transparent = true) {
5763
+ const texture = Textures.initTextureProps(new import_three18.DataTexture(pixels, width, height));
5764
+ texture.colorSpace = import_three18.SRGBColorSpace;
5765
+ texture.flipY = true;
5766
+ texture.needsUpdate = true;
5767
+ return new import_three18.MeshBasicMaterial({ map: texture, transparent, side: transparent ? import_three18.DoubleSide : import_three18.FrontSide, alphaTest: transparent ? 0.5 : 0 });
5768
+ }
5755
5769
  static isSlim(image, scale) {
5756
5770
  for (const [x, y] of [[46, 52], [54, 20]]) {
5757
5771
  for (let row = y * scale; row < (y + 12) * scale; row++) {
@@ -5814,6 +5828,7 @@ var SkinObject = class extends SceneObject {
5814
5828
  this.slim = false;
5815
5829
  this.detectedSlim = false;
5816
5830
  this.skinLoad = 0;
5831
+ this.capeLoad = 0;
5817
5832
  this.options = (0, import_ts_deepmerge7.default)({}, SceneObject.DEFAULT_OPTIONS, options ?? {});
5818
5833
  this.slim = this.options.slim ?? false;
5819
5834
  }
@@ -5883,6 +5898,7 @@ var SkinObject = class extends SceneObject {
5883
5898
  this.detectedSlim = texture.slim;
5884
5899
  this.updateSlim(this.options.slim ?? this.detectedSlim);
5885
5900
  for (const part of SKIN_PARTS) {
5901
+ if (part === "cape" /* CAPE */) continue;
5886
5902
  const mesh = this.getMeshByName(part);
5887
5903
  if (mesh) {
5888
5904
  mesh.material = texture.material;
@@ -5890,7 +5906,34 @@ var SkinObject = class extends SceneObject {
5890
5906
  }
5891
5907
  this.notifyDirty();
5892
5908
  }
5893
- //TODO: cape
5909
+ /** Load a vanilla cape texture, or pass undefined to remove the cape. */
5910
+ async setCapeTexture(src) {
5911
+ const load = ++this.capeLoad;
5912
+ if (src === void 0) {
5913
+ const group = this.getGroupByName("cape" /* CAPE */);
5914
+ if (group) this.remove(group);
5915
+ this.notifyDirty();
5916
+ return;
5917
+ }
5918
+ const material = await SkinTextures.getCape(src);
5919
+ if (load !== this.capeLoad) return;
5920
+ const mesh = this.getMeshByName("cape" /* CAPE */);
5921
+ if (mesh) {
5922
+ mesh.material = material;
5923
+ } else {
5924
+ const group = this.createAndAddGroup("cape" /* CAPE */, 0, 24, 2);
5925
+ group.rotation.set(toRadians(-6), Math.PI, 0);
5926
+ const geometry = this._getBoxGeometryFromDimensions(
5927
+ classicSkinGeometries.cape,
5928
+ classicSkinTextureCoordinates.cape,
5929
+ [64, 32],
5930
+ [64, 32]
5931
+ );
5932
+ const cape = this.createAndAddMesh("cape" /* CAPE */, group, geometry, material);
5933
+ cape.position.set(0, -8, -0.5);
5934
+ }
5935
+ this.notifyDirty();
5936
+ }
5894
5937
  /** Select the arm model, or pass undefined to use texture detection. */
5895
5938
  setSlim(slim) {
5896
5939
  this.options.slim = slim;
@@ -5918,6 +5961,7 @@ var SkinObject = class extends SceneObject {
5918
5961
  }
5919
5962
  dispose() {
5920
5963
  this.skinLoad++;
5964
+ this.capeLoad++;
5921
5965
  super.dispose();
5922
5966
  }
5923
5967
  //TODO: layer toggles