minerender 2.1.0-alpha.77 → 2.1.0-alpha.79

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.
package/dist/bundle.js CHANGED
@@ -43053,6 +43053,8 @@ void main() {
43053
43053
  ModelMerger = class {
43054
43054
  /** Loads the parent chain and returns a merged model without modifying the supplied definition. */
43055
43055
  static async mergeWithParents(model) {
43056
+ const parts = model.parts;
43057
+ if (parts) return { ...model, parts: await Promise.all(parts.map((part) => this.mergeWithParents(part))) };
43056
43058
  const models = await this.collectAllParents(model);
43057
43059
  let merged = {};
43058
43060
  for (const source of [...models, model]) {
@@ -43119,6 +43121,7 @@ void main() {
43119
43121
  init_PersistentCache();
43120
43122
  init_AssetKey();
43121
43123
  init_AssetParsers();
43124
+ init_DisplayPosition();
43122
43125
  Models = class _Models {
43123
43126
  // opened lazily: touching the store at import time would hit IndexedDB/disk just for loading
43124
43127
  // the library, and would require an EnvProvider before any entry has had a chance to register
@@ -43133,34 +43136,174 @@ void main() {
43133
43136
  return (await AssetLoader.getFirst([key, legacyKey], "list" /* LIST */))?.asset;
43134
43137
  }).then((r5) => r5?.files ?? []);
43135
43138
  }
43136
- static async loadAndMerge(key) {
43137
- const model = key.type === "item" ? await this.getItemModel(key) : await this.getRaw(key);
43139
+ static async loadAndMerge(key, context = {}) {
43140
+ const model = key.type === "item" ? await this.getItemModel(key, context) : await this.getRaw(key);
43138
43141
  if (!model) {
43139
43142
  return void 0;
43140
43143
  }
43141
43144
  return ModelMerger.mergeWithParents(model);
43142
43145
  }
43143
- static async getItemModel(key) {
43146
+ static async getItemModel(key, context = {}) {
43147
+ const preview = this.snapshotContext(key, context);
43144
43148
  const itemKey = new AssetKey(key.namespace, key.path, "items", void 0, key.rootType, ".json", key.root);
43145
- const model = await this.PERSISTENT_CACHE.getOrLoad(`item-v2:${AssetLoader.persistentKey(itemKey.serialize())}`, async () => {
43149
+ const cacheKey = itemKey.serialize() + this.contextKey(preview);
43150
+ const model = await this.PERSISTENT_CACHE.getOrLoad(`item-v3:${AssetLoader.persistentKey(cacheKey)}`, async () => {
43146
43151
  const result = await AssetLoader.getFirst([itemKey, key], "json" /* JSON */);
43147
43152
  if (!result) return void 0;
43148
43153
  if (result.key.assetType !== "items") return { ...result.asset, key };
43149
- const selected = this.defaultItemModel(result.asset.model, key);
43150
- const modelKey = AssetKey.parse("models", selected.model);
43151
- modelKey.root = key.root;
43152
- const model2 = await this.getRaw(modelKey);
43153
- if (!model2) throw new Error(`Item ${key.toNamespacedString()} references missing model ${selected.model}`);
43154
- return { ...model2, ...selected.special && { special: selected.special }, ...selected.tints && { tints: selected.tints } };
43154
+ const load = async (selected) => {
43155
+ if ("parts" in selected) {
43156
+ return { key, parts: await Promise.all(selected.parts.map(load)) };
43157
+ }
43158
+ if ("item" in selected) {
43159
+ const model3 = await this.getItemModel(selected.item, { displayContext: preview.displayContext });
43160
+ if (!model3) throw new Error(`Item ${key.toNamespacedString()} references missing item ${selected.item.toNamespacedString()}`);
43161
+ return model3;
43162
+ }
43163
+ const modelKey = AssetKey.parse("models", selected.model);
43164
+ modelKey.root = key.root;
43165
+ const model2 = await this.getRaw(modelKey);
43166
+ if (!model2) throw new Error(`Item ${key.toNamespacedString()} references missing model ${selected.model}`);
43167
+ return { ...model2, ...selected.special && { special: selected.special }, ...selected.tints && { tints: selected.tints } };
43168
+ };
43169
+ return load(this.selectItemModel(result.asset.model, key, preview));
43170
+ });
43171
+ const restore = (model2) => ({
43172
+ ...model2,
43173
+ key: Object.assign(new AssetKey("", ""), model2.key),
43174
+ ...model2.parts && { parts: model2.parts.map(restore) }
43155
43175
  });
43156
- return model ? { ...model, key: Object.assign(new AssetKey("", ""), model.key) } : void 0;
43176
+ return model ? restore(model) : void 0;
43177
+ }
43178
+ static contextIdentifier(id) {
43179
+ if (typeof id !== "string" || !/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(id)) {
43180
+ throw new Error(`Invalid item-preview identifier: ${id}`);
43181
+ }
43182
+ return id.includes(":") ? id : `minecraft:${id}`;
43183
+ }
43184
+ static snapshotContext(key, context) {
43185
+ const displayContext = context.displayContext ?? "gui" /* GUI */;
43186
+ if (displayContext !== "none" && !Object.values(DisplayPosition).includes(displayContext)) {
43187
+ throw new Error(`Unsupported item display context: ${displayContext}`);
43188
+ }
43189
+ for (const map of [context.properties, context.components, context.itemReferences]) {
43190
+ if (map !== void 0 && (!map || typeof map !== "object" || Array.isArray(map))) {
43191
+ throw new Error("Item-preview properties, components, and references must be objects keyed by identifier");
43192
+ }
43193
+ }
43194
+ const count = context.count === void 0 ? 1 : context.count;
43195
+ if (!Number.isSafeInteger(count) || count < 0) throw new Error("Item-preview count must be a nonnegative safe integer");
43196
+ const properties = {};
43197
+ for (const [id, value] of Object.entries(context.properties ?? {})) {
43198
+ const property = this.contextIdentifier(id);
43199
+ if (!["boolean", "string", "number"].includes(typeof value) || typeof value === "number" && !Number.isFinite(value)) {
43200
+ throw new Error(`Item-preview property ${property} must be a boolean, string, or finite number`);
43201
+ }
43202
+ if (Object.prototype.hasOwnProperty.call(properties, property)) throw new Error(`Duplicate item-preview property: ${property}`);
43203
+ properties[property] = value;
43204
+ }
43205
+ const components = {};
43206
+ for (const [id, value] of Object.entries(context.components ?? {})) {
43207
+ const component = this.contextIdentifier(id);
43208
+ if (Object.prototype.hasOwnProperty.call(components, component)) throw new Error(`Duplicate item-preview component: ${component}`);
43209
+ components[component] = this.snapshotJson(value);
43210
+ }
43211
+ const itemReferences = {};
43212
+ for (const [id, value] of Object.entries(context.itemReferences ?? {})) {
43213
+ const reference = this.contextIdentifier(id);
43214
+ if (!isAssetKey(value) || value.assetType !== "models" || value.type !== "item") {
43215
+ throw new Error(`Item-preview reference ${reference} must be an item-model AssetKey`);
43216
+ }
43217
+ this.contextIdentifier(`${value.namespace}:${value.path}`);
43218
+ if (Object.prototype.hasOwnProperty.call(itemReferences, reference)) throw new Error(`Duplicate item-preview reference: ${reference}`);
43219
+ itemReferences[reference] = Object.assign(new AssetKey("", ""), value, { root: value.root ?? key.root });
43220
+ }
43221
+ return { displayContext, properties, components, count, itemReferences };
43222
+ }
43223
+ static contextKey(context) {
43224
+ const itemReferences = Object.fromEntries(Object.entries(context.itemReferences).map(([id, key]) => [id, key.serialize()]));
43225
+ return `|item-v3:${JSON.stringify(this.snapshotJson({ ...context, itemReferences }))}`;
43226
+ }
43227
+ static snapshotJson(value, ancestors = /* @__PURE__ */ new Set()) {
43228
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) return value;
43229
+ if (typeof value !== "object" || ancestors.has(value) || !Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
43230
+ throw new Error("Item-preview components must contain finite JSON values without circular references");
43231
+ }
43232
+ ancestors.add(value);
43233
+ const copy = Array.isArray(value) ? Array.from(value, (entry) => this.snapshotJson(entry, ancestors)) : Object.fromEntries(Object.keys(value).sort().map((key) => [key, this.snapshotJson(value[key], ancestors)]));
43234
+ ancestors.delete(value);
43235
+ return copy;
43236
+ }
43237
+ static propertyValue(node, context) {
43238
+ const kind = node.type.replace(/^minecraft:/, "");
43239
+ const property = node.property ? this.contextIdentifier(node.property) : void 0;
43240
+ if (kind === "select" && property === "minecraft:display_context") return context.displayContext;
43241
+ if (property && Object.prototype.hasOwnProperty.call(context.properties, property)) return context.properties[property];
43242
+ const fallback = kind === "condition" ? false : kind === "range_dispatch" ? 0 : void 0;
43243
+ const component = (id) => context.components[this.contextIdentifier(id)];
43244
+ if (property === "minecraft:custom_model_data") {
43245
+ const index = node.index ?? 0;
43246
+ if (!Number.isSafeInteger(index) || index < 0) throw new Error("Item-model custom_model_data index must be a nonnegative integer");
43247
+ const data = component("custom_model_data");
43248
+ if (data === void 0) return fallback;
43249
+ if (!data || typeof data !== "object" || Array.isArray(data)) throw new Error("Item-preview custom_model_data must be an object");
43250
+ const field = kind === "condition" ? "flags" : kind === "select" ? "strings" : "floats";
43251
+ const values = data[field];
43252
+ if (values === void 0) return fallback;
43253
+ if (!Array.isArray(values)) throw new Error(`Item-preview custom_model_data.${field} must be an array`);
43254
+ return index < values.length ? values[index] : fallback;
43255
+ }
43256
+ if (kind === "condition" && property === "minecraft:has_component") {
43257
+ if (node.ignore_default) throw new Error("Item-preview has_component with ignore_default requires an explicit properties override; item defaults are unavailable");
43258
+ return Object.prototype.hasOwnProperty.call(context.components, this.contextIdentifier(node.component));
43259
+ }
43260
+ if (kind === "select") {
43261
+ switch (property) {
43262
+ case "minecraft:component":
43263
+ return component(node.component);
43264
+ case "minecraft:block_state": {
43265
+ const state = component("block_state");
43266
+ if (state === void 0) return void 0;
43267
+ if (!state || typeof state !== "object" || Array.isArray(state)) throw new Error("Item-preview block_state must be an object");
43268
+ if (typeof node.block_state_property !== "string") throw new Error("Item-model block_state requires block_state_property");
43269
+ return Object.prototype.hasOwnProperty.call(state, node.block_state_property) ? state[node.block_state_property] : void 0;
43270
+ }
43271
+ case "minecraft:charge_type": {
43272
+ const projectiles = component("charged_projectiles");
43273
+ if (projectiles === void 0) return "none";
43274
+ if (!Array.isArray(projectiles)) throw new Error("Item-preview charged_projectiles must be an array of item stacks");
43275
+ const ids = projectiles.map((stack) => {
43276
+ if (!stack || typeof stack !== "object" || Array.isArray(stack)) throw new Error("Item-preview charged_projectiles must contain item-stack objects");
43277
+ return this.contextIdentifier(stack.id);
43278
+ });
43279
+ return !ids.length ? "none" : ids.includes("minecraft:firework_rocket") ? "rocket" : "arrow";
43280
+ }
43281
+ }
43282
+ }
43283
+ if (kind === "range_dispatch" && (property === "minecraft:damage" || property === "minecraft:count")) {
43284
+ const damage = property === "minecraft:damage";
43285
+ const maximumComponent = component(damage ? "max_damage" : "max_stack_size");
43286
+ const maximum = maximumComponent === void 0 ? damage ? 0 : 1 : maximumComponent;
43287
+ const damageComponent = component("damage");
43288
+ const value = damage ? damageComponent === void 0 ? 0 : damageComponent : context.count;
43289
+ if (typeof value !== "number" || typeof maximum !== "number" || maximum < 0) throw new Error(`Item-preview ${property} requires numeric components and a nonnegative maximum`);
43290
+ const clamped = Math.max(0, Math.min(maximum, value));
43291
+ return node.normalize === false ? clamped : clamped / maximum;
43292
+ }
43293
+ return fallback;
43157
43294
  }
43158
- // Item previews use the GUI context, false conditions, and zero numeric properties.
43159
- static defaultItemModel(node, key) {
43295
+ static selectItemModel(node, key, context) {
43160
43296
  if (!node || typeof node.type !== "string") {
43161
43297
  throw new Error(`Unsupported item model definition for ${key.toNamespacedString()}`);
43162
43298
  }
43163
43299
  switch (node.type.replace(/^minecraft:/, "")) {
43300
+ case "composite":
43301
+ if (Array.isArray(node.models)) return { parts: node.models.map((child) => this.selectItemModel(child, key, context)) };
43302
+ break;
43303
+ case "bundle/selected_item": {
43304
+ const item = context.itemReferences["minecraft:bundle/selected_item"];
43305
+ return item ? { item } : { parts: [] };
43306
+ }
43164
43307
  case "model":
43165
43308
  if (typeof node.model === "string" && node.model) return { model: node.model, tints: node.tints };
43166
43309
  break;
@@ -43181,17 +43324,29 @@ void main() {
43181
43324
  }
43182
43325
  throw new Error(`Unsupported special item renderer ${special.type} for ${key.toNamespacedString()}`);
43183
43326
  }
43184
- case "condition":
43185
- return this.defaultItemModel(node.on_false, key);
43327
+ case "condition": {
43328
+ const value = this.propertyValue(node, context);
43329
+ if (typeof value !== "boolean") throw new Error(`Item-preview condition ${node.property} requires a boolean`);
43330
+ return this.selectItemModel(value ? node.on_true : node.on_false, key, context);
43331
+ }
43186
43332
  case "select": {
43187
- 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;
43188
- return this.defaultItemModel(selected ?? node.fallback, key);
43333
+ const value = this.propertyValue(node, context);
43334
+ const component = node.property && this.contextIdentifier(node.property) === "minecraft:component";
43335
+ if (!component && value !== void 0 && typeof value !== "string") throw new Error(`Item-preview selector ${node.property} requires a string`);
43336
+ const matches = (candidate) => component ? JSON.stringify(this.snapshotJson(candidate)) === JSON.stringify(this.snapshotJson(value)) : candidate === value;
43337
+ const selected = value === void 0 ? void 0 : node.cases?.find((entry) => matches(entry.when) || Array.isArray(entry.when) && entry.when.some(matches))?.model;
43338
+ return this.selectItemModel(selected ?? node.fallback, key, context);
43189
43339
  }
43190
43340
  case "range_dispatch": {
43341
+ const value = this.propertyValue(node, context);
43342
+ if (typeof value !== "number") throw new Error(`Item-preview range ${node.property} requires a number`);
43343
+ if (Number.isNaN(value)) return this.selectItemModel(node.fallback, key, context);
43344
+ const scaled = value * (node.scale ?? 1);
43345
+ if (!Number.isFinite(scaled)) throw new Error(`Item-preview range ${node.property} requires a finite value and scale`);
43191
43346
  const selected = node.entries?.reduce((match, entry) => {
43192
- return entry.threshold <= 0 && (!match || entry.threshold >= match.threshold) ? entry : match;
43347
+ return entry.threshold <= scaled && (!match || entry.threshold >= match.threshold) ? entry : match;
43193
43348
  }, void 0);
43194
- return this.defaultItemModel(selected?.model ?? node.fallback, key);
43349
+ return this.selectItemModel(selected?.model ?? node.fallback, key, context);
43195
43350
  }
43196
43351
  }
43197
43352
  throw new Error(`Unsupported item model ${node?.type ?? "definition"} for ${key.toNamespacedString()}`);
@@ -43218,25 +43373,28 @@ void main() {
43218
43373
  }
43219
43374
  /**
43220
43375
  * Loads a model and resolves its parent chain. Returns `undefined` when the model is missing.
43221
- * Item keys use GUI context, false conditions, and zero numeric properties.
43376
+ * Item keys default to GUI context and a stack count of 1. Unresolved conditions are false and numeric properties are zero.
43377
+ * Pass `context` to supply component values, property overrides, and item references for a preview.
43378
+ * Composite items retain independently merged children in `ItemModel.parts`.
43222
43379
  *
43223
43380
  * @param key - Model key, for example `AssetKey.parse("models", "minecraft:item/diamond_sword")`.
43224
43381
  */
43225
- static async getMerged(key) {
43382
+ static async getMerged(key, context = {}) {
43226
43383
  if (!key.assetType) {
43227
43384
  key.assetType = "models";
43228
43385
  }
43229
43386
  if (!key.extension) {
43230
43387
  key.extension = ".json";
43231
43388
  }
43232
- const keyStr = key.serialize();
43389
+ const preview = this.snapshotContext(key, context);
43390
+ const keyStr = key.serialize() + (key.type === "item" ? this.contextKey(preview) : "");
43233
43391
  return Caching.mergedModelCache.get(keyStr, (k4) => {
43234
- return _Models.loadAndMerge(key);
43392
+ return _Models.loadAndMerge(key, preview);
43235
43393
  });
43236
43394
  }
43237
43395
  /** Alias for {@link getMerged}. */
43238
- static async get(key) {
43239
- return this.getMerged(key);
43396
+ static async get(key, context = {}) {
43397
+ return this.getMerged(key, context);
43240
43398
  }
43241
43399
  /** Clears persisted models. Use {@link Caching.clear} to also discard in-memory assets. */
43242
43400
  static async clearCache() {
@@ -90454,22 +90612,45 @@ ${shader.fragmentShader}`.replace(
90454
90612
  this.originalModel = originalModel;
90455
90613
  this.isModelObject = true;
90456
90614
  this.specialMaterials = /* @__PURE__ */ new Map();
90615
+ this.geometries = /* @__PURE__ */ new Set();
90457
90616
  this.meshesCreated = false;
90458
90617
  this.options = (0, import_ts_deepmerge5.default)({}, _ModelObject.DEFAULT_OPTIONS, options ?? {});
90459
- if (originalModel.special) this.options.instanceMeshes = false;
90618
+ if (originalModel.special || originalModel.parts) this.options.instanceMeshes = false;
90460
90619
  if (this.options.tints) this.options.tints = { ...this.options.tints };
90461
90620
  this.addEventListener("added", () => this.updateAnimationSubscription());
90462
90621
  this.addEventListener("removed", () => this.updateAnimationSubscription());
90463
90622
  }
90464
90623
  async init() {
90624
+ const parts = this.originalModel.parts;
90625
+ if (parts) {
90626
+ try {
90627
+ for (const part of parts) {
90628
+ const object3 = new _ModelObject(part, { ...this.options, instanceMeshes: false });
90629
+ this.add(object3);
90630
+ await object3.init();
90631
+ }
90632
+ let order = 0;
90633
+ this.iterateAllMeshes((mesh) => {
90634
+ mesh.renderOrder = order++;
90635
+ for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
90636
+ material.transparent = true;
90637
+ }
90638
+ });
90639
+ } catch (error2) {
90640
+ this.disposeAndRemoveAllChildren();
90641
+ throw error2;
90642
+ }
90643
+ this.notifyDirty();
90644
+ return;
90645
+ }
90465
90646
  this.options.tints = await ItemTints.get(this.originalModel, this.options.tints);
90466
90647
  const special = this.originalModel.special;
90467
90648
  if (special) {
90468
- const parts = await SpecialItems.getParts(special, this.originalModel.key?.root);
90649
+ const parts2 = await SpecialItems.getParts(special, this.originalModel.key?.root);
90469
90650
  const transform = this.options.displayPosition ? DisplayTransforms.getMatrix(this.originalModel.display, this.options.displayPosition) : new Matrix4();
90470
90651
  transform.multiply(new Matrix4().makeTranslation(-8, -8, -8));
90471
90652
  try {
90472
- for (const part of parts) {
90653
+ for (const part of parts2) {
90473
90654
  const object3 = new EntityObject(part.model, { flip: false, wireframe: this.options.wireframe });
90474
90655
  object3.matrix.copy(transform).multiply(part.transform);
90475
90656
  object3.matrixWorldNeedsUpdate = true;
@@ -90520,6 +90701,7 @@ ${shader.fragmentShader}`.replace(
90520
90701
  if (this.atlas.model.elements) {
90521
90702
  this.atlas.model.elements?.forEach((el) => {
90522
90703
  const elGeo = this._getBoxGeometryFromElement(el).clone();
90704
+ this.geometries.add(elGeo);
90523
90705
  if (this.options.cullMask) {
90524
90706
  const indices = Array.from(elGeo.getIndex().array);
90525
90707
  elGeo.setIndex(indices.filter((_4, index) => {
@@ -90573,10 +90755,14 @@ ${shader.fragmentShader}`.replace(
90573
90755
  let combinedGeo;
90574
90756
  if (allGeos.length > 0) {
90575
90757
  combinedGeo = mergeBufferGeometries(allGeos);
90758
+ for (const geometry of allGeos) {
90759
+ this.geometries.delete(geometry);
90760
+ }
90576
90761
  } else {
90577
90762
  combinedGeo = new BoxGeometry(16, 16, 16);
90578
90763
  if (displayTransform) DisplayTransforms.apply(combinedGeo, displayTransform);
90579
90764
  }
90765
+ this.geometries.add(combinedGeo);
90580
90766
  combinedGeo.computeBoundingBox();
90581
90767
  let mesh;
90582
90768
  if (this.options.instanceMeshes) {
@@ -90601,6 +90787,9 @@ ${shader.fragmentShader}`.replace(
90601
90787
  const mat = Materials.createShadedCanvasMaterial(this.atlas.image.canvas, this.atlas.hasTransparency, false, true);
90602
90788
  this.atlasMaterial = mat;
90603
90789
  this.atlasTexture = mat.uniforms?.map?.value ?? mat.map;
90790
+ if (this.options.displayPosition === "gui" /* GUI */ && this.originalModel.gui_light === "front" /* FRONT */ && mat.uniforms?.SHADE) {
90791
+ mat.uniforms.SHADE.value = false;
90792
+ }
90604
90793
  this.iterateAllMeshes((mesh) => {
90605
90794
  if (mesh.geometry.hasAttribute("color")) mat.vertexColors = true;
90606
90795
  mesh.material = mat;
@@ -90609,7 +90798,9 @@ ${shader.fragmentShader}`.replace(
90609
90798
  }
90610
90799
  }
90611
90800
  updateAnimationSubscription() {
90612
- const active = !!this.parent && (!this.isInstanced || this.instanceCounter > 0);
90801
+ let root = this;
90802
+ while (root.parent && isModelObject(root.parent)) root = root.parent;
90803
+ const active = !!root.parent && (!this.isInstanced || this.instanceCounter > 0);
90613
90804
  if (active && this.atlas?.hasAnimation && this.atlasTexture) {
90614
90805
  if (!this.unsubscribeAtlas) {
90615
90806
  this.atlasTexture.needsUpdate = true;
@@ -90625,6 +90816,9 @@ ${shader.fragmentShader}`.replace(
90625
90816
  this.unsubscribeAtlas?.();
90626
90817
  this.unsubscribeAtlas = void 0;
90627
90818
  }
90819
+ for (const child of this.children) {
90820
+ if (isModelObject(child)) child.updateAnimationSubscription();
90821
+ }
90628
90822
  }
90629
90823
  nextInstance() {
90630
90824
  const reference = super.nextInstance();
@@ -90644,6 +90838,8 @@ ${shader.fragmentShader}`.replace(
90644
90838
  this.atlasMaterial = void 0;
90645
90839
  for (const material of this.specialMaterials.values()) material.dispose();
90646
90840
  this.specialMaterials.clear();
90841
+ for (const geometry of this.geometries) geometry.dispose();
90842
+ this.geometries.clear();
90647
90843
  super.disposeAndRemoveAllChildren();
90648
90844
  }
90649
90845
  };
@@ -93757,6 +93953,7 @@ ${shader.fragmentShader}`.replace(
93757
93953
  Env: () => Env,
93758
93954
  FluidModelObject: () => FluidModelObject,
93759
93955
  Fonts: () => Fonts,
93956
+ GUI_CONTAINER_LAYOUTS: () => GUI_CONTAINER_LAYOUTS,
93760
93957
  Geometries: () => Geometries,
93761
93958
  GuiHelper: () => GuiHelper,
93762
93959
  GuiLight: () => GuiLight,
@@ -97340,6 +97537,7 @@ property list uchar int vertex_index
97340
97537
  init_buffer2();
97341
97538
  init_process2();
97342
97539
  init_AssetKey();
97540
+ init_ModelTextures();
97343
97541
 
97344
97542
  // src/gui/GuiText.ts
97345
97543
  init_dirname();
@@ -97452,7 +97650,128 @@ property list uchar int vertex_index
97452
97650
  }
97453
97651
 
97454
97652
  // src/gui/GuiHelper.ts
97653
+ var GUI_CONTAINER_LAYOUTS = {
97654
+ generic_54: {
97655
+ texture: "minecraft:gui/container/generic_54",
97656
+ crop: [0, 0, 176, 222],
97657
+ slotOrigin: [8, 18],
97658
+ slotOffset: [18, 18],
97659
+ rowSize: 9,
97660
+ slotCount: 54
97661
+ },
97662
+ crafting_table: {
97663
+ texture: "minecraft:gui/container/crafting_table",
97664
+ crop: [0, 0, 176, 166],
97665
+ slotOrigin: [30, 17],
97666
+ slotOffset: [18, 18],
97667
+ rowSize: 3,
97668
+ slotCount: 9,
97669
+ resultPosition: [124, 35]
97670
+ }
97671
+ };
97455
97672
  var GuiHelper = class {
97673
+ /**
97674
+ * Creates a 182×5 boss bar using the active resource pack. Finite progress is clamped to 0–1.
97675
+ * The fill crops the source image; explicit crops do not apply `.mcmeta` sprite scaling.
97676
+ */
97677
+ static async bossBar(options = {}) {
97678
+ const { color: color3 = "pink", progress = 1, position = [0, 0] } = options;
97679
+ this.validatePosition(position);
97680
+ if (!["pink", "blue", "red", "green", "yellow", "purple", "white"].includes(color3)) {
97681
+ throw new Error(`Unsupported boss bar color: ${color3}`);
97682
+ }
97683
+ if (!Number.isFinite(progress)) throw new Error("Boss bar progress must be finite");
97684
+ const value = Math.max(0, Math.min(1, progress));
97685
+ const width = value > 0 ? Math.floor(value * 181) + 1 : 0;
97686
+ const texture = `minecraft:gui/sprites/boss_bar/${color3}`;
97687
+ const layers = [{
97688
+ name: "boss-bar-background",
97689
+ texture: `${texture}_background`,
97690
+ position: [...position],
97691
+ size: [182, 5]
97692
+ }];
97693
+ if (width > 0) {
97694
+ layers.push({
97695
+ ...await this.cropTexture(`${texture}_progress`, [0, 0, width, 5], [182, 5], position),
97696
+ name: "boss-bar-progress"
97697
+ });
97698
+ }
97699
+ return layers;
97700
+ }
97701
+ /**
97702
+ * Creates a 192×192 book page with optional arrows and supplied text. Buttons are hidden by default.
97703
+ * Callers choose the page content and button states; this helper does not paginate or handle clicks.
97704
+ */
97705
+ static async book(options = {}) {
97706
+ const {
97707
+ position = [0, 0],
97708
+ previous = "hidden",
97709
+ next = "hidden",
97710
+ text: text2,
97711
+ color: color3 = 0,
97712
+ shadow = false,
97713
+ maxWidth = 114,
97714
+ lineHeight = 9,
97715
+ ...style
97716
+ } = options;
97717
+ this.validatePosition(position);
97718
+ for (const state of [previous, next]) {
97719
+ if (!["hidden", "normal", "hover"].includes(state)) throw new Error(`Unsupported book button state: ${state}`);
97720
+ }
97721
+ const layers = [{
97722
+ ...await this.cropTexture("minecraft:gui/book", [0, 0, 192, 192], [256, 256], position),
97723
+ name: "book-background"
97724
+ }];
97725
+ for (const [name2, direction, state, x4] of [
97726
+ ["previous", "backward", previous, 43],
97727
+ ["next", "forward", next, 116]
97728
+ ]) {
97729
+ if (state === "hidden") continue;
97730
+ layers.push({
97731
+ name: `book-${name2}`,
97732
+ texture: `minecraft:gui/sprites/widget/page_${direction}${state === "hover" ? "_highlighted" : ""}`,
97733
+ position: [position[0] + x4, position[1] + 157],
97734
+ size: [23, 13]
97735
+ });
97736
+ }
97737
+ if (text2 !== void 0) {
97738
+ layers.push({
97739
+ name: "book-text",
97740
+ text: text2,
97741
+ position: [position[0] + 36, position[1] + 30],
97742
+ color: color3,
97743
+ shadow,
97744
+ maxWidth,
97745
+ lineHeight,
97746
+ ...style
97747
+ });
97748
+ }
97749
+ return layers;
97750
+ }
97751
+ /** Creates a container background. Use {@link GUI_CONTAINER_LAYOUTS} with {@link inventorySlot} to place its items. */
97752
+ static async container(type, position = [0, 0]) {
97753
+ this.validatePosition(position);
97754
+ if (type !== "generic_54" && type !== "crafting_table") throw new Error(`Unsupported GUI container: ${type}`);
97755
+ const layout = GUI_CONTAINER_LAYOUTS[type];
97756
+ return [{ ...await this.cropTexture(layout.texture, layout.crop, [256, 256], position), name: "background" }];
97757
+ }
97758
+ static validatePosition(position) {
97759
+ if (!Array.isArray(position) || position.length !== 2 || !position.every(Number.isFinite)) {
97760
+ throw new Error("GUI position must contain two finite coordinates");
97761
+ }
97762
+ }
97763
+ static async cropTexture(texture, crop, referenceSize, position) {
97764
+ const image = await ModelTextures.preload(AssetKey.parse("textures", texture));
97765
+ if (!image) throw new Error(`Could not load GUI texture ${texture}`);
97766
+ const [x4, y5, width, height] = crop;
97767
+ const scaleX = image.width / referenceSize[0], scaleY = image.height / referenceSize[1];
97768
+ return {
97769
+ texture,
97770
+ crop: [x4 * scaleX, y5 * scaleY, width * scaleX, height * scaleY],
97771
+ position: [...position],
97772
+ size: [width, height]
97773
+ };
97774
+ }
97456
97775
  /** Sizes a tooltip around supplied text; the first entry is its title. */
97457
97776
  static async tooltip(lines, options = {}) {
97458
97777
  if (!lines.length) return [];
@@ -97487,8 +97806,9 @@ property list uchar int vertex_index
97487
97806
  }
97488
97807
  /** Creates crafting-table layers for ingredients and the result; stack counts are not drawn. */
97489
97808
  static recipe(recipe, options = {}) {
97809
+ const layout = GUI_CONTAINER_LAYOUTS.crafting_table;
97490
97810
  const layers = [
97491
- { name: "background", texture: "minecraft:gui/container/crafting_table", crop: [0, 0, 176, 166] }
97811
+ { name: "background", texture: layout.texture, crop: [...layout.crop] }
97492
97812
  ];
97493
97813
  const itemKey = (ingredient) => {
97494
97814
  const item = typeof ingredient === "string" && !ingredient.startsWith("#") ? ingredient : typeof ingredient === "object" && "item" in ingredient ? ingredient.item : options.resolveIngredient?.(ingredient);
@@ -97503,7 +97823,7 @@ property list uchar int vertex_index
97503
97823
  layers.push({
97504
97824
  name: `ingredient-${slot}`,
97505
97825
  item: itemKey(ingredient),
97506
- position: this.inventorySlot(slot, [30, 17], [18, 18], 3)
97826
+ position: this.inventorySlot(slot, layout.slotOrigin, layout.slotOffset, layout.rowSize)
97507
97827
  });
97508
97828
  };
97509
97829
  switch (recipe.type) {
@@ -97538,7 +97858,7 @@ property list uchar int vertex_index
97538
97858
  layers.push({
97539
97859
  name: "result",
97540
97860
  item: itemKey("id" in recipe.result ? recipe.result.id : recipe.result.item),
97541
- position: [124, 35]
97861
+ position: [...layout.resultPosition]
97542
97862
  });
97543
97863
  return layers;
97544
97864
  }
@@ -97649,7 +97969,6 @@ property list uchar int vertex_index
97649
97969
  init_Materials();
97650
97970
  init_ModelObject();
97651
97971
  init_DisplayPosition();
97652
- init_GuiLight();
97653
97972
  var GuiObject = class extends SceneObject {
97654
97973
  constructor(textureLayers, options) {
97655
97974
  super({ ...options, instanceMeshes: false, mergeMeshes: false });
@@ -97717,12 +98036,16 @@ property list uchar int vertex_index
97717
98036
  mesh2.geometry.computeBoundingBox();
97718
98037
  bounds.union(mesh2.geometry.boundingBox.clone().applyMatrix4(new Matrix4().multiplyMatrices(inverse, mesh2.matrixWorld)));
97719
98038
  });
97720
- item.position.z = depth - bounds.min.z;
97721
- depth += bounds.max.z - bounds.min.z + 0.01;
98039
+ item.position.z = depth;
98040
+ if (!bounds.isEmpty()) {
98041
+ item.position.z -= bounds.min.z;
98042
+ depth += bounds.max.z - bounds.min.z;
98043
+ this.bounds.expandByPoint(new Vector2(bounds.min.x, -bounds.max.y));
98044
+ this.bounds.expandByPoint(new Vector2(bounds.max.x, -bounds.min.y));
98045
+ }
98046
+ depth += 0.01;
97722
98047
  this.bounds.expandByPoint(new Vector2(x4, y5));
97723
98048
  this.bounds.expandByPoint(new Vector2(x4 + width2, y5 + height2));
97724
- this.bounds.expandByPoint(new Vector2(bounds.min.x, -bounds.max.y));
97725
- this.bounds.expandByPoint(new Vector2(bounds.max.x, -bounds.min.y));
97726
98049
  continue;
97727
98050
  }
97728
98051
  const key = typeof layer.texture === "string" ? AssetKey.parse("textures", layer.texture) : layer.texture;
@@ -97762,15 +98085,13 @@ property list uchar int vertex_index
97762
98085
  item.name = `group:${layer.name ?? index}`;
97763
98086
  this.add(item);
97764
98087
  await item.init();
97765
- item.iterateAllMeshes((mesh) => {
97766
- if (!model.special) this.geometries.add(mesh.geometry);
98088
+ const meshes = [];
98089
+ item.iterateAllMeshes((mesh) => meshes.push(mesh));
98090
+ meshes.forEach((mesh, partIndex) => {
97767
98091
  mesh.name = `mesh:${layer.name ?? index}`;
97768
- mesh.renderOrder = index;
98092
+ mesh.renderOrder = index + partIndex / (meshes.length + 1) * 0.5;
97769
98093
  for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
97770
98094
  material.transparent = true;
97771
- if (model.gui_light === "front" /* FRONT */ && material.uniforms?.SHADE) {
97772
- material.uniforms.SHADE.value = false;
97773
- }
97774
98095
  }
97775
98096
  });
97776
98097
  return item;