minerender 2.1.0-alpha.53 → 2.1.0-alpha.54

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
@@ -33153,6 +33153,71 @@ var MineRender = (() => {
33153
33153
  alphaTest: 0.5
33154
33154
  });
33155
33155
  }
33156
+ /** Whether vanilla's render type for the mode culls back faces. */
33157
+ static entityModeCulls(mode2 = "cutout") {
33158
+ return mode2 === "cutout_cull" || mode2 === "solid" || mode2 === "eyes" || mode2 === "water_mask";
33159
+ }
33160
+ /** UV offset per tick of entity age for the scrolling modes, as vanilla's texture matrix applies it. */
33161
+ static entityModeScroll(mode2 = "cutout") {
33162
+ if (mode2 === "energy_swirl") return [0.01, 0.01];
33163
+ if (mode2 === "breeze_wind") return [0.02, 0];
33164
+ return void 0;
33165
+ }
33166
+ /**
33167
+ * Entity material for one of vanilla's render types. Entities are unlit here, so the lit and full-bright modes
33168
+ * differ only in blending and depth state. Culling is not a material property: {@link entityModeCulls} decides
33169
+ * whether the geometry gets inward faces.
33170
+ */
33171
+ static createEntityCanvasMaterial(canvas, mode2 = "cutout", tint) {
33172
+ const material = _Materials.createBasicCanvasMaterial(canvas);
33173
+ switch (mode2) {
33174
+ case "cutout":
33175
+ case "cutout_cull":
33176
+ break;
33177
+ case "cutout_z_offset":
33178
+ material.polygonOffset = true;
33179
+ material.polygonOffsetFactor = -1;
33180
+ material.polygonOffsetUnits = -10;
33181
+ break;
33182
+ case "solid":
33183
+ material.alphaTest = 0;
33184
+ break;
33185
+ case "translucent":
33186
+ case "breeze_wind":
33187
+ material.transparent = true;
33188
+ material.alphaTest = 0.1;
33189
+ break;
33190
+ case "translucent_emissive":
33191
+ material.transparent = true;
33192
+ material.alphaTest = 0.1;
33193
+ material.depthWrite = false;
33194
+ break;
33195
+ case "eyes":
33196
+ material.transparent = true;
33197
+ material.alphaTest = 0;
33198
+ material.depthWrite = false;
33199
+ break;
33200
+ case "energy_swirl":
33201
+ material.transparent = true;
33202
+ material.alphaTest = 0.1;
33203
+ material.blending = CustomBlending;
33204
+ material.blendSrc = OneFactor;
33205
+ material.blendDst = OneFactor;
33206
+ material.blendSrcAlpha = ZeroFactor;
33207
+ material.blendDstAlpha = OneFactor;
33208
+ material.color.set(8421504);
33209
+ break;
33210
+ case "water_mask":
33211
+ material.alphaTest = 0;
33212
+ material.colorWrite = false;
33213
+ break;
33214
+ }
33215
+ if (_Materials.entityModeScroll(mode2)) {
33216
+ material.map.wrapS = material.map.wrapT = RepeatWrapping;
33217
+ }
33218
+ if (tint !== void 0) material.color.multiply(new Color(tint));
33219
+ return material;
33220
+ }
33156
33221
  static createGuiCanvasMaterial(canvas) {
33157
33222
  const material = new MeshBasicMaterial({
33158
33223
  map: Textures.createCanvasTexture(canvas),
@@ -83512,32 +83577,44 @@ ${key}` : key;
83512
83577
  static async getLayerList(modelKey) {
83513
83578
  return Object.keys((await this.getModelFile(modelKey))?.layers ?? {});
83514
83579
  }
83580
+ /** The dataset's extra draws on top of `main`; their `when` labels are the states `EntityModelOptions.when` can enable. */
83581
+ static async getPassList(modelKey) {
83582
+ return (await this.getModelFile(modelKey))?.passes ?? [];
83583
+ }
83515
83584
  static async getEntity(modelKey, textureKey, options) {
83516
83585
  const model = await this.getModelFile(modelKey);
83517
83586
  if (!model) return void 0;
83518
- const names = [...new Set(options?.layers ?? [options?.layer ?? "main"])];
83519
- if (names.length === 0) throw new MineRenderError(`Entity ${model.id} requires at least one layer`);
83520
- const selected = names.map((name2) => {
83521
- const layer = model.layers[name2];
83587
+ const passes = options?.layers || options?.layer !== void 0 ? [] : (model.passes ?? []).filter((pass) => pass.when === void 0 || options?.when?.includes(pass.when));
83588
+ const draws = [...new Set(options?.layers ?? [options?.layer ?? "main"])].map((layer) => ({ layer }));
83589
+ if (draws.length === 0) throw new MineRenderError(`Entity ${model.id} requires at least one layer`);
83590
+ draws.push(...passes);
83591
+ const names = [];
83592
+ const selected = draws.map((pass) => {
83593
+ const layer = model.layers[pass.layer];
83522
83594
  if (!layer) {
83523
- throw new MineRenderError(`Entity ${model.id} has no layer "${name2}". Available layers: ${Object.keys(model.layers).join(", ")}`);
83595
+ throw new MineRenderError(`Entity ${model.id} has no layer "${pass.layer}". Available layers: ${Object.keys(model.layers).join(", ")}`);
83524
83596
  }
83525
- return { name: name2, layer };
83597
+ let name2 = pass.layer;
83598
+ for (let n4 = 2; names.includes(name2); n4++) name2 = `${pass.layer}#${n4}`;
83599
+ names.push(name2);
83600
+ return { name: name2, layer, pass };
83526
83601
  });
83527
- const layers = Object.fromEntries(await Promise.all(selected.map(async ({ name: name2, layer }, index) => {
83602
+ const layers = Object.fromEntries(await Promise.all(selected.map(async ({ name: name2, layer, pass }, index) => {
83528
83603
  const override = options?.textures?.[name2] ?? (index === 0 ? textureKey : void 0);
83604
+ const textureLocation = pass.textureLocation ?? layer.textureLocation;
83529
83605
  let texture;
83530
83606
  if (override) {
83531
83607
  const path = isAssetKey(override) ? override.getFullPath() : override.path;
83532
83608
  texture = isAssetKey(override) && override.assetType === "textures" && path.startsWith("entity/") ? override : new AssetKey(override.namespace, path, "textures", "entity", "assets", ".png", isAssetKey(override) ? override.root : void 0);
83533
- } else if (layer.textureLocation !== void 0) {
83534
- texture = AssetKey.parse("textures", layer.textureLocation.replace(/^([^:]+:)?textures\//, "$1"));
83609
+ } else if (textureLocation !== void 0) {
83610
+ texture = AssetKey.parse("textures", textureLocation.replace(/^([^:]+:)?textures\//, "$1"));
83535
83611
  } else {
83536
83612
  texture = await this.resolveTexture(modelKey);
83537
83613
  }
83538
- return [name2, { key: override ?? modelKey, texture, layer }];
83614
+ const render = pass.render ?? layer.render;
83615
+ return [name2, { key: override ?? modelKey, texture, layer, ...render && { render }, ...pass.tint && { tint: pass.tint } }];
83539
83616
  })));
83540
- return { ...layers[names[0]], id: model.id, layers };
83617
+ return { ...layers[names[0]], id: model.id, layers, ...model.transform && { transform: model.transform } };
83541
83618
  }
83542
83619
  static async resolveTexture(modelKey) {
83543
83620
  const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
@@ -83672,6 +83749,7 @@ ${key}` : key;
83672
83749
  init_Caching();
83673
83750
  var import_ts_deepmerge4 = __toESM(require_dist());
83674
83751
  init_three_module();
83752
+ init_Ticker();
83675
83753
  init_model();
83676
83754
  init_ModelTextures();
83677
83755
  init_AssetKey();
@@ -83683,6 +83761,10 @@ ${key}` : key;
83683
83761
  this.entity = entity;
83684
83762
  this.isEntityObject = true;
83685
83763
  this.meshesCreated = false;
83764
+ /** Scrolling materials are owned: their texture offset follows this entity's age. */
83765
+ this.scrollMaterials = [];
83766
+ /** Entity age in ticks, as vanilla's `ageInTicks`; drives the scrolling render modes. */
83767
+ this.age = 0;
83686
83768
  this.options = (0, import_ts_deepmerge4.default)({}, _EntityObject.DEFAULT_OPTIONS, options ?? {});
83687
83769
  }
83688
83770
  async init() {
@@ -83692,6 +83774,25 @@ ${key}` : key;
83692
83774
  dispose() {
83693
83775
  super.dispose();
83694
83776
  }
83777
+ disposeAndRemoveAllChildren() {
83778
+ this.clearScrollMaterials();
83779
+ super.disposeAndRemoveAllChildren();
83780
+ }
83781
+ clearScrollMaterials() {
83782
+ Ticker.remove(this.scrollTicker);
83783
+ this.scrollTicker = void 0;
83784
+ for (const { material } of this.scrollMaterials.splice(0)) {
83785
+ material.map?.dispose();
83786
+ material.dispose();
83787
+ }
83788
+ }
83789
+ /** Applies the entity age to the scrolling materials; the ticker calls this 20 times per second. */
83790
+ updateScroll() {
83791
+ for (const { offset, speed } of this.scrollMaterials) {
83792
+ offset.set(this.age * speed[0] % 1, 0 - this.age * speed[1] % 1);
83793
+ }
83794
+ this.notifyDirty();
83795
+ }
83695
83796
  getLayerGroup(name2) {
83696
83797
  return super.getGroupByName(`layer:${name2}`);
83697
83798
  }
@@ -83722,16 +83823,29 @@ ${key}` : key;
83722
83823
  createMeshes(force = false) {
83723
83824
  if (this.meshesCreated && !force) return;
83724
83825
  const modelRoot = new Object3D();
83725
- if (this.options.flip) modelRoot.scale.set(-1, -1, 1);
83826
+ const transform = this.options.flip === void 0 ? this.entity.transform : void 0;
83827
+ if (transform) {
83828
+ for (const op of transform) {
83829
+ const matrix = new Matrix4();
83830
+ if ("scale" in op) matrix.makeScale(...op.scale);
83831
+ else if ("translate" in op) matrix.makeTranslation(...op.translate);
83832
+ else matrix.makeRotationFromEuler(new Euler(...op.rotate, "ZYX"));
83833
+ modelRoot.matrix.multiply(matrix);
83834
+ }
83835
+ modelRoot.matrixAutoUpdate = false;
83836
+ } else if (this.options.flip ?? true) {
83837
+ modelRoot.scale.set(-1, -1, 1);
83838
+ }
83726
83839
  this.add(modelRoot);
83727
83840
  Object.entries(this.entityLayers).forEach(([name2, layer], index) => {
83728
83841
  const group = this.createGroup(`layer:${name2}`);
83729
83842
  modelRoot.add(group);
83730
- this.createPart("root", layer.layer.root, group, layer.layer.texture, Materials.MISSING_TEXTURE, index);
83843
+ const inward = !Materials.entityModeCulls(layer.render ?? layer.layer.render);
83844
+ this.createPart("root", layer.layer.root, group, layer.layer.texture, Materials.MISSING_TEXTURE, index, inward);
83731
83845
  });
83732
83846
  this.meshesCreated = true;
83733
83847
  }
83734
- createPart(name2, part, parent, textureSize, material, renderOrder) {
83848
+ createPart(name2, part, parent, textureSize, material, renderOrder, inward = true) {
83735
83849
  const anchor = this.createGroup(name2);
83736
83850
  anchor.position.fromArray(part.pose.offset);
83737
83851
  anchor.rotation.set(...part.pose.rotation, "ZYX");
@@ -83750,7 +83864,7 @@ ${key}` : key;
83750
83864
  uv
83751
83865
  ).clone();
83752
83866
  geometry.translate(cube.origin[0] + width / 2, cube.origin[1] + height / 2, cube.origin[2] + depth / 2);
83753
- if (Math.min(width + growX * 2, height + growY * 2, depth + growZ * 2) > 0) {
83867
+ if (inward && Math.min(width + growX * 2, height + growY * 2, depth + growZ * 2) > 0) {
83754
83868
  const index = Array.from(geometry.getIndex().array);
83755
83869
  geometry.setIndex(index.concat(index.slice().reverse()));
83756
83870
  }
@@ -83760,30 +83874,46 @@ ${key}` : key;
83760
83874
  if (this.options.wireframe) addWireframeToMesh(geometry, mesh);
83761
83875
  }
83762
83876
  for (const [childName, child] of Object.entries(part.children)) {
83763
- this.createPart(childName, child, anchor, size, material, renderOrder);
83877
+ this.createPart(childName, child, anchor, size, material, renderOrder, inward);
83764
83878
  }
83765
83879
  }
83766
83880
  async applyTextures() {
83881
+ this.clearScrollMaterials();
83767
83882
  await Promise.all(Object.entries(this.entityLayers).map(async ([name2, layer]) => {
83883
+ const mode2 = layer.render ?? layer.layer.render ?? "cutout";
83884
+ const tint = layer.tint === void 0 ? void 0 : this.options.tints?.[layer.tint];
83885
+ const scroll = Materials.entityModeScroll(mode2);
83768
83886
  const assetKeyStr = this.getTextureKey(layer).serialize();
83769
- const keyStr = `entity:${assetKeyStr}`;
83770
- let mat = Caching.materialCache.getIfPresent(keyStr);
83887
+ const keyStr = `entity:${mode2}:${tint === void 0 ? "" : new Color(tint).getHexString()}:${assetKeyStr}`;
83888
+ let mat = scroll ? void 0 : Caching.materialCache.getIfPresent(keyStr);
83771
83889
  if (!mat) {
83772
83890
  const cachedAsset = Caching.textureAssetCache.getIfPresent(assetKeyStr);
83773
83891
  const imageData = await this.loadTextures(layer);
83774
83892
  if (!imageData) return;
83775
83893
  const canvas = imageData.data.canvas;
83776
- const createMaterial = () => Materials.createBasicCanvasMaterial(canvas);
83777
- mat = cachedAsset && Caching.textureAssetCache.getIfPresent(assetKeyStr) === cachedAsset ? Caching.materialCache.get(keyStr, createMaterial) : createMaterial();
83894
+ const createMaterial = () => Materials.createEntityCanvasMaterial(canvas, mode2, tint);
83895
+ mat = !scroll && cachedAsset && Caching.textureAssetCache.getIfPresent(assetKeyStr) === cachedAsset ? Caching.materialCache.get(keyStr, createMaterial) : createMaterial();
83896
+ if (scroll) {
83897
+ this.scrollMaterials.push({ material: mat, offset: mat.map.offset, speed: scroll });
83898
+ }
83778
83899
  }
83779
83900
  this.getLayerGroup(name2)?.traverse((object) => {
83780
83901
  if (isMesh(object)) object.material = mat;
83781
83902
  });
83782
83903
  }));
83904
+ if (!this.children.length) {
83905
+ this.clearScrollMaterials();
83906
+ } else if (this.scrollMaterials.length) {
83907
+ this.updateScroll();
83908
+ this.scrollTicker = Ticker.add(() => {
83909
+ this.age++;
83910
+ this.updateScroll();
83911
+ });
83912
+ }
83783
83913
  this.notifyDirty();
83784
83914
  }
83785
83915
  };
83786
- _EntityObject.DEFAULT_OPTIONS = (0, import_ts_deepmerge4.default)({}, SceneObject.DEFAULT_OPTIONS, { flip: true });
83916
+ _EntityObject.DEFAULT_OPTIONS = (0, import_ts_deepmerge4.default)({}, SceneObject.DEFAULT_OPTIONS);
83787
83917
  var EntityObject = _EntityObject;
83788
83918
  function isEntityObject(obj) {
83789
83919
  return obj.isEntityObject;
@@ -86328,11 +86458,69 @@ property list uchar int vertex_index
86328
86458
  init_dirname();
86329
86459
  init_buffer2();
86330
86460
  init_process2();
86461
+ init_AssetKey();
86331
86462
  var GuiHelper = class {
86332
86463
  static inventorySlot(slot, origin = [0, 0], offset = [18, 18], rowSize = 9) {
86333
86464
  const [column, row] = typeof slot === "number" ? [slot % rowSize, Math.floor(slot / rowSize)] : slot;
86334
86465
  return [origin[0] + column * offset[0], origin[1] + row * offset[1]];
86335
86466
  }
86467
+ /** Creates crafting-table layers for ingredients and the result; stack counts are not drawn. */
86468
+ static recipe(recipe, options = {}) {
86469
+ const layers = [
86470
+ { name: "background", texture: "minecraft:gui/container/crafting_table", crop: [0, 0, 176, 166] }
86471
+ ];
86472
+ const itemKey = (ingredient) => {
86473
+ const item = typeof ingredient === "string" && !ingredient.startsWith("#") ? ingredient : typeof ingredient === "object" && "item" in ingredient ? ingredient.item : options.resolveIngredient?.(ingredient);
86474
+ if (isAssetKey(item)) return item;
86475
+ if (!item || item.startsWith("#")) {
86476
+ throw new Error(`Select a concrete item for recipe ingredient ${JSON.stringify(ingredient)}`);
86477
+ }
86478
+ const key = AssetKey.parse("models", item);
86479
+ return new AssetKey(key.namespace, key.getFullPath(), "models", "item");
86480
+ };
86481
+ const addIngredient = (ingredient, slot) => {
86482
+ layers.push({
86483
+ name: `ingredient-${slot}`,
86484
+ item: itemKey(ingredient),
86485
+ position: this.inventorySlot(slot, [30, 17], [18, 18], 3)
86486
+ });
86487
+ };
86488
+ switch (recipe.type) {
86489
+ case "minecraft:crafting_shaped":
86490
+ case "crafting_shaped": {
86491
+ const pattern = recipe.pattern;
86492
+ if (!pattern.length || pattern.length > 3 || !pattern[0].length || pattern[0].length > 3 || pattern.some((row) => row.length !== pattern[0].length)) {
86493
+ throw new Error("Crafting patterns must be rectangular, with 1\u20133 rows and columns");
86494
+ }
86495
+ const cells = pattern.flatMap((row, y5) => row.split("").map((symbol, x4) => ({ symbol, x: x4, y: y5 }))).filter((cell) => cell.symbol !== " ");
86496
+ if (!cells.length) throw new Error("Crafting patterns must contain an ingredient");
86497
+ const minX = Math.min(...cells.map((cell) => cell.x)), maxX = Math.max(...cells.map((cell) => cell.x));
86498
+ const minY = Math.min(...cells.map((cell) => cell.y)), maxY = Math.max(...cells.map((cell) => cell.y));
86499
+ const offsetX = minX === maxX ? 1 : 0, offsetY = minY === maxY ? 1 : 0;
86500
+ for (const { symbol, x: x4, y: y5 } of cells) {
86501
+ const ingredient = recipe.key[symbol];
86502
+ if (!ingredient) throw new Error(`Missing recipe ingredient for symbol ${JSON.stringify(symbol)}`);
86503
+ addIngredient(ingredient, (y5 - minY + offsetY) * 3 + x4 - minX + offsetX);
86504
+ }
86505
+ break;
86506
+ }
86507
+ case "minecraft:crafting_shapeless":
86508
+ case "crafting_shapeless":
86509
+ if (!recipe.ingredients.length || recipe.ingredients.length > 9) {
86510
+ throw new Error("Shapeless crafting recipes require 1\u20139 ingredients");
86511
+ }
86512
+ recipe.ingredients.forEach(addIngredient);
86513
+ break;
86514
+ default:
86515
+ throw new Error(`Unsupported crafting recipe type: ${recipe.type}`);
86516
+ }
86517
+ layers.push({
86518
+ name: "result",
86519
+ item: itemKey("id" in recipe.result ? recipe.result.id : recipe.result.item),
86520
+ position: [124, 35]
86521
+ });
86522
+ return layers;
86523
+ }
86336
86524
  };
86337
86525
 
86338
86526
  // src/gui/GuiLayer.ts