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.
@@ -623,6 +623,12 @@ declare enum AssetParser {
623
623
  declare abstract class AssetSource {
624
624
  protected constructor();
625
625
  abstract get<T extends MinecraftAsset>(key: AssetKey, parser: AssetParser | string): Promise<Maybe<T>>;
626
+ /**
627
+ * Identifies this source's content across sessions for persistent caching. Sources that
628
+ * cannot tell whether their content changed (an unnamed archive, for example) return
629
+ * undefined, which keeps their results out of long-lived caches.
630
+ */
631
+ get cacheId(): Maybe<string>;
626
632
  }
627
633
  /** A source failed to load or parse an asset; cause retains the original failure. */
628
634
  declare class AssetLoadError extends Error {
@@ -636,8 +642,18 @@ declare class AssetLoadError extends Error {
636
642
  declare class AssetLoader {
637
643
  static ROOT: string;
638
644
  private static _SOURCES;
645
+ /** Used when a registered source cannot identify its content, so nothing persists past this session. */
646
+ private static readonly SESSION_SCOPE;
639
647
  static get version(): string;
640
648
  static setVersion(version: string): void;
649
+ /**
650
+ * Scope for persistent cache keys. Empty with only the default vanilla sources, so their
651
+ * entries stay valid across sessions; otherwise it names every added source (resource packs,
652
+ * mirrors) so their results never masquerade as vanilla assets after a reload.
653
+ */
654
+ static get persistentScope(): string;
655
+ /** Prefixes a persistent cache key with the current source scope. */
656
+ static persistentKey(key: string): string;
641
657
  static addSource(key: string, source: AssetSource, override?: boolean): void;
642
658
  static removeSource(key: string): Maybe<AssetSource>;
643
659
  static getAll<T extends MinecraftAsset>(key: AssetKey, parser: AssetParser | string): Promise<T[]>;
@@ -903,12 +919,14 @@ declare class SkinObject extends SceneObject {
903
919
  private skinTextureSrc?;
904
920
  private skinMaterial?;
905
921
  private skinLoad;
906
- private capeTextureSrc?;
922
+ private capeLoad;
907
923
  constructor(options?: DeepPartial<SkinObjectOptions>);
908
924
  init(): Promise<void>;
909
925
  protected removeMeshes(): void;
910
926
  protected createMeshes(): void;
911
927
  setSkinTexture(src: string): Promise<void>;
928
+ /** Load a vanilla cape texture, or pass undefined to remove the cape. */
929
+ setCapeTexture(src?: string): Promise<void>;
912
930
  /** Select the arm model, or pass undefined to use texture detection. */
913
931
  setSlim(slim?: boolean): void;
914
932
  private updateSlim;
@@ -1186,6 +1204,7 @@ declare class HostedAssetSource extends AssetSource {
1186
1204
  private readonly _options;
1187
1205
  constructor(root: string, options?: Partial<HostedAssetSourceOptions>);
1188
1206
  get root(): string;
1207
+ get cacheId(): string;
1189
1208
  get options(): HostedAssetSourceOptions;
1190
1209
  get<T extends MinecraftAsset>(key: AssetKey, parser: AssetParser): Promise<Maybe<T>>;
1191
1210
  }
@@ -1200,6 +1219,8 @@ interface ArchiveEntry {
1200
1219
  }
1201
1220
 
1202
1221
  interface ArchiveProxy {
1222
+ /** Identifies the archive's content across sessions; undefined when it cannot be identified. */
1223
+ readonly id?: string;
1203
1224
  getEntries(): Promise<ArchiveEntry[]>;
1204
1225
  }
1205
1226
 
@@ -1207,6 +1228,7 @@ declare class ArchiveAssetSource extends AssetSource implements ArchiveProxy {
1207
1228
  readonly _archiveProxy: ArchiveProxy;
1208
1229
  constructor(archiveProxy: ArchiveProxy);
1209
1230
  static blob(blob: Blob): ArchiveAssetSource;
1231
+ get cacheId(): Maybe<string>;
1210
1232
  getEntries(): Promise<ArchiveEntry[]>;
1211
1233
  getEntry(path: string): Promise<Maybe<ArchiveEntry>>;
1212
1234
  get<T extends MinecraftAsset>(key: AssetKey, parser: AssetParser | string): Promise<Maybe<T>>;
@@ -1217,7 +1239,8 @@ declare class ArchiveAssetSource extends AssetSource implements ArchiveProxy {
1217
1239
  declare class BrowserArchiveProxy implements ArchiveProxy {
1218
1240
  readonly _blob: Blob;
1219
1241
  readonly _reader: ZipReader<Blob>;
1220
- constructor(blob: Blob);
1242
+ readonly id?: string;
1243
+ constructor(blob: Blob, id?: string);
1221
1244
  getEntries(): Promise<ArchiveEntry[]>;
1222
1245
  }
1223
1246
 
@@ -1562,6 +1585,8 @@ interface SkinTexture {
1562
1585
  }
1563
1586
  declare class SkinTextures {
1564
1587
  static get(src: string, legacy?: boolean): Promise<SkinTexture>;
1588
+ static getCape(src: string): Promise<MeshBasicMaterial>;
1589
+ private static createMaterial;
1565
1590
  private static isSlim;
1566
1591
  private static normalizeLegacy;
1567
1592
  }
@@ -623,6 +623,12 @@ declare enum AssetParser {
623
623
  declare abstract class AssetSource {
624
624
  protected constructor();
625
625
  abstract get<T extends MinecraftAsset>(key: AssetKey, parser: AssetParser | string): Promise<Maybe<T>>;
626
+ /**
627
+ * Identifies this source's content across sessions for persistent caching. Sources that
628
+ * cannot tell whether their content changed (an unnamed archive, for example) return
629
+ * undefined, which keeps their results out of long-lived caches.
630
+ */
631
+ get cacheId(): Maybe<string>;
626
632
  }
627
633
  /** A source failed to load or parse an asset; cause retains the original failure. */
628
634
  declare class AssetLoadError extends Error {
@@ -636,8 +642,18 @@ declare class AssetLoadError extends Error {
636
642
  declare class AssetLoader {
637
643
  static ROOT: string;
638
644
  private static _SOURCES;
645
+ /** Used when a registered source cannot identify its content, so nothing persists past this session. */
646
+ private static readonly SESSION_SCOPE;
639
647
  static get version(): string;
640
648
  static setVersion(version: string): void;
649
+ /**
650
+ * Scope for persistent cache keys. Empty with only the default vanilla sources, so their
651
+ * entries stay valid across sessions; otherwise it names every added source (resource packs,
652
+ * mirrors) so their results never masquerade as vanilla assets after a reload.
653
+ */
654
+ static get persistentScope(): string;
655
+ /** Prefixes a persistent cache key with the current source scope. */
656
+ static persistentKey(key: string): string;
641
657
  static addSource(key: string, source: AssetSource, override?: boolean): void;
642
658
  static removeSource(key: string): Maybe<AssetSource>;
643
659
  static getAll<T extends MinecraftAsset>(key: AssetKey, parser: AssetParser | string): Promise<T[]>;
@@ -903,12 +919,14 @@ declare class SkinObject extends SceneObject {
903
919
  private skinTextureSrc?;
904
920
  private skinMaterial?;
905
921
  private skinLoad;
906
- private capeTextureSrc?;
922
+ private capeLoad;
907
923
  constructor(options?: DeepPartial<SkinObjectOptions>);
908
924
  init(): Promise<void>;
909
925
  protected removeMeshes(): void;
910
926
  protected createMeshes(): void;
911
927
  setSkinTexture(src: string): Promise<void>;
928
+ /** Load a vanilla cape texture, or pass undefined to remove the cape. */
929
+ setCapeTexture(src?: string): Promise<void>;
912
930
  /** Select the arm model, or pass undefined to use texture detection. */
913
931
  setSlim(slim?: boolean): void;
914
932
  private updateSlim;
@@ -1186,6 +1204,7 @@ declare class HostedAssetSource extends AssetSource {
1186
1204
  private readonly _options;
1187
1205
  constructor(root: string, options?: Partial<HostedAssetSourceOptions>);
1188
1206
  get root(): string;
1207
+ get cacheId(): string;
1189
1208
  get options(): HostedAssetSourceOptions;
1190
1209
  get<T extends MinecraftAsset>(key: AssetKey, parser: AssetParser): Promise<Maybe<T>>;
1191
1210
  }
@@ -1200,6 +1219,8 @@ interface ArchiveEntry {
1200
1219
  }
1201
1220
 
1202
1221
  interface ArchiveProxy {
1222
+ /** Identifies the archive's content across sessions; undefined when it cannot be identified. */
1223
+ readonly id?: string;
1203
1224
  getEntries(): Promise<ArchiveEntry[]>;
1204
1225
  }
1205
1226
 
@@ -1207,6 +1228,7 @@ declare class ArchiveAssetSource extends AssetSource implements ArchiveProxy {
1207
1228
  readonly _archiveProxy: ArchiveProxy;
1208
1229
  constructor(archiveProxy: ArchiveProxy);
1209
1230
  static blob(blob: Blob): ArchiveAssetSource;
1231
+ get cacheId(): Maybe<string>;
1210
1232
  getEntries(): Promise<ArchiveEntry[]>;
1211
1233
  getEntry(path: string): Promise<Maybe<ArchiveEntry>>;
1212
1234
  get<T extends MinecraftAsset>(key: AssetKey, parser: AssetParser | string): Promise<Maybe<T>>;
@@ -1217,7 +1239,8 @@ declare class ArchiveAssetSource extends AssetSource implements ArchiveProxy {
1217
1239
  declare class BrowserArchiveProxy implements ArchiveProxy {
1218
1240
  readonly _blob: Blob;
1219
1241
  readonly _reader: ZipReader<Blob>;
1220
- constructor(blob: Blob);
1242
+ readonly id?: string;
1243
+ constructor(blob: Blob, id?: string);
1221
1244
  getEntries(): Promise<ArchiveEntry[]>;
1222
1245
  }
1223
1246
 
@@ -1562,6 +1585,8 @@ interface SkinTexture {
1562
1585
  }
1563
1586
  declare class SkinTextures {
1564
1587
  static get(src: string, legacy?: boolean): Promise<SkinTexture>;
1588
+ static getCape(src: string): Promise<MeshBasicMaterial>;
1589
+ private static createMaterial;
1565
1590
  private static isSlim;
1566
1591
  private static normalizeLegacy;
1567
1592
  }
@@ -1468,7 +1468,7 @@ var Models = class _Models {
1468
1468
  }
1469
1469
  static async getItemModel(key) {
1470
1470
  const itemKey = new AssetKey(key.namespace, key.path, "items", void 0, key.rootType, ".json", key.root);
1471
- const model = await this.PERSISTENT_CACHE.getOrLoad(itemKey.serialize(), async () => {
1471
+ const model = await this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(itemKey.serialize()), async () => {
1472
1472
  const result = await AssetLoader.getFirst([itemKey, key], "json" /* JSON */);
1473
1473
  if (!result) return void 0;
1474
1474
  if (result.key.assetType !== "items") return { ...result.asset, key };
@@ -1514,7 +1514,7 @@ var Models = class _Models {
1514
1514
  }
1515
1515
  const keyStr = key.serialize();
1516
1516
  return Caching.rawModelCache.get(keyStr, (k) => {
1517
- return this.PERSISTENT_CACHE.getOrLoad(keyStr, (k1) => {
1517
+ return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
1518
1518
  return AssetLoader.get(key, "model" /* MODEL */);
1519
1519
  });
1520
1520
  }).then((asset) => {
@@ -1586,7 +1586,7 @@ var BlockStates = class {
1586
1586
  }
1587
1587
  const keyStr = key.serialize();
1588
1588
  return Caching.blockStateCache.get(keyStr, (k) => {
1589
- return this.PERSISTENT_CACHE.getOrLoad(keyStr, (k1) => {
1589
+ return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
1590
1590
  return AssetLoader.get(key, "blockstate" /* BLOCKSTATE */);
1591
1591
  });
1592
1592
  }).then((asset) => {
@@ -1632,6 +1632,14 @@ var Assets = class {
1632
1632
  var AssetSource = class {
1633
1633
  constructor() {
1634
1634
  }
1635
+ /**
1636
+ * Identifies this source's content across sessions for persistent caching. Sources that
1637
+ * cannot tell whether their content changed (an unnamed archive, for example) return
1638
+ * undefined, which keeps their results out of long-lived caches.
1639
+ */
1640
+ get cacheId() {
1641
+ return void 0;
1642
+ }
1635
1643
  };
1636
1644
  var AssetLoadError = class extends Error {
1637
1645
  constructor(source, key, path, cause) {
@@ -1844,6 +1852,9 @@ var _HostedAssetSource = class _HostedAssetSource extends AssetSource {
1844
1852
  get root() {
1845
1853
  return this._root;
1846
1854
  }
1855
+ get cacheId() {
1856
+ return `hosted:${this._root}`;
1857
+ }
1847
1858
  get options() {
1848
1859
  return this._options;
1849
1860
  }
@@ -1920,9 +1931,10 @@ var HostedAssetSource = _HostedAssetSource;
1920
1931
  // src/assets/source/archive/BrowserArchiveProxy.ts
1921
1932
  var import_zip = require("@zip.js/zip.js");
1922
1933
  var BrowserArchiveProxy = class {
1923
- constructor(blob) {
1934
+ constructor(blob, id) {
1924
1935
  this._blob = blob;
1925
1936
  this._reader = new import_zip.ZipReader(new import_zip.BlobReader(this._blob));
1937
+ this.id = id ?? (typeof File !== "undefined" && blob instanceof File ? `${blob.name}:${blob.size}:${blob.lastModified}` : void 0);
1926
1938
  }
1927
1939
  async getEntries() {
1928
1940
  return (await this._reader.getEntries()).map((e) => {
@@ -1947,6 +1959,10 @@ var ArchiveAssetSource = class _ArchiveAssetSource extends AssetSource {
1947
1959
  static blob(blob) {
1948
1960
  return new _ArchiveAssetSource(new BrowserArchiveProxy(blob));
1949
1961
  }
1962
+ get cacheId() {
1963
+ const id = this._archiveProxy.id;
1964
+ return id === void 0 ? void 0 : `archive:${id}`;
1965
+ }
1950
1966
  async getEntries() {
1951
1967
  return this._archiveProxy.getEntries();
1952
1968
  }
@@ -1989,6 +2005,7 @@ var ArchiveAssetSource = class _ArchiveAssetSource extends AssetSource {
1989
2005
 
1990
2006
  // src/assets/AssetLoader.ts
1991
2007
  var p4 = prefix("AssetLoader");
2008
+ var FALLBACK_ROOT = "https://raw.githubusercontent.com/InventivetalentDev/minerender-fallback-assets/master";
1992
2009
  var _AssetLoader = class _AssetLoader {
1993
2010
  static get version() {
1994
2011
  return this.ROOT.substring(this.ROOT.lastIndexOf("/") + 1);
@@ -2004,6 +2021,28 @@ var _AssetLoader = class _AssetLoader {
2004
2021
  }
2005
2022
  Caching.clear();
2006
2023
  }
2024
+ /**
2025
+ * Scope for persistent cache keys. Empty with only the default vanilla sources, so their
2026
+ * entries stay valid across sessions; otherwise it names every added source (resource packs,
2027
+ * mirrors) so their results never masquerade as vanilla assets after a reload.
2028
+ */
2029
+ static get persistentScope() {
2030
+ const parts = [];
2031
+ for (const { key, source } of this._SOURCES) {
2032
+ const id = source.cacheId;
2033
+ if (key === "mcassets" && id === `hosted:${this.ROOT}`) continue;
2034
+ if (key === "mcassets-fallback" && id === `hosted:${FALLBACK_ROOT}`) continue;
2035
+ if (id === void 0) return this.SESSION_SCOPE;
2036
+ parts.push(`${key}=${id}`);
2037
+ }
2038
+ return parts.join("|");
2039
+ }
2040
+ /** Prefixes a persistent cache key with the current source scope. */
2041
+ static persistentKey(key) {
2042
+ const scope = this.persistentScope;
2043
+ return scope ? `${scope}
2044
+ ${key}` : key;
2045
+ }
2007
2046
  static addSource(key, source, override = true) {
2008
2047
  if (override) {
2009
2048
  const existing = this.removeSource(key);
@@ -2065,7 +2104,9 @@ var _AssetLoader = class _AssetLoader {
2065
2104
  };
2066
2105
  _AssetLoader.ROOT = DEFAULT_ROOT;
2067
2106
  _AssetLoader._SOURCES = [];
2068
- _AssetLoader.addSource("mcassets-fallback", new HostedAssetSource("https://raw.githubusercontent.com/InventivetalentDev/minerender-fallback-assets/master", { retryDefaults: false }));
2107
+ /** Used when a registered source cannot identify its content, so nothing persists past this session. */
2108
+ _AssetLoader.SESSION_SCOPE = `session:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
2109
+ _AssetLoader.addSource("mcassets-fallback", new HostedAssetSource(FALLBACK_ROOT, { retryDefaults: false }));
2069
2110
  _AssetLoader.addSource("mcassets", new HostedAssetSource(_AssetLoader.ROOT, { retryDefaults: false }));
2070
2111
  var AssetLoader = _AssetLoader;
2071
2112
 
@@ -2108,7 +2149,7 @@ var ModelTextures = class {
2108
2149
  }
2109
2150
  const keyStr = key.serialize();
2110
2151
  return Caching.textureMetaCache.get(keyStr, (k) => {
2111
- return this.PERSISTENT_META_CACHE.getOrLoad(keyStr, (k1) => {
2152
+ return this.PERSISTENT_META_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
2112
2153
  return AssetLoader.get(key, "meta" /* META */).then((asset) => {
2113
2154
  if (asset)
2114
2155
  asset.key = key;
@@ -4755,7 +4796,9 @@ var _BlockObject = class _BlockObject extends SceneObject {
4755
4796
  if (typeof variant.y !== "undefined") {
4756
4797
  rotation.y = toRadians(clampRotationDegrees(typeof variant.x !== "undefined" ? variant.y : 360 - variant.y));
4757
4798
  }
4758
- const model = await Models.getMerged(AssetKey.parse("models", variant.model));
4799
+ const modelKey = AssetKey.parse("models", variant.model);
4800
+ modelKey.root = this.blockState.key?.root;
4801
+ const model = await Models.getMerged(modelKey);
4759
4802
  const options = {
4760
4803
  ...this.options,
4761
4804
  tints: model ? await BlockTints.get(this.blockState.key, this.state, model, this.options.tints) : this.options.tints,
@@ -5551,56 +5594,12 @@ var baseSkinCoordinates = {
5551
5594
  }
5552
5595
  },
5553
5596
  cape: {
5554
- //TODO
5555
- east: {
5556
- uv: [
5557
- 0,
5558
- 36,
5559
- 4,
5560
- 48
5561
- ]
5562
- },
5563
- west: {
5564
- uv: [
5565
- 8,
5566
- 36,
5567
- 12,
5568
- 48
5569
- ]
5570
- },
5571
- north: {
5572
- uv: [
5573
- 4,
5574
- 36,
5575
- 8,
5576
- 48
5577
- ]
5578
- },
5579
- south: {
5580
- uv: [
5581
- 12,
5582
- 36,
5583
- 16,
5584
- 48
5585
- ]
5586
- },
5587
- up: {
5588
- uv: [
5589
- 4,
5590
- 32,
5591
- 8,
5592
- 36
5593
- ]
5594
- },
5595
- down: {
5596
- uv: [
5597
- 8,
5598
- 32,
5599
- 12,
5600
- 36
5601
- ],
5602
- rotation: 180
5603
- }
5597
+ east: { uv: [0, 1, 1, 17] },
5598
+ west: { uv: [11, 1, 12, 17] },
5599
+ north: { uv: [1, 1, 11, 17] },
5600
+ south: { uv: [12, 1, 22, 17] },
5601
+ up: { uv: [11, 1, 1, 0] },
5602
+ down: { uv: [21, 0, 11, 1] }
5604
5603
  }
5605
5604
  };
5606
5605
  var classicSkinTextureCoordinates = {
@@ -5699,14 +5698,27 @@ var SkinTextures = class {
5699
5698
  const key = `skin:${serializeImageKey({ src })}:${isLegacy}`;
5700
5699
  const material = Caching.materialCache.get(key, () => {
5701
5700
  const pixels = isLegacy ? this.normalizeLegacy(image, scale) : new Uint8Array(image.data);
5702
- const texture = Textures.initTextureProps(new import_three18.DataTexture(pixels, image.width, image.width));
5703
- texture.colorSpace = import_three18.SRGBColorSpace;
5704
- texture.flipY = true;
5705
- texture.needsUpdate = true;
5706
- return new import_three18.MeshBasicMaterial({ map: texture, transparent: true, side: import_three18.DoubleSide, alphaTest: 0.5 });
5701
+ return this.createMaterial(pixels, image.width, image.width);
5707
5702
  });
5708
5703
  return { material, slim, legacy: isLegacy };
5709
5704
  }
5705
+ static async getCape(src) {
5706
+ const image = await ImageLoader.getData(src);
5707
+ const scale = image.width / 64;
5708
+ if (!Number.isInteger(scale) || scale < 1 || image.height !== image.width / 2) {
5709
+ throw new MineRenderError(`Invalid cape dimensions ${image.width}x${image.height}; expected 64x32 or an integer-scaled equivalent`);
5710
+ }
5711
+ return Caching.materialCache.get(`cape:${serializeImageKey({ src })}`, () => {
5712
+ return this.createMaterial(new Uint8Array(image.data), image.width, image.height, false);
5713
+ });
5714
+ }
5715
+ static createMaterial(pixels, width, height, transparent = true) {
5716
+ const texture = Textures.initTextureProps(new import_three18.DataTexture(pixels, width, height));
5717
+ texture.colorSpace = import_three18.SRGBColorSpace;
5718
+ texture.flipY = true;
5719
+ texture.needsUpdate = true;
5720
+ return new import_three18.MeshBasicMaterial({ map: texture, transparent, side: transparent ? import_three18.DoubleSide : import_three18.FrontSide, alphaTest: transparent ? 0.5 : 0 });
5721
+ }
5710
5722
  static isSlim(image, scale) {
5711
5723
  for (const [x, y] of [[46, 52], [54, 20]]) {
5712
5724
  for (let row = y * scale; row < (y + 12) * scale; row++) {
@@ -5769,6 +5781,7 @@ var SkinObject = class extends SceneObject {
5769
5781
  this.slim = false;
5770
5782
  this.detectedSlim = false;
5771
5783
  this.skinLoad = 0;
5784
+ this.capeLoad = 0;
5772
5785
  this.options = (0, import_ts_deepmerge7.default)({}, SceneObject.DEFAULT_OPTIONS, options ?? {});
5773
5786
  this.slim = this.options.slim ?? false;
5774
5787
  }
@@ -5838,6 +5851,7 @@ var SkinObject = class extends SceneObject {
5838
5851
  this.detectedSlim = texture.slim;
5839
5852
  this.updateSlim(this.options.slim ?? this.detectedSlim);
5840
5853
  for (const part of SKIN_PARTS) {
5854
+ if (part === "cape" /* CAPE */) continue;
5841
5855
  const mesh = this.getMeshByName(part);
5842
5856
  if (mesh) {
5843
5857
  mesh.material = texture.material;
@@ -5845,7 +5859,34 @@ var SkinObject = class extends SceneObject {
5845
5859
  }
5846
5860
  this.notifyDirty();
5847
5861
  }
5848
- //TODO: cape
5862
+ /** Load a vanilla cape texture, or pass undefined to remove the cape. */
5863
+ async setCapeTexture(src) {
5864
+ const load = ++this.capeLoad;
5865
+ if (src === void 0) {
5866
+ const group = this.getGroupByName("cape" /* CAPE */);
5867
+ if (group) this.remove(group);
5868
+ this.notifyDirty();
5869
+ return;
5870
+ }
5871
+ const material = await SkinTextures.getCape(src);
5872
+ if (load !== this.capeLoad) return;
5873
+ const mesh = this.getMeshByName("cape" /* CAPE */);
5874
+ if (mesh) {
5875
+ mesh.material = material;
5876
+ } else {
5877
+ const group = this.createAndAddGroup("cape" /* CAPE */, 0, 24, 2);
5878
+ group.rotation.set(toRadians(-6), Math.PI, 0);
5879
+ const geometry = this._getBoxGeometryFromDimensions(
5880
+ classicSkinGeometries.cape,
5881
+ classicSkinTextureCoordinates.cape,
5882
+ [64, 32],
5883
+ [64, 32]
5884
+ );
5885
+ const cape = this.createAndAddMesh("cape" /* CAPE */, group, geometry, material);
5886
+ cape.position.set(0, -8, -0.5);
5887
+ }
5888
+ this.notifyDirty();
5889
+ }
5849
5890
  /** Select the arm model, or pass undefined to use texture detection. */
5850
5891
  setSlim(slim) {
5851
5892
  this.options.slim = slim;
@@ -5873,6 +5914,7 @@ var SkinObject = class extends SceneObject {
5873
5914
  }
5874
5915
  dispose() {
5875
5916
  this.skinLoad++;
5917
+ this.capeLoad++;
5876
5918
  super.dispose();
5877
5919
  }
5878
5920
  //TODO: layer toggles