minerender 2.1.0-alpha.53 → 2.1.0-alpha.55

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.
@@ -290,6 +290,12 @@ var init_Caching = __esm({
290
290
  static {
291
291
  this.entityTextureCache = _Caching.createAssetCache();
292
292
  }
293
+ static {
294
+ this.entityAnimationCache = _Caching.createAssetCache();
295
+ }
296
+ static {
297
+ this.blockEntityIndexCache = _Caching.createAssetCache();
298
+ }
293
299
  static createAssetCache() {
294
300
  return Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
295
301
  }
@@ -314,7 +320,9 @@ var init_Caching = __esm({
314
320
  this.defaultBlockStatesCache,
315
321
  this.blockTintCache,
316
322
  this.entityModelCache,
317
- this.entityTextureCache
323
+ this.entityTextureCache,
324
+ this.entityAnimationCache,
325
+ this.blockEntityIndexCache
318
326
  ];
319
327
  }
320
328
  static clear() {
@@ -843,7 +851,7 @@ var init_Textures = __esm({
843
851
  });
844
852
 
845
853
  // src/Materials.ts
846
- import { Color, DoubleSide, FrontSide, MeshBasicMaterial, MeshStandardMaterial, ShaderChunk, ShaderMaterial } from "three";
854
+ import { Color, CustomBlending, DoubleSide, OneFactor, RepeatWrapping, FrontSide, MeshBasicMaterial, MeshStandardMaterial, ShaderChunk, ShaderMaterial, ZeroFactor } from "three";
847
855
  var Materials;
848
856
  var init_Materials = __esm({
849
857
  "src/Materials.ts"() {
@@ -885,6 +893,71 @@ var init_Materials = __esm({
885
893
  alphaTest: 0.5
886
894
  });
887
895
  }
896
+ /** Whether vanilla's render type for the mode culls back faces. */
897
+ static entityModeCulls(mode = "cutout") {
898
+ return mode === "cutout_cull" || mode === "solid" || mode === "eyes" || mode === "water_mask";
899
+ }
900
+ /** UV offset per tick of entity age for the scrolling modes, as vanilla's texture matrix applies it. */
901
+ static entityModeScroll(mode = "cutout") {
902
+ if (mode === "energy_swirl") return [0.01, 0.01];
903
+ if (mode === "breeze_wind") return [0.02, 0];
904
+ return void 0;
905
+ }
906
+ /**
907
+ * Entity material for one of vanilla's render types. Entities are unlit here, so the lit and full-bright modes
908
+ * differ only in blending and depth state. Culling is not a material property: {@link entityModeCulls} decides
909
+ * whether the geometry gets inward faces.
910
+ */
911
+ static createEntityCanvasMaterial(canvas, mode = "cutout", tint) {
912
+ const material = _Materials.createBasicCanvasMaterial(canvas);
913
+ switch (mode) {
914
+ case "cutout":
915
+ case "cutout_cull":
916
+ break;
917
+ case "cutout_z_offset":
918
+ material.polygonOffset = true;
919
+ material.polygonOffsetFactor = -1;
920
+ material.polygonOffsetUnits = -10;
921
+ break;
922
+ case "solid":
923
+ material.alphaTest = 0;
924
+ break;
925
+ case "translucent":
926
+ case "breeze_wind":
927
+ material.transparent = true;
928
+ material.alphaTest = 0.1;
929
+ break;
930
+ case "translucent_emissive":
931
+ material.transparent = true;
932
+ material.alphaTest = 0.1;
933
+ material.depthWrite = false;
934
+ break;
935
+ case "eyes":
936
+ material.transparent = true;
937
+ material.alphaTest = 0;
938
+ material.depthWrite = false;
939
+ break;
940
+ case "energy_swirl":
941
+ material.transparent = true;
942
+ material.alphaTest = 0.1;
943
+ material.blending = CustomBlending;
944
+ material.blendSrc = OneFactor;
945
+ material.blendDst = OneFactor;
946
+ material.blendSrcAlpha = ZeroFactor;
947
+ material.blendDstAlpha = OneFactor;
948
+ material.color.set(8421504);
949
+ break;
950
+ case "water_mask":
951
+ material.alphaTest = 0;
952
+ material.colorWrite = false;
953
+ break;
954
+ }
955
+ if (_Materials.entityModeScroll(mode)) {
956
+ material.map.wrapS = material.map.wrapT = RepeatWrapping;
957
+ }
958
+ if (tint !== void 0) material.color.multiply(new Color(tint));
959
+ return material;
960
+ }
888
961
  static createGuiCanvasMaterial(canvas) {
889
962
  const material = new MeshBasicMaterial({
890
963
  map: Textures.createCanvasTexture(canvas),
@@ -3096,7 +3169,7 @@ var init_InstanceReference = __esm({
3096
3169
  });
3097
3170
 
3098
3171
  // src/renderer/SceneObject.ts
3099
- import { Color as Color2, Euler as Euler3, InstancedBufferAttribute, InstancedMesh, MathUtils, Matrix4 as Matrix43, Mesh as Mesh3, Object3D as Object3D2, Quaternion as Quaternion3, Vector3 as Vector35 } from "three";
3172
+ import { Color as Color2, Euler as Euler3, InstancedBufferAttribute, InstancedMesh, MathUtils, Matrix4 as Matrix44, Mesh as Mesh3, Object3D as Object3D2, Quaternion as Quaternion3, Vector3 as Vector35 } from "three";
3100
3173
  function isSceneObject(obj) {
3101
3174
  return obj.isSceneObject;
3102
3175
  }
@@ -3312,7 +3385,7 @@ var init_SceneObject = __esm({
3312
3385
  mesh.instanceColor.needsUpdate = true;
3313
3386
  }
3314
3387
  if (this._scene) this._scene.stats.instanceCount++;
3315
- this.setMatrixAt(index, new Matrix43());
3388
+ this.setMatrixAt(index, new Matrix44());
3316
3389
  return reference;
3317
3390
  }
3318
3391
  /** Returns the live reference for a raycast hit on this object's instance mesh. */
@@ -3325,7 +3398,7 @@ var init_SceneObject = __esm({
3325
3398
  removeInstanceAt(index) {
3326
3399
  if (!this.instanceReferences.has(index)) return;
3327
3400
  const mesh = this.instanceMesh;
3328
- this.setMatrixAt(index, new Matrix43().makeScale(0, 0, 0));
3401
+ this.setMatrixAt(index, new Matrix44().makeScale(0, 0, 0));
3329
3402
  this.instanceReferences.delete(index);
3330
3403
  this.freeInstanceIndices.push(index);
3331
3404
  this._instanceCounter = this.instanceReferences.size;
@@ -3355,7 +3428,7 @@ var init_SceneObject = __esm({
3355
3428
  }
3356
3429
  //</editor-fold>
3357
3430
  //<editor-fold desc="TRANSFORMATION">
3358
- getMatrixAt(index, matrix = new Matrix43()) {
3431
+ getMatrixAt(index, matrix = new Matrix44()) {
3359
3432
  if (!this.instanceReferences.has(index)) throw new MineRenderError("Instance is not active");
3360
3433
  this.instanceMesh.getMatrixAt(index, matrix);
3361
3434
  return matrix;
@@ -3374,7 +3447,7 @@ var init_SceneObject = __esm({
3374
3447
  const oldPosition = new Vector35();
3375
3448
  const oldRotation = new Quaternion3();
3376
3449
  const oldScale = new Vector35();
3377
- const matrix = new Matrix43();
3450
+ const matrix = new Matrix44();
3378
3451
  if (!scale || !rotation || !position) {
3379
3452
  this.getMatrixAt(index, matrix).decompose(oldPosition, oldRotation, oldScale);
3380
3453
  }
@@ -4122,7 +4195,7 @@ var init_ModelCulling = __esm({
4122
4195
  });
4123
4196
 
4124
4197
  // src/model/scene/ModelObject.ts
4125
- import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute5, Color as Color7, Euler as Euler5, Matrix4 as Matrix46 } from "three";
4198
+ import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute5, Color as Color8, Euler as Euler6, Matrix4 as Matrix48 } from "three";
4126
4199
  function isModelObject(obj) {
4127
4200
  return obj.isModelObject;
4128
4201
  }
@@ -4188,7 +4261,7 @@ var init_ModelObject = __esm({
4188
4261
  elGeo.clearGroups();
4189
4262
  }
4190
4263
  if (this.options.uvLockRotation) {
4191
- UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler5(...this.options.uvLockRotation));
4264
+ UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler6(...this.options.uvLockRotation));
4192
4265
  }
4193
4266
  if (this.options.tints) {
4194
4267
  const colors = new Float32Array(elGeo.getAttribute("position").count * 3).fill(1);
@@ -4197,19 +4270,19 @@ var init_ModelObject = __esm({
4197
4270
  if (tintIndex === void 0 || tintIndex < 0) continue;
4198
4271
  const tint = this.options.tints[tintIndex];
4199
4272
  if (tint === void 0) continue;
4200
- const color = new Color7(tint);
4273
+ const color = new Color8(tint);
4201
4274
  for (let vertex = 0; vertex < 4; vertex++) {
4202
4275
  color.toArray(colors, (faceIndex * 4 + vertex) * 3);
4203
4276
  }
4204
4277
  }
4205
4278
  elGeo.setAttribute("color", new BufferAttribute5(colors, 3));
4206
4279
  }
4207
- elGeo.applyMatrix4(new Matrix46().makeTranslation((el.to[0] - el.from[0]) / 2, (el.to[1] - el.from[1]) / 2, (el.to[2] - el.from[2]) / 2));
4208
- elGeo.applyMatrix4(new Matrix46().makeTranslation(el.from[0], el.from[1], el.from[2]));
4280
+ elGeo.applyMatrix4(new Matrix48().makeTranslation((el.to[0] - el.from[0]) / 2, (el.to[1] - el.from[1]) / 2, (el.to[2] - el.from[2]) / 2));
4281
+ elGeo.applyMatrix4(new Matrix48().makeTranslation(el.from[0], el.from[1], el.from[2]));
4209
4282
  if (el.rotation) {
4210
4283
  applyElementRotation(el.rotation, elGeo);
4211
4284
  }
4212
- elGeo.applyMatrix4(new Matrix46().makeTranslation(-8, -8, -8));
4285
+ elGeo.applyMatrix4(new Matrix48().makeTranslation(-8, -8, -8));
4213
4286
  if (displayTransform) DisplayTransforms.apply(elGeo, displayTransform);
4214
4287
  if (this.options.mergeMeshes) {
4215
4288
  allGeos.push(elGeo);
@@ -4461,7 +4534,7 @@ __export(FluidModelObject_exports, {
4461
4534
  FluidModelObject: () => FluidModelObject,
4462
4535
  sampleFluid: () => sampleFluid
4463
4536
  });
4464
- import { BufferAttribute as BufferAttribute6, Color as Color8, DoubleSide as DoubleSide3 } from "three";
4537
+ import { BufferAttribute as BufferAttribute6, Color as Color9, DoubleSide as DoubleSide3 } from "three";
4465
4538
  function sampleFluid(kind, sample) {
4466
4539
  const cells = [];
4467
4540
  const key = [];
@@ -4512,7 +4585,7 @@ var init_FluidModelObject = __esm({
4512
4585
  }
4513
4586
  }
4514
4587
  geometry.clearGroups();
4515
- const color = new Color8(this.options.tints?.[0] ?? (this.kind === "water" ? 4159204 : 16777215));
4588
+ const color = new Color9(this.options.tints?.[0] ?? (this.kind === "water" ? 4159204 : 16777215));
4516
4589
  const colors = new Float32Array(geometry.getAttribute("position").count * 3);
4517
4590
  for (let i = 0; i < colors.length; i += 3) color.toArray(colors, i);
4518
4591
  geometry.setAttribute("color", new BufferAttribute6(colors, 3));
@@ -4778,6 +4851,59 @@ function shutdown() {
4778
4851
  init_AssetKey();
4779
4852
  init_AssetLoader();
4780
4853
  init_Assets();
4854
+
4855
+ // src/assets/BlockEntities.ts
4856
+ init_AssetKey();
4857
+ init_AssetLoader();
4858
+ init_source();
4859
+ init_Caching();
4860
+ import { Matrix4 as Matrix43 } from "three";
4861
+ var BlockEntities = class {
4862
+ /**
4863
+ * Loads the block index of a version. Versions and sources without one yield an empty index, and so does
4864
+ * a failed load: the index is optional, and blocks must not retry or fail with it one by one. The empty
4865
+ * result is cached like any other; `Caching.clear()` or `AssetLoader.setVersion()` forces a new attempt.
4866
+ */
4867
+ static async getIndex(root) {
4868
+ const key = new AssetKey("entity-models", "blocks", void 0, void 0, void 0, ".json", root);
4869
+ key.rootType = void 0;
4870
+ const index = await Caching.blockEntityIndexCache.get(key.serialize(), async () => {
4871
+ try {
4872
+ return await AssetLoader.get(key, "json" /* JSON */) ?? {};
4873
+ } catch (error) {
4874
+ console.warn("Could not load the block entity index; block entities are drawn as plain block models", error);
4875
+ return {};
4876
+ }
4877
+ });
4878
+ return index ?? {};
4879
+ }
4880
+ /** The index entry of a block ID such as `minecraft:chest`. */
4881
+ static entry(index, block) {
4882
+ const entry = Object.prototype.hasOwnProperty.call(index, block) ? index[block] : void 0;
4883
+ return Array.isArray(entry?.parts) ? entry : void 0;
4884
+ }
4885
+ /** Selects the parts and placement for a block ID such as `minecraft:chest`; undefined when nothing is drawn. */
4886
+ static resolve(index, block, state = {}) {
4887
+ const entry = this.entry(index, block);
4888
+ if (!entry) return void 0;
4889
+ const value = (property) => String(state[property]).toLowerCase();
4890
+ const parts = entry.parts.filter((part) => Object.entries(part.when ?? {}).every(([property, values]) => String(values).split("|").includes(value(property))));
4891
+ if (!parts.length) return void 0;
4892
+ let rotation = 0;
4893
+ if (entry.rotation) {
4894
+ const selected = value(entry.rotation.property);
4895
+ rotation = "degrees" in entry.rotation ? entry.rotation.degrees[selected] : Number(selected) * entry.rotation.step;
4896
+ if (!Number.isFinite(rotation)) rotation = 0;
4897
+ }
4898
+ return { parts, rotation, translation: entry.translation ?? [0, 0, 0] };
4899
+ }
4900
+ /** Placement in block space (0..16 per axis), applied on top of the model's own transform. */
4901
+ static matrix(resolved, matrix = new Matrix43()) {
4902
+ return matrix.makeTranslation(8, 0, 8).multiply(new Matrix43().makeRotationY(resolved.rotation * Math.PI / 180)).multiply(new Matrix43().makeTranslation(resolved.translation[0] - 8, resolved.translation[1], resolved.translation[2] - 8));
4903
+ }
4904
+ };
4905
+
4906
+ // src/assets/index.ts
4781
4907
  init_BlockStates();
4782
4908
 
4783
4909
  // src/assets/Entities.ts
@@ -4809,32 +4935,53 @@ var Entities = class {
4809
4935
  static async getLayerList(modelKey) {
4810
4936
  return Object.keys((await this.getModelFile(modelKey))?.layers ?? {});
4811
4937
  }
4938
+ /** The dataset's extra draws on top of `main`; their `when` labels are the states `EntityModelOptions.when` can enable. */
4939
+ static async getPassList(modelKey) {
4940
+ return (await this.getModelFile(modelKey))?.passes ?? [];
4941
+ }
4812
4942
  static async getEntity(modelKey, textureKey, options) {
4813
4943
  const model = await this.getModelFile(modelKey);
4814
4944
  if (!model) return void 0;
4815
- const names = [...new Set(options?.layers ?? [options?.layer ?? "main"])];
4816
- if (names.length === 0) throw new MineRenderError(`Entity ${model.id} requires at least one layer`);
4817
- const selected = names.map((name) => {
4818
- const layer = model.layers[name];
4945
+ const passes = options?.layers || options?.layer !== void 0 ? [] : (model.passes ?? []).filter((pass) => pass.when === void 0 || options?.when?.includes(pass.when));
4946
+ const draws = [...new Set(options?.layers ?? [options?.layer ?? "main"])].map((layer) => ({ layer }));
4947
+ if (draws.length === 0) throw new MineRenderError(`Entity ${model.id} requires at least one layer`);
4948
+ draws.push(...passes);
4949
+ const names = [];
4950
+ const selected = draws.map((pass) => {
4951
+ const layer = model.layers[pass.layer];
4819
4952
  if (!layer) {
4820
- throw new MineRenderError(`Entity ${model.id} has no layer "${name}". Available layers: ${Object.keys(model.layers).join(", ")}`);
4953
+ throw new MineRenderError(`Entity ${model.id} has no layer "${pass.layer}". Available layers: ${Object.keys(model.layers).join(", ")}`);
4821
4954
  }
4822
- return { name, layer };
4955
+ let name = pass.layer;
4956
+ for (let n = 2; names.includes(name); n++) name = `${pass.layer}#${n}`;
4957
+ names.push(name);
4958
+ return { name, layer, pass };
4823
4959
  });
4824
- const layers = Object.fromEntries(await Promise.all(selected.map(async ({ name, layer }, index) => {
4960
+ const layers = Object.fromEntries(await Promise.all(selected.map(async ({ name, layer, pass }, index) => {
4825
4961
  const override = options?.textures?.[name] ?? (index === 0 ? textureKey : void 0);
4962
+ const textureLocation = pass.textureLocation ?? layer.textureLocation;
4826
4963
  let texture;
4827
4964
  if (override) {
4828
4965
  const path = isAssetKey(override) ? override.getFullPath() : override.path;
4829
4966
  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);
4830
- } else if (layer.textureLocation !== void 0) {
4831
- texture = AssetKey.parse("textures", layer.textureLocation.replace(/^([^:]+:)?textures\//, "$1"));
4967
+ } else if (textureLocation !== void 0) {
4968
+ texture = AssetKey.parse("textures", textureLocation.replace(/^([^:]+:)?textures\//, "$1"));
4832
4969
  } else {
4833
4970
  texture = await this.resolveTexture(modelKey);
4834
4971
  }
4835
- return [name, { key: override ?? modelKey, texture, layer }];
4972
+ const render = pass.render ?? layer.render;
4973
+ return [name, { key: override ?? modelKey, texture, layer, ...render && { render }, ...pass.tint && { tint: pass.tint } }];
4836
4974
  })));
4837
- return { ...layers[names[0]], id: model.id, layers };
4975
+ return { ...layers[names[0]], id: model.id, layers, ...model.transform && { transform: model.transform } };
4976
+ }
4977
+ /**
4978
+ * Vanilla's keyframe animations of a model by name, or undefined when the model or the selected version has none.
4979
+ */
4980
+ static async getAnimations(modelKey) {
4981
+ const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
4982
+ const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models/animations", ".json");
4983
+ const file = await Caching.entityAnimationCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
4984
+ return file?.animations;
4838
4985
  }
4839
4986
  static async resolveTexture(modelKey) {
4840
4987
  const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
@@ -4912,15 +5059,129 @@ function trimCanvas(source) {
4912
5059
  return canvas;
4913
5060
  }
4914
5061
 
5062
+ // src/entity/EntityAnimation.ts
5063
+ var ENTITY_ANIMATION_CHANNELS = ["position", "rotation", "scale"];
5064
+ function catmullRom(delta, p0, p1, p22, p32) {
5065
+ return 0.5 * (2 * p1 + (p22 - p0) * delta + (2 * p0 - 5 * p1 + 4 * p22 - p32) * delta * delta + (3 * p1 - p0 - 3 * p22 + p32) * delta * delta * delta);
5066
+ }
5067
+ function sampleEntityKeyframes(keyframes, time) {
5068
+ if (keyframes.length === 0) return [0, 0, 0];
5069
+ let low = 0, high = keyframes.length;
5070
+ while (low < high) {
5071
+ const middle = low + high >>> 1;
5072
+ if (time <= keyframes[middle].time) high = middle;
5073
+ else low = middle + 1;
5074
+ }
5075
+ const a = Math.max(0, low - 1);
5076
+ const b = Math.min(keyframes.length - 1, a + 1);
5077
+ const from = keyframes[a];
5078
+ const to = keyframes[b];
5079
+ const delta = a === b ? 0 : Math.min(1, Math.max(0, (time - from.time) / (to.time - from.time)));
5080
+ if (to.interpolation === "catmullrom") {
5081
+ const before = keyframes[Math.max(0, a - 1)].value;
5082
+ const after = keyframes[Math.min(keyframes.length - 1, b + 1)].value;
5083
+ return [0, 1, 2].map((i) => catmullRom(delta, before[i], from.value[i], to.value[i], after[i]));
5084
+ }
5085
+ const target = to.pre ?? to.value;
5086
+ return [0, 1, 2].map((i) => from.value[i] + (target[i] - from.value[i]) * delta);
5087
+ }
5088
+ function entityAnimationTime(animation, time, loop = animation.loop) {
5089
+ return loop && animation.length > 0 ? time % animation.length : time;
5090
+ }
5091
+ function sampleEntityAnimation(animation, time, loop = animation.loop) {
5092
+ const elapsed = entityAnimationTime(animation, time, loop);
5093
+ const pose = {};
5094
+ for (const [name, bone] of Object.entries(animation.bones)) {
5095
+ const sampled = pose[name] = {};
5096
+ for (const channel of ENTITY_ANIMATION_CHANNELS) {
5097
+ const keyframes = bone[channel];
5098
+ if (keyframes?.length) sampled[channel] = sampleEntityKeyframes(keyframes, elapsed);
5099
+ }
5100
+ }
5101
+ return pose;
5102
+ }
5103
+
5104
+ // src/entity/scene/EntityAnimationPlayer.ts
5105
+ var ZERO = [0, 0, 0];
5106
+ var EntityAnimationPlayer = class {
5107
+ constructor() {
5108
+ this.parts = [];
5109
+ this._time = 0;
5110
+ this.loop = false;
5111
+ this.speed = 1;
5112
+ }
5113
+ get animation() {
5114
+ return this._animation;
5115
+ }
5116
+ get time() {
5117
+ return this._time;
5118
+ }
5119
+ /**
5120
+ * Starts an animation on the named parts below each layer root. The current pose of a part becomes its default;
5121
+ * bones without a matching part are skipped.
5122
+ */
5123
+ play(layers, animation, options = {}) {
5124
+ this.stop();
5125
+ this._animation = animation;
5126
+ this._time = options.time ?? 0;
5127
+ this.loop = options.loop ?? animation.loop;
5128
+ this.speed = options.speed ?? 1;
5129
+ for (const layer of layers) {
5130
+ for (const bone of Object.keys(animation.bones)) {
5131
+ const object = layer.getObjectByName(`group:${bone}`);
5132
+ if (!object) continue;
5133
+ this.parts.push({ bone, object, position: object.position.clone(), rotation: object.rotation.clone(), scale: object.scale.clone() });
5134
+ }
5135
+ }
5136
+ this.apply();
5137
+ }
5138
+ /** Restores the default poses. Returns whether an animation was active. */
5139
+ stop() {
5140
+ if (!this._animation) return false;
5141
+ for (const part of this.parts) {
5142
+ part.object.position.copy(part.position);
5143
+ part.object.rotation.copy(part.rotation);
5144
+ part.object.scale.copy(part.scale);
5145
+ }
5146
+ this.clear();
5147
+ return true;
5148
+ }
5149
+ /** Forgets the animation without touching the parts, for parts that no longer exist. */
5150
+ clear() {
5151
+ this.parts = [];
5152
+ this._animation = void 0;
5153
+ this._time = 0;
5154
+ }
5155
+ setTime(seconds) {
5156
+ if (!this._animation) return false;
5157
+ this._time = seconds;
5158
+ this.apply();
5159
+ return true;
5160
+ }
5161
+ advance(deltaSeconds) {
5162
+ return this.setTime(this._time + deltaSeconds * this.speed);
5163
+ }
5164
+ apply() {
5165
+ const pose = sampleEntityAnimation(this._animation, this._time, this.loop);
5166
+ for (const part of this.parts) {
5167
+ const { position = ZERO, rotation = ZERO, scale = ZERO } = pose[part.bone];
5168
+ part.object.position.set(part.position.x + position[0], part.position.y + position[1], part.position.z + position[2]);
5169
+ part.object.rotation.set(part.rotation.x + rotation[0], part.rotation.y + rotation[1], part.rotation.z + rotation[2], part.rotation.order);
5170
+ part.object.scale.set(part.scale.x + scale[0], part.scale.y + scale[1], part.scale.z + scale[2]);
5171
+ }
5172
+ }
5173
+ };
5174
+
4915
5175
  // src/entity/scene/EntityObject.ts
4916
5176
  init_SceneObject();
4917
5177
  init_Caching();
4918
5178
  var import_ts_deepmerge4 = __toESM(require_dist());
5179
+ init_Ticker();
4919
5180
  init_model();
4920
5181
  init_ModelTextures();
4921
5182
  init_AssetKey();
4922
5183
  init_Materials();
4923
- import { Object3D as Object3D3 } from "three";
5184
+ import { Color as Color3, Euler as Euler4, Matrix4 as Matrix45, Object3D as Object3D3 } from "three";
4924
5185
  init_three();
4925
5186
  var EntityObject = class _EntityObject extends SceneObject {
4926
5187
  constructor(entity, options) {
@@ -4928,10 +5189,15 @@ var EntityObject = class _EntityObject extends SceneObject {
4928
5189
  this.entity = entity;
4929
5190
  this.isEntityObject = true;
4930
5191
  this.meshesCreated = false;
5192
+ /** Scrolling materials are owned: their texture offset follows this entity's age. */
5193
+ this.scrollMaterials = [];
5194
+ /** Entity age in ticks, as vanilla's `ageInTicks`; drives the scrolling render modes. */
5195
+ this.age = 0;
5196
+ this.animationPlayer = new EntityAnimationPlayer();
4931
5197
  this.options = (0, import_ts_deepmerge4.default)({}, _EntityObject.DEFAULT_OPTIONS, options ?? {});
4932
5198
  }
4933
5199
  static {
4934
- this.DEFAULT_OPTIONS = (0, import_ts_deepmerge4.default)({}, SceneObject.DEFAULT_OPTIONS, { flip: true });
5200
+ this.DEFAULT_OPTIONS = (0, import_ts_deepmerge4.default)({}, SceneObject.DEFAULT_OPTIONS);
4935
5201
  }
4936
5202
  async init() {
4937
5203
  this.createMeshes();
@@ -4940,6 +5206,59 @@ var EntityObject = class _EntityObject extends SceneObject {
4940
5206
  dispose() {
4941
5207
  super.dispose();
4942
5208
  }
5209
+ disposeAndRemoveAllChildren() {
5210
+ this.clearScrollMaterials();
5211
+ this.animationPlayer.clear();
5212
+ super.disposeAndRemoveAllChildren();
5213
+ }
5214
+ clearScrollMaterials() {
5215
+ Ticker.remove(this.scrollTicker);
5216
+ this.scrollTicker = void 0;
5217
+ for (const { material } of this.scrollMaterials.splice(0)) {
5218
+ material.map?.dispose();
5219
+ material.dispose();
5220
+ }
5221
+ }
5222
+ /** Applies the entity age to the scrolling materials; the ticker calls this 20 times per second. */
5223
+ updateScroll() {
5224
+ for (const { offset, speed } of this.scrollMaterials) {
5225
+ offset.set(this.age * speed[0] % 1, 0 - this.age * speed[1] % 1);
5226
+ }
5227
+ this.notifyDirty();
5228
+ }
5229
+ //<editor-fold desc="ANIMATION">
5230
+ /** The animation started by {@link playAnimation}, until {@link stopAnimation} or disposal. */
5231
+ get animation() {
5232
+ return this.animationPlayer.animation;
5233
+ }
5234
+ /** Seconds since the animation started, before looping. */
5235
+ get animationTime() {
5236
+ return this.animationPlayer.time;
5237
+ }
5238
+ /**
5239
+ * Poses the object from a keyframe animation of {@link Entities.getAnimations}, replacing the current one.
5240
+ * Parts of every layer are matched by name and their current pose is the default the offsets add to.
5241
+ * The object owns no clock: call {@link advanceAnimation} per frame, or {@link setAnimationTime}.
5242
+ */
5243
+ playAnimation(animation, options) {
5244
+ this.createMeshes();
5245
+ const layers = Object.keys(this.entityLayers).map((name) => this.getLayerGroup(name)).filter((layer) => !!layer);
5246
+ this.animationPlayer.play(layers, animation, options);
5247
+ this.notifyDirty();
5248
+ }
5249
+ /** Stops the animation and restores the default pose. */
5250
+ stopAnimation() {
5251
+ if (this.animationPlayer.stop()) this.notifyDirty();
5252
+ }
5253
+ /** Poses the object at an explicit time in seconds. Has no effect without an animation. */
5254
+ setAnimationTime(seconds) {
5255
+ if (this.animationPlayer.setTime(seconds)) this.notifyDirty();
5256
+ }
5257
+ /** Advances the animation by a frame delta in seconds, scaled by its speed. Has no effect without an animation. */
5258
+ advanceAnimation(deltaSeconds) {
5259
+ if (this.animationPlayer.advance(deltaSeconds)) this.notifyDirty();
5260
+ }
5261
+ //</editor-fold>
4943
5262
  getLayerGroup(name) {
4944
5263
  return super.getGroupByName(`layer:${name}`);
4945
5264
  }
@@ -4970,16 +5289,29 @@ var EntityObject = class _EntityObject extends SceneObject {
4970
5289
  createMeshes(force = false) {
4971
5290
  if (this.meshesCreated && !force) return;
4972
5291
  const modelRoot = new Object3D3();
4973
- if (this.options.flip) modelRoot.scale.set(-1, -1, 1);
5292
+ const transform = this.options.flip === void 0 ? this.entity.transform : void 0;
5293
+ if (transform) {
5294
+ for (const op of transform) {
5295
+ const matrix = new Matrix45();
5296
+ if ("scale" in op) matrix.makeScale(...op.scale);
5297
+ else if ("translate" in op) matrix.makeTranslation(...op.translate);
5298
+ else matrix.makeRotationFromEuler(new Euler4(...op.rotate, "ZYX"));
5299
+ modelRoot.matrix.multiply(matrix);
5300
+ }
5301
+ modelRoot.matrixAutoUpdate = false;
5302
+ } else if (this.options.flip ?? true) {
5303
+ modelRoot.scale.set(-1, -1, 1);
5304
+ }
4974
5305
  this.add(modelRoot);
4975
5306
  Object.entries(this.entityLayers).forEach(([name, layer], index) => {
4976
5307
  const group = this.createGroup(`layer:${name}`);
4977
5308
  modelRoot.add(group);
4978
- this.createPart("root", layer.layer.root, group, layer.layer.texture, Materials.MISSING_TEXTURE, index);
5309
+ const inward = !Materials.entityModeCulls(layer.render ?? layer.layer.render);
5310
+ this.createPart("root", layer.layer.root, group, layer.layer.texture, Materials.MISSING_TEXTURE, index, inward);
4979
5311
  });
4980
5312
  this.meshesCreated = true;
4981
5313
  }
4982
- createPart(name, part, parent, textureSize, material, renderOrder) {
5314
+ createPart(name, part, parent, textureSize, material, renderOrder, inward = true) {
4983
5315
  const anchor = this.createGroup(name);
4984
5316
  anchor.position.fromArray(part.pose.offset);
4985
5317
  anchor.rotation.set(...part.pose.rotation, "ZYX");
@@ -4998,7 +5330,7 @@ var EntityObject = class _EntityObject extends SceneObject {
4998
5330
  uv
4999
5331
  ).clone();
5000
5332
  geometry.translate(cube.origin[0] + width / 2, cube.origin[1] + height / 2, cube.origin[2] + depth / 2);
5001
- if (Math.min(width + growX * 2, height + growY * 2, depth + growZ * 2) > 0) {
5333
+ if (inward && Math.min(width + growX * 2, height + growY * 2, depth + growZ * 2) > 0) {
5002
5334
  const index = Array.from(geometry.getIndex().array);
5003
5335
  geometry.setIndex(index.concat(index.slice().reverse()));
5004
5336
  }
@@ -5008,26 +5340,42 @@ var EntityObject = class _EntityObject extends SceneObject {
5008
5340
  if (this.options.wireframe) addWireframeToMesh(geometry, mesh);
5009
5341
  }
5010
5342
  for (const [childName, child] of Object.entries(part.children)) {
5011
- this.createPart(childName, child, anchor, size, material, renderOrder);
5343
+ this.createPart(childName, child, anchor, size, material, renderOrder, inward);
5012
5344
  }
5013
5345
  }
5014
5346
  async applyTextures() {
5347
+ this.clearScrollMaterials();
5015
5348
  await Promise.all(Object.entries(this.entityLayers).map(async ([name, layer]) => {
5349
+ const mode = layer.render ?? layer.layer.render ?? "cutout";
5350
+ const tint = layer.tint === void 0 ? void 0 : this.options.tints?.[layer.tint];
5351
+ const scroll = Materials.entityModeScroll(mode);
5016
5352
  const assetKeyStr = this.getTextureKey(layer).serialize();
5017
- const keyStr = `entity:${assetKeyStr}`;
5018
- let mat = Caching.materialCache.getIfPresent(keyStr);
5353
+ const keyStr = `entity:${mode}:${tint === void 0 ? "" : new Color3(tint).getHexString()}:${assetKeyStr}`;
5354
+ let mat = scroll ? void 0 : Caching.materialCache.getIfPresent(keyStr);
5019
5355
  if (!mat) {
5020
5356
  const cachedAsset = Caching.textureAssetCache.getIfPresent(assetKeyStr);
5021
5357
  const imageData = await this.loadTextures(layer);
5022
5358
  if (!imageData) return;
5023
5359
  const canvas = imageData.data.canvas;
5024
- const createMaterial = () => Materials.createBasicCanvasMaterial(canvas);
5025
- mat = cachedAsset && Caching.textureAssetCache.getIfPresent(assetKeyStr) === cachedAsset ? Caching.materialCache.get(keyStr, createMaterial) : createMaterial();
5360
+ const createMaterial = () => Materials.createEntityCanvasMaterial(canvas, mode, tint);
5361
+ mat = !scroll && cachedAsset && Caching.textureAssetCache.getIfPresent(assetKeyStr) === cachedAsset ? Caching.materialCache.get(keyStr, createMaterial) : createMaterial();
5362
+ if (scroll) {
5363
+ this.scrollMaterials.push({ material: mat, offset: mat.map.offset, speed: scroll });
5364
+ }
5026
5365
  }
5027
5366
  this.getLayerGroup(name)?.traverse((object) => {
5028
5367
  if (isMesh(object)) object.material = mat;
5029
5368
  });
5030
5369
  }));
5370
+ if (!this.children.length) {
5371
+ this.clearScrollMaterials();
5372
+ } else if (this.scrollMaterials.length) {
5373
+ this.updateScroll();
5374
+ this.scrollTicker = Ticker.add(() => {
5375
+ this.age++;
5376
+ this.updateScroll();
5377
+ });
5378
+ }
5031
5379
  this.notifyDirty();
5032
5380
  }
5033
5381
  };
@@ -5039,11 +5387,11 @@ function isEntityObject(obj) {
5039
5387
  init_MineRenderError();
5040
5388
 
5041
5389
  // src/export/SceneExporter.ts
5042
- import { ClampToEdgeWrapping as ClampToEdgeWrapping2, Color as Color6, Float32BufferAttribute as Float32BufferAttribute3, Matrix4 as Matrix45, Mesh as Mesh5, MeshBasicMaterial as MeshBasicMaterial2, NearestFilter as NearestFilter3, RGBAFormat as RGBAFormat3, Scene as Scene4, Source as Source2, Texture as Texture4, UnsignedByteType } from "three";
5390
+ import { ClampToEdgeWrapping as ClampToEdgeWrapping2, Color as Color7, Float32BufferAttribute as Float32BufferAttribute3, Matrix4 as Matrix47, Mesh as Mesh5, MeshBasicMaterial as MeshBasicMaterial2, NearestFilter as NearestFilter3, RGBAFormat as RGBAFormat3, Scene as Scene4, Source as Source2, Texture as Texture4, UnsignedByteType } from "three";
5043
5391
 
5044
5392
  // ../../node_modules/three/examples/jsm/exporters/OBJExporter.js
5045
5393
  import {
5046
- Color as Color3,
5394
+ Color as Color4,
5047
5395
  Matrix3,
5048
5396
  Vector2 as Vector22,
5049
5397
  Vector3 as Vector36
@@ -5055,7 +5403,7 @@ var OBJExporter = class {
5055
5403
  let indexVertexUvs = 0;
5056
5404
  let indexNormals = 0;
5057
5405
  const vertex = new Vector36();
5058
- const color = new Color3();
5406
+ const color = new Color4();
5059
5407
  const normal = new Vector36();
5060
5408
  const uv = new Vector22();
5061
5409
  const face = [];
@@ -5186,7 +5534,7 @@ var OBJExporter = class {
5186
5534
  import {
5187
5535
  Matrix3 as Matrix32,
5188
5536
  Vector3 as Vector37,
5189
- Color as Color4
5537
+ Color as Color5
5190
5538
  } from "three";
5191
5539
  var PLYExporter = class {
5192
5540
  parse(object, onDone, options = {}) {
@@ -5244,7 +5592,7 @@ var PLYExporter = class {
5244
5592
  includeIndices = false;
5245
5593
  }
5246
5594
  });
5247
- const tempColor = new Color4();
5595
+ const tempColor = new Color5();
5248
5596
  includeIndices = includeIndices && excludeAttributes.indexOf("index") === -1;
5249
5597
  includeNormals = includeNormals && excludeAttributes.indexOf("normal") === -1;
5250
5598
  includeColors = includeColors && excludeAttributes.indexOf("color") === -1;
@@ -5456,7 +5804,7 @@ property list uchar int vertex_index
5456
5804
  import {
5457
5805
  BufferAttribute as BufferAttribute3,
5458
5806
  ClampToEdgeWrapping,
5459
- Color as Color5,
5807
+ Color as Color6,
5460
5808
  DoubleSide as DoubleSide2,
5461
5809
  InterpolateDiscrete,
5462
5810
  InterpolateLinear,
@@ -5465,14 +5813,14 @@ import {
5465
5813
  LinearMipmapLinearFilter,
5466
5814
  LinearMipmapNearestFilter,
5467
5815
  MathUtils as MathUtils2,
5468
- Matrix4 as Matrix44,
5816
+ Matrix4 as Matrix46,
5469
5817
  MirroredRepeatWrapping,
5470
5818
  NearestFilter as NearestFilter2,
5471
5819
  NearestMipmapLinearFilter,
5472
5820
  NearestMipmapNearestFilter,
5473
5821
  PropertyBinding,
5474
5822
  RGBAFormat as RGBAFormat2,
5475
- RepeatWrapping,
5823
+ RepeatWrapping as RepeatWrapping2,
5476
5824
  Scene as Scene3,
5477
5825
  Source,
5478
5826
  SRGBColorSpace as SRGBColorSpace4,
@@ -5690,7 +6038,7 @@ THREE_TO_WEBGL[LinearFilter] = WEBGL_CONSTANTS.LINEAR;
5690
6038
  THREE_TO_WEBGL[LinearMipmapNearestFilter] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST;
5691
6039
  THREE_TO_WEBGL[LinearMipmapLinearFilter] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR;
5692
6040
  THREE_TO_WEBGL[ClampToEdgeWrapping] = WEBGL_CONSTANTS.CLAMP_TO_EDGE;
5693
- THREE_TO_WEBGL[RepeatWrapping] = WEBGL_CONSTANTS.REPEAT;
6041
+ THREE_TO_WEBGL[RepeatWrapping2] = WEBGL_CONSTANTS.REPEAT;
5694
6042
  THREE_TO_WEBGL[MirroredRepeatWrapping] = WEBGL_CONSTANTS.MIRRORED_REPEAT;
5695
6043
  var PATH_PROPERTIES = {
5696
6044
  scale: "scale",
@@ -5698,7 +6046,7 @@ var PATH_PROPERTIES = {
5698
6046
  quaternion: "rotation",
5699
6047
  morphTargetInfluences: "weights"
5700
6048
  };
5701
- var DEFAULT_SPECULAR_COLOR = new Color5();
6049
+ var DEFAULT_SPECULAR_COLOR = new Color6();
5702
6050
  var GLB_HEADER_BYTES = 12;
5703
6051
  var GLB_HEADER_MAGIC = 1179937895;
5704
6052
  var GLB_VERSION = 2;
@@ -6755,7 +7103,7 @@ var GLTFWriter = class {
6755
7103
  if (rootJoint === void 0) return null;
6756
7104
  const joints = [];
6757
7105
  const inverseBindMatrices = new Float32Array(skeleton.bones.length * 16);
6758
- const temporaryBoneInverse = new Matrix44();
7106
+ const temporaryBoneInverse = new Matrix46();
6759
7107
  for (let i = 0; i < skeleton.bones.length; ++i) {
6760
7108
  joints.push(nodeMap.get(skeleton.bones[i]));
6761
7109
  temporaryBoneInverse.copy(skeleton.boneInverses[i]);
@@ -7221,7 +7569,7 @@ var GLTFMeshGpuInstancing = class {
7221
7569
  const translationAttr = new Float32Array(mesh.count * 3);
7222
7570
  const rotationAttr = new Float32Array(mesh.count * 4);
7223
7571
  const scaleAttr = new Float32Array(mesh.count * 3);
7224
- const matrix = new Matrix44();
7572
+ const matrix = new Matrix46();
7225
7573
  const position = new Vector38();
7226
7574
  const quaternion = new Quaternion4();
7227
7575
  const scale = new Vector38();
@@ -7395,7 +7743,7 @@ var SceneExporter = class {
7395
7743
  }
7396
7744
  };
7397
7745
  function localMatrix(object) {
7398
- return object.matrixAutoUpdate ? new Matrix45().compose(object.position, object.quaternion, object.scale) : object.matrix.clone();
7746
+ return object.matrixAutoUpdate ? new Matrix47().compose(object.position, object.quaternion, object.scale) : object.matrix.clone();
7399
7747
  }
7400
7748
  function createSnapshot(root, gltf, bakeColors = false) {
7401
7749
  const scene = new Scene4();
@@ -7480,7 +7828,7 @@ function createSnapshot(root, gltf, bakeColors = false) {
7480
7828
  const material = copyMaterial(sourceMaterial, geometry);
7481
7829
  if (!sourceMaterial.vertexColors) geometry.deleteAttribute("color");
7482
7830
  if (bakeColors || instanceColor) {
7483
- const tint = (material.color ?? new Color6()).clone();
7831
+ const tint = (material.color ?? new Color7()).clone();
7484
7832
  if (instanceColor) tint.multiply(instanceColor);
7485
7833
  if (bakeColors) {
7486
7834
  const original = geometry.getAttribute("color");
@@ -7507,9 +7855,9 @@ function createSnapshot(root, gltf, bakeColors = false) {
7507
7855
  const instanced = mesh;
7508
7856
  if (instanced.isInstancedMesh) {
7509
7857
  for (let i = 0; i < instanced.count; i++) {
7510
- const matrix = new Matrix45();
7858
+ const matrix = new Matrix47();
7511
7859
  instanced.getMatrixAt(i, matrix);
7512
- const color = instanced.instanceColor ? new Color6().fromBufferAttribute(instanced.instanceColor, i) : void 0;
7860
+ const color = instanced.instanceColor ? new Color7().fromBufferAttribute(instanced.instanceColor, i) : void 0;
7513
7861
  addMesh(mesh, world.clone().multiply(matrix), color);
7514
7862
  }
7515
7863
  } else {
@@ -7521,7 +7869,7 @@ function createSnapshot(root, gltf, bakeColors = false) {
7521
7869
  try {
7522
7870
  const ancestors = [];
7523
7871
  for (let parent = root.parent; parent; parent = parent.parent) ancestors.unshift(parent);
7524
- const world = new Matrix45();
7872
+ const world = new Matrix47();
7525
7873
  for (const ancestor of ancestors) {
7526
7874
  if (!ancestor.visible) return { scene, dispose };
7527
7875
  world.multiply(localMatrix(ancestor));
@@ -7588,11 +7936,69 @@ function copyDataTexture(source) {
7588
7936
  }
7589
7937
 
7590
7938
  // src/gui/GuiHelper.ts
7939
+ init_AssetKey();
7591
7940
  var GuiHelper = class {
7592
7941
  static inventorySlot(slot, origin = [0, 0], offset = [18, 18], rowSize = 9) {
7593
7942
  const [column, row] = typeof slot === "number" ? [slot % rowSize, Math.floor(slot / rowSize)] : slot;
7594
7943
  return [origin[0] + column * offset[0], origin[1] + row * offset[1]];
7595
7944
  }
7945
+ /** Creates crafting-table layers for ingredients and the result; stack counts are not drawn. */
7946
+ static recipe(recipe, options = {}) {
7947
+ const layers = [
7948
+ { name: "background", texture: "minecraft:gui/container/crafting_table", crop: [0, 0, 176, 166] }
7949
+ ];
7950
+ const itemKey = (ingredient) => {
7951
+ const item = typeof ingredient === "string" && !ingredient.startsWith("#") ? ingredient : typeof ingredient === "object" && "item" in ingredient ? ingredient.item : options.resolveIngredient?.(ingredient);
7952
+ if (isAssetKey(item)) return item;
7953
+ if (!item || item.startsWith("#")) {
7954
+ throw new Error(`Select a concrete item for recipe ingredient ${JSON.stringify(ingredient)}`);
7955
+ }
7956
+ const key = AssetKey.parse("models", item);
7957
+ return new AssetKey(key.namespace, key.getFullPath(), "models", "item");
7958
+ };
7959
+ const addIngredient = (ingredient, slot) => {
7960
+ layers.push({
7961
+ name: `ingredient-${slot}`,
7962
+ item: itemKey(ingredient),
7963
+ position: this.inventorySlot(slot, [30, 17], [18, 18], 3)
7964
+ });
7965
+ };
7966
+ switch (recipe.type) {
7967
+ case "minecraft:crafting_shaped":
7968
+ case "crafting_shaped": {
7969
+ const pattern = recipe.pattern;
7970
+ if (!pattern.length || pattern.length > 3 || !pattern[0].length || pattern[0].length > 3 || pattern.some((row) => row.length !== pattern[0].length)) {
7971
+ throw new Error("Crafting patterns must be rectangular, with 1\u20133 rows and columns");
7972
+ }
7973
+ const cells = pattern.flatMap((row, y) => row.split("").map((symbol, x) => ({ symbol, x, y }))).filter((cell) => cell.symbol !== " ");
7974
+ if (!cells.length) throw new Error("Crafting patterns must contain an ingredient");
7975
+ const minX = Math.min(...cells.map((cell) => cell.x)), maxX = Math.max(...cells.map((cell) => cell.x));
7976
+ const minY = Math.min(...cells.map((cell) => cell.y)), maxY = Math.max(...cells.map((cell) => cell.y));
7977
+ const offsetX = minX === maxX ? 1 : 0, offsetY = minY === maxY ? 1 : 0;
7978
+ for (const { symbol, x, y } of cells) {
7979
+ const ingredient = recipe.key[symbol];
7980
+ if (!ingredient) throw new Error(`Missing recipe ingredient for symbol ${JSON.stringify(symbol)}`);
7981
+ addIngredient(ingredient, (y - minY + offsetY) * 3 + x - minX + offsetX);
7982
+ }
7983
+ break;
7984
+ }
7985
+ case "minecraft:crafting_shapeless":
7986
+ case "crafting_shapeless":
7987
+ if (!recipe.ingredients.length || recipe.ingredients.length > 9) {
7988
+ throw new Error("Shapeless crafting recipes require 1\u20139 ingredients");
7989
+ }
7990
+ recipe.ingredients.forEach(addIngredient);
7991
+ break;
7992
+ default:
7993
+ throw new Error(`Unsupported crafting recipe type: ${recipe.type}`);
7994
+ }
7995
+ layers.push({
7996
+ name: "result",
7997
+ item: itemKey("id" in recipe.result ? recipe.result.id : recipe.result.item),
7998
+ position: [124, 35]
7999
+ });
8000
+ return layers;
8001
+ }
7596
8002
  };
7597
8003
 
7598
8004
  // src/gui/scene/GuiObject.ts
@@ -8180,7 +8586,7 @@ init_ModelMerger();
8180
8586
  init_BlockStates();
8181
8587
  init_MineRenderError();
8182
8588
  init_util();
8183
- import { Euler as Euler6 } from "three";
8589
+ import { Euler as Euler7 } from "three";
8184
8590
  function matchesCondition(condition, state) {
8185
8591
  return Object.entries(condition).every(([key, value]) => {
8186
8592
  if (Array.isArray(value)) {
@@ -8255,7 +8661,7 @@ var BlockStateResolver = class {
8255
8661
  return variants[variants.length - 1];
8256
8662
  }
8257
8663
  static rotation(variant) {
8258
- const rotation = new Euler6();
8664
+ const rotation = new Euler7();
8259
8665
  if (typeof variant.x !== "undefined") {
8260
8666
  rotation.x = toRadians(clampRotationDegrees(variant.x));
8261
8667
  }
@@ -8365,7 +8771,7 @@ var import_ts_deepmerge6 = __toESM(require_dist());
8365
8771
  init_MineRenderError();
8366
8772
  init_InstanceReference();
8367
8773
  init_AssetKey();
8368
- import { Euler as Euler7, Matrix4 as Matrix47, Quaternion as Quaternion5, Vector3 as Vector311 } from "three";
8774
+ import { Euler as Euler8, Matrix4 as Matrix49, Quaternion as Quaternion5, Vector3 as Vector311 } from "three";
8369
8775
  init_ModelCulling();
8370
8776
  init_FluidGeometry();
8371
8777
  var BlockObject = class _BlockObject extends SceneObject {
@@ -8382,6 +8788,9 @@ var BlockObject = class _BlockObject extends SceneObject {
8382
8788
  this._variants = [];
8383
8789
  this._models = [];
8384
8790
  this._cullMask = 0;
8791
+ this._entities = [];
8792
+ this._entityPlacement = new Matrix49();
8793
+ this._entityGeneration = 0;
8385
8794
  this.options = (0, import_ts_deepmerge6.default)({}, _BlockObject.DEFAULT_OPTIONS, options ?? {});
8386
8795
  }
8387
8796
  static {
@@ -8406,6 +8815,8 @@ var BlockObject = class _BlockObject extends SceneObject {
8406
8815
  }
8407
8816
  clearModels() {
8408
8817
  this.removeModels(this._models.splice(0));
8818
+ this._entityGeneration++;
8819
+ this.removeEntities(this._entities.splice(0));
8409
8820
  this._fluidKey = void 0;
8410
8821
  this._fluidModel = void 0;
8411
8822
  this._isInstanced = false;
@@ -8417,6 +8828,57 @@ var BlockObject = class _BlockObject extends SceneObject {
8417
8828
  if (!isInstanceReference(model)) model.dispose();
8418
8829
  }
8419
8830
  }
8831
+ removeEntities(entities) {
8832
+ for (const entity of entities) {
8833
+ entity.removeFromScene();
8834
+ entity.dispose();
8835
+ }
8836
+ }
8837
+ /** Entity models drawn for this block instead of its block model; they are scene children, unlike the block. */
8838
+ get blockEntities() {
8839
+ return this._entities;
8840
+ }
8841
+ /**
8842
+ * Draws the block through the dataset's block-entity index. Returns false, leaving nothing behind,
8843
+ * when the block is not listed or one of its models cannot be loaded.
8844
+ */
8845
+ async createBlockEntities() {
8846
+ const key = this.blockState.key;
8847
+ if (!key || this.options.displayPosition) return false;
8848
+ const generation = this._entityGeneration;
8849
+ const created = [];
8850
+ let resolved;
8851
+ try {
8852
+ resolved = BlockEntities.resolve(await BlockEntities.getIndex(key.root), key.toNamespacedString(), this.state);
8853
+ if (!resolved) return false;
8854
+ for (const part of resolved.parts) {
8855
+ const [namespace, path] = part.model.includes(":") ? part.model.split(":") : [key.namespace, part.model];
8856
+ const texture = part.textureLocation === void 0 ? void 0 : AssetKey.parse("textures", part.textureLocation.replace(/^([^:]+:)?textures\//, "$1"));
8857
+ if (texture) texture.root = key.root;
8858
+ const entity = await Entities.getEntity(new BasicAssetKey(namespace, path), texture, { layer: part.layer });
8859
+ if (!entity) throw new MineRenderError(`Missing entity model ${part.model}`);
8860
+ created.push(await this.scene.addEntity(entity, { wireframe: this.options.wireframe }));
8861
+ }
8862
+ } catch (error) {
8863
+ console.warn(`Could not draw block entity ${key.toNamespacedString()}, using its block model`, error);
8864
+ this.removeEntities(created);
8865
+ return false;
8866
+ }
8867
+ if (generation !== this._entityGeneration) {
8868
+ this.removeEntities(created);
8869
+ return true;
8870
+ }
8871
+ BlockEntities.matrix(resolved, this._entityPlacement);
8872
+ this._entities = created;
8873
+ this.placeEntities();
8874
+ return true;
8875
+ }
8876
+ placeEntities() {
8877
+ const matrix = new Matrix49().makeTranslation(this.position.x - 8, this.position.y - 8, this.position.z - 8).multiply(this._entityPlacement);
8878
+ for (const entity of this._entities) {
8879
+ matrix.decompose(entity.position, entity.quaternion, entity.scale);
8880
+ }
8881
+ }
8420
8882
  get isOccluding() {
8421
8883
  return this._models.some((model) => {
8422
8884
  const object = isInstanceReference(model) ? model.instanceable : model;
@@ -8436,7 +8898,7 @@ var BlockObject = class _BlockObject extends SceneObject {
8436
8898
  const scale = new Vector311();
8437
8899
  this.getModelMatrix(model).decompose(position, rotation, scale);
8438
8900
  if (position.distanceToSquared(this.position) > 1e-10 || scale.distanceToSquared(new Vector311(1, 1, 1)) > 1e-10) return 0;
8439
- return ModelCulling.toLocalMask(worldMask, new Euler7().setFromQuaternion(rotation));
8901
+ return ModelCulling.toLocalMask(worldMask, new Euler8().setFromQuaternion(rotation));
8440
8902
  }
8441
8903
  async setCullMask(worldMask) {
8442
8904
  worldMask &= 63;
@@ -8498,7 +8960,7 @@ var BlockObject = class _BlockObject extends SceneObject {
8498
8960
  () => new FluidModelObject2(kind, surface.sample, this.blockState.key, this.options)
8499
8961
  );
8500
8962
  const previous = this._fluidModel;
8501
- const matrix = previous ? this.getModelMatrix(previous) : new Matrix47().makeTranslation(...this.position.toArray());
8963
+ const matrix = previous ? this.getModelMatrix(previous) : new Matrix49().makeTranslation(...this.position.toArray());
8502
8964
  if (isInstanceReference(replacement)) replacement.setMatrix(matrix);
8503
8965
  else matrix.decompose(replacement.position, replacement.quaternion, replacement.scale);
8504
8966
  this._models = this._models.filter((model) => model !== previous);
@@ -8528,13 +8990,20 @@ var BlockObject = class _BlockObject extends SceneObject {
8528
8990
  await this.updateFluid(this._fluidSampler);
8529
8991
  return;
8530
8992
  }
8993
+ const hasEntities = await this.createBlockEntities();
8531
8994
  const variantsToCreate = await this.mapStateToVariant(this.state);
8532
8995
  this._variants = variantsToCreate;
8533
8996
  for (let blockStateVariant of variantsToCreate) {
8997
+ if (hasEntities && !await this.hasGeometry(blockStateVariant)) continue;
8534
8998
  this._models.push(await this.createVariant(blockStateVariant));
8535
8999
  }
8536
9000
  await this.updateFluid(this._fluidSampler);
8537
9001
  }
9002
+ async hasGeometry(variant) {
9003
+ const modelKey = AssetKey.parse("models", variant.model);
9004
+ modelKey.root = this.blockState.key?.root;
9005
+ return !!(await Models.getMerged(modelKey))?.elements?.length;
9006
+ }
8538
9007
  getSingleVariant(variants) {
8539
9008
  return BlockStateResolver.choose(variants);
8540
9009
  }
@@ -8629,7 +9098,7 @@ var BlockObject = class _BlockObject extends SceneObject {
8629
9098
  setPositionRotationScaleAt(index, position, rotation, scale) {
8630
9099
  super.setPositionRotationScaleAt(index, position, rotation, scale);
8631
9100
  }
8632
- getMatrixAt(index, matrix = new Matrix47()) {
9101
+ getMatrixAt(index, matrix = new Matrix49()) {
8633
9102
  if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
8634
9103
  const child = this._models[0];
8635
9104
  if (child && isModelObject(child)) {
@@ -8654,6 +9123,7 @@ var BlockObject = class _BlockObject extends SceneObject {
8654
9123
  for (let model of this._models) {
8655
9124
  model.setPosition(position);
8656
9125
  }
9126
+ this.placeEntities();
8657
9127
  this.notifyDirty();
8658
9128
  }
8659
9129
  //TODO: should override rotation + scale methods
@@ -13191,7 +13661,7 @@ init_CubeFace();
13191
13661
  init_AssetKey();
13192
13662
  init_Models();
13193
13663
  init_CubeFace();
13194
- import { Matrix4 as Matrix48, Vector3 as Vector314 } from "three";
13664
+ import { Matrix4 as Matrix410, Vector3 as Vector314 } from "three";
13195
13665
  init_ModelCulling();
13196
13666
  init_ModelObject();
13197
13667
  init_UVMapper();
@@ -13254,7 +13724,7 @@ var SectionModels = class {
13254
13724
  });
13255
13725
  await object.init();
13256
13726
  const geometry = object.children[0].geometry;
13257
- geometry.applyMatrix4(new Matrix48().makeRotationFromEuler(rotation));
13727
+ geometry.applyMatrix4(new Matrix410().makeRotationFromEuler(rotation));
13258
13728
  object.clear();
13259
13729
  const cullFaces = CUBE_FACES.map((face) => {
13260
13730
  const local = CUBE_FACES.findIndex((name) => name === atlas.model.elements[0].faces[face]?.cullface);
@@ -13666,7 +14136,8 @@ var Chunk = class _Chunk {
13666
14136
  this.data.set(index, void 0);
13667
14137
  return void 0;
13668
14138
  }
13669
- const template = getFluidKind(blockState.key, stored.properties) ? void 0 : await this.sectionModels?.get(blockState, stored.properties);
14139
+ const perBlock = !this.sectionModels || getFluidKind(blockState.key, stored.properties) || blockState.key && BlockEntities.entry(await BlockEntities.getIndex(blockState.key.root), blockState.key.toNamespacedString());
14140
+ const template = perBlock ? void 0 : await this.sectionModels.get(blockState, stored.properties);
13670
14141
  if (template) {
13671
14142
  this.sectionBlocks.set(index, { index, template, cullMask: 0 });
13672
14143
  this.meshDirty = true;
@@ -13775,6 +14246,7 @@ export {
13775
14246
  BUILTIN_GENERATED,
13776
14247
  BasicAssetKey,
13777
14248
  BatchedExecutor,
14249
+ BlockEntities,
13778
14250
  BlockObject,
13779
14251
  BlockStateResolver,
13780
14252
  BlockStates,
@@ -13796,7 +14268,9 @@ export {
13796
14268
  DefaultBlockStates,
13797
14269
  DisplayPosition,
13798
14270
  DisplayTransforms,
14271
+ ENTITY_ANIMATION_CHANNELS,
13799
14272
  Entities,
14273
+ EntityAnimationPlayer,
13800
14274
  EntityObject,
13801
14275
  Env,
13802
14276
  FluidModelObject,
@@ -13872,6 +14346,7 @@ export {
13872
14346
  createCanvas2 as createCanvas,
13873
14347
  createFluidGeometry,
13874
14348
  createImage,
14349
+ entityAnimationTime,
13875
14350
  estimateBytesUsed,
13876
14351
  getBlockFluidState,
13877
14352
  getFluidKind,
@@ -13904,6 +14379,8 @@ export {
13904
14379
  mergeVertices,
13905
14380
  prefix,
13906
14381
  registerNodeEnv,
14382
+ sampleEntityAnimation,
14383
+ sampleEntityKeyframes,
13907
14384
  sampleFluid,
13908
14385
  serializeBoxGeometryKey,
13909
14386
  serializeImageKey,