minerender 2.1.0-alpha.3 → 2.1.0-alpha.30

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.
@@ -23,14 +23,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
24
  mod
25
25
  ));
26
- var __decorateClass = (decorators, target, key, kind) => {
27
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
28
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
29
- if (decorator = decorators[i])
30
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
31
- if (kind && result) __defProp(target, key, result);
32
- return result;
33
- };
34
26
 
35
27
  // ../../node_modules/ts-deepmerge/dist/index.js
36
28
  var require_dist = __commonJS({
@@ -40,8 +32,8 @@ var require_dist = __commonJS({
40
32
  __assign = Object.assign || function(t) {
41
33
  for (var s, i = 1, n = arguments.length; i < n; i++) {
42
34
  s = arguments[i];
43
- for (var p13 in s) if (Object.prototype.hasOwnProperty.call(s, p13))
44
- t[p13] = s[p13];
35
+ for (var p12 in s) if (Object.prototype.hasOwnProperty.call(s, p12))
36
+ t[p12] = s[p12];
45
37
  }
46
38
  return t;
47
39
  };
@@ -85,7 +77,7 @@ var require_dist = __commonJS({
85
77
  return false;
86
78
  };
87
79
  var PROTECTED_KEYS = ["__proto__"];
88
- var merge11 = function() {
80
+ var merge10 = function() {
89
81
  var objects = [];
90
82
  for (var _i = 0; _i < arguments.length; _i++) {
91
83
  objects[_i] = arguments[_i];
@@ -96,9 +88,9 @@ var require_dist = __commonJS({
96
88
  return;
97
89
  }
98
90
  if (Array.isArray(result[key]) && Array.isArray(current[key])) {
99
- result[key] = merge11.options.mergeArrays ? Array.from(new Set(result[key].concat(current[key]))) : current[key];
91
+ result[key] = merge10.options.mergeArrays ? Array.from(new Set(result[key].concat(current[key]))) : current[key];
100
92
  } else if (isObject(result[key]) && isObject(current[key])) {
101
- result[key] = merge11(result[key], current[key]);
93
+ result[key] = merge10(result[key], current[key]);
102
94
  } else {
103
95
  result[key] = current[key];
104
96
  }
@@ -109,18 +101,18 @@ var require_dist = __commonJS({
109
101
  var defaultOptions = {
110
102
  mergeArrays: true
111
103
  };
112
- merge11.options = defaultOptions;
113
- merge11.withOptions = function(options) {
104
+ merge10.options = defaultOptions;
105
+ merge10.withOptions = function(options) {
114
106
  var objects = [];
115
107
  for (var _i = 1; _i < arguments.length; _i++) {
116
108
  objects[_i - 1] = arguments[_i];
117
109
  }
118
- merge11.options = __assign({ mergeArrays: true }, options);
119
- var result = merge11.apply(void 0, __spreadArray([], __read(objects), false));
120
- merge11.options = defaultOptions;
110
+ merge10.options = __assign({ mergeArrays: true }, options);
111
+ var result = merge10.apply(void 0, __spreadArray([], __read(objects), false));
112
+ merge10.options = defaultOptions;
121
113
  return result;
122
114
  };
123
- exports.default = merge11;
115
+ exports.default = merge10;
124
116
  }
125
117
  });
126
118
 
@@ -154,17 +146,20 @@ var _PersistentCache = class _PersistentCache {
154
146
  static open(name) {
155
147
  return Env.provider.openCache(name, _PersistentCache.VERSION);
156
148
  }
149
+ /** Loads a missing entry and stores only non-nullish results. */
157
150
  async getOrLoad(key, loader) {
158
151
  let v = await this.get(key);
159
152
  if (typeof v === "undefined" || v === null) {
160
153
  v = await loader(key);
161
- await this.put(key, v);
154
+ if (typeof v !== "undefined" && v !== null) {
155
+ await this.put(key, v);
156
+ }
162
157
  }
163
158
  return v;
164
159
  }
165
160
  };
166
161
  /** Bump to invalidate every persisted entry. */
167
- _PersistentCache.VERSION = 1;
162
+ _PersistentCache.VERSION = 2;
168
163
  var PersistentCache = _PersistentCache;
169
164
 
170
165
  // src/env/browser/BrowserCache.ts
@@ -265,7 +260,8 @@ var BrowserEnv = class {
265
260
  }
266
261
  openCache(name, version) {
267
262
  return new BrowserCache(localforage.createInstance({
268
- name,
263
+ // IndexedDB schema upgrades retain records; each cache generation needs its own database.
264
+ name: name + version,
269
265
  version
270
266
  }));
271
267
  }
@@ -282,7 +278,7 @@ registerBrowserEnv();
282
278
  // src/Axis.ts
283
279
  import { Vector3 } from "three";
284
280
  var Axis = /* @__PURE__ */ ((Axis3) => {
285
- Axis3["X"] = "X";
281
+ Axis3["X"] = "x";
286
282
  Axis3["Y"] = "y";
287
283
  Axis3["Z"] = "z";
288
284
  return Axis3;
@@ -290,7 +286,7 @@ var Axis = /* @__PURE__ */ ((Axis3) => {
290
286
  var AXES = Object.values(Axis);
291
287
  function axisToVec3(axis) {
292
288
  switch (axis) {
293
- case "X" /* X */:
289
+ case "x" /* X */:
294
290
  return new Vector3(1, 0, 0);
295
291
  case "y" /* Y */:
296
292
  return new Vector3(0, 1, 0);
@@ -393,7 +389,10 @@ function serializeMeshKey(key) {
393
389
  // src/cache/Caching.ts
394
390
  import { Caches } from "@inventivetalent/loading-cache";
395
391
  import { Time } from "@inventivetalent/time";
396
- var Caching = class {
392
+ var _Caching = class _Caching {
393
+ static createAssetCache() {
394
+ return Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
395
+ }
397
396
  /** Every cache above, so clear()/end() can never fall out of sync with the field list again. */
398
397
  static get all() {
399
398
  return [
@@ -410,7 +409,12 @@ var Caching = class {
410
409
  this.rawModelCache,
411
410
  this.mergedModelCache,
412
411
  this.modelTextureAtlasCache,
413
- this.blockStateCache
412
+ this.blockStateCache,
413
+ this.listAssetCache,
414
+ this.defaultBlockStatesCache,
415
+ this.blockTintCache,
416
+ this.entityModelCache,
417
+ this.entityTextureCache
414
418
  ];
415
419
  }
416
420
  static clear() {
@@ -421,8 +425,8 @@ var Caching = class {
421
425
  /**
422
426
  * Clears every cache and stops its expiry timer.
423
427
  *
424
- * loading-cache reschedules that timer indefinitely, so a Node process will not exit until
425
- * this is called - see {@link shutdown}.
428
+ * Expiry timers are unref'd in Node, so idle processes can exit without this cleanup.
429
+ * Use {@link shutdown} to also stop requests and the shared Ticker.
426
430
  */
427
431
  static end() {
428
432
  for (const cache of this.all) {
@@ -436,20 +440,26 @@ var Caching = class {
436
440
  };
437
441
  }
438
442
  };
439
- Caching.rawImageCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(5)).buildAsync();
440
- Caching.imageDataCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(10)).buildAsync();
441
- Caching.canvasImageDataCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(10)).buildAsync();
442
- Caching.wrappedImageCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(10)).buildAsync();
443
- Caching.boxGeometryCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(20)).build();
444
- Caching.textureCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).build();
445
- Caching.materialCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).build();
446
- Caching.boxMeshCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).build();
447
- Caching.textureAssetCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
448
- Caching.textureMetaCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
449
- Caching.rawModelCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
450
- Caching.mergedModelCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
451
- Caching.modelTextureAtlasCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
452
- Caching.blockStateCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
443
+ _Caching.rawImageCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(5)).buildAsync();
444
+ _Caching.imageDataCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(10)).buildAsync();
445
+ _Caching.canvasImageDataCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(10)).buildAsync();
446
+ _Caching.wrappedImageCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(10)).buildAsync();
447
+ _Caching.boxGeometryCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(20)).build();
448
+ _Caching.textureCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).build();
449
+ _Caching.materialCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).build();
450
+ _Caching.boxMeshCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).build();
451
+ _Caching.textureAssetCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
452
+ _Caching.textureMetaCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
453
+ _Caching.rawModelCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
454
+ _Caching.mergedModelCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
455
+ _Caching.modelTextureAtlasCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
456
+ _Caching.blockStateCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
457
+ _Caching.listAssetCache = _Caching.createAssetCache();
458
+ _Caching.defaultBlockStatesCache = _Caching.createAssetCache();
459
+ _Caching.blockTintCache = _Caching.createAssetCache();
460
+ _Caching.entityModelCache = _Caching.createAssetCache();
461
+ _Caching.entityTextureCache = _Caching.createAssetCache();
462
+ var Caching = _Caching;
453
463
 
454
464
  // src/Geometries.ts
455
465
  var Geometries = class _Geometries {
@@ -477,6 +487,7 @@ import { CanvasTexture, Texture as Texture2 } from "three";
477
487
 
478
488
  // src/texture/TextureLoader.ts
479
489
  import { RGBAFormat, Texture } from "three";
490
+ import * as THREE from "three";
480
491
 
481
492
  // src/canvas/CanvasCompat.ts
482
493
  function createImage(width, height) {
@@ -487,9 +498,7 @@ function createCanvas(width, height) {
487
498
  }
488
499
 
489
500
  // src/request/Requests.ts
490
- import axios from "axios";
491
- import { JobQueue } from "jobqu";
492
- import { Time as Time2 } from "@inventivetalent/time";
501
+ import { JobCancelledError, JobQueue } from "jobqu";
493
502
 
494
503
  // src/util/log.ts
495
504
  var Reset = "\x1B[0m";
@@ -499,198 +508,170 @@ function prefix(prefix2) {
499
508
  return `${FgGreen}MR${BgBlack}${prefix2 ?? ""}${Reset}`;
500
509
  }
501
510
 
502
- // src/util/BatchedExecutor.ts
503
- var BatchedExecutor = class {
504
- constructor(interval = 1, batch = 30) {
505
- this.interval = interval;
506
- this.batch = batch;
507
- this.queue = [];
508
- this.task = setInterval(() => this.run());
509
- }
510
- submit(task) {
511
- this.queue.push(task);
512
- }
513
- run() {
514
- for (let i = 0; i < this.batch; i++) {
515
- this.runNext();
516
- }
517
- }
518
- runNext() {
519
- let next = this.queue.shift();
520
- if (next) next();
511
+ // src/request/Requests.ts
512
+ var p = prefix("Requests");
513
+ var RequestError = class extends Error {
514
+ constructor(message, response, cause) {
515
+ super(message);
516
+ this.response = response;
517
+ this.cause = cause;
518
+ this.name = "RequestError";
521
519
  }
522
520
  };
523
-
524
- // src/util/model.ts
525
- import { AxesHelper, BoxGeometry as BoxGeometry2, EdgesGeometry, LineBasicMaterial, LineSegments, Vector3 as Vector32 } from "three";
526
- function applyElementRotation(rotation, geometry) {
527
- const origin = new Vector32(rotation.origin[0], rotation.origin[1], rotation.origin[2]);
528
- geometry.translate(-origin.x, -origin.y, -origin.z);
529
- switch (rotation.axis) {
530
- case "X" /* X */:
531
- geometry.rotateX(toRadians(rotation.angle));
532
- break;
533
- case "y" /* Y */:
534
- geometry.rotateY(toRadians(rotation.angle));
535
- break;
536
- case "z" /* Z */:
537
- geometry.rotateZ(toRadians(rotation.angle));
538
- break;
539
- }
540
- if (rotation.rescale) {
541
- if (Math.abs(rotation.angle) === 22.5) {
542
- const s = 1 / Math.cos(0.39269909262657166);
543
- geometry.scale(s, 1, s);
544
- } else {
545
- const s = 1 / Math.cos(0.7853981852531433);
546
- geometry.scale(s, 1, s);
521
+ async function fetchRequest(config, defaultBaseURL) {
522
+ const { url, baseURL = defaultBaseURL, timeout = 5e3, responseType = "json", signal, ...options } = config;
523
+ const target = baseURL && !/^([a-z][a-z\d+.-]*:|\/\/)/i.test(url) ? `${baseURL.replace(/\/+$/, "")}/${url.replace(/^\/+/, "")}` : url;
524
+ const headers = new Headers(options.headers);
525
+ if (typeof window === "undefined" && !headers.has("User-Agent")) {
526
+ headers.set("User-Agent", "MineRender");
527
+ }
528
+ const attemptSignal = AbortSignal.any([
529
+ ...signal ? [signal] : [],
530
+ ...timeout === 0 ? [] : [AbortSignal.timeout(timeout)]
531
+ ]);
532
+ const request = new Request(target, { ...options, headers, signal: attemptSignal });
533
+ attemptSignal.throwIfAborted();
534
+ try {
535
+ const response = await fetch(request);
536
+ const result = {
537
+ data: void 0,
538
+ status: response.status,
539
+ statusText: response.statusText,
540
+ headers: response.headers,
541
+ url: response.url
542
+ };
543
+ if (!response.ok) {
544
+ await response.body?.cancel().catch(() => {
545
+ });
546
+ throw new RequestError(`Request failed with status ${response.status}`, result);
547
547
  }
548
+ const emptyBody = request.method === "HEAD" || response.status === 204 || response.status === 205;
549
+ const data = responseType === "arraybuffer" ? await response.arrayBuffer() : emptyBody || response.body === null ? void 0 : await response.json();
550
+ return { ...result, data };
551
+ } catch (error) {
552
+ attemptSignal.throwIfAborted();
553
+ if (/^https?:/.test(request.url) && request.redirect !== "error" && error instanceof TypeError) {
554
+ throw new RequestError("Network request failed", void 0, error);
555
+ }
556
+ throw error;
548
557
  }
549
- geometry.translate(origin.x, origin.y, origin.z);
550
558
  }
551
- function applyModelPartRotation(modelPart, geometry) {
552
- const origin = new Vector32(modelPart.pivotX, modelPart.pivotY, modelPart.pivotZ);
553
- geometry.translate(-origin.x, -origin.y, -origin.z);
554
- geometry.rotateX(toRadians(modelPart.pitch));
555
- geometry.rotateY(toRadians(modelPart.yaw));
556
- geometry.rotateZ(toRadians(modelPart.roll));
557
- geometry.translate(origin.x, origin.y, origin.z);
558
- }
559
- function applyGenericRotation(axis, rotation, obj) {
560
- switch (axis) {
561
- case "X" /* X */:
562
- obj.rotateOnAxis(new Vector32(1, 0, 0), toRadians(rotation));
563
- break;
564
- case "y" /* Y */:
565
- obj.rotateOnAxis(new Vector32(0, 1, 0), toRadians(rotation));
566
- break;
567
- case "z" /* Z */:
568
- obj.rotateOnAxis(new Vector32(0, 0, 1), toRadians(rotation));
569
- break;
559
+ async function abortable(promise, signal) {
560
+ if (!signal) return promise;
561
+ let abort;
562
+ const cancelled = new Promise((_, reject) => {
563
+ abort = () => reject(signal.reason);
564
+ signal.addEventListener("abort", abort, { once: true });
565
+ if (signal.aborted) abort();
566
+ });
567
+ try {
568
+ return await Promise.race([promise, cancelled]);
569
+ } finally {
570
+ signal.removeEventListener("abort", abort);
570
571
  }
571
572
  }
572
- function addBox3WireframeToObject(box, obj, color = 16777215, w = 2) {
573
- let size = new Vector32();
574
- box.getSize(size);
575
- let boxGeo = new BoxGeometry2(size.x, size.y, size.z, 1, 1, 1);
576
- let wireGeo = new EdgesGeometry(boxGeo);
577
- let wireMat = new LineBasicMaterial({ color, linewidth: w });
578
- let wireframe = new LineSegments(wireGeo, wireMat);
579
- obj.add(wireframe);
580
- let axes = new AxesHelper(1);
581
- obj.add(axes);
582
- }
583
- function addWireframeToObject(obj, color = 16777215, w = 2) {
584
- let boxGeo = new BoxGeometry2(16, 16, 16, 1, 1, 1);
585
- let wireGeo = new EdgesGeometry(boxGeo);
586
- let wireMat = new LineBasicMaterial({ color, linewidth: w });
587
- let wireframe = new LineSegments(wireGeo, wireMat);
588
- obj.add(wireframe);
589
- let axes = new AxesHelper(1);
590
- obj.add(axes);
591
- }
592
- function addWireframeToMesh(geo, mesh, color = 16777215, w = 2) {
593
- let wireGeo = new EdgesGeometry(geo);
594
- let wireMat = new LineBasicMaterial({ color, linewidth: w });
595
- let wireframe = new LineSegments(wireGeo, wireMat);
596
- mesh.add(wireframe);
597
- let axes = new AxesHelper(1);
598
- mesh.add(axes);
599
- }
600
-
601
- // src/util/performance.ts
602
- function captureException(e, ...extra) {
603
- console.error(e, extra);
604
- }
605
- function timingStart(marker) {
606
- console.time(marker);
607
- }
608
- function timingEnd(marker) {
609
- console.timeEnd(marker);
610
- }
611
-
612
- // src/util/three.ts
613
- function isObject3D(obj) {
614
- return obj.isObject3D;
615
- }
616
- function isMesh(obj) {
617
- return obj.isMesh;
618
- }
619
- function isInstancedMesh(obj) {
620
- return obj.isInstancedMesh;
621
- }
622
- function isPerspectiveCamera(obj) {
623
- return obj.isPerspectiveCamera;
624
- }
625
- function isOrthographicCamera(obj) {
626
- return obj.isOrthographicCamera;
627
- }
628
-
629
- // src/request/Requests.ts
630
- var p = prefix("Requests");
631
- if (typeof window === "undefined") {
632
- axios.defaults.headers["User-Agent"] = "MineRender";
633
- }
634
- var _Requests = class _Requests {
635
- static genericRequest(request) {
636
- return this.genericQueue.add(request).catch(async (e) => {
637
- console.debug(p, "generic catch", e.code, e);
638
- if (this.shouldRetry(e, request)) {
639
- await sleep(100);
640
- return this.genericRequest(request);
641
- }
642
- throw e;
643
- });
644
- }
645
- static mcAssetRequest(request) {
646
- return this.mcAssetRequestQueue.add(request).catch((e) => {
647
- console.debug(p, "mcAssetRequest catch", e.code, e);
648
- throw e;
649
- });
573
+ var RequestQueue = class {
574
+ constructor() {
575
+ this.pendingRequests = 0;
576
+ this.stopped = new AbortController();
577
+ this.queue = new JobQueue(
578
+ (request) => fetchRequest(request, this.baseURL),
579
+ { interval: 10, maxPerRun: 1, maxActive: 8 }
580
+ );
650
581
  }
651
- static shouldRetry(e, request) {
652
- if ("code" in e) {
653
- if ("ECONNABORTED" === e.code) {
654
- if ("minerenderRetry" in request) {
655
- if (request["minerenderRetry"] > 0) {
656
- request["minerenderRetry"]--;
657
- return true;
582
+ async request(request) {
583
+ this.pendingRequests++;
584
+ const signal = request.signal;
585
+ try {
586
+ for (let retries = 0; ; retries++) {
587
+ signal?.throwIfAborted();
588
+ try {
589
+ return await abortable(this.queue.add(request), signal);
590
+ } catch (error) {
591
+ console.debug(p, "Request failed", error);
592
+ const delay = this.retryDelay(error, request, retries);
593
+ if (delay === void 0) {
594
+ throw error;
658
595
  }
659
- } else {
660
- request["minerenderRetry"] = 3;
661
- return true;
596
+ await this.waitForRetry(delay, signal);
662
597
  }
663
598
  }
599
+ } finally {
600
+ if (signal?.aborted) {
601
+ this.queue.remove(request);
602
+ }
603
+ this.pendingRequests--;
664
604
  }
665
- return false;
605
+ }
606
+ retryDelay(error, request, retries) {
607
+ if (this.stopped.signal.aborted || retries >= 3 || (request.method ?? "get").toLowerCase() !== "get" || request.signal?.aborted) {
608
+ return void 0;
609
+ }
610
+ if (error instanceof RequestError) {
611
+ const status = error.response?.status;
612
+ if (status !== void 0 && status !== 408 && status !== 429 && !(status >= 500 && status < 600)) return void 0;
613
+ } else if (!(error instanceof DOMException && error.name === "TimeoutError")) {
614
+ return void 0;
615
+ }
616
+ let delay = 100 * 2 ** retries;
617
+ const retryAfter = error instanceof RequestError ? error.response?.headers.get("Retry-After") : void 0;
618
+ if (retryAfter !== void 0 && retryAfter !== null) {
619
+ const seconds = Number(retryAfter);
620
+ if (!retryAfter.trim() || !Number.isFinite(seconds) || seconds < 0) {
621
+ return void 0;
622
+ }
623
+ delay = Math.max(delay, seconds * 1e3);
624
+ }
625
+ return delay;
626
+ }
627
+ async waitForRetry(delay, signal) {
628
+ let timer;
629
+ const waiting = new Promise((resolve) => {
630
+ timer = setTimeout(resolve, delay);
631
+ });
632
+ try {
633
+ await abortable(waiting, AbortSignal.any([this.stopped.signal, ...signal ? [signal] : []]));
634
+ } finally {
635
+ clearTimeout(timer);
636
+ }
637
+ }
638
+ get pendingSize() {
639
+ return this.pendingRequests;
640
+ }
641
+ end() {
642
+ this.queue.end();
643
+ this.stopped.abort(new JobCancelledError("ended"));
644
+ }
645
+ };
646
+ var Requests = class {
647
+ static genericRequest(request) {
648
+ return this.genericQueue.request(request);
649
+ }
650
+ static mcAssetRequest(request) {
651
+ return this.mcAssetRequestQueue.request(request);
666
652
  }
667
653
  static setMcAssetRoot(root) {
668
- this.mcAssetInstance.defaults.baseURL = root;
654
+ this.mcAssetRequestQueue.baseURL = root;
669
655
  }
656
+ /** Counts unsettled calls, including retry delays and requests still running after shutdown. */
670
657
  static get queueSizes() {
671
658
  return {
672
- generic: this.genericQueue.size,
673
- mcAsset: this.mcAssetRequestQueue.size
659
+ generic: this.genericQueue.pendingSize,
660
+ mcAsset: this.mcAssetRequestQueue.pendingSize
674
661
  };
675
662
  }
676
663
  /**
677
- * Stops both queue timers. jobqu reschedules itself indefinitely, so a Node process will not
678
- * exit until this is called - see {@link shutdown}.
664
+ * Permanently stops both queues and rejects waiting, retrying, and future requests.
665
+ * Requests already running can finish. Idle queues hold no timer, so Node can exit without
666
+ * this cleanup. See {@link shutdown}.
679
667
  */
680
668
  static end() {
681
669
  this.genericQueue.end();
682
670
  this.mcAssetRequestQueue.end();
683
671
  }
684
672
  };
685
- _Requests.axiosInstance = axios.create({
686
- timeout: 5e3
687
- });
688
- _Requests.mcAssetInstance = axios.create({
689
- timeout: 5e3
690
- });
691
- _Requests.genericQueue = new JobQueue((request) => _Requests.axiosInstance.request(request), Time2.millis(10), 1);
692
- _Requests.mcAssetRequestQueue = new JobQueue((request) => _Requests.mcAssetInstance.request(request), Time2.millis(10), 1);
693
- var Requests = _Requests;
673
+ Requests.genericQueue = new RequestQueue();
674
+ Requests.mcAssetRequestQueue = new RequestQueue();
694
675
 
695
676
  // src/WrappedImage.ts
696
677
  var WrappedImage = class {
@@ -728,10 +709,10 @@ var WrappedImage = class {
728
709
  return this.animated ? this.width : this.height;
729
710
  }
730
711
  get frameCount() {
731
- return this.height / this.width;
712
+ return this.height / this.frameHeight;
732
713
  }
733
714
  getFrameY(frame) {
734
- return this.height / this.frameCount * Math.max(0, Math.min(this.frameCount, frame));
715
+ return this.frameHeight * Math.max(0, Math.min(this.frameCount - 1, frame));
735
716
  }
736
717
  getSectionData(sx, sy, sw, sh) {
737
718
  return this.dta.data.getImageData(sx, sy, sw, sh);
@@ -741,7 +722,7 @@ var WrappedImage = class {
741
722
  }
742
723
  getFrameSectionData(frame) {
743
724
  const y = this.getFrameY(frame);
744
- return this.getSectionData(0, y, this.width, this.width + y);
725
+ return this.getSectionData(0, y, this.frameWidth, this.frameHeight);
745
726
  }
746
727
  };
747
728
 
@@ -755,29 +736,41 @@ var ImageLoader = class _ImageLoader {
755
736
  static async loadAsync(src) {
756
737
  return new Promise((resolve, reject) => {
757
738
  const image = this._createImage();
758
- image.src = src;
759
739
  image.onload = () => resolve(image);
760
- image.onerror = (err) => reject(err);
740
+ image.onerror = (err) => reject(err instanceof Error ? err : new Error("Failed to decode image"));
741
+ image.src = src;
761
742
  });
762
743
  }
763
744
  static loadElement(src, onload, onerr) {
764
745
  const image = this._createImage();
765
- image.src = src;
766
746
  if (onload)
767
747
  image.onload = onload;
768
748
  if (onerr)
769
749
  image.onerror = onerr;
750
+ image.src = src;
770
751
  return image;
771
752
  }
772
753
  static async loadData(src) {
773
754
  console.debug(p2, "loadData", src);
774
- return await this.infoToData(await this.getInfo(src));
755
+ const image = await this.loadCanvasData(src);
756
+ return image.data.getImageData(0, 0, image.width, image.height);
775
757
  }
776
758
  static async loadCanvasData(src) {
777
- return await this.infoToCanvasData(await this.getInfo(src));
759
+ const keyStr = serializeImageKey({ src });
760
+ const info = this.getInfo(src);
761
+ const cached = Caching.rawImageCache.getIfPresent(keyStr);
762
+ try {
763
+ return await this.infoToCanvasData(await info);
764
+ } catch (err) {
765
+ if (cached && Caching.rawImageCache.getIfPresent(keyStr) === cached) {
766
+ Caching.rawImageCache.invalidate(keyStr);
767
+ }
768
+ throw err;
769
+ }
778
770
  }
779
771
  static async infoToCanvasData(info) {
780
- const image = await _ImageLoader.loadAsync(info.src);
772
+ const type = info.type === "jpg" ? "jpeg" : info.type ?? "png";
773
+ const image = await _ImageLoader.loadAsync(`data:image/${type};base64,${info.data.toString("base64")}`);
781
774
  const canvas = createCanvas(info.width, info.height);
782
775
  const context = canvas.getContext("2d");
783
776
  context.drawImage(image, 0, 0);
@@ -788,11 +781,8 @@ var ImageLoader = class _ImageLoader {
788
781
  };
789
782
  }
790
783
  static async infoToData(info) {
791
- const image = await _ImageLoader.loadAsync(info.src);
792
- const canvas = createCanvas(info.width, info.height);
793
- const context = canvas.getContext("2d");
794
- context.drawImage(image, 0, 0);
795
- return context.getImageData(0, 0, canvas.width, canvas.height);
784
+ const image = await this.infoToCanvasData(info);
785
+ return image.data.getImageData(0, 0, image.width, image.height);
796
786
  }
797
787
  static async getData(src) {
798
788
  const keyStr = serializeImageKey({ src });
@@ -807,38 +797,25 @@ var ImageLoader = class _ImageLoader {
807
797
  });
808
798
  }
809
799
  static async processResponse(response) {
810
- const src = response.config.url;
800
+ const src = response.url;
811
801
  const data = Buffer2.from(response.data);
812
802
  const { width, height, type } = Env.provider.imageSize(data);
803
+ if (!width || !height) {
804
+ throw new Error("Invalid or unsupported image dimensions");
805
+ }
813
806
  return {
814
807
  src,
815
- width: width || 0,
816
- height: height || 0,
808
+ width,
809
+ height,
817
810
  type,
818
811
  data
819
812
  };
820
813
  }
821
814
  static async loadInfo(src) {
822
- if (src.startsWith("data:image/png;base64")) {
823
- return this.processResponse({
824
- config: {
825
- url: src
826
- },
827
- data: Buffer2.from(src.substr("data:image/png;base64,".length), "base64")
828
- });
829
- }
830
815
  return Requests.genericRequest({
831
816
  url: src,
832
817
  responseType: "arraybuffer"
833
- }).then(this.processResponse).catch((err) => {
834
- return {
835
- src,
836
- width: 0,
837
- height: 0,
838
- type: void 0,
839
- data: Buffer2.from([0])
840
- };
841
- });
818
+ }).then(this.processResponse);
842
819
  }
843
820
  static async getInfo(src) {
844
821
  const keyStr = serializeImageKey({ src });
@@ -860,15 +837,22 @@ var ImageLoader = class _ImageLoader {
860
837
 
861
838
  // src/texture/TextureLoader.ts
862
839
  var TextureLoader = class {
840
+ /** Reports whether image loading failed, so cached textures can be retried. */
841
+ static hasFailed(texture) {
842
+ return this.failedTextures.has(texture);
843
+ }
863
844
  static createTexture() {
864
845
  return new Texture();
865
846
  }
866
847
  static loadInBackground(src, format = RGBAFormat, rotation = 0) {
867
848
  const texture = this.createTexture();
868
- const image = ImageLoader.loadElement(src);
869
- image.onload = function() {
849
+ texture.colorSpace = THREE.SRGBColorSpace;
850
+ const image = ImageLoader.loadElement(src, () => {
870
851
  texture.needsUpdate = true;
871
- };
852
+ }, (error) => {
853
+ this.failedTextures.add(texture);
854
+ console.warn("Failed to load texture", src, error);
855
+ });
872
856
  texture.image = image;
873
857
  texture.format = format;
874
858
  texture.rotation = rotation;
@@ -876,22 +860,27 @@ var TextureLoader = class {
876
860
  }
877
861
  static load(src, format = RGBAFormat, rotation = 0) {
878
862
  const texture = new Texture();
863
+ texture.colorSpace = THREE.SRGBColorSpace;
879
864
  ImageLoader.getData(src).then((image) => {
880
- texture.needsUpdate = true;
881
865
  texture.image = image;
866
+ texture.needsUpdate = true;
867
+ }).catch((error) => {
868
+ this.failedTextures.add(texture);
869
+ console.warn("Failed to load texture", src, error);
882
870
  });
883
871
  texture.format = format;
884
872
  texture.rotation = rotation;
885
873
  return Textures.initTextureProps(texture);
886
874
  }
887
875
  };
876
+ TextureLoader.failedTextures = /* @__PURE__ */ new WeakSet();
888
877
 
889
878
  // src/texture/Textures.ts
890
- import * as THREE from "three";
879
+ import * as THREE2 from "three";
891
880
  var Textures = class _Textures {
892
881
  static initTextureProps(texture) {
893
- texture.magFilter = THREE.NearestFilter;
894
- texture.minFilter = THREE.NearestFilter;
882
+ texture.magFilter = THREE2.NearestFilter;
883
+ texture.minFilter = THREE2.NearestFilter;
895
884
  texture.anisotropy = 0;
896
885
  return texture;
897
886
  }
@@ -902,10 +891,16 @@ var Textures = class _Textures {
902
891
  return new Texture2();
903
892
  }
904
893
  static createCanvasTexture(canvas) {
905
- return this.initTextureProps(new CanvasTexture(canvas));
894
+ const texture = new CanvasTexture(canvas);
895
+ texture.colorSpace = THREE2.SRGBColorSpace;
896
+ return this.initTextureProps(texture);
906
897
  }
907
898
  static getImage(key) {
908
899
  const keyStr = serializeTextureKey(key);
900
+ const cached = Caching.textureCache.peek(keyStr);
901
+ if (cached && TextureLoader.hasFailed(cached)) {
902
+ Caching.textureCache.invalidate(keyStr);
903
+ }
909
904
  return Caching.textureCache.get(keyStr, (k) => {
910
905
  return _Textures.createImage(key);
911
906
  });
@@ -949,6 +944,7 @@ var _Materials = class _Materials {
949
944
  uniform bool SHADE;
950
945
 
951
946
  varying vec2 vUv;
947
+ varying vec3 vTint;
952
948
  varying float light;
953
949
  varying float lift;
954
950
 
@@ -961,7 +957,11 @@ var _Materials = class _Materials {
961
957
 
962
958
  if (SHADE) {
963
959
 
964
- vec3 N = vec3( modelMatrix * instanceMatrix * vec4(normal, 0.0) );
960
+ #ifdef USE_INSTANCING
961
+ vec3 N = vec3( modelMatrix * instanceMatrix * vec4(normal, 0.0) );
962
+ #else
963
+ vec3 N = vec3( modelMatrix * vec4(normal, 0.0) );
964
+ #endif
965
965
 
966
966
  float yLight = (1.0+N.y) * 0.5;
967
967
  light = yLight * (1.0-AMBIENT) + N.x*N.x * XFAC + N.z*N.z * ZFAC + AMBIENT;
@@ -979,6 +979,7 @@ var _Materials = class _Materials {
979
979
  }
980
980
 
981
981
  vUv = uv;
982
+ vTint = color;
982
983
 
983
984
  #ifdef USE_INSTANCING
984
985
  gl_Position = projectionMatrix * viewMatrix * modelMatrix * instanceMatrix * vec4(position, 1.0);
@@ -1000,12 +1001,14 @@ var _Materials = class _Materials {
1000
1001
  uniform float BRIGHTNESS;
1001
1002
 
1002
1003
  varying vec2 vUv;
1004
+ varying vec3 vTint;
1003
1005
  varying float light;
1004
1006
  varying float lift;
1005
1007
 
1006
1008
  void main(void)
1007
1009
  {
1008
1010
  vec4 color = texture2D(map, vUv);
1011
+ color.rgb *= vTint;
1009
1012
 
1010
1013
  if (color.a < 0.01) discard;
1011
1014
 
@@ -1019,6 +1022,9 @@ var _Materials = class _Materials {
1019
1022
  gl_FragColor = vec4(lift + color.rgb * light2, 1.0);
1020
1023
 
1021
1024
  }
1025
+
1026
+ #include <tonemapping_fragment>
1027
+ #include <colorspace_fragment>
1022
1028
  }
1023
1029
  `;
1024
1030
  try {
@@ -1047,6 +1053,10 @@ var _Materials = class _Materials {
1047
1053
  }
1048
1054
  static getImage(key) {
1049
1055
  const keyStr = serializeMaterialKey(key);
1056
+ const map = Caching.materialCache.peek(keyStr)?.map;
1057
+ if (map && TextureLoader.hasFailed(map)) {
1058
+ Caching.materialCache.invalidate(keyStr);
1059
+ }
1050
1060
  return Caching.materialCache.get(keyStr, (k) => {
1051
1061
  return _Materials.createImage(key);
1052
1062
  });
@@ -1071,10 +1081,10 @@ _Materials.MISSING_TEXTURE_SRC = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA
1071
1081
  var Materials = _Materials;
1072
1082
 
1073
1083
  // src/Meshes.ts
1074
- import { Mesh as Mesh2 } from "three";
1084
+ import { Mesh } from "three";
1075
1085
  var Meshes = class _Meshes {
1076
1086
  static createBox(key) {
1077
- return new Mesh2(Geometries.getBox(key.geometry), Materials.getImage(key.material));
1087
+ return new Mesh(Geometries.getBox(key.geometry), Materials.getImage(key.material));
1078
1088
  }
1079
1089
  static getBox(key) {
1080
1090
  const keyStr = serializeMeshKey(key);
@@ -1085,7 +1095,7 @@ var Meshes = class _Meshes {
1085
1095
  };
1086
1096
 
1087
1097
  // src/UVMapper.ts
1088
- import { Float32BufferAttribute as Float32BufferAttribute2, Vector2 } from "three";
1098
+ import { Float32BufferAttribute as Float32BufferAttribute2, Vector2, Vector3 as Vector34 } from "three";
1089
1099
 
1090
1100
  // src/model/ModelMerger.ts
1091
1101
  var import_ts_deepmerge = __toESM(require_dist());
@@ -1121,6 +1131,7 @@ var AssetKey = class _AssetKey extends BasicAssetKey {
1121
1131
  this.rootType = rootType;
1122
1132
  this.extension = extension;
1123
1133
  this.root = root;
1134
+ this.isAssetKey = true;
1124
1135
  }
1125
1136
  static parse(assetType, str, origin) {
1126
1137
  let namespace = origin?.namespace || DEFAULT_NAMESPACE;
@@ -1136,12 +1147,15 @@ var AssetKey = class _AssetKey extends BasicAssetKey {
1136
1147
  split.shift();
1137
1148
  }
1138
1149
  let path = split.join("/");
1139
- let extension = assetType === "textures" ? ".png" : origin?.assetType || ".json";
1150
+ const extension = assetType === "textures" ? ".png" : origin?.extension ?? ".json";
1151
+ if (extension && path.endsWith(extension)) {
1152
+ path = path.slice(0, -extension.length);
1153
+ }
1140
1154
  return new _AssetKey(namespace, path, assetType, type, "assets", extension, origin?.root);
1141
1155
  }
1142
1156
  toString() {
1143
1157
  let a = [
1144
- typeof this.root !== "undefined" || AssetLoader.ROOT !== DEFAULT_ROOT ? this.root ?? AssetLoader.ROOT : "__root__",
1158
+ this.root ?? AssetLoader.ROOT,
1145
1159
  this.rootType ?? "__rootType__",
1146
1160
  this.assetType ?? "__assetType__",
1147
1161
  this.type ?? "__type__",
@@ -1151,12 +1165,12 @@ var AssetKey = class _AssetKey extends BasicAssetKey {
1151
1165
  return a.join("/");
1152
1166
  }
1153
1167
  getFullPath() {
1154
- let p13 = [];
1168
+ let p12 = [];
1155
1169
  if (this.type) {
1156
- p13.push(this.type);
1170
+ p12.push(this.type);
1157
1171
  }
1158
- p13.push(this.path);
1159
- return p13.join("/");
1172
+ p12.push(this.path);
1173
+ return p12.join("/");
1160
1174
  }
1161
1175
  toNamespacedString() {
1162
1176
  return this.namespace + ":" + this.getFullPath();
@@ -1166,22 +1180,84 @@ var AssetKey = class _AssetKey extends BasicAssetKey {
1166
1180
  }
1167
1181
  };
1168
1182
  function isBasicAssetKey(obj) {
1169
- return "namespace" in obj && "path" in obj;
1183
+ return obj !== null && typeof obj === "object" && typeof obj.namespace === "string" && typeof obj.path === "string";
1170
1184
  }
1171
1185
  function isAssetKey(obj) {
1172
- return isBasicAssetKey(obj) && "parse" in obj;
1186
+ return isBasicAssetKey(obj) && obj.isAssetKey === true;
1173
1187
  }
1174
1188
 
1189
+ // src/model/DisplayTransforms.ts
1190
+ import { Euler, Matrix4, Quaternion, Vector3 as Vector32 } from "three";
1191
+
1192
+ // src/model/DisplayPosition.ts
1193
+ var DisplayPosition = /* @__PURE__ */ ((DisplayPosition2) => {
1194
+ DisplayPosition2["THIRDPERSON_RIGHTHAND"] = "thirdperson_righthand";
1195
+ DisplayPosition2["THIRDPERSON_LEFTHAND"] = "thirdperson_lefthand";
1196
+ DisplayPosition2["FIRSTPERSON_RIGHTHAND"] = "firstperson_righthand";
1197
+ DisplayPosition2["FIRSTPERSON_LEFTHAND"] = "firstperson_lefthand";
1198
+ DisplayPosition2["GUI"] = "gui";
1199
+ DisplayPosition2["HEAD"] = "head";
1200
+ DisplayPosition2["GROUND"] = "ground";
1201
+ DisplayPosition2["FIXED"] = "fixed";
1202
+ DisplayPosition2["ON_SHELF"] = "on_shelf";
1203
+ return DisplayPosition2;
1204
+ })(DisplayPosition || {});
1205
+ var DISPLAY_POSITIONS = Object.values(DisplayPosition);
1206
+
1207
+ // src/model/DisplayTransforms.ts
1208
+ var DisplayTransforms = class {
1209
+ /** Resolve hand fallbacks within one model, before inheriting parent display entries. */
1210
+ static withHandFallbacks(display) {
1211
+ if (!display) return void 0;
1212
+ const resolved = { ...display };
1213
+ for (const [left, right] of [
1214
+ ["thirdperson_lefthand" /* THIRDPERSON_LEFTHAND */, "thirdperson_righthand" /* THIRDPERSON_RIGHTHAND */],
1215
+ ["firstperson_lefthand" /* FIRSTPERSON_LEFTHAND */, "firstperson_righthand" /* FIRSTPERSON_RIGHTHAND */]
1216
+ ]) {
1217
+ if (resolved[left] === void 0 && resolved[right] !== void 0) resolved[left] = resolved[right];
1218
+ }
1219
+ return resolved;
1220
+ }
1221
+ /** Build a display pose for geometry already centered at the model's [8, 8, 8] pivot. */
1222
+ static getMatrix(display, position) {
1223
+ const transform = this.withHandFallbacks(display)?.[position] ?? {};
1224
+ const left = position === "firstperson_lefthand" /* FIRSTPERSON_LEFTHAND */ || position === "thirdperson_lefthand" /* THIRDPERSON_LEFTHAND */;
1225
+ const sign = left ? -1 : 1;
1226
+ const [x, y, z] = transform.rotation ?? [0, 0, 0];
1227
+ const rotation = new Quaternion().setFromEuler(new Euler(toRadians(x), sign * toRadians(y), sign * toRadians(z), "XYZ"));
1228
+ const translation = new Vector32(...transform.translation ?? [0, 0, 0]).clampScalar(-80, 80);
1229
+ translation.x *= sign;
1230
+ const scale = new Vector32(...transform.scale ?? [1, 1, 1]).clampScalar(-4, 4);
1231
+ return new Matrix4().compose(translation, rotation, scale);
1232
+ }
1233
+ static apply(geometry, matrix) {
1234
+ geometry.applyMatrix4(matrix);
1235
+ if (matrix.determinant() < 0) {
1236
+ if (!geometry.index) geometry.setIndex(Array.from({ length: geometry.getAttribute("position").count }, (_, i) => i));
1237
+ const index = geometry.index;
1238
+ for (let i = 0; i < index.count; i += 3) {
1239
+ const second = index.getX(i + 1);
1240
+ index.setX(i + 1, index.getX(i + 2));
1241
+ index.setX(i + 2, second);
1242
+ }
1243
+ index.needsUpdate = true;
1244
+ }
1245
+ }
1246
+ };
1247
+
1175
1248
  // src/model/ModelMerger.ts
1176
1249
  var ModelMerger = class {
1177
1250
  static async mergeWithParents(model) {
1178
1251
  const models = await this.collectAllParents(model);
1179
1252
  let merged = {};
1180
- for (let parentModel of models) {
1181
- merged = (0, import_ts_deepmerge.default)(merged, parentModel);
1253
+ for (const source of [...models, model]) {
1254
+ const inheritedDisplay = merged.display;
1255
+ merged = import_ts_deepmerge.default.withOptions({ mergeArrays: false }, merged, source);
1256
+ if (source.display) {
1257
+ merged.display = { ...inheritedDisplay, ...DisplayTransforms.withHandFallbacks(source.display) };
1258
+ }
1182
1259
  }
1183
- merged = (0, import_ts_deepmerge.default)(merged, model);
1184
- merged.hierarchy = models.map((m) => m.parent).filter((p13) => `${p13}`);
1260
+ merged.hierarchy = models.map((m) => m.parent).filter((p12) => `${p12}`);
1185
1261
  merged.hierarchy.push(`${merged.parent}`);
1186
1262
  return merged;
1187
1263
  }
@@ -1191,6 +1267,7 @@ var ModelMerger = class {
1191
1267
  }
1192
1268
  const models = [];
1193
1269
  const parentKey = AssetKey.parse("models", model.parent);
1270
+ parentKey.root = model.key?.root;
1194
1271
  const parentModel = await Models.getRaw(parentKey);
1195
1272
  if (parentModel) {
1196
1273
  models.unshift(parentModel);
@@ -1200,9 +1277,6 @@ var ModelMerger = class {
1200
1277
  }
1201
1278
  };
1202
1279
 
1203
- // src/assets/Models.ts
1204
- import { Memoize } from "typescript-memoize";
1205
-
1206
1280
  // src/assets/source/parser/AssetParsers.ts
1207
1281
  var AssetParser = /* @__PURE__ */ ((AssetParser2) => {
1208
1282
  AssetParser2["MODEL"] = "model";
@@ -1216,30 +1290,65 @@ var AssetParser = /* @__PURE__ */ ((AssetParser2) => {
1216
1290
  })(AssetParser || {});
1217
1291
 
1218
1292
  // src/assets/Models.ts
1219
- var _Models = class _Models {
1293
+ var Models = class _Models {
1220
1294
  // opened lazily: touching the store at import time would hit IndexedDB/disk just for loading
1221
1295
  // the library, and would require an EnvProvider before any entry has had a chance to register
1222
1296
  static get PERSISTENT_CACHE() {
1223
1297
  return this._persistentCache ?? (this._persistentCache = PersistentCache.open("minerender-models"));
1224
1298
  }
1225
1299
  static async getItemList() {
1226
- return AssetLoader.get(new AssetKey(
1227
- DEFAULT_NAMESPACE,
1228
- "_list",
1229
- "models",
1230
- "item",
1231
- "assets",
1232
- ".json",
1233
- DEFAULT_ROOT
1234
- ), "list" /* LIST */).then((r) => r?.files ?? []);
1300
+ const key = new AssetKey(DEFAULT_NAMESPACE, "_list", "items");
1301
+ const legacyKey = new AssetKey(DEFAULT_NAMESPACE, "_list", "models", "item");
1302
+ return Caching.listAssetCache.get(key.serialize(), async () => {
1303
+ return (await AssetLoader.getFirst([key, legacyKey], "list" /* LIST */))?.asset;
1304
+ }).then((r) => r?.files ?? []);
1235
1305
  }
1236
1306
  static async loadAndMerge(key) {
1237
- const model = await this.getRaw(key);
1307
+ const model = key.type === "item" ? await this.getItemModel(key) : await this.getRaw(key);
1238
1308
  if (!model) {
1239
1309
  return void 0;
1240
1310
  }
1241
1311
  return ModelMerger.mergeWithParents(model);
1242
1312
  }
1313
+ static async getItemModel(key) {
1314
+ const itemKey = new AssetKey(key.namespace, key.path, "items", void 0, key.rootType, ".json", key.root);
1315
+ const model = await this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(itemKey.serialize()), async () => {
1316
+ const result = await AssetLoader.getFirst([itemKey, key], "json" /* JSON */);
1317
+ if (!result) return void 0;
1318
+ if (result.key.assetType !== "items") return { ...result.asset, key };
1319
+ const reference = this.defaultItemModel(result.asset.model, key);
1320
+ const modelKey = AssetKey.parse("models", reference);
1321
+ modelKey.root = key.root;
1322
+ const model2 = await this.getRaw(modelKey);
1323
+ if (!model2) throw new Error(`Item ${key.toNamespacedString()} references missing model ${reference}`);
1324
+ return model2;
1325
+ });
1326
+ return model ? { ...model, key: Object.assign(new AssetKey("", ""), model.key) } : void 0;
1327
+ }
1328
+ // Item previews use the GUI context, false conditions, and zero numeric properties.
1329
+ static defaultItemModel(node, key) {
1330
+ if (!node || typeof node.type !== "string") {
1331
+ throw new Error(`Unsupported item model definition for ${key.toNamespacedString()}`);
1332
+ }
1333
+ switch (node.type.replace(/^minecraft:/, "")) {
1334
+ case "model":
1335
+ if (typeof node.model === "string" && node.model) return node.model;
1336
+ break;
1337
+ case "condition":
1338
+ return this.defaultItemModel(node.on_false, key);
1339
+ case "select": {
1340
+ const selected = node.property?.replace(/^minecraft:/, "") === "display_context" ? node.cases?.find((entry) => Array.isArray(entry.when) ? entry.when.includes("gui") : entry.when === "gui")?.model : void 0;
1341
+ return this.defaultItemModel(selected ?? node.fallback, key);
1342
+ }
1343
+ case "range_dispatch": {
1344
+ const selected = node.entries?.reduce((match, entry) => {
1345
+ return entry.threshold <= 0 && (!match || entry.threshold >= match.threshold) ? entry : match;
1346
+ }, void 0);
1347
+ return this.defaultItemModel(selected?.model ?? node.fallback, key);
1348
+ }
1349
+ }
1350
+ throw new Error(`Unsupported item model ${node?.type ?? "definition"} for ${key.toNamespacedString()}`);
1351
+ }
1243
1352
  static async getRaw(key) {
1244
1353
  if (!key.assetType) {
1245
1354
  key.assetType = "models";
@@ -1249,7 +1358,7 @@ var _Models = class _Models {
1249
1358
  }
1250
1359
  const keyStr = key.serialize();
1251
1360
  return Caching.rawModelCache.get(keyStr, (k) => {
1252
- return this.PERSISTENT_CACHE.getOrLoad(keyStr, (k1) => {
1361
+ return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
1253
1362
  return AssetLoader.get(key, "model" /* MODEL */);
1254
1363
  });
1255
1364
  }).then((asset) => {
@@ -1278,31 +1387,32 @@ var _Models = class _Models {
1278
1387
  return this.PERSISTENT_CACHE.clear();
1279
1388
  }
1280
1389
  };
1281
- __decorateClass([
1282
- Memoize()
1283
- ], _Models, "getItemList", 1);
1284
- var Models = _Models;
1285
1390
 
1286
1391
  // src/assets/BlockStates.ts
1287
- import { Memoize as Memoize2 } from "typescript-memoize";
1288
1392
  var BlockStates = class {
1289
1393
  static get PERSISTENT_CACHE() {
1290
1394
  return this._persistentCache ?? (this._persistentCache = PersistentCache.open("minerender-blockstates"));
1291
1395
  }
1396
+ // BlockState names are hardcoded
1292
1397
  static async getList() {
1293
- return AssetLoader.get(new AssetKey(
1398
+ const key = new AssetKey(
1294
1399
  DEFAULT_NAMESPACE,
1295
1400
  "_list",
1296
1401
  "blockstates",
1297
1402
  void 0,
1298
1403
  "assets",
1299
1404
  ".json",
1300
- DEFAULT_ROOT
1301
- ), "list" /* LIST */).then((r) => r?.files ?? []);
1405
+ AssetLoader.ROOT
1406
+ );
1407
+ return Caching.listAssetCache.get(key.serialize(), () => {
1408
+ return AssetLoader.get(key, "list" /* LIST */);
1409
+ }).then((r) => r?.files ?? []);
1302
1410
  }
1303
1411
  static async getDefaultStates() {
1304
1412
  const key = AssetKey.parse("blockstates", "minerender:defaultBlockStates");
1305
- return AssetLoader.get(key, "json" /* JSON */);
1413
+ return Caching.defaultBlockStatesCache.get(key.serialize(), () => {
1414
+ return AssetLoader.get(key, "json" /* JSON */);
1415
+ });
1306
1416
  }
1307
1417
  static async getDefaultState(key) {
1308
1418
  const defaultStates = await this.getDefaultStates();
@@ -1320,7 +1430,7 @@ var BlockStates = class {
1320
1430
  }
1321
1431
  const keyStr = key.serialize();
1322
1432
  return Caching.blockStateCache.get(keyStr, (k) => {
1323
- return this.PERSISTENT_CACHE.getOrLoad(keyStr, (k1) => {
1433
+ return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
1324
1434
  return AssetLoader.get(key, "blockstate" /* BLOCKSTATE */);
1325
1435
  });
1326
1436
  }).then((asset) => {
@@ -1341,17 +1451,11 @@ var BlockStates = class {
1341
1451
  await this.PERSISTENT_CACHE.clear();
1342
1452
  }
1343
1453
  };
1344
- __decorateClass([
1345
- Memoize2()
1346
- ], BlockStates, "getList", 1);
1347
- __decorateClass([
1348
- Memoize2()
1349
- ], BlockStates, "getDefaultStates", 1);
1350
1454
  var DefaultBlockStates = class {
1351
1455
  };
1352
1456
 
1353
1457
  // src/assets/Assets.ts
1354
- var DEFAULT_ROOT = "https://assets.mcasset.cloud/1.17.1";
1458
+ var DEFAULT_ROOT = "https://assets.mcasset.cloud/1.21.11";
1355
1459
  var DEFAULT_NAMESPACE = "minecraft";
1356
1460
  var Assets = class {
1357
1461
  /**
@@ -1372,8 +1476,145 @@ var Assets = class {
1372
1476
  var AssetSource = class {
1373
1477
  constructor() {
1374
1478
  }
1479
+ /**
1480
+ * Identifies this source's content across sessions for persistent caching. Sources that
1481
+ * cannot tell whether their content changed (an unnamed archive, for example) return
1482
+ * undefined, which keeps their results out of long-lived caches.
1483
+ */
1484
+ get cacheId() {
1485
+ return void 0;
1486
+ }
1487
+ };
1488
+ var AssetLoadError = class extends Error {
1489
+ constructor(source, key, path, cause) {
1490
+ super(`Failed to load ${key.toNamespacedString()} from ${path}${cause instanceof Error ? `: ${cause.message}` : ""}`);
1491
+ this.source = source;
1492
+ this.key = key;
1493
+ this.path = path;
1494
+ this.cause = cause;
1495
+ this.name = "AssetLoadError";
1496
+ }
1497
+ };
1498
+
1499
+ // src/util/BatchedExecutor.ts
1500
+ var BatchedExecutor = class {
1501
+ constructor(interval = 1, batch = 30) {
1502
+ this.interval = interval;
1503
+ this.batch = batch;
1504
+ this.queue = [];
1505
+ this.task = setInterval(() => this.run());
1506
+ }
1507
+ submit(task) {
1508
+ this.queue.push(task);
1509
+ }
1510
+ run() {
1511
+ for (let i = 0; i < this.batch; i++) {
1512
+ this.runNext();
1513
+ }
1514
+ }
1515
+ runNext() {
1516
+ let next = this.queue.shift();
1517
+ if (next) next();
1518
+ }
1375
1519
  };
1376
1520
 
1521
+ // src/util/model.ts
1522
+ import { AxesHelper, BoxGeometry as BoxGeometry2, EdgesGeometry, LineBasicMaterial, LineSegments, Vector3 as Vector33 } from "three";
1523
+ function applyElementRotation(rotation, geometry) {
1524
+ const origin = new Vector33(rotation.origin[0], rotation.origin[1], rotation.origin[2]);
1525
+ geometry.translate(-origin.x, -origin.y, -origin.z);
1526
+ switch (rotation.axis) {
1527
+ case "x" /* X */:
1528
+ geometry.rotateX(toRadians(rotation.angle));
1529
+ break;
1530
+ case "y" /* Y */:
1531
+ geometry.rotateY(toRadians(rotation.angle));
1532
+ break;
1533
+ case "z" /* Z */:
1534
+ geometry.rotateZ(toRadians(rotation.angle));
1535
+ break;
1536
+ }
1537
+ if (rotation.rescale) {
1538
+ if (Math.abs(rotation.angle) === 22.5) {
1539
+ const s = 1 / Math.cos(0.39269909262657166);
1540
+ geometry.scale(s, 1, s);
1541
+ } else {
1542
+ const s = 1 / Math.cos(0.7853981852531433);
1543
+ geometry.scale(s, 1, s);
1544
+ }
1545
+ }
1546
+ geometry.translate(origin.x, origin.y, origin.z);
1547
+ }
1548
+ function applyGenericRotation(axis, rotation, obj) {
1549
+ switch (axis) {
1550
+ case "x" /* X */:
1551
+ obj.rotateOnAxis(new Vector33(1, 0, 0), toRadians(rotation));
1552
+ break;
1553
+ case "y" /* Y */:
1554
+ obj.rotateOnAxis(new Vector33(0, 1, 0), toRadians(rotation));
1555
+ break;
1556
+ case "z" /* Z */:
1557
+ obj.rotateOnAxis(new Vector33(0, 0, 1), toRadians(rotation));
1558
+ break;
1559
+ }
1560
+ }
1561
+ function addBox3WireframeToObject(box, obj, color = 16777215, w = 2) {
1562
+ let size = new Vector33();
1563
+ box.getSize(size);
1564
+ let boxGeo = new BoxGeometry2(size.x, size.y, size.z, 1, 1, 1);
1565
+ let wireGeo = new EdgesGeometry(boxGeo);
1566
+ let wireMat = new LineBasicMaterial({ color, linewidth: w });
1567
+ let wireframe = new LineSegments(wireGeo, wireMat);
1568
+ obj.add(wireframe);
1569
+ let axes = new AxesHelper(1);
1570
+ obj.add(axes);
1571
+ }
1572
+ function addWireframeToObject(obj, color = 16777215, w = 2) {
1573
+ let boxGeo = new BoxGeometry2(16, 16, 16, 1, 1, 1);
1574
+ let wireGeo = new EdgesGeometry(boxGeo);
1575
+ let wireMat = new LineBasicMaterial({ color, linewidth: w });
1576
+ let wireframe = new LineSegments(wireGeo, wireMat);
1577
+ obj.add(wireframe);
1578
+ let axes = new AxesHelper(1);
1579
+ obj.add(axes);
1580
+ }
1581
+ function addWireframeToMesh(geo, mesh, color = 16777215, w = 2) {
1582
+ let wireGeo = new EdgesGeometry(geo);
1583
+ let wireMat = new LineBasicMaterial({ color, linewidth: w });
1584
+ let wireframe = new LineSegments(wireGeo, wireMat);
1585
+ mesh.add(wireframe);
1586
+ let axes = new AxesHelper(1);
1587
+ mesh.add(axes);
1588
+ }
1589
+
1590
+ // src/util/performance.ts
1591
+ function captureException(e, ...extra) {
1592
+ console.error(e, extra);
1593
+ }
1594
+ function timingStart(marker) {
1595
+ console.time(marker);
1596
+ }
1597
+ function timingEnd(marker) {
1598
+ console.timeEnd(marker);
1599
+ }
1600
+
1601
+ // src/util/three.ts
1602
+ function isObject3D(obj) {
1603
+ return obj.isObject3D;
1604
+ }
1605
+ function isMesh(obj) {
1606
+ return obj.isMesh;
1607
+ }
1608
+ function isInstancedMesh(obj) {
1609
+ return obj.isInstancedMesh;
1610
+ }
1611
+ function isPerspectiveCamera(obj) {
1612
+ return obj.isPerspectiveCamera;
1613
+ }
1614
+ function isOrthographicCamera(obj) {
1615
+ return obj.isOrthographicCamera;
1616
+ }
1617
+
1377
1618
  // src/nbt/NBTHelper.ts
1378
1619
  var NBTHelper = class {
1379
1620
  static async fromBuffer(buffer) {
@@ -1402,17 +1643,17 @@ var _HostedAssetSource = class _HostedAssetSource extends AssetSource {
1402
1643
  if (typeof namespaced !== "undefined") {
1403
1644
  return namespaced;
1404
1645
  }
1405
- if (typeof key.root !== "undefined" && key.root !== DEFAULT_ROOT || typeof this.root !== "undefined" && this.root !== DEFAULT_ROOT) {
1646
+ if (typeof key.root !== "undefined" && key.root !== AssetLoader.ROOT || typeof this.root !== "undefined" && this.root !== AssetLoader.ROOT) {
1406
1647
  console.info(p3, "Retrying", key, "with default root+namespace");
1407
- const namespacedRootedKey = new AssetKey(DEFAULT_NAMESPACE, key.path, key.assetType, key.type, key.rootType, key.extension, DEFAULT_ROOT);
1648
+ const namespacedRootedKey = new AssetKey(DEFAULT_NAMESPACE, key.path, key.assetType, key.type, key.rootType, key.extension, AssetLoader.ROOT);
1408
1649
  const namespacedRooted = await this.load(namespacedRootedKey, parser);
1409
1650
  if (typeof namespacedRooted !== "undefined") {
1410
1651
  return namespacedRooted;
1411
1652
  }
1412
1653
  }
1413
- } else if (typeof key.root !== "undefined" && key.root !== DEFAULT_ROOT || typeof this.root !== "undefined" && this.root !== DEFAULT_ROOT) {
1654
+ } else if (typeof key.root !== "undefined" && key.root !== AssetLoader.ROOT || typeof this.root !== "undefined" && this.root !== AssetLoader.ROOT) {
1414
1655
  console.info(p3, "Retrying", key, "with default root");
1415
- const rootKey = new AssetKey(key.namespace, key.path, key.assetType, key.type, key.rootType, key.extension, DEFAULT_ROOT);
1656
+ const rootKey = new AssetKey(key.namespace, key.path, key.assetType, key.type, key.rootType, key.extension, AssetLoader.ROOT);
1416
1657
  const rooted = await this.load(rootKey, parser);
1417
1658
  if (typeof rooted !== "undefined") {
1418
1659
  return rooted;
@@ -1424,31 +1665,25 @@ var _HostedAssetSource = class _HostedAssetSource extends AssetSource {
1424
1665
  console.info(p3, "Loading", key);
1425
1666
  const url = `${this.assetBasePath(key)}${key.type !== void 0 ? key.type + "/" : ""}${key.path}${key.extension}`;
1426
1667
  console.debug(p3, url);
1427
- let req = {
1428
- url
1429
- };
1430
- parser.config(req);
1431
- return await Requests.mcAssetRequest(req).then((response) => {
1432
- if (response && response.data) {
1433
- try {
1434
- return parser.parse(response);
1435
- } catch (e) {
1436
- console.warn("Parser failed to process response", response, e);
1437
- throw e;
1438
- }
1439
- }
1440
- return void 0;
1441
- }).catch((err) => {
1442
- if (err.response) {
1443
- let response = err.response;
1444
- if (response.status === 404) {
1445
- console.debug(p3, key, "not found");
1668
+ const request = { url };
1669
+ try {
1670
+ parser.config(request);
1671
+ let response;
1672
+ try {
1673
+ response = await Requests.mcAssetRequest(request);
1674
+ } catch (cause) {
1675
+ if (cause instanceof RequestError && cause.response?.status === 404) {
1446
1676
  return void 0;
1447
1677
  }
1678
+ throw cause;
1448
1679
  }
1449
- console.debug(p3, "Failed to load", key, err?.message);
1450
- return void 0;
1451
- });
1680
+ if (response.data === void 0) {
1681
+ throw new Error("Asset response has no data");
1682
+ }
1683
+ return await parser.parse(response);
1684
+ } catch (cause) {
1685
+ throw new AssetLoadError(this, key, request.url, cause);
1686
+ }
1452
1687
  }
1453
1688
  assetBasePath(key) {
1454
1689
  return `${key.root ?? this.root ?? AssetLoader.ROOT}/${key.rootType !== void 0 ? key.rootType + "/" : ""}${key.namespace !== void 0 ? key.namespace + "/" : ""}${key.assetType !== void 0 ? key.assetType + "/" : ""}`;
@@ -1461,6 +1696,9 @@ var _HostedAssetSource = class _HostedAssetSource extends AssetSource {
1461
1696
  get root() {
1462
1697
  return this._root;
1463
1698
  }
1699
+ get cacheId() {
1700
+ return `hosted:${this._root}`;
1701
+ }
1464
1702
  get options() {
1465
1703
  return this._options;
1466
1704
  }
@@ -1537,9 +1775,10 @@ var HostedAssetSource = _HostedAssetSource;
1537
1775
  // src/assets/source/archive/BrowserArchiveProxy.ts
1538
1776
  import { BlobReader, BlobWriter, ZipReader } from "@zip.js/zip.js";
1539
1777
  var BrowserArchiveProxy = class {
1540
- constructor(blob) {
1778
+ constructor(blob, id) {
1541
1779
  this._blob = blob;
1542
1780
  this._reader = new ZipReader(new BlobReader(this._blob));
1781
+ this.id = id ?? (typeof File !== "undefined" && blob instanceof File ? `${blob.name}:${blob.size}:${blob.lastModified}` : void 0);
1543
1782
  }
1544
1783
  async getEntries() {
1545
1784
  return (await this._reader.getEntries()).map((e) => {
@@ -1555,7 +1794,6 @@ var BrowserArchiveProxy = class {
1555
1794
  };
1556
1795
 
1557
1796
  // src/assets/source/archive/ArchiveAssetSource.ts
1558
- var p4 = prefix("ArchiveAssetSource");
1559
1797
  var ArchiveAssetSource = class _ArchiveAssetSource extends AssetSource {
1560
1798
  constructor(archiveProxy) {
1561
1799
  super();
@@ -1565,6 +1803,10 @@ var ArchiveAssetSource = class _ArchiveAssetSource extends AssetSource {
1565
1803
  static blob(blob) {
1566
1804
  return new _ArchiveAssetSource(new BrowserArchiveProxy(blob));
1567
1805
  }
1806
+ get cacheId() {
1807
+ const id = this._archiveProxy.id;
1808
+ return id === void 0 ? void 0 : `archive:${id}`;
1809
+ }
1568
1810
  async getEntries() {
1569
1811
  return this._archiveProxy.getEntries();
1570
1812
  }
@@ -1574,74 +1816,31 @@ var ArchiveAssetSource = class _ArchiveAssetSource extends AssetSource {
1574
1816
  }
1575
1817
  async get(key, parser) {
1576
1818
  const responseParser = HostedAssetSource.PARSER_MAP.get(parser);
1577
- console.log("ArchiveAssetSource.get", key, parser);
1578
- let entries = await this.getEntries();
1579
- const path = `${this.assetBasePath(key)}${key.type !== void 0 ? key.type + "/" : ""}${key.path}${key.extension}`;
1580
- console.log(path);
1581
- const entry = await this.getEntry(path);
1582
- console.log(entry);
1583
- if (!entry) {
1584
- return void 0;
1585
- }
1586
- let blob = await entry.getData();
1587
- const url = URL.createObjectURL(blob);
1588
- console.log(url);
1589
- let req = {
1590
- url
1591
- };
1592
- responseParser.config(req);
1593
- return Requests.genericRequest(req).then((response) => {
1594
- if (response && response.data) {
1595
- try {
1596
- return responseParser.parse(response);
1597
- } catch (e) {
1598
- console.warn("Parser failed to process response", response, e);
1599
- throw e;
1600
- }
1601
- }
1602
- return void 0;
1603
- }).catch((err) => {
1604
- if (err.response) {
1605
- let response = err.response;
1606
- if (response.status === 404) {
1607
- console.debug(p4, key, "not found");
1608
- return void 0;
1609
- }
1610
- }
1611
- console.debug(p4, "Failed to load", key, err?.message);
1612
- return void 0;
1613
- });
1819
+ return this.load(key, responseParser);
1614
1820
  }
1615
- //TODO: abstract this
1616
1821
  async load(key, parser) {
1617
- console.info(p4, "Loading", key);
1618
- const url = `${this.assetBasePath(key)}${key.type !== void 0 ? key.type + "/" : ""}${key.path}${key.extension}`;
1619
- console.debug(p4, url);
1620
- let req = {
1621
- url
1622
- };
1623
- parser.config(req);
1624
- return await Requests.genericRequest(req).then((response) => {
1625
- if (response && response.data) {
1626
- try {
1627
- return parser.parse(response);
1628
- } catch (e) {
1629
- console.warn("Parser failed to process response", response, e);
1630
- throw e;
1631
- }
1822
+ const path = `${this.assetBasePath(key)}${key.type !== void 0 ? key.type + "/" : ""}${key.path}${key.extension}`;
1823
+ let url;
1824
+ try {
1825
+ const entry = await this.getEntry(path);
1826
+ if (!entry) {
1827
+ return void 0;
1632
1828
  }
1633
- return void 0;
1634
- }).catch((err) => {
1635
- if (err.response) {
1636
- let response = err.response;
1637
- if (response.status === 404) {
1638
- console.debug(p4, key, "not found");
1639
- return void 0;
1640
- }
1829
+ url = URL.createObjectURL(await entry.getData());
1830
+ const request = { url };
1831
+ parser.config(request);
1832
+ const response = await Requests.genericRequest(request);
1833
+ if (response.data === void 0) {
1834
+ throw new Error("Asset response has no data");
1641
1835
  }
1642
- console.debug(p4, "Failed to load", key, err?.message);
1643
- return void 0;
1644
- });
1836
+ return await parser.parse(response);
1837
+ } catch (cause) {
1838
+ throw new AssetLoadError(this, key, path, cause);
1839
+ } finally {
1840
+ if (url !== void 0) {
1841
+ URL.revokeObjectURL(url);
1842
+ }
1843
+ }
1645
1844
  }
1646
1845
  assetBasePath(key) {
1647
1846
  return `${key.rootType !== void 0 ? key.rootType + "/" : ""}${key.namespace !== void 0 ? key.namespace + "/" : ""}${key.assetType !== void 0 ? key.assetType + "/" : ""}`;
@@ -1649,18 +1848,54 @@ var ArchiveAssetSource = class _ArchiveAssetSource extends AssetSource {
1649
1848
  };
1650
1849
 
1651
1850
  // src/assets/AssetLoader.ts
1652
- var import_ts_deepmerge3 = __toESM(require_dist());
1653
- var p5 = prefix("AssetLoader");
1851
+ var p4 = prefix("AssetLoader");
1852
+ var FALLBACK_ROOT = "https://raw.githubusercontent.com/InventivetalentDev/minerender-fallback-assets/master";
1654
1853
  var _AssetLoader = class _AssetLoader {
1854
+ static get version() {
1855
+ return this.ROOT.substring(this.ROOT.lastIndexOf("/") + 1);
1856
+ }
1857
+ static setVersion(version) {
1858
+ this.ROOT = `https://assets.mcasset.cloud/${version}`;
1859
+ const source = new HostedAssetSource(this.ROOT, { retryDefaults: false });
1860
+ const index = this._SOURCES.findIndex((s) => s.key === "mcassets");
1861
+ if (index !== -1) {
1862
+ this._SOURCES[index] = { key: "mcassets", source };
1863
+ } else {
1864
+ this.addSource("mcassets", source);
1865
+ }
1866
+ Caching.clear();
1867
+ }
1868
+ /**
1869
+ * Scope for persistent cache keys. Empty with only the default vanilla sources, so their
1870
+ * entries stay valid across sessions; otherwise it names every added source (resource packs,
1871
+ * mirrors) so their results never masquerade as vanilla assets after a reload.
1872
+ */
1873
+ static get persistentScope() {
1874
+ const parts = [];
1875
+ for (const { key, source } of this._SOURCES) {
1876
+ const id = source.cacheId;
1877
+ if (key === "mcassets" && id === `hosted:${this.ROOT}`) continue;
1878
+ if (key === "mcassets-fallback" && id === `hosted:${FALLBACK_ROOT}`) continue;
1879
+ if (id === void 0) return this.SESSION_SCOPE;
1880
+ parts.push(`${key}=${id}`);
1881
+ }
1882
+ return parts.join("|");
1883
+ }
1884
+ /** Prefixes a persistent cache key with the current source scope. */
1885
+ static persistentKey(key) {
1886
+ const scope = this.persistentScope;
1887
+ return scope ? `${scope}
1888
+ ${key}` : key;
1889
+ }
1655
1890
  static addSource(key, source, override = true) {
1656
1891
  if (override) {
1657
1892
  const existing = this.removeSource(key);
1658
1893
  if (existing) {
1659
- console.log(p5, "Removed existing AssetSource", key);
1894
+ console.log(p4, "Removed existing AssetSource", key);
1660
1895
  }
1661
1896
  }
1662
1897
  this._SOURCES.unshift({ key, source });
1663
- console.log(p5, "Added AssetSource", key);
1898
+ console.log(p4, "Added AssetSource", key);
1664
1899
  }
1665
1900
  static removeSource(key) {
1666
1901
  const index = this._SOURCES.findIndex((s) => s.key === key);
@@ -1673,7 +1908,6 @@ var _AssetLoader = class _AssetLoader {
1673
1908
  return void 0;
1674
1909
  }
1675
1910
  static async getAll(key, parser) {
1676
- console.log(this._SOURCES);
1677
1911
  let promises = [];
1678
1912
  for (const source of this._SOURCES) {
1679
1913
  promises.push(source.source.get(key, parser));
@@ -1682,30 +1916,42 @@ var _AssetLoader = class _AssetLoader {
1682
1916
  return results.filter((r) => r != void 0);
1683
1917
  });
1684
1918
  }
1919
+ /** Returns the first defined result in source-priority order, without merging assets. */
1685
1920
  static async get(key, parser) {
1686
- console.log(this._SOURCES);
1687
- let promises = [];
1688
- for (const source of this._SOURCES) {
1689
- promises.push(source.source.get(key, parser));
1690
- }
1691
- return Promise.all(promises).then((results) => {
1692
- const fallback = results[results.length - 1];
1693
- for (const result of results) {
1694
- if (result) {
1695
- if (fallback) {
1696
- return (0, import_ts_deepmerge3.default)({}, fallback, result);
1697
- }
1698
- return result;
1921
+ const keys = [key];
1922
+ if (key.rootType === "data" && (key.assetType === "structure" || key.assetType === "structures")) {
1923
+ keys.push(new AssetKey(
1924
+ key.namespace,
1925
+ key.path,
1926
+ key.assetType === "structure" ? "structures" : "structure",
1927
+ key.type,
1928
+ key.rootType,
1929
+ key.extension,
1930
+ key.root
1931
+ ));
1932
+ }
1933
+ return (await this.getFirst(keys, parser))?.asset;
1934
+ }
1935
+ /** Tries each path within a source before considering lower-priority sources. */
1936
+ static async getFirst(keys, parser) {
1937
+ const sources = [...this._SOURCES];
1938
+ for (const source of sources) {
1939
+ for (const key of keys) {
1940
+ const result = await source.source.get(key, parser);
1941
+ if (result !== void 0) {
1942
+ return { key, asset: result };
1699
1943
  }
1700
1944
  }
1701
- return void 0;
1702
- });
1945
+ }
1946
+ return void 0;
1703
1947
  }
1704
1948
  };
1705
1949
  _AssetLoader.ROOT = DEFAULT_ROOT;
1706
1950
  _AssetLoader._SOURCES = [];
1707
- _AssetLoader.addSource("mcassets-fallback", new HostedAssetSource("https://raw.githubusercontent.com/InventivetalentDev/minerender-fallback-assets/master", { retryDefaults: false }));
1708
- _AssetLoader.addSource("mcassets", new HostedAssetSource(DEFAULT_ROOT, { retryDefaults: false }));
1951
+ /** Used when a registered source cannot identify its content, so nothing persists past this session. */
1952
+ _AssetLoader.SESSION_SCOPE = `session:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
1953
+ _AssetLoader.addSource("mcassets-fallback", new HostedAssetSource(FALLBACK_ROOT, { retryDefaults: false }));
1954
+ _AssetLoader.addSource("mcassets", new HostedAssetSource(_AssetLoader.ROOT, { retryDefaults: false }));
1709
1955
  var AssetLoader = _AssetLoader;
1710
1956
 
1711
1957
  // src/assets/ModelTextures.ts
@@ -1714,11 +1960,21 @@ var ModelTextures = class {
1714
1960
  return this._persistentMetaCache ?? (this._persistentMetaCache = PersistentCache.open("minerender-texturemeta"));
1715
1961
  }
1716
1962
  static async get(key) {
1717
- const asset = await this.preload(key);
1718
- if (asset) {
1719
- return ImageLoader.infoToCanvasData(asset);
1963
+ const keyStr = key.serialize();
1964
+ const pending = this.preload(key);
1965
+ const cached = Caching.textureAssetCache.getIfPresent(keyStr);
1966
+ try {
1967
+ const asset = await pending;
1968
+ if (asset) {
1969
+ return await ImageLoader.infoToCanvasData(asset);
1970
+ }
1971
+ return void 0;
1972
+ } catch (err) {
1973
+ if (cached && Caching.textureAssetCache.getIfPresent(keyStr) === cached) {
1974
+ Caching.textureAssetCache.invalidate(keyStr);
1975
+ }
1976
+ throw err;
1720
1977
  }
1721
- return void 0;
1722
1978
  }
1723
1979
  static async preload(key) {
1724
1980
  const keyStr = key.serialize();
@@ -1737,7 +1993,7 @@ var ModelTextures = class {
1737
1993
  }
1738
1994
  const keyStr = key.serialize();
1739
1995
  return Caching.textureMetaCache.get(keyStr, (k) => {
1740
- return this.PERSISTENT_META_CACHE.getOrLoad(keyStr, (k1) => {
1996
+ return this.PERSISTENT_META_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
1741
1997
  return AssetLoader.get(key, "meta" /* META */).then((asset) => {
1742
1998
  if (asset)
1743
1999
  asset.key = key;
@@ -1888,17 +2144,20 @@ var TextureAtlas = class {
1888
2144
  // src/model/ModelGenerator.ts
1889
2145
  var ModelGenerator = class {
1890
2146
  static generateItemModel(imageData, layer) {
2147
+ const tintindex = this.ITEM_LAYERS.indexOf(layer);
1891
2148
  const mainElement = {
1892
2149
  from: [0, 0, 7.5],
1893
2150
  to: [16, 16, 8.5],
1894
2151
  faces: {
1895
2152
  "south": {
1896
2153
  uv: [0, 0, 16, 16],
1897
- texture: `#${layer}`
2154
+ texture: `#${layer}`,
2155
+ tintindex
1898
2156
  },
1899
2157
  "north": {
1900
2158
  uv: [16, 0, 0, 16],
1901
- texture: `#${layer}`
2159
+ texture: `#${layer}`,
2160
+ tintindex
1902
2161
  }
1903
2162
  },
1904
2163
  shade: true
@@ -1909,11 +2168,19 @@ var ModelGenerator = class {
1909
2168
  ModelGenerator.ITEM_LAYERS = ["layer0", "layer1", "layer2", "layer3", "layer4"];
1910
2169
 
1911
2170
  // src/UVMapper.ts
1912
- var p6 = prefix("UVMapper");
2171
+ var p5 = prefix("UVMapper");
1913
2172
  var DEFAULT_UV = [0, 0, 16, 16];
1914
2173
  var X = 0;
1915
2174
  var Y = 1;
1916
2175
  var Z = 2;
2176
+ var FACE_UV_AXES = {
2177
+ east: [new Vector34(0, 0, -1), new Vector34(0, -1, 0)],
2178
+ west: [new Vector34(0, 0, 1), new Vector34(0, -1, 0)],
2179
+ up: [new Vector34(1, 0, 0), new Vector34(0, 0, 1)],
2180
+ down: [new Vector34(1, 0, 0), new Vector34(0, 0, -1)],
2181
+ south: [new Vector34(1, 0, 0), new Vector34(0, -1, 0)],
2182
+ north: [new Vector34(-1, 0, 0), new Vector34(0, -1, 0)]
2183
+ };
1917
2184
  var UVMapper = class {
1918
2185
  static makeUv(uvCoord, originalTextureSize, actualTextureSize) {
1919
2186
  if (uvCoord === 0) return 0;
@@ -2011,6 +2278,42 @@ var UVMapper = class {
2011
2278
  static facesToUv(faces, originalTextureSize, actualTextureSize) {
2012
2279
  return new Float32BufferAttribute2(this.facesToUvArray(faces, originalTextureSize, actualTextureSize), 2);
2013
2280
  }
2281
+ static lockUvs(geometry, faces, atlas, rotation) {
2282
+ const uv = geometry.getAttribute("uv");
2283
+ for (let faceIndex = 0; faceIndex < CUBE_FACES.length; faceIndex++) {
2284
+ const faceName = CUBE_FACES[faceIndex];
2285
+ const texture = faces[faceName]?.texture?.substring(1);
2286
+ const position = texture && atlas.positions[texture];
2287
+ const size = texture && atlas.sizes[texture];
2288
+ if (!position || !size) continue;
2289
+ const [sourceU, sourceV] = FACE_UV_AXES[faceName];
2290
+ const uAxis = sourceU.clone().applyEuler(rotation);
2291
+ const vAxis = sourceV.clone().applyEuler(rotation);
2292
+ const normal = vAxis.clone().cross(uAxis);
2293
+ const targetFace = CUBE_FACES.find((name) => {
2294
+ const [u, v] = FACE_UV_AXES[name];
2295
+ return v.clone().cross(u).dot(normal) > 0.5;
2296
+ });
2297
+ const [targetU, targetV] = FACE_UV_AXES[targetFace];
2298
+ const uu = Math.round(targetU.dot(uAxis));
2299
+ const uvCross = Math.round(targetU.dot(vAxis));
2300
+ const vu = Math.round(targetV.dot(uAxis));
2301
+ const vv = Math.round(targetV.dot(vAxis));
2302
+ for (let corner = 0; corner < 4; corner++) {
2303
+ const index = faceIndex * 4 + corner;
2304
+ const u = (uv.getX(index) * atlas.image.width - position[0]) / size[0] - 0.5;
2305
+ const v = ((1 - uv.getY(index)) * atlas.image.height - position[1]) / size[1] - 0.5;
2306
+ const lockedU = uu * u + uvCross * v + 0.5;
2307
+ const lockedV = vu * u + vv * v + 0.5;
2308
+ uv.setXY(
2309
+ index,
2310
+ (position[0] + lockedU * size[0]) / atlas.image.width,
2311
+ 1 - (position[1] + lockedV * size[1]) / atlas.image.height
2312
+ );
2313
+ }
2314
+ }
2315
+ uv.needsUpdate = true;
2316
+ }
2014
2317
  static async getAtlas(model) {
2015
2318
  const keyStr = model.key.serialize();
2016
2319
  return Caching.modelTextureAtlasCache.get(keyStr, (k) => {
@@ -2086,7 +2389,7 @@ var UVMapper = class {
2086
2389
  const metaMap = {};
2087
2390
  const model = { ...originalModel };
2088
2391
  const isItemModel = !("elements" in model);
2089
- console.debug(p6, "Creating Atlas for", model.key);
2392
+ console.debug(p5, "Creating Atlas for", model.key);
2090
2393
  if (model.textures) {
2091
2394
  const promises = [];
2092
2395
  const uniqueTextureNames = [];
@@ -2100,7 +2403,7 @@ var UVMapper = class {
2100
2403
  const assetKey = AssetKey.parse("textures", textureValue, model.key);
2101
2404
  promises.push(ModelTextures.get(assetKey).then((asset) => {
2102
2405
  if (!asset) {
2103
- console.warn(p6, "Missing texture", assetKey);
2406
+ console.warn(p5, "Missing texture", assetKey);
2104
2407
  return;
2105
2408
  }
2106
2409
  textureMap[textureKey] = new WrappedImage(asset);
@@ -2112,7 +2415,7 @@ var UVMapper = class {
2112
2415
  }
2113
2416
  await Promise.all(promises);
2114
2417
  const textureCount = uniqueTextureNames.length;
2115
- console.debug(p6, "Creating Atlas for", textureCount, "textures");
2418
+ console.debug(p5, "Creating Atlas for", textureCount, "textures");
2116
2419
  this.fillMissingTextureKeys(model.textures, textureMap);
2117
2420
  this.fillMissingTextureKeys(model.textures, metaMap);
2118
2421
  const sizes = {};
@@ -2132,7 +2435,7 @@ var UVMapper = class {
2132
2435
  this.fillMissingTextureKeys(model.textures, sizes);
2133
2436
  const s = Math.ceil(Math.sqrt(textureCount + 1));
2134
2437
  const size = Math.ceil(s * maxWidth);
2135
- console.debug(p6, "Atlas size:", size);
2438
+ console.debug(p5, "Atlas size:", size);
2136
2439
  const squaredSize = size * size;
2137
2440
  const missing = await ImageLoader.getData("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABAAQMAAACQp+OdAAAABlBMVEX/AP8AAACfphTyAAAAFUlEQVQoz2MIhQKGVVAwKjIqQrwIAHRz/wFI17TEAAAAAElFTkSuQmCC");
2138
2441
  const empty = await ImageLoader.getData("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABAAQMAAACQp+OdAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAA5JREFUKM9jGAWjgAIAAAJAAAFSSwuTAAAAAElFTkSuQmCC");
@@ -2187,10 +2490,10 @@ var UVMapper = class {
2187
2490
  }
2188
2491
  }
2189
2492
  const atlasImageData = image.toDataURL();
2190
- console.log(p6, "Atlas Image for", model.key, atlasImageData);
2493
+ console.log(p5, "Atlas Image for", model.key, atlasImageData);
2191
2494
  this.fillMissingTextureKeys(model.textures, positions);
2192
2495
  if (isItemModel) {
2193
- console.debug(p6, "Generating item model for", model);
2496
+ console.debug(p5, "Generating item model for", model);
2194
2497
  model.elements = [];
2195
2498
  for (let layerName of ModelGenerator.ITEM_LAYERS) {
2196
2499
  const textureImage = textureMap[layerName];
@@ -2198,7 +2501,7 @@ var UVMapper = class {
2198
2501
  model.elements.push(...ModelGenerator.generateItemModel(textureImage.data, layerName));
2199
2502
  }
2200
2503
  }
2201
- console.debug(p6, "Item model elements", model.elements);
2504
+ console.debug(p5, "Item model elements", model.elements);
2202
2505
  }
2203
2506
  if (!model.elements) {
2204
2507
  }
@@ -2216,17 +2519,13 @@ var UVMapper = class {
2216
2519
  this.setCubeFaceUvInArray(uv, faceIndex * 4, [0, 1, 8 / size, (size - 8) / size]);
2217
2520
  continue;
2218
2521
  }
2522
+ if (!face.texture) continue;
2219
2523
  if (!face.uv) {
2220
2524
  face.uv = this.getFallbackUv(element, faceName);
2221
2525
  }
2222
- let faceTexture = face.texture;
2223
- let texPosition;
2224
- let texSize;
2225
- if (faceTexture) {
2226
- faceTexture = faceTexture.substr(1);
2227
- texPosition = positions[faceTexture] || [0, 0];
2228
- texSize = sizes[faceTexture] || [16, 16];
2229
- }
2526
+ const faceTexture = face.texture.substr(1);
2527
+ const texPosition = positions[faceTexture] || [0, 0];
2528
+ const texSize = sizes[faceTexture] || [16, 16];
2230
2529
  let texPosV = new Vector2(texPosition[0], texPosition[1]);
2231
2530
  const [tl, tr, bl, br] = this.expandAndRotateMcUvs(face.uv, face.rotation, texSize[0], texSize[1]);
2232
2531
  tl.add(texPosV);
@@ -2258,7 +2557,7 @@ var UVMapper = class {
2258
2557
  hasTransparency
2259
2558
  );
2260
2559
  } else {
2261
- console.warn(p6, "Model does not have any textures", model);
2560
+ console.warn(p5, "Model does not have any textures", model);
2262
2561
  }
2263
2562
  return void 0;
2264
2563
  }
@@ -2300,18 +2599,18 @@ var MinecraftCubeTexture = class {
2300
2599
  }
2301
2600
  getUvEast(w, h, l) {
2302
2601
  return [
2303
- [this.mapW(this.u), this.mapH(this.v + l + h)],
2304
- [this.mapW(this.u + l), this.mapH(this.v + l + h)],
2305
- [this.mapW(this.u), this.mapH(this.v + l)],
2306
- [this.mapW(this.u + l), this.mapH(this.v + l)]
2602
+ [this.mapW(this.u + l + w + l), this.mapH(this.v + l + h)],
2603
+ [this.mapW(this.u + l + w), this.mapH(this.v + l + h)],
2604
+ [this.mapW(this.u + l + w + l), this.mapH(this.v + l)],
2605
+ [this.mapW(this.u + l + w), this.mapH(this.v + l)]
2307
2606
  ];
2308
2607
  }
2309
2608
  getUvWest(w, h, l) {
2310
2609
  return [
2311
- [this.mapW(this.u + l + w), this.mapH(this.v + l + h)],
2312
- [this.mapW(this.u + l + w + l), this.mapH(this.v + l + h)],
2313
- [this.mapW(this.u + l + w), this.mapH(this.v + l)],
2314
- [this.mapW(this.u + l + w + l), this.mapH(this.v + l)]
2610
+ [this.mapW(this.u + l), this.mapH(this.v + l + h)],
2611
+ [this.mapW(this.u), this.mapH(this.v + l + h)],
2612
+ [this.mapW(this.u + l), this.mapH(this.v + l)],
2613
+ [this.mapW(this.u), this.mapH(this.v + l)]
2315
2614
  ];
2316
2615
  }
2317
2616
  getUvUp(w, h, l) {
@@ -2324,26 +2623,26 @@ var MinecraftCubeTexture = class {
2324
2623
  }
2325
2624
  getUvDown(w, h, l) {
2326
2625
  return [
2327
- [this.mapW(this.u + l), this.mapH(this.v + l)],
2328
- [this.mapW(this.u + l + w), this.mapH(this.v + l)],
2329
2626
  [this.mapW(this.u + l), this.mapH(this.v)],
2330
- [this.mapW(this.u + l + w), this.mapH(this.v)]
2627
+ [this.mapW(this.u + l + w), this.mapH(this.v)],
2628
+ [this.mapW(this.u + l), this.mapH(this.v + l)],
2629
+ [this.mapW(this.u + l + w), this.mapH(this.v + l)]
2331
2630
  ];
2332
2631
  }
2333
2632
  getUvSouth(w, h, l) {
2334
2633
  return [
2335
- [this.mapW(this.u + l + w + l), this.mapH(this.v + l + h)],
2336
2634
  [this.mapW(this.u + l + w + l + w), this.mapH(this.v + l + h)],
2337
- [this.mapW(this.u + l + w + l), this.mapH(this.v + l)],
2338
- [this.mapW(this.u + l + w + l + w), this.mapH(this.v + l)]
2635
+ [this.mapW(this.u + l + w + l), this.mapH(this.v + l + h)],
2636
+ [this.mapW(this.u + l + w + l + w), this.mapH(this.v + l)],
2637
+ [this.mapW(this.u + l + w + l), this.mapH(this.v + l)]
2339
2638
  ];
2340
2639
  }
2341
2640
  getUvNorth(w, h, l) {
2342
2641
  return [
2343
- [this.mapW(this.u + l), this.mapH(this.v + l + h)],
2344
2642
  [this.mapW(this.u + l + w), this.mapH(this.v + l + h)],
2345
- [this.mapW(this.u + l), this.mapH(this.v + l)],
2346
- [this.mapW(this.u + l + w), this.mapH(this.v + l)]
2643
+ [this.mapW(this.u + l), this.mapH(this.v + l + h)],
2644
+ [this.mapW(this.u + l + w), this.mapH(this.v + l)],
2645
+ [this.mapW(this.u + l), this.mapH(this.v + l)]
2347
2646
  ];
2348
2647
  }
2349
2648
  getFaceUv(face, w, h, l) {
@@ -2362,10 +2661,12 @@ var MinecraftCubeTexture = class {
2362
2661
  return this.getUvNorth(w, h, l);
2363
2662
  }
2364
2663
  }
2365
- toUvArray(w, h, l) {
2664
+ toUvArray(w, h, l, mirror = false) {
2366
2665
  const uv = [];
2367
2666
  for (let i = 0; i < CUBE_FACES.length; i++) {
2368
- UVMapper.setFaceUvInArray(uv, i * 4, this.getFaceUv(CUBE_FACES[i], w, h, l));
2667
+ const face = mirror && i < 2 ? CUBE_FACES[1 - i] : CUBE_FACES[i];
2668
+ const corners = this.getFaceUv(face, w, h, l);
2669
+ UVMapper.setFaceUvInArray(uv, i * 4, mirror ? [corners[1], corners[0], corners[3], corners[2]] : corners);
2369
2670
  }
2370
2671
  return uv;
2371
2672
  }
@@ -2395,76 +2696,85 @@ function shutdown() {
2395
2696
  Caching.end();
2396
2697
  }
2397
2698
 
2699
+ // src/error/MineRenderError.ts
2700
+ var MineRenderError = class _MineRenderError extends Error {
2701
+ constructor(msg) {
2702
+ super(msg);
2703
+ Object.setPrototypeOf(this, _MineRenderError.prototype);
2704
+ }
2705
+ get name() {
2706
+ return "MineRenderError";
2707
+ }
2708
+ };
2709
+
2398
2710
  // src/assets/Entities.ts
2399
- import { Memoize as Memoize3 } from "typescript-memoize";
2400
2711
  var Entities = class {
2401
- static async getBlockEntityModels() {
2402
- const key = AssetKey.parse("models", "minerender:blockEntityModels");
2403
- console.log(key);
2404
- return AssetLoader.get(key, "json" /* JSON */);
2405
- }
2406
- static async getEntityModels() {
2407
- const key = AssetKey.parse("models", "minerender:entityModels");
2408
- console.log(key);
2409
- return AssetLoader.get(key, "json" /* JSON */);
2410
- }
2411
- // BlockEntity names are hardcoded
2412
- static async getBlockList() {
2413
- const models = await this.getBlockEntityModels();
2414
- if (!models) {
2415
- return [];
2416
- }
2417
- return Object.keys(models);
2418
- }
2419
2712
  static async getEntityList() {
2420
- const models = await this.getEntityModels();
2421
- if (!models) {
2422
- return [];
2423
- }
2424
- return Object.keys(models);
2425
- }
2426
- static async getBlock(modelKey, textureKey) {
2427
- const models = await this.getBlockEntityModels();
2428
- if (!models) {
2429
- return void 0;
2430
- }
2431
- if (!textureKey) {
2432
- textureKey = modelKey;
2433
- }
2434
- const baseKey = modelKey.path.includes("/") ? new BasicAssetKey(modelKey.namespace, modelKey.path.split("/")[0]) : modelKey;
2435
- return {
2436
- key: textureKey,
2437
- parts: models[baseKey.toNamespacedString()]
2713
+ const collect = async (path) => {
2714
+ const prefix2 = path ? `${path}/` : "";
2715
+ const key = new AssetKey(DEFAULT_NAMESPACE, `${prefix2}_list`, void 0, void 0, "entity-models", ".json");
2716
+ const list = await Caching.listAssetCache.get(key.serialize(), () => AssetLoader.get(key, "list" /* LIST */));
2717
+ if (!list) return [];
2718
+ const files = list.files.filter((file) => file.endsWith(".json") && file !== "_list.json").map((file) => `${prefix2}${file.slice(0, -5)}`);
2719
+ const children = await Promise.all(list.directories.map((directory) => collect(`${prefix2}${directory}`)));
2720
+ return [...files, ...children.flat()];
2438
2721
  };
2722
+ return collect("");
2723
+ }
2724
+ static async getEntity(modelKey, textureKey, options) {
2725
+ const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
2726
+ const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models", ".json");
2727
+ const model = await Caching.entityModelCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
2728
+ if (!model) return void 0;
2729
+ const layerName = options?.layer ?? "main";
2730
+ const layer = model.layers[layerName];
2731
+ if (!layer) {
2732
+ throw new MineRenderError(`Entity ${model.id} has no layer "${layerName}". Available layers: ${Object.keys(model.layers).join(", ")}`);
2733
+ }
2734
+ let texture;
2735
+ if (textureKey) {
2736
+ const path2 = isAssetKey(textureKey) ? textureKey.getFullPath() : textureKey.path;
2737
+ texture = isAssetKey(textureKey) && textureKey.assetType === "textures" && path2.startsWith("entity/") ? textureKey : new AssetKey(textureKey.namespace, path2, "textures", "entity", "assets", ".png", isAssetKey(textureKey) ? textureKey.root : void 0);
2738
+ } else if (layer.textureLocation !== void 0) {
2739
+ texture = AssetKey.parse("textures", layer.textureLocation.replace(/^([^:]+:)?textures\//, "$1"));
2740
+ } else {
2741
+ texture = await this.resolveTexture(modelKey);
2742
+ }
2743
+ return { key: textureKey ?? modelKey, texture, layer, id: model.id };
2744
+ }
2745
+ static async resolveTexture(modelKey) {
2746
+ const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
2747
+ const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models", ".json");
2748
+ return Caching.entityTextureCache.get(key.serialize(), async () => {
2749
+ const name = path.split("/").pop();
2750
+ for (const candidate of [path, `${path}/${name}`]) {
2751
+ const texture = new AssetKey(modelKey.namespace, candidate, "textures", "entity", "assets", ".png");
2752
+ if (await ModelTextures.get(texture)) return texture;
2753
+ }
2754
+ const listKey = new AssetKey(modelKey.namespace, "_list", `${name}_variant`, void 0, "data", ".json");
2755
+ const list = await AssetLoader.get(listKey, "list" /* LIST */);
2756
+ const files = list?.files.filter((file2) => file2.endsWith(".json") && file2 !== "_list.json");
2757
+ const file = files?.find((file2) => file2 === "temperate.json") ?? files?.[0];
2758
+ if (!file) return void 0;
2759
+ const variantKey = new AssetKey(modelKey.namespace, file.slice(0, -5), `${name}_variant`, void 0, "data", ".json");
2760
+ const variant = await AssetLoader.get(variantKey, "json" /* JSON */);
2761
+ const assets = variant?.assets;
2762
+ const textureId = variant?.asset_id ?? (typeof assets?.wild === "string" ? assets.wild : Object.values(assets ?? {}).find((value) => typeof value === "string"));
2763
+ return typeof textureId === "string" ? AssetKey.parse("textures", textureId) : void 0;
2764
+ });
2439
2765
  }
2440
- static async getEntity(modelKey, textureKey) {
2441
- const models = await this.getEntityModels();
2442
- if (!models) {
2443
- return void 0;
2444
- }
2445
- if (!textureKey) {
2446
- textureKey = modelKey;
2447
- }
2448
- const baseKey = modelKey.path.includes("/") ? new BasicAssetKey(modelKey.namespace, modelKey.path.split("/")[0]) : modelKey;
2449
- return {
2450
- key: textureKey,
2451
- parts: models[baseKey.toNamespacedString()]
2452
- };
2766
+ /** @deprecated Use getEntityList(); block entities share the entity dataset. */
2767
+ static getBlockList() {
2768
+ return this.getEntityList();
2769
+ }
2770
+ /** @deprecated Use getEntity(); block entities share the entity dataset. */
2771
+ static getBlock(modelKey, textureKey, options) {
2772
+ return this.getEntity(modelKey, textureKey, options);
2453
2773
  }
2454
- };
2455
- __decorateClass([
2456
- Memoize3()
2457
- ], Entities, "getBlockEntityModels", 1);
2458
- __decorateClass([
2459
- Memoize3()
2460
- ], Entities, "getEntityModels", 1);
2461
- var EntityModels = class {
2462
- };
2463
- var BlockEntityModels = class {
2464
2774
  };
2465
2775
 
2466
2776
  // src/renderer/SceneObject.ts
2467
- import { Euler, InstancedMesh, MathUtils, Matrix4 as Matrix42, Mesh as Mesh3, Object3D as Object3D2, Quaternion as Quaternion2, Vector3 as Vector33 } from "three";
2777
+ import { Euler as Euler3, InstancedMesh, MathUtils, Matrix4 as Matrix43, Mesh as Mesh3, Object3D as Object3D2, Quaternion as Quaternion3, Vector3 as Vector35 } from "three";
2468
2778
 
2469
2779
  // src/instance/InstanceReference.ts
2470
2780
  var InstanceReference = class {
@@ -2509,20 +2819,9 @@ function isInstanceReference(obj) {
2509
2819
  return obj.isInstanceReference;
2510
2820
  }
2511
2821
 
2512
- // src/error/MineRenderError.ts
2513
- var MineRenderError = class _MineRenderError extends Error {
2514
- constructor(msg) {
2515
- super(msg);
2516
- Object.setPrototypeOf(this, _MineRenderError.prototype);
2517
- }
2518
- get name() {
2519
- return "MineRenderError";
2520
- }
2521
- };
2522
-
2523
2822
  // src/renderer/SceneObject.ts
2524
- var import_ts_deepmerge4 = __toESM(require_dist());
2525
- var p7 = prefix("SceneObject");
2823
+ var import_ts_deepmerge3 = __toESM(require_dist());
2824
+ var p6 = prefix("SceneObject");
2526
2825
  var _SceneObject = class _SceneObject extends Object3D2 {
2527
2826
  constructor(options) {
2528
2827
  super();
@@ -2530,7 +2829,7 @@ var _SceneObject = class _SceneObject extends Object3D2 {
2530
2829
  this.materialCallbacks = {};
2531
2830
  this._isInstanced = false;
2532
2831
  this._instanceCounter = 0;
2533
- this.options = (0, import_ts_deepmerge4.default)({}, _SceneObject.DEFAULT_OPTIONS, options ?? {});
2832
+ this.options = (0, import_ts_deepmerge3.default)({}, _SceneObject.DEFAULT_OPTIONS, options ?? {});
2534
2833
  console.log("SceneObject options", this.options);
2535
2834
  }
2536
2835
  set scene(scene) {
@@ -2624,6 +2923,7 @@ var _SceneObject = class _SceneObject extends Object3D2 {
2624
2923
  }
2625
2924
  createInstancedMesh(name, geometry, material, count) {
2626
2925
  const mesh = new InstancedMesh(geometry, material, count);
2926
+ mesh.count = 0;
2627
2927
  if (name) {
2628
2928
  mesh.name = `mesh:${name}`;
2629
2929
  }
@@ -2644,15 +2944,8 @@ var _SceneObject = class _SceneObject extends Object3D2 {
2644
2944
  return this.toggleObjectVisibility(this.getMeshByName(name), visible);
2645
2945
  }
2646
2946
  iterateAllMeshes(cb) {
2647
- this.children.forEach((obj) => {
2648
- if (obj.isMesh) {
2649
- cb(obj);
2650
- }
2651
- obj.children.forEach((obj1) => {
2652
- if (obj1.isMesh) {
2653
- cb(obj1);
2654
- }
2655
- });
2947
+ this.traverse((obj) => {
2948
+ if (isMesh(obj)) cb(obj);
2656
2949
  });
2657
2950
  }
2658
2951
  //</editor-fold>
@@ -2700,10 +2993,14 @@ var _SceneObject = class _SceneObject extends Object3D2 {
2700
2993
  nextInstance() {
2701
2994
  if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
2702
2995
  const i = this._instanceCounter++;
2703
- this.setMatrixAt(i, new Matrix42());
2704
- console.debug(p7, "nextInstance " + i);
2996
+ this.setMatrixAt(i, new Matrix43());
2997
+ const mesh = this.children[0];
2998
+ if (mesh && isInstancedMesh(mesh)) {
2999
+ mesh.count = Math.min(this._instanceCounter, mesh.instanceMatrix.count);
3000
+ }
3001
+ console.debug(p6, "nextInstance " + i);
2705
3002
  if (i === this.options.maxInstanceCount) {
2706
- console.warn(p7, "Max instance count reached for " + this);
3003
+ console.warn(p6, "Max instance count reached for " + this);
2707
3004
  }
2708
3005
  if (this.scene) {
2709
3006
  this.scene.stats.instanceCount++;
@@ -2713,7 +3010,7 @@ var _SceneObject = class _SceneObject extends Object3D2 {
2713
3010
  }
2714
3011
  //</editor-fold>
2715
3012
  //<editor-fold desc="TRANSFORMATION">
2716
- getMatrixAt(index, matrix = new Matrix42()) {
3013
+ getMatrixAt(index, matrix = new Matrix43()) {
2717
3014
  if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
2718
3015
  const child = this.children[0];
2719
3016
  if (child && isInstancedMesh(child)) {
@@ -2732,16 +3029,16 @@ var _SceneObject = class _SceneObject extends Object3D2 {
2732
3029
  }
2733
3030
  setPositionRotationScaleAt(index, position, rotation, scale) {
2734
3031
  if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
2735
- const oldPosition = new Vector33();
2736
- const oldRotation = new Quaternion2();
2737
- const oldScale = new Vector33();
2738
- const matrix = new Matrix42();
3032
+ const oldPosition = new Vector35();
3033
+ const oldRotation = new Quaternion3();
3034
+ const oldScale = new Vector35();
3035
+ const matrix = new Matrix43();
2739
3036
  if (!scale || !rotation || !position) {
2740
3037
  this.getMatrixAt(index, matrix).decompose(oldPosition, oldRotation, oldScale);
2741
3038
  }
2742
3039
  matrix.compose(
2743
3040
  position ? position : oldPosition,
2744
- rotation ? new Quaternion2().setFromEuler(rotation) : oldRotation,
3041
+ rotation ? new Quaternion3().setFromEuler(rotation) : oldRotation,
2745
3042
  scale ? scale : oldScale
2746
3043
  );
2747
3044
  this.setMatrixAt(index, matrix);
@@ -2750,7 +3047,7 @@ var _SceneObject = class _SceneObject extends Object3D2 {
2750
3047
  if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
2751
3048
  this.setPositionRotationScaleAt(index, position);
2752
3049
  }
2753
- getPositionAt(index, vector = new Vector33()) {
3050
+ getPositionAt(index, vector = new Vector35()) {
2754
3051
  if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
2755
3052
  const matrix = this.getMatrixAt(index);
2756
3053
  vector.setFromMatrixPosition(matrix);
@@ -2760,7 +3057,7 @@ var _SceneObject = class _SceneObject extends Object3D2 {
2760
3057
  if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
2761
3058
  this.setPositionRotationScaleAt(index, void 0, rotation, void 0);
2762
3059
  }
2763
- getRotationAt(index, euler = new Euler()) {
3060
+ getRotationAt(index, euler = new Euler3()) {
2764
3061
  if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
2765
3062
  const matrix = this.getMatrixAt(index);
2766
3063
  euler.setFromRotationMatrix(matrix);
@@ -2770,7 +3067,7 @@ var _SceneObject = class _SceneObject extends Object3D2 {
2770
3067
  if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
2771
3068
  this.setPositionRotationScaleAt(index, void 0, void 0, scale);
2772
3069
  }
2773
- getScaleAt(index, vector = new Vector33()) {
3070
+ getScaleAt(index, vector = new Vector35()) {
2774
3071
  if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
2775
3072
  const matrix = this.getMatrixAt(index);
2776
3073
  vector.setFromMatrixScale(matrix);
@@ -2879,7 +3176,7 @@ var _SceneObject = class _SceneObject extends Object3D2 {
2879
3176
  }
2880
3177
  //</editor-fold>
2881
3178
  };
2882
- _SceneObject.DEFAULT_OPTIONS = (0, import_ts_deepmerge4.default)({}, {
3179
+ _SceneObject.DEFAULT_OPTIONS = (0, import_ts_deepmerge3.default)({}, {
2883
3180
  instanceMeshes: true,
2884
3181
  maxInstanceCount: 50,
2885
3182
  mergeMeshes: true,
@@ -2891,16 +3188,15 @@ function isSceneObject(obj) {
2891
3188
  }
2892
3189
 
2893
3190
  // src/entity/scene/EntityObject.ts
2894
- var import_ts_deepmerge5 = __toESM(require_dist());
3191
+ var import_ts_deepmerge4 = __toESM(require_dist());
2895
3192
  import { Object3D as Object3D3 } from "three";
2896
- import * as THREE2 from "three";
2897
- var EntityObject = class extends SceneObject {
3193
+ var _EntityObject = class _EntityObject extends SceneObject {
2898
3194
  constructor(entity, options) {
2899
3195
  super();
2900
3196
  this.entity = entity;
2901
3197
  this.isEntityObject = true;
2902
3198
  this.meshesCreated = false;
2903
- this.options = (0, import_ts_deepmerge5.default)({}, SceneObject.DEFAULT_OPTIONS, options ?? {});
3199
+ this.options = (0, import_ts_deepmerge4.default)({}, _EntityObject.DEFAULT_OPTIONS, options ?? {});
2904
3200
  }
2905
3201
  async init() {
2906
3202
  this.createMeshes();
@@ -2909,65 +3205,78 @@ var EntityObject = class extends SceneObject {
2909
3205
  dispose() {
2910
3206
  super.dispose();
2911
3207
  }
2912
- async loadTextures() {
2913
- this.imageData = await ModelTextures.get(new AssetKey(
2914
- this.entity.key?.namespace ?? DEFAULT_NAMESPACE,
2915
- this.entity.key.path,
2916
- //TODO: texture may differ from entity name; most of them are in subdirectories for multiple variants etc.
3208
+ get textureKey() {
3209
+ if (this.entity.texture) return this.entity.texture;
3210
+ const key = this.entity.key;
3211
+ return new AssetKey(
3212
+ key.namespace,
3213
+ isAssetKey(key) ? key.getFullPath() : key.path,
2917
3214
  "textures",
2918
3215
  "entity",
2919
3216
  "assets",
2920
3217
  ".png"
2921
- ));
3218
+ );
3219
+ }
3220
+ async loadTextures() {
3221
+ this.imageData = await ModelTextures.get(this.textureKey);
2922
3222
  }
2923
- //TODO: abstract this, since extracted model data can be used for entities, blocks, players
2924
3223
  createMeshes(force = false) {
2925
3224
  if (this.meshesCreated && !force) return;
2926
- const mat = Materials.MISSING_TEXTURE;
2927
- for (let partName in this.entity.parts) {
2928
- const part = this.entity.parts[partName];
2929
- const texture = new MinecraftCubeTexture(part.textureOffsetU, part.textureOffsetV, part.textureWidth, part.textureHeight);
2930
- const partAnchor = new Object3D3();
2931
- partAnchor.position.x = part.pivotX;
2932
- partAnchor.position.y = part.pivotY;
2933
- partAnchor.position.z = part.pivotZ;
2934
- for (let cube of part.cubes) {
2935
- const w = cube.maxX - cube.minX;
2936
- const h = cube.maxY - cube.minY;
2937
- const l = cube.maxZ - cube.minZ;
2938
- const uv = texture.toUvArray(w, h, l);
2939
- const cubeGeo = this._getBoxGeometryForDimensionsAndUv(w, h, l, uv).clone();
2940
- cubeGeo.applyMatrix4(new THREE2.Matrix4().makeTranslation(w / 2, h / 2, l / 2));
2941
- cubeGeo.applyMatrix4(new THREE2.Matrix4().makeTranslation(cube.minX, cube.minY, cube.minZ));
2942
- applyModelPartRotation(part, cubeGeo);
2943
- const mesh = this.createMesh(partName, cubeGeo, mat);
2944
- partAnchor.add(mesh);
2945
- if (this.options.wireframe) {
2946
- addWireframeToMesh(cubeGeo, mesh);
2947
- }
2948
- }
2949
- this.add(partAnchor);
2950
- }
3225
+ const modelRoot = new Object3D3();
3226
+ if (this.options.flip) modelRoot.scale.set(-1, -1, 1);
3227
+ this.add(modelRoot);
3228
+ this.createPart("root", this.entity.layer.root, modelRoot, this.entity.layer.texture, Materials.MISSING_TEXTURE);
2951
3229
  this.meshesCreated = true;
2952
3230
  }
2953
- async applyTextures() {
2954
- const textureAsset = await ModelTextures.preload(new AssetKey(
2955
- this.entity.key?.namespace ?? DEFAULT_NAMESPACE,
2956
- this.entity.key.path,
2957
- "textures",
2958
- "entity",
2959
- "assets",
2960
- ".png"
2961
- ));
2962
- if (textureAsset) {
2963
- let mat = Materials.getImage({ texture: { src: textureAsset.src } });
2964
- this.iterateAllMeshes((mesh) => {
2965
- mesh.material = mat;
2966
- });
3231
+ createPart(name, part, parent, textureSize, material) {
3232
+ const anchor = this.createGroup(name);
3233
+ anchor.position.fromArray(part.pose.offset);
3234
+ anchor.rotation.set(...part.pose.rotation, "ZYX");
3235
+ anchor.scale.fromArray(part.pose.scale ?? [1, 1, 1]);
3236
+ parent.add(anchor);
3237
+ const size = part.texture ?? textureSize;
3238
+ for (const cube of part.cubes) {
3239
+ const [width, height, depth] = cube.size;
3240
+ const [growX, growY, growZ] = cube.grow ?? [0, 0, 0];
3241
+ const texture = new MinecraftCubeTexture(...cube.uv, ...size);
3242
+ const uv = size[0] === 0 || size[1] === 0 ? new Array(48).fill(0) : texture.toUvArray(width, height, depth, cube.mirror);
3243
+ const geometry = this._getBoxGeometryForDimensionsAndUv(
3244
+ width + growX * 2,
3245
+ height + growY * 2,
3246
+ depth + growZ * 2,
3247
+ uv
3248
+ ).clone();
3249
+ geometry.translate(cube.origin[0] + width / 2, cube.origin[1] + height / 2, cube.origin[2] + depth / 2);
3250
+ const mesh = this.createMesh(name, geometry, material);
3251
+ anchor.add(mesh);
3252
+ if (this.options.wireframe) addWireframeToMesh(geometry, mesh);
3253
+ }
3254
+ for (const [childName, child] of Object.entries(part.children)) {
3255
+ this.createPart(childName, child, anchor, size, material);
2967
3256
  }
2968
3257
  }
3258
+ async applyTextures() {
3259
+ const assetKeyStr = this.textureKey.serialize();
3260
+ const keyStr = `entity:${assetKeyStr}`;
3261
+ let mat = Caching.materialCache.getIfPresent(keyStr);
3262
+ if (!mat) {
3263
+ const pending = this.loadTextures();
3264
+ const cachedAsset = Caching.textureAssetCache.getIfPresent(assetKeyStr);
3265
+ await pending;
3266
+ if (!this.imageData) return;
3267
+ const canvas = this.imageData.data.canvas;
3268
+ const createMaterial = () => Materials.createBasicCanvasMaterial(canvas);
3269
+ mat = cachedAsset && Caching.textureAssetCache.getIfPresent(assetKeyStr) === cachedAsset ? Caching.materialCache.get(keyStr, createMaterial) : createMaterial();
3270
+ this.imageData = void 0;
3271
+ }
3272
+ this.iterateAllMeshes((mesh) => {
3273
+ mesh.material = mat;
3274
+ });
3275
+ this.notifyDirty();
3276
+ }
2969
3277
  };
2970
- EntityObject.DEFAULT_OPTIONS = (0, import_ts_deepmerge5.default)({}, SceneObject.DEFAULT_OPTIONS, {});
3278
+ _EntityObject.DEFAULT_OPTIONS = (0, import_ts_deepmerge4.default)({}, SceneObject.DEFAULT_OPTIONS, { flip: true });
3279
+ var EntityObject = _EntityObject;
2971
3280
  function isEntityObject(obj) {
2972
3281
  return obj.isEntityObject;
2973
3282
  }
@@ -2979,7 +3288,7 @@ var GuiObject = class extends SceneObject {
2979
3288
 
2980
3289
  // src/inspector/SceneInspector.ts
2981
3290
  import { Raycaster, Vector2 as Vector22 } from "three";
2982
- var p8 = prefix("SceneInspector");
3291
+ var p7 = prefix("SceneInspector");
2983
3292
  var help = "<span>Ctrl+Click to Select<br/></span><br/>";
2984
3293
  var SceneInspector = class {
2985
3294
  constructor(renderer) {
@@ -3048,7 +3357,7 @@ var SceneInspector = class {
3048
3357
  }
3049
3358
  selectObject(targetObject, targetIntersection) {
3050
3359
  this.selectedObject = targetObject;
3051
- console.log(p8, "selected", targetObject);
3360
+ console.log(p7, "selected", targetObject);
3052
3361
  this.redraw(targetObject, targetIntersection);
3053
3362
  }
3054
3363
  redraw(targetObject, targetIntersection) {
@@ -3346,7 +3655,7 @@ var SceneStatsDisplay = class {
3346
3655
  };
3347
3656
 
3348
3657
  // src/instance/InstanceManager.ts
3349
- var p9 = prefix("InstanceManager");
3658
+ var p8 = prefix("InstanceManager");
3350
3659
  var InstanceManager = class {
3351
3660
  constructor() {
3352
3661
  this.isInstanceManager = true;
@@ -3354,7 +3663,7 @@ var InstanceManager = class {
3354
3663
  }
3355
3664
  async get(key) {
3356
3665
  if (key in this.instanceCache) {
3357
- console.debug(p9, "key in cache", key);
3666
+ console.debug(p8, "key in cache", key);
3358
3667
  return (await this.instanceCache[key]).nextInstance();
3359
3668
  }
3360
3669
  return void 0;
@@ -3363,13 +3672,17 @@ var InstanceManager = class {
3363
3672
  if (key in this.instanceCache) {
3364
3673
  return await this.get(key);
3365
3674
  }
3366
- console.debug(p9, "key not in cache", key);
3367
- this.instanceCache[key] = new Promise(async (resolve) => {
3368
- const obj = await supplier();
3369
- const instance = obj.nextInstance();
3370
- resolve(instance);
3371
- });
3372
- return await this.instanceCache[key];
3675
+ console.debug(p8, "key not in cache", key);
3676
+ const pending = Promise.resolve().then(supplier).then((obj) => obj.nextInstance());
3677
+ this.instanceCache[key] = pending;
3678
+ try {
3679
+ return await pending;
3680
+ } catch (error) {
3681
+ if (this.instanceCache[key] === pending) {
3682
+ delete this.instanceCache[key];
3683
+ }
3684
+ throw error;
3685
+ }
3373
3686
  }
3374
3687
  reset() {
3375
3688
  for (let instanceCacheKey in this.instanceCache) {
@@ -3386,20 +3699,6 @@ function isInstanceable(obj) {
3386
3699
  return "nextInstance" in obj;
3387
3700
  }
3388
3701
 
3389
- // src/model/DisplayPosition.ts
3390
- var DisplayPosition = /* @__PURE__ */ ((DisplayPosition2) => {
3391
- DisplayPosition2["THIRDPERSON_RIGHTHAND"] = "thirdperson_righthand";
3392
- DisplayPosition2["THIRDPERSON_LEFTHAND"] = "thirdperson_lefthand";
3393
- DisplayPosition2["FIRSTPERSON_RIGHTHAND"] = "firstperson_righthand";
3394
- DisplayPosition2["FIRSTPERSON_LEFTHAND"] = "firstperson_lefthand";
3395
- DisplayPosition2["GUI"] = "gui";
3396
- DisplayPosition2["HEAD"] = "head";
3397
- DisplayPosition2["GROUND"] = "ground";
3398
- DisplayPosition2["FIXED"] = "fixed";
3399
- return DisplayPosition2;
3400
- })(DisplayPosition || {});
3401
- var DISPLAY_POSITIONS = Object.values(DisplayPosition);
3402
-
3403
3702
  // src/model/GuiLight.ts
3404
3703
  var GuiLight = /* @__PURE__ */ ((GuiLight2) => {
3405
3704
  GuiLight2["FRONT"] = "front";
@@ -3422,8 +3721,93 @@ function isQuadArray(obj) {
3422
3721
  return Array.isArray(obj) && obj.length === 4;
3423
3722
  }
3424
3723
 
3724
+ // src/model/block/blockTints.json
3725
+ var blockTints_default = {
3726
+ "minecraft:large_fern": { source: "grass" },
3727
+ "minecraft:tall_grass": { source: "grass" },
3728
+ "minecraft:grass_block": { source: "grass" },
3729
+ "minecraft:fern": { source: "grass" },
3730
+ "minecraft:grass": { source: "grass" },
3731
+ "minecraft:short_grass": { source: "grass" },
3732
+ "minecraft:potted_fern": { source: "grass" },
3733
+ "minecraft:bush": { source: "grass" },
3734
+ "minecraft:pink_petals": { source: "grass", untinted: [0] },
3735
+ "minecraft:wildflowers": { source: "grass", untinted: [0] },
3736
+ "minecraft:spruce_leaves": { source: "constant", color: 6396257 },
3737
+ "minecraft:birch_leaves": { source: "constant", color: 8431445 },
3738
+ "minecraft:oak_leaves": { source: "constant", color: 4764952 },
3739
+ "minecraft:jungle_leaves": { source: "constant", color: 4764952 },
3740
+ "minecraft:acacia_leaves": { source: "constant", color: 4764952 },
3741
+ "minecraft:dark_oak_leaves": { source: "constant", color: 4764952 },
3742
+ "minecraft:vine": { source: "constant", color: 4764952 },
3743
+ "minecraft:mangrove_leaves": { source: "constant", color: 4764952 },
3744
+ "minecraft:leaf_litter": { source: "constant", color: 6044722 },
3745
+ "minecraft:water_cauldron": { source: "constant", color: 4159204 },
3746
+ "minecraft:redstone_wire": { source: "redstone" },
3747
+ "minecraft:attached_melon_stem": { source: "constant", color: 14731036 },
3748
+ "minecraft:attached_pumpkin_stem": { source: "constant", color: 14731036 },
3749
+ "minecraft:melon_stem": { source: "stem" },
3750
+ "minecraft:pumpkin_stem": { source: "stem" },
3751
+ "minecraft:lily_pad": { source: "constant", color: 7455580 }
3752
+ };
3753
+
3754
+ // src/model/block/BlockTints.ts
3755
+ var rules = blockTints_default;
3756
+ var BlockTints = class {
3757
+ /** Resolves block preview colors without biome context; explicit tints take precedence. */
3758
+ static async get(key, state, model, tints) {
3759
+ const rule = key && rules[key.toNamespacedString()];
3760
+ if (!key || !rule) return tints;
3761
+ const indices = /* @__PURE__ */ new Set();
3762
+ for (const element of model.elements ?? []) {
3763
+ for (const face of Object.values(element.faces)) {
3764
+ const index = face?.tintindex;
3765
+ if (index !== void 0 && index >= 0 && tints?.[index] === void 0 && !rule.untinted?.includes(index)) {
3766
+ indices.add(index);
3767
+ }
3768
+ }
3769
+ }
3770
+ if (!indices.size) return tints;
3771
+ let color;
3772
+ switch (rule.source) {
3773
+ case "constant":
3774
+ color = rule.color;
3775
+ break;
3776
+ case "grass":
3777
+ color = await this.grassColor(key);
3778
+ break;
3779
+ case "redstone": {
3780
+ const power = Number(state.power ?? 0) / 15;
3781
+ const red = power * 0.6 + (power > 0 ? 0.4 : 0.3);
3782
+ const green = Math.max(0, power * power * 0.7 - 0.5);
3783
+ const blue = Math.max(0, power * power * 0.6 - 0.7);
3784
+ color = Math.floor(red * 255) << 16 | Math.floor(green * 255) << 8 | Math.floor(blue * 255);
3785
+ break;
3786
+ }
3787
+ case "stem": {
3788
+ const age = Number(state.age ?? 0);
3789
+ color = age * 32 << 16 | 255 - age * 8 << 8 | age * 4;
3790
+ break;
3791
+ }
3792
+ }
3793
+ return { ...Object.fromEntries([...indices].map((index) => [index, color])), ...tints };
3794
+ }
3795
+ static async grassColor(origin) {
3796
+ const key = AssetKey.parse("textures", "minecraft:colormap/grass", origin);
3797
+ return await Caching.blockTintCache.get(key.serialize(), async () => {
3798
+ const image = await ModelTextures.get(key);
3799
+ if (!image) throw new MineRenderError(`Missing grass colormap: ${key.toNamespacedString()}`);
3800
+ if (image.width !== 256 || image.height !== 256) {
3801
+ throw new MineRenderError("Grass colormaps must be 256 \xD7 256 pixels");
3802
+ }
3803
+ const [red, green, blue] = image.data.getImageData(127, 127, 1, 1).data;
3804
+ return red << 16 | green << 8 | blue;
3805
+ });
3806
+ }
3807
+ };
3808
+
3425
3809
  // src/model/scene/ModelObject.ts
3426
- import { BoxGeometry as BoxGeometry5, Matrix4 as Matrix45 } from "three";
3810
+ import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute4, Color as Color3, Euler as Euler4, Matrix4 as Matrix44 } from "three";
3427
3811
 
3428
3812
  // src/three/BufferGeometryUtils.js
3429
3813
  import {
@@ -3435,7 +3819,7 @@ import {
3435
3819
  TriangleFanDrawMode,
3436
3820
  TriangleStripDrawMode,
3437
3821
  TrianglesDrawMode,
3438
- Vector3 as Vector35
3822
+ Vector3 as Vector36
3439
3823
  } from "three";
3440
3824
  function computeTangents(geometry) {
3441
3825
  geometry.computeTangents();
@@ -3754,15 +4138,15 @@ function computeMorphedAttributes(object) {
3754
4138
  console.error("THREE.BufferGeometryUtils: Geometry is not of type BufferGeometry.");
3755
4139
  return null;
3756
4140
  }
3757
- const _vA = new Vector35();
3758
- const _vB = new Vector35();
3759
- const _vC = new Vector35();
3760
- const _tempA = new Vector35();
3761
- const _tempB = new Vector35();
3762
- const _tempC = new Vector35();
3763
- const _morphA = new Vector35();
3764
- const _morphB = new Vector35();
3765
- const _morphC = new Vector35();
4141
+ const _vA = new Vector36();
4142
+ const _vB = new Vector36();
4143
+ const _vC = new Vector36();
4144
+ const _tempA = new Vector36();
4145
+ const _tempB = new Vector36();
4146
+ const _tempC = new Vector36();
4147
+ const _morphA = new Vector36();
4148
+ const _morphB = new Vector36();
4149
+ const _morphC = new Vector36();
3766
4150
  function _calculateMorphedAttributeData(object2, material2, attribute, morphAttribute, morphTargetsRelative2, a2, b2, c2, modifiedAttributeArray) {
3767
4151
  _vA.fromBufferAttribute(attribute, a2);
3768
4152
  _vB.fromBufferAttribute(attribute, b2);
@@ -3968,15 +4352,16 @@ function computeMorphedAttributes(object) {
3968
4352
  }
3969
4353
 
3970
4354
  // src/model/scene/ModelObject.ts
3971
- var import_ts_deepmerge6 = __toESM(require_dist());
3972
- var p10 = prefix("ModelObject");
4355
+ var import_ts_deepmerge5 = __toESM(require_dist());
4356
+ var p9 = prefix("ModelObject");
3973
4357
  var _ModelObject = class _ModelObject extends SceneObject {
3974
4358
  constructor(originalModel, options) {
3975
4359
  super(options);
3976
4360
  this.originalModel = originalModel;
3977
4361
  this.isModelObject = true;
3978
4362
  this.meshesCreated = false;
3979
- this.options = (0, import_ts_deepmerge6.default)({}, _ModelObject.DEFAULT_OPTIONS, options ?? {});
4363
+ this.options = (0, import_ts_deepmerge5.default)({}, _ModelObject.DEFAULT_OPTIONS, options ?? {});
4364
+ if (this.options.tints) this.options.tints = { ...this.options.tints };
3980
4365
  console.log("ModelObject options", this.options);
3981
4366
  }
3982
4367
  async init() {
@@ -3998,17 +4383,36 @@ var _ModelObject = class _ModelObject extends SceneObject {
3998
4383
  createMeshes(force = false) {
3999
4384
  if (this.meshesCreated && !force) return;
4000
4385
  const mat = Materials.MISSING_TEXTURE;
4386
+ const displayTransform = this.options.displayPosition ? DisplayTransforms.getMatrix(this.originalModel.display, this.options.displayPosition) : void 0;
4001
4387
  let allGeos = [];
4002
4388
  if (this.atlas) {
4003
4389
  if (this.atlas.model.elements) {
4004
4390
  this.atlas.model.elements?.forEach((el) => {
4005
4391
  const elGeo = this._getBoxGeometryFromElement(el).clone();
4006
- elGeo.applyMatrix4(new Matrix45().makeTranslation((el.to[0] - el.from[0]) / 2, (el.to[1] - el.from[1]) / 2, (el.to[2] - el.from[2]) / 2));
4007
- elGeo.applyMatrix4(new Matrix45().makeTranslation(el.from[0], el.from[1], el.from[2]));
4392
+ if (this.options.uvLockRotation) {
4393
+ UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler4(...this.options.uvLockRotation));
4394
+ }
4395
+ if (this.options.tints) {
4396
+ const colors = new Float32Array(elGeo.getAttribute("position").count * 3).fill(1);
4397
+ for (const [faceIndex, faceName] of CUBE_FACES.entries()) {
4398
+ const tintIndex = el.faces[faceName]?.tintindex;
4399
+ if (tintIndex === void 0 || tintIndex < 0) continue;
4400
+ const tint = this.options.tints[tintIndex];
4401
+ if (tint === void 0) continue;
4402
+ const color = new Color3(tint);
4403
+ for (let vertex = 0; vertex < 4; vertex++) {
4404
+ color.toArray(colors, (faceIndex * 4 + vertex) * 3);
4405
+ }
4406
+ }
4407
+ elGeo.setAttribute("color", new BufferAttribute4(colors, 3));
4408
+ }
4409
+ elGeo.applyMatrix4(new Matrix44().makeTranslation((el.to[0] - el.from[0]) / 2, (el.to[1] - el.from[1]) / 2, (el.to[2] - el.from[2]) / 2));
4410
+ elGeo.applyMatrix4(new Matrix44().makeTranslation(el.from[0], el.from[1], el.from[2]));
4008
4411
  if (el.rotation) {
4009
4412
  applyElementRotation(el.rotation, elGeo);
4010
4413
  }
4011
- elGeo.applyMatrix4(new Matrix45().makeTranslation(-8, -8, -8));
4414
+ elGeo.applyMatrix4(new Matrix44().makeTranslation(-8, -8, -8));
4415
+ if (displayTransform) DisplayTransforms.apply(elGeo, displayTransform);
4012
4416
  if (this.options.mergeMeshes) {
4013
4417
  allGeos.push(elGeo);
4014
4418
  } else {
@@ -4019,10 +4423,10 @@ var _ModelObject = class _ModelObject extends SceneObject {
4019
4423
  }
4020
4424
  });
4021
4425
  } else {
4022
- console.debug(p10, this.atlas.model, "has no elements");
4426
+ console.debug(p9, this.atlas.model, "has no elements");
4023
4427
  }
4024
4428
  } else {
4025
- console.debug(p10, "Missing atlas for", this);
4429
+ console.debug(p9, "Missing atlas for", this);
4026
4430
  }
4027
4431
  if (this.options.mergeMeshes) {
4028
4432
  let combinedGeo;
@@ -4030,6 +4434,7 @@ var _ModelObject = class _ModelObject extends SceneObject {
4030
4434
  combinedGeo = mergeBufferGeometries(allGeos);
4031
4435
  } else {
4032
4436
  combinedGeo = new BoxGeometry5(16, 16, 16);
4437
+ if (displayTransform) DisplayTransforms.apply(combinedGeo, displayTransform);
4033
4438
  }
4034
4439
  combinedGeo.computeBoundingBox();
4035
4440
  let mesh;
@@ -4059,6 +4464,7 @@ var _ModelObject = class _ModelObject extends SceneObject {
4059
4464
  /*TODO: get this from render options*/
4060
4465
  );
4061
4466
  this.iterateAllMeshes((mesh) => {
4467
+ if (mesh.geometry.hasAttribute("color")) mat.vertexColors = true;
4062
4468
  mesh.material = mat;
4063
4469
  });
4064
4470
  if (this.atlas.hasAnimation) {
@@ -4074,16 +4480,26 @@ var _ModelObject = class _ModelObject extends SceneObject {
4074
4480
  }
4075
4481
  }
4076
4482
  };
4077
- _ModelObject.DEFAULT_OPTIONS = (0, import_ts_deepmerge6.default)({}, SceneObject.DEFAULT_OPTIONS, {});
4483
+ _ModelObject.DEFAULT_OPTIONS = (0, import_ts_deepmerge5.default)({}, SceneObject.DEFAULT_OPTIONS, {});
4078
4484
  var ModelObject = _ModelObject;
4079
4485
  function isModelObject(obj) {
4080
4486
  return obj.isModelObject;
4081
4487
  }
4082
4488
 
4083
4489
  // src/model/block/scene/BlockObject.ts
4084
- var import_ts_deepmerge7 = __toESM(require_dist());
4085
- import { Euler as Euler3, Matrix4 as Matrix46, Vector3 as Vector36 } from "three";
4086
- var p11 = prefix("BlockObject");
4490
+ var import_ts_deepmerge6 = __toESM(require_dist());
4491
+ import { Euler as Euler5, Matrix4 as Matrix45, Vector3 as Vector37 } from "three";
4492
+ var p10 = prefix("BlockObject");
4493
+ function matchesCondition(condition, state) {
4494
+ return Object.entries(condition).every(([key, value]) => {
4495
+ if (Array.isArray(value)) {
4496
+ if (key === "OR") return value.some((child) => matchesCondition(child, state));
4497
+ if (key === "AND") return value.every((child) => matchesCondition(child, state));
4498
+ return false;
4499
+ }
4500
+ return typeof value === "string" && state[key] !== void 0 && value.split("|").includes(`${state[key]}`);
4501
+ });
4502
+ }
4087
4503
  var _BlockObject = class _BlockObject extends SceneObject {
4088
4504
  constructor(blockState, options) {
4089
4505
  super(options);
@@ -4097,11 +4513,11 @@ var _BlockObject = class _BlockObject extends SceneObject {
4097
4513
  this._instanceModel = [];
4098
4514
  this._variants = [];
4099
4515
  this._models = [];
4100
- this.options = (0, import_ts_deepmerge7.default)({}, _BlockObject.DEFAULT_OPTIONS, options ?? {});
4516
+ this.options = (0, import_ts_deepmerge6.default)({}, _BlockObject.DEFAULT_OPTIONS, options ?? {});
4101
4517
  }
4102
4518
  async init() {
4103
4519
  if (this.options.applyDefaultState) {
4104
- const defaultState = this.blockState.key ? BlockStates.getDefaultState(this.blockState.key) : void 0;
4520
+ const defaultState = this.blockState.key ? await BlockStates.getDefaultState(this.blockState.key) : void 0;
4105
4521
  if (defaultState && Object.keys(defaultState).length > 0) {
4106
4522
  const state = {};
4107
4523
  for (let k in defaultState) {
@@ -4112,15 +4528,9 @@ var _BlockObject = class _BlockObject extends SceneObject {
4112
4528
  if (this.blockState.variants) {
4113
4529
  await this.setState(Object.keys(this.blockState.variants)[0]);
4114
4530
  } else if (this.blockState.multipart) {
4115
- for (let part of this.blockState.multipart) {
4116
- if (part.when && !("OR" in part.when)) {
4117
- const k = Object.keys(part.when)[0];
4118
- if (k) {
4119
- await this.setState(k, part.when[k].split("|")[0]);
4120
- break;
4121
- }
4122
- }
4123
- }
4531
+ const condition = this.blockState.multipart.map((part) => part.when).find((when) => when !== void 0 && Object.values(when).every((value) => typeof value === "string"));
4532
+ const state = Object.fromEntries(Object.entries(condition ?? {}).map(([key, value]) => [key, value.split("|")[0]]));
4533
+ await this.setState(state);
4124
4534
  }
4125
4535
  }
4126
4536
  } else {
@@ -4135,7 +4545,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
4135
4545
  if (isModelObject(model)) {
4136
4546
  model.removeFromScene();
4137
4547
  } else if (isInstanceReference(model)) {
4138
- model.setScale(new Vector36(0, 0, 0));
4548
+ model.setScale(new Vector37(0, 0, 0));
4139
4549
  }
4140
4550
  }
4141
4551
  super.removeFromScene();
@@ -4177,55 +4587,11 @@ var _BlockObject = class _BlockObject extends SceneObject {
4177
4587
  } else if (this.blockState.multipart) {
4178
4588
  for (let part of this.blockState.multipart) {
4179
4589
  if (!part.apply) {
4180
- console.debug(p11, "Missing apply for blockState part", part);
4590
+ console.debug(p10, "Missing apply for blockState part", part);
4181
4591
  continue;
4182
4592
  }
4183
- if (!part.when) {
4593
+ if (!part.when || matchesCondition(part.when, state)) {
4184
4594
  out.push(this.getSingleVariant(part.apply));
4185
- } else {
4186
- let matches = true;
4187
- if ("OR" in part.when) {
4188
- const or = part.when.OR;
4189
- let anyMatch = false;
4190
- for (let o of or) {
4191
- for (let k in o) {
4192
- const split = o[k].split("|");
4193
- let m = false;
4194
- for (let s of split) {
4195
- if (`${state[k]}` === `${s}`) {
4196
- m = true;
4197
- break;
4198
- }
4199
- }
4200
- if (m) {
4201
- anyMatch = true;
4202
- break;
4203
- }
4204
- }
4205
- }
4206
- if (!anyMatch) {
4207
- matches = false;
4208
- }
4209
- } else {
4210
- for (let k in part.when) {
4211
- const split = part.when[k].split("|");
4212
- let m = false;
4213
- for (let s of split) {
4214
- if (`${state[k]}` === `${s}`) {
4215
- m = true;
4216
- break;
4217
- }
4218
- }
4219
- if (!m) {
4220
- matches = false;
4221
- break;
4222
- }
4223
- }
4224
- }
4225
- if (matches) {
4226
- const variants = part.apply;
4227
- out.push(this.getSingleVariant(variants));
4228
- }
4229
4595
  }
4230
4596
  }
4231
4597
  }
@@ -4240,7 +4606,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
4240
4606
  }
4241
4607
  for (let model of this._models) {
4242
4608
  if (isInstanceReference(model)) {
4243
- model.setScale(new Vector36(0, 0, 0));
4609
+ model.setScale(new Vector37(0, 0, 0));
4244
4610
  } else {
4245
4611
  model.dispose();
4246
4612
  }
@@ -4255,11 +4621,21 @@ var _BlockObject = class _BlockObject extends SceneObject {
4255
4621
  }
4256
4622
  }
4257
4623
  getSingleVariant(variants) {
4258
- if (Array.isArray(variants)) {
4259
- return variants[0];
4260
- } else {
4261
- return variants;
4262
- }
4624
+ if (!Array.isArray(variants)) return variants;
4625
+ if (!variants.length) throw new MineRenderError("Blockstate variant arrays must not be empty");
4626
+ const total = variants.reduce((sum, variant) => {
4627
+ const weight = variant.weight ?? 1;
4628
+ if (!Number.isInteger(weight) || weight < 1) {
4629
+ throw new MineRenderError(`Invalid blockstate variant weight: ${weight}`);
4630
+ }
4631
+ return sum + weight;
4632
+ }, 0);
4633
+ let choice = Math.random() * total;
4634
+ for (const variant of variants) {
4635
+ choice -= variant.weight ?? 1;
4636
+ if (choice < 0) return variant;
4637
+ }
4638
+ return variants[variants.length - 1];
4263
4639
  }
4264
4640
  // @deprecated
4265
4641
  async createAVariant(variants, instanceInfo) {
@@ -4267,28 +4643,27 @@ var _BlockObject = class _BlockObject extends SceneObject {
4267
4643
  await this.createVariant(variant);
4268
4644
  }
4269
4645
  async createVariant(variant) {
4270
- const model = await Models.getMerged(AssetKey.parse("models", variant.model));
4271
- const obj = await this.scene.addModel(model, this.options);
4272
- if (isInstanceReference(obj) || obj.isInstanced) {
4273
- this._isInstanced = true;
4274
- this._instanceCounter = 1;
4275
- }
4276
- let rotation = new Euler3(0, 0, 0);
4646
+ const rotation = new Euler5();
4277
4647
  if (typeof variant.x !== "undefined") {
4278
4648
  rotation.x = toRadians(clampRotationDegrees(variant.x));
4279
4649
  }
4280
4650
  if (typeof variant.y !== "undefined") {
4281
- if (typeof variant.x !== "undefined") {
4282
- rotation.y = toRadians(clampRotationDegrees(variant.y));
4283
- } else {
4284
- rotation.y = toRadians(clampRotationDegrees(360 - variant.y));
4285
- }
4651
+ rotation.y = toRadians(clampRotationDegrees(typeof variant.x !== "undefined" ? variant.y : 360 - variant.y));
4652
+ }
4653
+ const modelKey = AssetKey.parse("models", variant.model);
4654
+ modelKey.root = this.blockState.key?.root;
4655
+ const model = await Models.getMerged(modelKey);
4656
+ const options = {
4657
+ ...this.options,
4658
+ tints: model ? await BlockTints.get(this.blockState.key, this.state, model, this.options.tints) : this.options.tints,
4659
+ uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
4660
+ };
4661
+ const obj = await this.scene.addModel(model, options);
4662
+ if (isInstanceReference(obj) || obj.isInstanced) {
4663
+ this._isInstanced = true;
4664
+ this._instanceCounter = 1;
4286
4665
  }
4287
4666
  obj.setRotation(rotation);
4288
- setTimeout(() => {
4289
- obj.setRotation(rotation);
4290
- }, 150);
4291
- console.log(obj);
4292
4667
  return obj;
4293
4668
  }
4294
4669
  async resetState() {
@@ -4357,7 +4732,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
4357
4732
  setPositionRotationScaleAt(index, position, rotation, scale) {
4358
4733
  super.setPositionRotationScaleAt(index, position, rotation, scale);
4359
4734
  }
4360
- getMatrixAt(index, matrix = new Matrix46()) {
4735
+ getMatrixAt(index, matrix = new Matrix45()) {
4361
4736
  if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
4362
4737
  const child = this._models[0];
4363
4738
  if (child && isModelObject(child)) {
@@ -4378,15 +4753,16 @@ var _BlockObject = class _BlockObject extends SceneObject {
4378
4753
  }
4379
4754
  }
4380
4755
  setPosition(position) {
4381
- super.setPosition(position);
4756
+ this.position.copy(position);
4382
4757
  for (let model of this._models) {
4383
4758
  model.setPosition(position);
4384
4759
  }
4760
+ this.notifyDirty();
4385
4761
  }
4386
4762
  //TODO: should override rotation + scale methods
4387
4763
  //TODO
4388
4764
  };
4389
- _BlockObject.DEFAULT_OPTIONS = (0, import_ts_deepmerge7.default)({}, ModelObject.DEFAULT_OPTIONS, {
4765
+ _BlockObject.DEFAULT_OPTIONS = (0, import_ts_deepmerge6.default)({}, ModelObject.DEFAULT_OPTIONS, {
4390
4766
  applyDefaultState: true
4391
4767
  });
4392
4768
  var BlockObject = _BlockObject;
@@ -4436,7 +4812,7 @@ var StructureParser = class {
4436
4812
 
4437
4813
  // src/renderer/MineRenderScene.ts
4438
4814
  import { Scene as Scene2 } from "three";
4439
- var import_ts_deepmerge9 = __toESM(require_dist());
4815
+ var import_ts_deepmerge8 = __toESM(require_dist());
4440
4816
 
4441
4817
  // src/skin/SkinPart.ts
4442
4818
  var SkinPart = /* @__PURE__ */ ((SkinPart2) => {
@@ -5072,62 +5448,52 @@ var baseSkinCoordinates = {
5072
5448
  }
5073
5449
  },
5074
5450
  cape: {
5075
- //TODO
5076
- east: {
5077
- uv: [
5078
- 0,
5079
- 36,
5080
- 4,
5081
- 48
5082
- ]
5083
- },
5084
- west: {
5085
- uv: [
5086
- 8,
5087
- 36,
5088
- 12,
5089
- 48
5090
- ]
5091
- },
5092
- north: {
5093
- uv: [
5094
- 4,
5095
- 36,
5096
- 8,
5097
- 48
5098
- ]
5099
- },
5100
- south: {
5101
- uv: [
5102
- 12,
5103
- 36,
5104
- 16,
5105
- 48
5106
- ]
5107
- },
5108
- up: {
5109
- uv: [
5110
- 4,
5111
- 32,
5112
- 8,
5113
- 36
5114
- ]
5115
- },
5116
- down: {
5117
- uv: [
5118
- 8,
5119
- 32,
5120
- 12,
5121
- 36
5122
- ],
5123
- rotation: 180
5124
- }
5451
+ east: { uv: [0, 1, 1, 17] },
5452
+ west: { uv: [11, 1, 12, 17] },
5453
+ north: { uv: [1, 1, 11, 17] },
5454
+ south: { uv: [12, 1, 22, 17] },
5455
+ up: { uv: [11, 1, 1, 0] },
5456
+ down: { uv: [21, 0, 11, 1] }
5125
5457
  }
5126
5458
  };
5127
5459
  var classicSkinTextureCoordinates = {
5128
5460
  ...baseSkinCoordinates
5129
5461
  };
5130
- var slimSkinTextureCoordinates = { ...baseSkinCoordinates };
5462
+ var slimSkinTextureCoordinates = {
5463
+ ...baseSkinCoordinates,
5464
+ leftArm: {
5465
+ ...baseSkinCoordinates.leftArm,
5466
+ west: { uv: [39, 52, 43, 64] },
5467
+ north: { uv: [36, 52, 39, 64] },
5468
+ south: { uv: [43, 52, 46, 64] },
5469
+ up: { uv: [39, 52, 36, 48] },
5470
+ down: { uv: [42, 48, 39, 52] }
5471
+ },
5472
+ rightArm: {
5473
+ ...baseSkinCoordinates.rightArm,
5474
+ west: { uv: [47, 20, 51, 32] },
5475
+ north: { uv: [44, 20, 47, 32] },
5476
+ south: { uv: [51, 20, 54, 32] },
5477
+ up: { uv: [47, 20, 44, 16] },
5478
+ down: { uv: [50, 16, 47, 20] }
5479
+ },
5480
+ leftSleeve: {
5481
+ ...baseSkinCoordinates.leftSleeve,
5482
+ west: { uv: [55, 52, 59, 64] },
5483
+ north: { uv: [52, 52, 55, 64] },
5484
+ south: { uv: [59, 52, 62, 64] },
5485
+ up: { uv: [55, 52, 52, 48] },
5486
+ down: { uv: [58, 48, 55, 52], rotation: 180 }
5487
+ },
5488
+ rightSleeve: {
5489
+ ...baseSkinCoordinates.rightSleeve,
5490
+ west: { uv: [47, 36, 51, 48] },
5491
+ north: { uv: [44, 36, 47, 48] },
5492
+ south: { uv: [51, 36, 54, 48] },
5493
+ up: { uv: [47, 36, 44, 32] },
5494
+ down: { uv: [50, 32, 47, 36], rotation: 180 }
5495
+ }
5496
+ };
5131
5497
 
5132
5498
  // src/skin/SkinGeometries.ts
5133
5499
  var box8inner = [8, 8, 8];
@@ -5167,15 +5533,118 @@ var slimSkinGeometries = {
5167
5533
  };
5168
5534
 
5169
5535
  // src/skin/scene/SkinObject.ts
5170
- var import_ts_deepmerge8 = __toESM(require_dist());
5536
+ var import_ts_deepmerge7 = __toESM(require_dist());
5537
+
5538
+ // src/skin/SkinTextures.ts
5539
+ import { DataTexture, DoubleSide as DoubleSide2, FrontSide as FrontSide2, MeshBasicMaterial as MeshBasicMaterial3, SRGBColorSpace as SRGBColorSpace3 } from "three";
5540
+ var SkinTextures = class {
5541
+ static async get(src, legacy) {
5542
+ const image = await ImageLoader.getData(src);
5543
+ const scale = image.width / 64;
5544
+ if (!Number.isInteger(scale) || scale < 1 || image.height !== image.width && image.height !== image.width / 2) {
5545
+ throw new MineRenderError(`Invalid skin dimensions ${image.width}x${image.height}; expected 64x64 or 64x32, or an integer-scaled equivalent`);
5546
+ }
5547
+ const isLegacy = legacy ?? image.height === image.width / 2;
5548
+ if (!isLegacy && image.height !== image.width) {
5549
+ throw new MineRenderError(`Invalid skin dimensions ${image.width}x${image.height}; non-legacy skins must be square`);
5550
+ }
5551
+ const slim = !isLegacy && this.isSlim(image, scale);
5552
+ const key = `skin:${serializeImageKey({ src })}:${isLegacy}`;
5553
+ const material = Caching.materialCache.get(key, () => {
5554
+ const pixels = isLegacy ? this.normalizeLegacy(image, scale) : new Uint8Array(image.data);
5555
+ return this.createMaterial(pixels, image.width, image.width);
5556
+ });
5557
+ return { material, slim, legacy: isLegacy };
5558
+ }
5559
+ static async getCape(src, layout = "minecraft") {
5560
+ const image = await ImageLoader.getData(src);
5561
+ const scale = image.width / 64;
5562
+ if (layout === "minecraft" && (!Number.isInteger(scale) || scale < 1 || image.height !== image.width / 2)) {
5563
+ throw new MineRenderError(`Invalid cape dimensions ${image.width}x${image.height}; expected 64x32 or an integer-scaled equivalent`);
5564
+ }
5565
+ return Caching.materialCache.get(`cape:${serializeImageKey({ src })}`, () => {
5566
+ return this.createMaterial(new Uint8Array(image.data), image.width, image.height, false);
5567
+ });
5568
+ }
5569
+ static createMaterial(pixels, width, height, transparent = true) {
5570
+ const texture = Textures.initTextureProps(new DataTexture(pixels, width, height));
5571
+ texture.colorSpace = SRGBColorSpace3;
5572
+ texture.flipY = true;
5573
+ texture.needsUpdate = true;
5574
+ return new MeshBasicMaterial3({ map: texture, transparent, side: transparent ? DoubleSide2 : FrontSide2, alphaTest: transparent ? 0.5 : 0 });
5575
+ }
5576
+ static isSlim(image, scale) {
5577
+ for (const [x, y] of [[46, 52], [54, 20]]) {
5578
+ for (let row = y * scale; row < (y + 12) * scale; row++) {
5579
+ for (let column = x * scale; column < (x + 1) * scale; column++) {
5580
+ if (image.data[(row * image.width + column) * 4 + 3] !== 0) return false;
5581
+ }
5582
+ }
5583
+ }
5584
+ return true;
5585
+ }
5586
+ static normalizeLegacy(image, scale) {
5587
+ const pixels = new Uint8Array(image.width * image.width * 4);
5588
+ pixels.set(image.data.subarray(0, image.width * image.width / 2 * 4));
5589
+ for (const [sx, sy, dx, dy, width, height] of [
5590
+ [4, 16, 20, 48, 4, 4],
5591
+ [8, 16, 24, 48, 4, 4],
5592
+ [0, 20, 24, 52, 4, 12],
5593
+ [4, 20, 20, 52, 4, 12],
5594
+ [8, 20, 16, 52, 4, 12],
5595
+ [12, 20, 28, 52, 4, 12],
5596
+ [44, 16, 36, 48, 4, 4],
5597
+ [48, 16, 40, 48, 4, 4],
5598
+ [40, 20, 40, 52, 4, 12],
5599
+ [44, 20, 36, 52, 4, 12],
5600
+ [48, 20, 32, 52, 4, 12],
5601
+ [52, 20, 44, 52, 4, 12]
5602
+ ]) {
5603
+ for (let y = 0; y < height * scale; y++) {
5604
+ for (let x = 0; x < width * scale; x++) {
5605
+ const source = ((sy * scale + y) * image.width + sx * scale + x) * 4;
5606
+ const target = ((dy * scale + y) * image.width + (dx + width) * scale - 1 - x) * 4;
5607
+ pixels.set(image.data.subarray(source, source + 4), target);
5608
+ }
5609
+ }
5610
+ }
5611
+ let clearHat = true;
5612
+ for (let y = 0; y < 32 * scale; y++) {
5613
+ for (let x = 32 * scale; x < image.width; x++) {
5614
+ if (pixels[(y * image.width + x) * 4 + 3] < 128) clearHat = false;
5615
+ }
5616
+ }
5617
+ for (let y = 0; y < image.width; y++) {
5618
+ for (let x = 0; x < image.width; x++) {
5619
+ const alpha = (y * image.width + x) * 4 + 3;
5620
+ if (x < 32 * scale && y < 16 * scale || y >= 16 * scale && y < 32 * scale || x >= 16 * scale && x < 48 * scale && y >= 48 * scale) {
5621
+ pixels[alpha] = 255;
5622
+ } else if (clearHat && x >= 32 * scale && y < 32 * scale) {
5623
+ pixels[alpha] = 0;
5624
+ }
5625
+ }
5626
+ }
5627
+ return pixels;
5628
+ }
5629
+ };
5630
+
5631
+ // src/skin/CapeLayout.ts
5632
+ var capeTextureSizes = {
5633
+ minecraft: [64, 32],
5634
+ optifine: [46, 22],
5635
+ labymod: [22, 17]
5636
+ };
5637
+
5638
+ // src/skin/scene/SkinObject.ts
5171
5639
  var SkinObject = class extends SceneObject {
5172
5640
  constructor(options) {
5173
5641
  super();
5174
5642
  this.slim = false;
5175
- this.legacy = false;
5176
- this.skinTextureWidth = 64;
5177
- this.skinTextureHeight = 64;
5178
- this.options = (0, import_ts_deepmerge8.default)({}, SceneObject.DEFAULT_OPTIONS, options ?? {});
5643
+ this.detectedSlim = false;
5644
+ this.skinLoad = 0;
5645
+ this.capeLoad = 0;
5646
+ this.options = (0, import_ts_deepmerge7.default)({}, SceneObject.DEFAULT_OPTIONS, options ?? {});
5647
+ this.slim = this.options.slim ?? false;
5179
5648
  }
5180
5649
  async init() {
5181
5650
  this.createMeshes();
@@ -5184,15 +5653,13 @@ var SkinObject = class extends SceneObject {
5184
5653
  }
5185
5654
  createMeshes() {
5186
5655
  console.log("#createMeshes");
5187
- const mat = Materials.MISSING_TEXTURE;
5656
+ const mat = this.skinMaterial ?? Materials.MISSING_TEXTURE;
5188
5657
  {
5189
5658
  const headGroup = this.createAndAddGroup("head", 0, 28, 0, "y" /* Y */, -4);
5190
5659
  const headGeo = this.getBoxGeometry("head" /* HEAD */);
5191
5660
  const head = this.createAndAddMesh("head", headGroup, headGeo, mat, "y" /* Y */, 4);
5192
- if (!this.legacy) {
5193
- const hatGeo = this.getBoxGeometry("hat" /* HAT */);
5194
- const hat = this.createAndAddMesh("hat", headGroup, hatGeo, mat, "y" /* Y */, 4);
5195
- }
5661
+ const hatGeo = this.getBoxGeometry("hat" /* HAT */);
5662
+ const hat = this.createAndAddMesh("hat", headGroup, hatGeo, mat, "y" /* Y */, 4);
5196
5663
  }
5197
5664
  {
5198
5665
  const bodyGroup = this.createAndAddGroup("body", 0, 18, 0);
@@ -5235,46 +5702,90 @@ var SkinObject = class extends SceneObject {
5235
5702
  }
5236
5703
  console.log("#createMeshes done");
5237
5704
  }
5238
- setSkinTexture(src) {
5705
+ async setSkinTexture(src) {
5239
5706
  if (typeof src === "undefined") return;
5240
5707
  this.skinTextureSrc = src;
5241
- const mat = Materials.getImage({
5242
- texture: { src },
5243
- transparent: true
5244
- });
5245
- for (let part of SKIN_PARTS) {
5246
- let mesh = this.getMeshByName(part);
5708
+ const load = ++this.skinLoad;
5709
+ const texture = await SkinTextures.get(src, this.options.legacy);
5710
+ if (load !== this.skinLoad) return;
5711
+ this.skinMaterial = texture.material;
5712
+ this.detectedSlim = texture.slim;
5713
+ this.updateSlim(this.options.slim ?? this.detectedSlim);
5714
+ for (const part of SKIN_PARTS) {
5715
+ if (part === "cape" /* CAPE */) continue;
5716
+ const mesh = this.getMeshByName(part);
5247
5717
  if (mesh) {
5248
- mesh.material = mat;
5718
+ mesh.material = texture.material;
5249
5719
  }
5250
5720
  }
5721
+ this.notifyDirty();
5722
+ }
5723
+ /** Load a cape texture in the selected layout, or pass undefined to remove the cape. */
5724
+ async setCapeTexture(src, layout = "minecraft") {
5725
+ const load = ++this.capeLoad;
5726
+ if (src === void 0) {
5727
+ const group = this.getGroupByName("cape" /* CAPE */);
5728
+ if (group) this.remove(group);
5729
+ this.notifyDirty();
5730
+ return;
5731
+ }
5732
+ const material = await SkinTextures.getCape(src, layout);
5733
+ if (load !== this.capeLoad) return;
5734
+ const size = capeTextureSizes[layout];
5735
+ const geometry = this._getBoxGeometryFromDimensions(
5736
+ classicSkinGeometries.cape,
5737
+ classicSkinTextureCoordinates.cape,
5738
+ size,
5739
+ size
5740
+ );
5741
+ const mesh = this.getMeshByName("cape" /* CAPE */);
5742
+ if (mesh) {
5743
+ mesh.material = material;
5744
+ mesh.geometry = geometry;
5745
+ } else {
5746
+ const group = this.createAndAddGroup("cape" /* CAPE */, 0, 24, 2);
5747
+ group.rotation.set(toRadians(-6), Math.PI, 0);
5748
+ const cape = this.createAndAddMesh("cape" /* CAPE */, group, geometry, material);
5749
+ cape.position.set(0, -8, -0.5);
5750
+ }
5751
+ this.notifyDirty();
5251
5752
  }
5252
- //TODO: cape
5753
+ /** Select the arm model, or pass undefined to use texture detection. */
5253
5754
  setSlim(slim) {
5254
- console.log("#setSlim");
5255
- const changed = slim !== this.slim;
5755
+ this.options.slim = slim;
5756
+ this.updateSlim(slim ?? this.detectedSlim);
5757
+ }
5758
+ updateSlim(slim) {
5759
+ if (slim === this.slim) return;
5256
5760
  this.slim = slim;
5257
- if (changed) {
5258
- this.createMeshes();
5259
- }
5260
- if (changed) {
5261
- if (this.skinTextureSrc) {
5262
- this.setSkinTexture(this.skinTextureSrc);
5263
- }
5264
- }
5265
- console.log("#setSlim done");
5761
+ for (const part of ["leftArm" /* LEFT_ARM */, "leftSleeve" /* LEFT_SLEEVE */, "rightArm" /* RIGHT_ARM */, "rightSleeve" /* RIGHT_SLEEVE */]) {
5762
+ const mesh = this.getMeshByName(part);
5763
+ if (mesh) mesh.geometry = this.getBoxGeometry(part);
5764
+ }
5765
+ const offset = slim ? 0.5 : -0.5;
5766
+ const leftArm = this.getGroupByName("leftArm" /* LEFT_ARM */);
5767
+ const rightArm = this.getGroupByName("rightArm" /* RIGHT_ARM */);
5768
+ if (leftArm) leftArm.position.x += offset;
5769
+ if (rightArm) rightArm.position.x -= offset;
5770
+ this.notifyDirty();
5266
5771
  }
5267
- setLegacy(legacy) {
5268
- if (legacy !== this.legacy) {
5269
- }
5270
- this.legacy = legacy;
5772
+ /** Select the texture layout, or pass undefined to detect it from image dimensions. */
5773
+ async setLegacy(legacy) {
5774
+ if (legacy === this.options.legacy) return;
5775
+ this.options.legacy = legacy;
5776
+ if (this.skinTextureSrc) await this.setSkinTexture(this.skinTextureSrc);
5777
+ }
5778
+ dispose() {
5779
+ this.skinLoad++;
5780
+ this.capeLoad++;
5781
+ super.dispose();
5271
5782
  }
5272
5783
  //TODO: layer toggles
5273
5784
  getBoxGeometry(part) {
5274
5785
  console.log("slim", this.slim);
5275
5786
  const coordinates = this.slim ? slimSkinTextureCoordinates : classicSkinTextureCoordinates;
5276
5787
  const geometries = this.slim ? slimSkinGeometries : classicSkinGeometries;
5277
- return this._getBoxGeometryFromDimensions(geometries[part], coordinates[part], [64, 64], [this.skinTextureWidth, this.skinTextureHeight]);
5788
+ return this._getBoxGeometryFromDimensions(geometries[part], coordinates[part], [64, 64], [64, 64]);
5278
5789
  }
5279
5790
  };
5280
5791
 
@@ -5292,7 +5803,7 @@ var _MineRenderScene = class _MineRenderScene extends Scene2 {
5292
5803
  this.dirty = true;
5293
5804
  }
5294
5805
  };
5295
- this.options = (0, import_ts_deepmerge9.default)({}, _MineRenderScene.DEFAULT_OPTIONS, options ?? {});
5806
+ this.options = (0, import_ts_deepmerge8.default)({}, _MineRenderScene.DEFAULT_OPTIONS, options ?? {});
5296
5807
  }
5297
5808
  add(...objects) {
5298
5809
  for (const object of objects) {
@@ -5350,7 +5861,14 @@ var _MineRenderScene = class _MineRenderScene extends Scene2 {
5350
5861
  const obj = await objectSupplier();
5351
5862
  if (obj?.options?.instanceMeshes && asset.key && asset.key?.assetType === "models") {
5352
5863
  console.log("instanceMeshes + key");
5353
- const key = asset.key.serialize();
5864
+ let key = asset.key.serialize();
5865
+ if (isModelObject(obj)) {
5866
+ const { displayPosition, uvLockRotation, tints } = obj.options;
5867
+ if (displayPosition) key += `|display:${displayPosition}`;
5868
+ if (uvLockRotation) key += `|uvlock:${uvLockRotation.join(",")}`;
5869
+ const palette = Object.entries(tints ?? {}).sort(([a], [b]) => Number(a) - Number(b));
5870
+ if (palette.length) key += `|tints:${JSON.stringify(palette)}`;
5871
+ }
5354
5872
  return this.instanceManager.getOrCreate(key, async () => {
5355
5873
  obj.scene = this;
5356
5874
  await obj.init();
@@ -5377,10 +5895,10 @@ var _MineRenderScene = class _MineRenderScene extends Scene2 {
5377
5895
  this.dirty = true;
5378
5896
  const obj = new SkinObject(options);
5379
5897
  obj.scene = this;
5380
- await obj.init();
5381
5898
  if (skin) {
5382
- obj.setSkinTexture(skin);
5899
+ await obj.setSkinTexture(skin);
5383
5900
  }
5901
+ await obj.init();
5384
5902
  parent.add(obj);
5385
5903
  return obj;
5386
5904
  }
@@ -5388,15 +5906,15 @@ var _MineRenderScene = class _MineRenderScene extends Scene2 {
5388
5906
  return this.addSceneObject(entity, () => new EntityObject(entity, options), options, parent);
5389
5907
  }
5390
5908
  };
5391
- _MineRenderScene.DEFAULT_OPTIONS = (0, import_ts_deepmerge9.default)({}, {});
5909
+ _MineRenderScene.DEFAULT_OPTIONS = (0, import_ts_deepmerge8.default)({}, {});
5392
5910
  var MineRenderScene = _MineRenderScene;
5393
5911
  function isMineRenderScene(obj) {
5394
5912
  return obj.isMineRenderScene;
5395
5913
  }
5396
5914
 
5397
5915
  // src/renderer/Renderer.ts
5398
- import { AxesHelper as AxesHelper3, GridHelper, LinearEncoding, OrthographicCamera, PCFSoftShadowMap, PerspectiveCamera, Vector3 as Vector38, WebGLRenderer } from "three";
5399
- var import_ts_deepmerge10 = __toESM(require_dist());
5916
+ import { AxesHelper as AxesHelper2, GridHelper, OrthographicCamera, PCFSoftShadowMap, PerspectiveCamera, SRGBColorSpace as SRGBColorSpace4, Vector3 as Vector39, WebGLRenderer } from "three";
5917
+ var import_ts_deepmerge9 = __toESM(require_dist());
5400
5918
  import Stats from "stats.js";
5401
5919
  import { EffectComposer, RenderPass } from "postprocessing";
5402
5920
 
@@ -5404,11 +5922,11 @@ import { EffectComposer, RenderPass } from "postprocessing";
5404
5922
  import {
5405
5923
  EventDispatcher,
5406
5924
  MOUSE,
5407
- Quaternion as Quaternion3,
5925
+ Quaternion as Quaternion4,
5408
5926
  Spherical,
5409
5927
  TOUCH,
5410
5928
  Vector2 as Vector23,
5411
- Vector3 as Vector37
5929
+ Vector3 as Vector38
5412
5930
  } from "three";
5413
5931
  var _changeEvent = { type: "change" };
5414
5932
  var _startEvent = { type: "start" };
@@ -5422,7 +5940,7 @@ var OrbitControls = class extends EventDispatcher {
5422
5940
  this.domElement = domElement;
5423
5941
  this.domElement.style.touchAction = "none";
5424
5942
  this.enabled = true;
5425
- this.target = new Vector37();
5943
+ this.target = new Vector38();
5426
5944
  this.minDistance = 0;
5427
5945
  this.maxDistance = Infinity;
5428
5946
  this.minZoom = 0;
@@ -5474,11 +5992,11 @@ var OrbitControls = class extends EventDispatcher {
5474
5992
  state = STATE.NONE;
5475
5993
  };
5476
5994
  this.update = function() {
5477
- const offset = new Vector37();
5478
- const quat = new Quaternion3().setFromUnitVectors(object.up, new Vector37(0, 1, 0));
5995
+ const offset = new Vector38();
5996
+ const quat = new Quaternion4().setFromUnitVectors(object.up, new Vector38(0, 1, 0));
5479
5997
  const quatInverse = quat.clone().invert();
5480
- const lastPosition = new Vector37();
5481
- const lastQuaternion = new Quaternion3();
5998
+ const lastPosition = new Vector38();
5999
+ const lastQuaternion = new Quaternion4();
5482
6000
  const twoPI = 2 * Math.PI;
5483
6001
  return function update() {
5484
6002
  const position = scope.object.position;
@@ -5567,7 +6085,7 @@ var OrbitControls = class extends EventDispatcher {
5567
6085
  const spherical = new Spherical();
5568
6086
  const sphericalDelta = new Spherical();
5569
6087
  let scale = 1;
5570
- const panOffset = new Vector37();
6088
+ const panOffset = new Vector38();
5571
6089
  let zoomChanged = false;
5572
6090
  const rotateStart = new Vector23();
5573
6091
  const rotateEnd = new Vector23();
@@ -5593,7 +6111,7 @@ var OrbitControls = class extends EventDispatcher {
5593
6111
  sphericalDelta.phi -= angle;
5594
6112
  }
5595
6113
  const panLeft = function() {
5596
- const v = new Vector37();
6114
+ const v = new Vector38();
5597
6115
  return function panLeft2(distance, objectMatrix) {
5598
6116
  v.setFromMatrixColumn(objectMatrix, 0);
5599
6117
  v.multiplyScalar(-distance);
@@ -5601,7 +6119,7 @@ var OrbitControls = class extends EventDispatcher {
5601
6119
  };
5602
6120
  }();
5603
6121
  const panUp = function() {
5604
- const v = new Vector37();
6122
+ const v = new Vector38();
5605
6123
  return function panUp2(distance, objectMatrix) {
5606
6124
  if (scope.screenSpacePanning === true) {
5607
6125
  v.setFromMatrixColumn(objectMatrix, 1);
@@ -5614,7 +6132,7 @@ var OrbitControls = class extends EventDispatcher {
5614
6132
  };
5615
6133
  }();
5616
6134
  const pan = function() {
5617
- const offset = new Vector37();
6135
+ const offset = new Vector38();
5618
6136
  return function pan2(deltaX, deltaY) {
5619
6137
  const element = scope.domElement;
5620
6138
  if (scope.object.isPerspectiveCamera) {
@@ -6054,8 +6572,6 @@ var MapControls = class extends OrbitControls {
6054
6572
  var _Renderer = class _Renderer {
6055
6573
  constructor(options) {
6056
6574
  this._dirty = true;
6057
- this._animationTimer = void 0;
6058
- this._animationFrame = void 0;
6059
6575
  this._resizeListener = void 0;
6060
6576
  this._disposed = false;
6061
6577
  this._debugHelpers = [];
@@ -6063,9 +6579,9 @@ var _Renderer = class _Renderer {
6063
6579
  this._changeListener = () => {
6064
6580
  this._dirty = true;
6065
6581
  };
6066
- this.options = (0, import_ts_deepmerge10.default)({}, _Renderer.DEFAULT_OPTIONS, options ?? {});
6582
+ this.options = (0, import_ts_deepmerge9.default)({}, _Renderer.DEFAULT_OPTIONS, options ?? {});
6067
6583
  this._animationLoop = this.animate.bind(this);
6068
- this._fpsTimer = this.options.render.fpsLimit > 0 ? 1e3 / this.options.render.fpsLimit : void 0;
6584
+ this._frameInterval = this.options.render.fpsLimit > 0 ? 1e3 / this.options.render.fpsLimit : void 0;
6069
6585
  this._scene = this.createScene();
6070
6586
  this._camera = this.createCamera();
6071
6587
  this._renderer = this.createRenderer();
@@ -6112,13 +6628,13 @@ var _Renderer = class _Renderer {
6112
6628
  renderer.setClearColor(0, 0);
6113
6629
  renderer.shadowMap.enabled = true;
6114
6630
  renderer.shadowMap.type = PCFSoftShadowMap;
6115
- renderer.outputEncoding = LinearEncoding;
6631
+ renderer.outputColorSpace = SRGBColorSpace4;
6116
6632
  renderer.setSize(this.viewWidth, this.viewHeight);
6117
6633
  return renderer;
6118
6634
  }
6119
6635
  createComposer() {
6636
+ if (!this.options.composer.enabled) return void 0;
6120
6637
  const composer = new EffectComposer(this.renderer);
6121
- if (!this.options.composer.enabled) return composer;
6122
6638
  composer.setSize(this.viewWidth, this.viewHeight);
6123
6639
  composer.addPass(new RenderPass(this.scene, this.camera));
6124
6640
  return composer;
@@ -6159,7 +6675,7 @@ var _Renderer = class _Renderer {
6159
6675
  this.scene.add(gridHelper3);
6160
6676
  }
6161
6677
  if (this.options.debug.axes) {
6162
- const axesHelper = new AxesHelper3(64);
6678
+ const axesHelper = new AxesHelper2(64);
6163
6679
  this._debugHelpers.push(axesHelper);
6164
6680
  this.scene.add(axesHelper);
6165
6681
  }
@@ -6227,7 +6743,7 @@ var _Renderer = class _Renderer {
6227
6743
  this.camera.updateProjectionMatrix();
6228
6744
  }
6229
6745
  this.renderer.setSize(width, height);
6230
- this.composer.setSize(width, height);
6746
+ this.composer?.setSize(width, height);
6231
6747
  this._dirty = true;
6232
6748
  }
6233
6749
  //<editor-fold desc="RENDER">
@@ -6246,12 +6762,7 @@ var _Renderer = class _Renderer {
6246
6762
  stop() {
6247
6763
  if (this._disposed) return;
6248
6764
  this.renderer.setAnimationLoop(null);
6249
- if (this._animationTimer)
6250
- clearTimeout(this._animationTimer);
6251
- this._animationTimer = void 0;
6252
- if (this._animationFrame)
6253
- cancelAnimationFrame(this._animationFrame);
6254
- this._animationFrame = void 0;
6765
+ this._nextFrameTime = void 0;
6255
6766
  }
6256
6767
  /**
6257
6768
  * Stops rendering and releases owned resources. Repeated calls have no effect, and a disposed
@@ -6291,25 +6802,30 @@ var _Renderer = class _Renderer {
6291
6802
  }
6292
6803
  }
6293
6804
  this._debugHelpers.length = 0;
6294
- this.composer.dispose();
6805
+ this.composer?.dispose();
6295
6806
  this.renderer.dispose();
6296
6807
  this.renderer.forceContextLoss();
6297
6808
  }
6298
- animate(t) {
6809
+ animate(t = performance.now()) {
6299
6810
  if (this._disposed) return;
6300
- if (this._stats) {
6301
- this._stats.begin();
6302
- }
6303
6811
  if (this._controls?.enabled) {
6304
6812
  this._controls.update();
6305
6813
  }
6306
6814
  if (this._disposed) return;
6307
- if (this.dirty || this.options.render.renderAlways) {
6308
- if (this.options.composer.enabled) {
6309
- this.composer.render();
6310
- } else {
6311
- this.renderer.render(this.scene, this.camera);
6312
- }
6815
+ if (!this.dirty && !this.options.render.renderAlways) return;
6816
+ const interval = this._frameInterval;
6817
+ if (interval) {
6818
+ const next = this._nextFrameTime;
6819
+ if (next !== void 0 && t + 0.1 < next) return;
6820
+ this._nextFrameTime = next !== void 0 && t - next < interval ? next + interval : t + interval;
6821
+ }
6822
+ if (this._stats) {
6823
+ this._stats.begin();
6824
+ }
6825
+ if (this.options.composer.enabled && this.composer) {
6826
+ this.composer.render();
6827
+ } else {
6828
+ this.renderer.render(this.scene, this.camera);
6313
6829
  }
6314
6830
  this.dirty = false;
6315
6831
  if (this._stats) {
@@ -6348,7 +6864,7 @@ var _Renderer = class _Renderer {
6348
6864
  }
6349
6865
  ///
6350
6866
  };
6351
- _Renderer.DEFAULT_OPTIONS = (0, import_ts_deepmerge10.default)({}, {
6867
+ _Renderer.DEFAULT_OPTIONS = (0, import_ts_deepmerge9.default)({}, {
6352
6868
  camera: {
6353
6869
  type: "perspective",
6354
6870
  near: 1,
@@ -6363,8 +6879,8 @@ _Renderer.DEFAULT_OPTIONS = (0, import_ts_deepmerge10.default)({}, {
6363
6879
  top: void 0,
6364
6880
  bottom: void 0
6365
6881
  },
6366
- position: new Vector38(50, 50, 50),
6367
- lookingAt: new Vector38(0, 0, 0)
6882
+ position: new Vector39(50, 50, 50),
6883
+ lookingAt: new Vector39(0, 0, 0)
6368
6884
  },
6369
6885
  render: {
6370
6886
  fpsLimit: 60,
@@ -6397,7 +6913,7 @@ var Skins = class {
6397
6913
  return this.fromUsername(uuidOrUsername);
6398
6914
  }
6399
6915
  static async fromUuid(uuid) {
6400
- return this.getSkinProxySkinUrl(uuid);
6916
+ return this.getMcProxySkinUrl(uuid);
6401
6917
  }
6402
6918
  static async fromUsername(username) {
6403
6919
  return this.usernameToUuid(username).then((uuid) => {
@@ -6424,7 +6940,7 @@ var Skins = class {
6424
6940
  return this.capeFromUsername(uuidOrUsername);
6425
6941
  }
6426
6942
  static async capeFromUuid(uuid) {
6427
- return this.getSkinProxyCapeUrl(uuid);
6943
+ return this.getMcProxyCapeUrl(uuid);
6428
6944
  }
6429
6945
  static async capeFromUsername(username) {
6430
6946
  return this.usernameToUuid(username).then((uuid) => {
@@ -6434,19 +6950,31 @@ var Skins = class {
6434
6950
  return void 0;
6435
6951
  });
6436
6952
  }
6437
- static async capeFromCapesDev(uuid, capeType = "minecraft") {
6438
- return this.getCapesDev(uuid, capeType).then((cape) => {
6439
- if (cape) {
6440
- return cape?.stillImageUrl;
6441
- }
6442
- return void 0;
6443
- });
6953
+ /** Returns a full static cape texture for a username or UUID. */
6954
+ static async capeFromCapesDev(player, capeType = "minecraft") {
6955
+ const cape = await this.getCapesDev(player, capeType);
6956
+ return cape?.exists ? cape.imageUrls?.still?.full : void 0;
6444
6957
  }
6445
6958
  //</editor-fold>
6446
6959
  //<editor-fold desc="Helpers">
6447
6960
  static async usernameToUuid(username) {
6448
- return this.usernameToUuidSkinProxy(username);
6961
+ return this.usernameToUuidMcProxy(username);
6962
+ }
6963
+ static async usernameToUuidMcProxy(username) {
6964
+ return Requests.genericRequest({
6965
+ baseURL: "https://mcproxy.dev",
6966
+ url: "/uuid/" + username
6967
+ }).then((res) => {
6968
+ if (res.status === 200) {
6969
+ return res.data["data"]["id"];
6970
+ }
6971
+ return void 0;
6972
+ }).catch((err) => {
6973
+ console.warn(err);
6974
+ return void 0;
6975
+ });
6449
6976
  }
6977
+ /** @deprecated **/
6450
6978
  static async usernameToUuidSkinProxy(username) {
6451
6979
  return Requests.genericRequest({
6452
6980
  baseURL: "https://minecraft-skin-proxy.inventive.workers.dev",
@@ -6499,18 +7027,15 @@ var Skins = class {
6499
7027
  return void 0;
6500
7028
  });
6501
7029
  }
6502
- static async getCapesDev(uuid, type = "all") {
7030
+ static async getCapesDev(player, type = "all") {
6503
7031
  return Requests.genericRequest({
6504
7032
  baseURL: "https://api.capes.dev",
6505
- url: "/load/" + uuid + "/" + type
7033
+ url: "/load/" + encodeURIComponent(player) + "/" + encodeURIComponent(type)
6506
7034
  }).then((res) => {
6507
7035
  if (res.status === 200) {
6508
7036
  return res.data;
6509
7037
  }
6510
7038
  return void 0;
6511
- }).catch((err) => {
6512
- console.warn(err);
6513
- return void 0;
6514
7039
  });
6515
7040
  }
6516
7041
  static getCrafatarSkinUrl(uuid) {
@@ -6525,20 +7050,28 @@ var Skins = class {
6525
7050
  static getMineSkinPlayerSkinUrl(uuid) {
6526
7051
  return "https://mineskin.org/skin/player/" + uuid;
6527
7052
  }
7053
+ /** @deprecated **/
6528
7054
  static getSkinProxySkinUrl(uuid) {
6529
7055
  return "https://minecraft-skin-proxy.inventive.workers.dev/skin/" + uuid;
6530
7056
  }
7057
+ /** @deprecated **/
6531
7058
  static getSkinProxyCapeUrl(uuid) {
6532
7059
  return "https://minecraft-skin-proxy.inventive.workers.dev/cape/" + uuid;
6533
7060
  }
7061
+ static getMcProxySkinUrl(uuid) {
7062
+ return "https://mcproxy.dev/skin/" + uuid;
7063
+ }
7064
+ static getMcProxyCapeUrl(uuid) {
7065
+ return "https://mcproxy.dev/cape/" + uuid;
7066
+ }
6534
7067
  //</editor-fold>
6535
7068
  };
6536
7069
 
6537
7070
  // src/world/Chunk.ts
6538
- import { Box3 as Box32, Object3D as Object3D6, Vector3 as Vector310 } from "three";
7071
+ import { Box3 as Box32, Object3D as Object3D6, Vector3 as Vector311 } from "three";
6539
7072
 
6540
7073
  // src/world/MineRenderWorld.ts
6541
- import { Vector3 as Vector39 } from "three";
7074
+ import { Vector3 as Vector310 } from "three";
6542
7075
  var MineRenderWorld = class {
6543
7076
  constructor(scene) {
6544
7077
  this._size = 4;
@@ -6549,10 +7082,10 @@ var MineRenderWorld = class {
6549
7082
  }
6550
7083
  getBlockAt(posOrX, y, z) {
6551
7084
  if (typeof posOrX == "number") {
6552
- return this.getBlockAt(new Vector39(posOrX, y, z));
7085
+ return this.getBlockAt(new Vector310(posOrX, y, z));
6553
7086
  }
6554
7087
  if (isTripleArray(posOrX)) {
6555
- return this.getBlockAt(new Vector39(posOrX[0], posOrX[1], posOrX[2]));
7088
+ return this.getBlockAt(new Vector310(posOrX[0], posOrX[1], posOrX[2]));
6556
7089
  }
6557
7090
  const chunk = this.getChunkAt(posOrX);
6558
7091
  if (typeof chunk === "undefined") {
@@ -6563,10 +7096,10 @@ var MineRenderWorld = class {
6563
7096
  }
6564
7097
  async setBlockAt(posOrX, yOrBlock, z, block) {
6565
7098
  if (typeof posOrX == "number") {
6566
- return this.setBlockAt(new Vector39(posOrX, yOrBlock, z), block);
7099
+ return this.setBlockAt(new Vector310(posOrX, yOrBlock, z), block);
6567
7100
  }
6568
7101
  if (isTripleArray(posOrX)) {
6569
- return this.setBlockAt(new Vector39(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
7102
+ return this.setBlockAt(new Vector310(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
6570
7103
  }
6571
7104
  this.validatePosBounds(posOrX);
6572
7105
  const chunk = this.getOrCreateChunkAt(posOrX);
@@ -6612,14 +7145,14 @@ var MineRenderWorld = class {
6612
7145
  return this._chunks[index];
6613
7146
  }
6614
7147
  static worldToScenePosition(pos) {
6615
- return new Vector39(
7148
+ return new Vector310(
6616
7149
  pos.x * 16,
6617
7150
  pos.y * 16,
6618
7151
  pos.z * 16
6619
7152
  );
6620
7153
  }
6621
7154
  static sceneToWorldPosition(pos) {
6622
- return new Vector39(
7155
+ return new Vector310(
6623
7156
  pos.x / 16,
6624
7157
  pos.y / 16,
6625
7158
  pos.z / 16
@@ -6648,7 +7181,7 @@ var MineRenderWorld = class {
6648
7181
  };
6649
7182
 
6650
7183
  // src/world/Chunk.ts
6651
- var p12 = prefix("Chunk");
7184
+ var p11 = prefix("Chunk");
6652
7185
  var Chunk = class _Chunk {
6653
7186
  constructor(scene, x, y, z) {
6654
7187
  this._blocks = [];
@@ -6661,7 +7194,7 @@ var Chunk = class _Chunk {
6661
7194
  scene.add(anchor);
6662
7195
  anchor.position.set(this.x * 256 + 128 - 8, this.y * 256 + 128 - 8, this.z * 256 + 128 - 8);
6663
7196
  addBox3WireframeToObject(
6664
- new Box32(new Vector310(0, 0, 0), new Vector310(256, 256, 256)),
7197
+ new Box32(new Vector311(0, 0, 0), new Vector311(256, 256, 256)),
6665
7198
  anchor,
6666
7199
  16776960,
6667
7200
  4
@@ -6669,10 +7202,10 @@ var Chunk = class _Chunk {
6669
7202
  }
6670
7203
  getBlockAt(posOrX, y, z) {
6671
7204
  if (typeof posOrX == "number") {
6672
- return this.getBlockAt(new Vector310(posOrX, y, z));
7205
+ return this.getBlockAt(new Vector311(posOrX, y, z));
6673
7206
  }
6674
7207
  if (isTripleArray(posOrX)) {
6675
- return this.getBlockAt(new Vector310(posOrX[0], posOrX[1], posOrX[2]));
7208
+ return this.getBlockAt(new Vector311(posOrX[0], posOrX[1], posOrX[2]));
6676
7209
  }
6677
7210
  const pos = this.worldPosToChunkPos(posOrX);
6678
7211
  const index = _Chunk.chunkPosToBlockIndex(pos);
@@ -6680,10 +7213,10 @@ var Chunk = class _Chunk {
6680
7213
  }
6681
7214
  async setBlockAt(posOrX, yOrBlock, z, block) {
6682
7215
  if (typeof posOrX == "number") {
6683
- return this.setBlockAt(new Vector310(posOrX, yOrBlock, z), block);
7216
+ return this.setBlockAt(new Vector311(posOrX, yOrBlock, z), block);
6684
7217
  }
6685
7218
  if (isTripleArray(posOrX)) {
6686
- return this.setBlockAt(new Vector310(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
7219
+ return this.setBlockAt(new Vector311(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
6687
7220
  }
6688
7221
  const worldPos = posOrX;
6689
7222
  const pos = this.worldPosToChunkPos(worldPos);
@@ -6700,7 +7233,7 @@ var Chunk = class _Chunk {
6700
7233
  const index = _Chunk.chunkPosToBlockIndex(pos);
6701
7234
  const current = this._blocks[index];
6702
7235
  if (typeof current !== "undefined") {
6703
- console.debug(p12, "deleting existing block at", pos, worldPos, index);
7236
+ console.debug(p11, "deleting existing block at", pos, worldPos, index);
6704
7237
  current.object.removeFromScene();
6705
7238
  delete this._blocks[index];
6706
7239
  }
@@ -6729,7 +7262,7 @@ var Chunk = class _Chunk {
6729
7262
  }
6730
7263
  async clear() {
6731
7264
  let promises = [];
6732
- let pos = new Vector310();
7265
+ let pos = new Vector311();
6733
7266
  for (let x = 0; x < 16; x++) {
6734
7267
  for (let z = 0; z < 16; z++) {
6735
7268
  for (let y = 0; y < 16; y++) {
@@ -6748,14 +7281,14 @@ var Chunk = class _Chunk {
6748
7281
  return pos.y * 16 * 16 + pos.z * 16 + pos.x;
6749
7282
  }
6750
7283
  worldPosToChunkPos(pos) {
6751
- return new Vector310(
7284
+ return new Vector311(
6752
7285
  pos.x - this.x * 16,
6753
7286
  pos.y - this.y * 16,
6754
7287
  pos.z - this.z * 16
6755
7288
  );
6756
7289
  }
6757
7290
  chunkPosToWorldPos(pos) {
6758
- return new Vector310(
7291
+ return new Vector311(
6759
7292
  this.x * 16 + pos.x,
6760
7293
  this.y * 16 + pos.y,
6761
7294
  this.z * 16 + pos.z
@@ -6766,6 +7299,7 @@ export {
6766
7299
  AXES,
6767
7300
  ArchiveAssetSource,
6768
7301
  AssetKey,
7302
+ AssetLoadError,
6769
7303
  AssetLoader,
6770
7304
  AssetParser,
6771
7305
  AssetSource,
@@ -6775,9 +7309,9 @@ export {
6775
7309
  BUILTIN_GENERATED,
6776
7310
  BasicAssetKey,
6777
7311
  BatchedExecutor,
6778
- BlockEntityModels,
6779
7312
  BlockObject,
6780
7313
  BlockStates,
7314
+ BlockTints,
6781
7315
  BrowserArchiveProxy,
6782
7316
  BrowserCache,
6783
7317
  BrowserEnv,
@@ -6794,8 +7328,8 @@ export {
6794
7328
  DISPLAY_POSITIONS,
6795
7329
  DefaultBlockStates,
6796
7330
  DisplayPosition,
7331
+ DisplayTransforms,
6797
7332
  Entities,
6798
- EntityModels,
6799
7333
  EntityObject,
6800
7334
  Env,
6801
7335
  Geometries,
@@ -6823,6 +7357,7 @@ export {
6823
7357
  OrbitControls,
6824
7358
  PersistentCache,
6825
7359
  Renderer,
7360
+ RequestError,
6826
7361
  Requests,
6827
7362
  SKIN_PARTS,
6828
7363
  SceneInspector,
@@ -6832,6 +7367,7 @@ export {
6832
7367
  SchematicParser,
6833
7368
  SkinObject,
6834
7369
  SkinPart,
7370
+ SkinTextures,
6835
7371
  Skins,
6836
7372
  StructureParser,
6837
7373
  TextureAtlas,
@@ -6846,10 +7382,10 @@ export {
6846
7382
  addWireframeToObject,
6847
7383
  applyElementRotation,
6848
7384
  applyGenericRotation,
6849
- applyModelPartRotation,
6850
7385
  axisToVec3,
6851
7386
  base64decode,
6852
7387
  base64encode,
7388
+ capeTextureSizes,
6853
7389
  captureException,
6854
7390
  changeEvent,
6855
7391
  clampRotationDegrees,