minerender 2.1.0-alpha.54 → 2.1.0-alpha.59

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
@@ -32176,7 +32176,9 @@ var MineRender = (() => {
32176
32176
  this.defaultBlockStatesCache,
32177
32177
  this.blockTintCache,
32178
32178
  this.entityModelCache,
32179
- this.entityTextureCache
32179
+ this.entityTextureCache,
32180
+ this.entityAnimationCache,
32181
+ this.blockEntityIndexCache
32180
32182
  ];
32181
32183
  }
32182
32184
  static clear() {
@@ -32221,6 +32223,8 @@ var MineRender = (() => {
32221
32223
  _Caching.blockTintCache = _Caching.createAssetCache();
32222
32224
  _Caching.entityModelCache = _Caching.createAssetCache();
32223
32225
  _Caching.entityTextureCache = _Caching.createAssetCache();
32226
+ _Caching.entityAnimationCache = _Caching.createAssetCache();
32227
+ _Caching.blockEntityIndexCache = _Caching.createAssetCache();
32224
32228
  Caching = _Caching;
32225
32229
  }
32226
32230
  });
@@ -34072,6 +34076,11 @@ var MineRender = (() => {
34072
34076
  mesh.add(wireframe);
34073
34077
  let axes = new AxesHelper(1);
34074
34078
  mesh.add(axes);
34079
+ return () => {
34080
+ wireGeo.dispose();
34081
+ wireMat.dispose();
34082
+ axes.dispose();
34083
+ };
34075
34084
  }
34076
34085
  var init_model = __esm({
34077
34086
  "src/util/model.ts"() {
@@ -83042,6 +83051,7 @@ ${key}` : key;
83042
83051
  BUILTIN_GENERATED: () => BUILTIN_GENERATED,
83043
83052
  BasicAssetKey: () => BasicAssetKey,
83044
83053
  BatchedExecutor: () => BatchedExecutor,
83054
+ BlockEntities: () => BlockEntities,
83045
83055
  BlockObject: () => BlockObject,
83046
83056
  BlockStateResolver: () => BlockStateResolver,
83047
83057
  BlockStates: () => BlockStates,
@@ -83065,7 +83075,9 @@ ${key}` : key;
83065
83075
  DefaultBlockStates: () => DefaultBlockStates,
83066
83076
  DisplayPosition: () => DisplayPosition,
83067
83077
  DisplayTransforms: () => DisplayTransforms,
83078
+ ENTITY_ANIMATION_CHANNELS: () => ENTITY_ANIMATION_CHANNELS,
83068
83079
  Entities: () => Entities,
83080
+ EntityAnimationPlayer: () => EntityAnimationPlayer,
83069
83081
  EntityObject: () => EntityObject,
83070
83082
  Env: () => Env,
83071
83083
  FluidModelObject: () => FluidModelObject,
@@ -83139,6 +83151,7 @@ ${key}` : key;
83139
83151
  createCanvas: () => createCanvas,
83140
83152
  createFluidGeometry: () => createFluidGeometry,
83141
83153
  createImage: () => createImage,
83154
+ entityAnimationTime: () => entityAnimationTime,
83142
83155
  estimateBytesUsed: () => estimateBytesUsed,
83143
83156
  getBlockFluidState: () => getBlockFluidState,
83144
83157
  getFluidKind: () => getFluidKind,
@@ -83172,6 +83185,8 @@ ${key}` : key;
83172
83185
  prefix: () => prefix,
83173
83186
  probeImageSize: () => probeImageSize,
83174
83187
  registerBrowserEnv: () => registerBrowserEnv,
83188
+ sampleEntityAnimation: () => sampleEntityAnimation,
83189
+ sampleEntityKeyframes: () => sampleEntityKeyframes,
83175
83190
  sampleFluid: () => sampleFluid,
83176
83191
  serializeBoxGeometryKey: () => serializeBoxGeometryKey,
83177
83192
  serializeImageKey: () => serializeImageKey,
@@ -83543,6 +83558,62 @@ ${key}` : key;
83543
83558
  init_AssetKey();
83544
83559
  init_AssetLoader();
83545
83560
  init_Assets();
83561
+
83562
+ // src/assets/BlockEntities.ts
83563
+ init_dirname();
83564
+ init_buffer2();
83565
+ init_process2();
83566
+ init_three_module();
83567
+ init_AssetKey();
83568
+ init_AssetLoader();
83569
+ init_source();
83570
+ init_Caching();
83571
+ var BlockEntities = class {
83572
+ /**
83573
+ * Loads the block index of a version. Versions and sources without one yield an empty index, and so does
83574
+ * a failed load: the index is optional, and blocks must not retry or fail with it one by one. The empty
83575
+ * result is cached like any other; `Caching.clear()` or `AssetLoader.setVersion()` forces a new attempt.
83576
+ */
83577
+ static async getIndex(root) {
83578
+ const key = new AssetKey("entity-models", "blocks", void 0, void 0, void 0, ".json", root);
83579
+ key.rootType = void 0;
83580
+ const index = await Caching.blockEntityIndexCache.get(key.serialize(), async () => {
83581
+ try {
83582
+ return await AssetLoader.get(key, "json" /* JSON */) ?? {};
83583
+ } catch (error) {
83584
+ console.warn("Could not load the block entity index; block entities are drawn as plain block models", error);
83585
+ return {};
83586
+ }
83587
+ });
83588
+ return index ?? {};
83589
+ }
83590
+ /** The index entry of a block ID such as `minecraft:chest`. */
83591
+ static entry(index, block) {
83592
+ const entry = Object.prototype.hasOwnProperty.call(index, block) ? index[block] : void 0;
83593
+ return Array.isArray(entry?.parts) ? entry : void 0;
83594
+ }
83595
+ /** Selects the parts and placement for a block ID such as `minecraft:chest`; undefined when nothing is drawn. */
83596
+ static resolve(index, block, state = {}) {
83597
+ const entry = this.entry(index, block);
83598
+ if (!entry) return void 0;
83599
+ const value = (property) => String(state[property]).toLowerCase();
83600
+ const parts = entry.parts.filter((part) => Object.entries(part.when ?? {}).every(([property, values]) => String(values).split("|").includes(value(property))));
83601
+ if (!parts.length) return void 0;
83602
+ let rotation = 0;
83603
+ if (entry.rotation) {
83604
+ const selected = value(entry.rotation.property);
83605
+ rotation = "degrees" in entry.rotation ? entry.rotation.degrees[selected] : Number(selected) * entry.rotation.step;
83606
+ if (!Number.isFinite(rotation)) rotation = 0;
83607
+ }
83608
+ return { parts, rotation, translation: entry.translation ?? [0, 0, 0] };
83609
+ }
83610
+ /** Placement in block space (0..16 per axis), applied on top of the model's own transform. */
83611
+ static matrix(resolved, matrix = new Matrix4()) {
83612
+ return matrix.makeTranslation(8, 0, 8).multiply(new Matrix4().makeRotationY(resolved.rotation * Math.PI / 180)).multiply(new Matrix4().makeTranslation(resolved.translation[0] - 8, resolved.translation[1], resolved.translation[2] - 8));
83613
+ }
83614
+ };
83615
+
83616
+ // src/assets/index.ts
83546
83617
  init_BlockStates();
83547
83618
 
83548
83619
  // src/assets/Entities.ts
@@ -83616,6 +83687,15 @@ ${key}` : key;
83616
83687
  })));
83617
83688
  return { ...layers[names[0]], id: model.id, layers, ...model.transform && { transform: model.transform } };
83618
83689
  }
83690
+ /**
83691
+ * Vanilla's keyframe animations of a model by name, or undefined when the model or the selected version has none.
83692
+ */
83693
+ static async getAnimations(modelKey) {
83694
+ const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
83695
+ const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models/animations", ".json");
83696
+ const file = await Caching.entityAnimationCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
83697
+ return file?.animations;
83698
+ }
83619
83699
  static async resolveTexture(modelKey) {
83620
83700
  const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
83621
83701
  const key = new AssetKey(modelKey.namespace, path, void 0, void 0, "entity-models", ".json");
@@ -83731,6 +83811,51 @@ ${key}` : key;
83731
83811
  init_buffer2();
83732
83812
  init_process2();
83733
83813
 
83814
+ // src/entity/EntityAnimation.ts
83815
+ init_dirname();
83816
+ init_buffer2();
83817
+ init_process2();
83818
+ var ENTITY_ANIMATION_CHANNELS = ["position", "rotation", "scale"];
83819
+ function catmullRom(delta, p0, p1, p22, p32) {
83820
+ 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);
83821
+ }
83822
+ function sampleEntityKeyframes(keyframes, time) {
83823
+ if (keyframes.length === 0) return [0, 0, 0];
83824
+ let low = 0, high = keyframes.length;
83825
+ while (low < high) {
83826
+ const middle = low + high >>> 1;
83827
+ if (time <= keyframes[middle].time) high = middle;
83828
+ else low = middle + 1;
83829
+ }
83830
+ const a4 = Math.max(0, low - 1);
83831
+ const b4 = Math.min(keyframes.length - 1, a4 + 1);
83832
+ const from = keyframes[a4];
83833
+ const to = keyframes[b4];
83834
+ const delta = a4 === b4 ? 0 : Math.min(1, Math.max(0, (time - from.time) / (to.time - from.time)));
83835
+ if (to.interpolation === "catmullrom") {
83836
+ const before = keyframes[Math.max(0, a4 - 1)].value;
83837
+ const after = keyframes[Math.min(keyframes.length - 1, b4 + 1)].value;
83838
+ return [0, 1, 2].map((i4) => catmullRom(delta, before[i4], from.value[i4], to.value[i4], after[i4]));
83839
+ }
83840
+ const target = to.pre ?? to.value;
83841
+ return [0, 1, 2].map((i4) => from.value[i4] + (target[i4] - from.value[i4]) * delta);
83842
+ }
83843
+ function entityAnimationTime(animation, time, loop = animation.loop) {
83844
+ return loop && animation.length > 0 ? time % animation.length : time;
83845
+ }
83846
+ function sampleEntityAnimation(animation, time, loop = animation.loop) {
83847
+ const elapsed = entityAnimationTime(animation, time, loop);
83848
+ const pose = {};
83849
+ for (const [name2, bone] of Object.entries(animation.bones)) {
83850
+ const sampled = pose[name2] = {};
83851
+ for (const channel of ENTITY_ANIMATION_CHANNELS) {
83852
+ const keyframes = bone[channel];
83853
+ if (keyframes?.length) sampled[channel] = sampleEntityKeyframes(keyframes, elapsed);
83854
+ }
83855
+ }
83856
+ return pose;
83857
+ }
83858
+
83734
83859
  // src/entity/EntityModel.ts
83735
83860
  init_dirname();
83736
83861
  init_buffer2();
@@ -83741,6 +83866,80 @@ ${key}` : key;
83741
83866
  init_buffer2();
83742
83867
  init_process2();
83743
83868
 
83869
+ // src/entity/scene/EntityAnimationPlayer.ts
83870
+ init_dirname();
83871
+ init_buffer2();
83872
+ init_process2();
83873
+ var ZERO = [0, 0, 0];
83874
+ var EntityAnimationPlayer = class {
83875
+ constructor() {
83876
+ this.parts = [];
83877
+ this._time = 0;
83878
+ this.loop = false;
83879
+ this.speed = 1;
83880
+ }
83881
+ get animation() {
83882
+ return this._animation;
83883
+ }
83884
+ get time() {
83885
+ return this._time;
83886
+ }
83887
+ /**
83888
+ * Starts an animation on the named parts below each layer root. The current pose of a part becomes its default;
83889
+ * bones without a matching part are skipped.
83890
+ */
83891
+ play(layers, animation, options = {}) {
83892
+ this.stop();
83893
+ this._animation = animation;
83894
+ this._time = options.time ?? 0;
83895
+ this.loop = options.loop ?? animation.loop;
83896
+ this.speed = options.speed ?? 1;
83897
+ for (const layer of layers) {
83898
+ for (const bone of Object.keys(animation.bones)) {
83899
+ const object = layer.getObjectByName(`group:${bone}`);
83900
+ if (!object) continue;
83901
+ this.parts.push({ bone, object, position: object.position.clone(), rotation: object.rotation.clone(), scale: object.scale.clone() });
83902
+ }
83903
+ }
83904
+ this.apply();
83905
+ }
83906
+ /** Restores the default poses. Returns whether an animation was active. */
83907
+ stop() {
83908
+ if (!this._animation) return false;
83909
+ for (const part of this.parts) {
83910
+ part.object.position.copy(part.position);
83911
+ part.object.rotation.copy(part.rotation);
83912
+ part.object.scale.copy(part.scale);
83913
+ }
83914
+ this.clear();
83915
+ return true;
83916
+ }
83917
+ /** Forgets the animation without touching the parts, for parts that no longer exist. */
83918
+ clear() {
83919
+ this.parts = [];
83920
+ this._animation = void 0;
83921
+ this._time = 0;
83922
+ }
83923
+ setTime(seconds) {
83924
+ if (!this._animation) return false;
83925
+ this._time = seconds;
83926
+ this.apply();
83927
+ return true;
83928
+ }
83929
+ advance(deltaSeconds) {
83930
+ return this.setTime(this._time + deltaSeconds * this.speed);
83931
+ }
83932
+ apply() {
83933
+ const pose = sampleEntityAnimation(this._animation, this._time, this.loop);
83934
+ for (const part of this.parts) {
83935
+ const { position = ZERO, rotation = ZERO, scale = ZERO } = pose[part.bone];
83936
+ part.object.position.set(part.position.x + position[0], part.position.y + position[1], part.position.z + position[2]);
83937
+ part.object.rotation.set(part.rotation.x + rotation[0], part.rotation.y + rotation[1], part.rotation.z + rotation[2], part.rotation.order);
83938
+ part.object.scale.set(part.scale.x + scale[0], part.scale.y + scale[1], part.scale.z + scale[2]);
83939
+ }
83940
+ }
83941
+ };
83942
+
83744
83943
  // src/entity/scene/EntityObject.ts
83745
83944
  init_dirname();
83746
83945
  init_buffer2();
@@ -83761,11 +83960,16 @@ ${key}` : key;
83761
83960
  this.entity = entity;
83762
83961
  this.isEntityObject = true;
83763
83962
  this.meshesCreated = false;
83963
+ this.geometries = /* @__PURE__ */ new Set();
83964
+ this.disposeWireframes = [];
83764
83965
  /** Scrolling materials are owned: their texture offset follows this entity's age. */
83765
83966
  this.scrollMaterials = [];
83766
83967
  /** Entity age in ticks, as vanilla's `ageInTicks`; drives the scrolling render modes. */
83767
83968
  this.age = 0;
83969
+ this.animationPlayer = new EntityAnimationPlayer();
83768
83970
  this.options = (0, import_ts_deepmerge4.default)({}, _EntityObject.DEFAULT_OPTIONS, options ?? {});
83971
+ this.addEventListener("added", () => this.updateScrollSubscription());
83972
+ this.addEventListener("removed", () => this.updateScrollSubscription());
83769
83973
  }
83770
83974
  async init() {
83771
83975
  this.createMeshes();
@@ -83776,6 +83980,10 @@ ${key}` : key;
83776
83980
  }
83777
83981
  disposeAndRemoveAllChildren() {
83778
83982
  this.clearScrollMaterials();
83983
+ for (const geometry of this.geometries) geometry.dispose();
83984
+ this.geometries.clear();
83985
+ for (const dispose of this.disposeWireframes.splice(0)) dispose();
83986
+ this.animationPlayer.clear();
83779
83987
  super.disposeAndRemoveAllChildren();
83780
83988
  }
83781
83989
  clearScrollMaterials() {
@@ -83786,6 +83994,19 @@ ${key}` : key;
83786
83994
  material.dispose();
83787
83995
  }
83788
83996
  }
83997
+ updateScrollSubscription() {
83998
+ if (this.parent && this.scrollMaterials.length) {
83999
+ if (this.scrollTicker === void 0) {
84000
+ this.scrollTicker = Ticker.add(() => {
84001
+ this.age++;
84002
+ this.updateScroll();
84003
+ });
84004
+ }
84005
+ } else {
84006
+ Ticker.remove(this.scrollTicker);
84007
+ this.scrollTicker = void 0;
84008
+ }
84009
+ }
83789
84010
  /** Applies the entity age to the scrolling materials; the ticker calls this 20 times per second. */
83790
84011
  updateScroll() {
83791
84012
  for (const { offset, speed } of this.scrollMaterials) {
@@ -83793,6 +84014,39 @@ ${key}` : key;
83793
84014
  }
83794
84015
  this.notifyDirty();
83795
84016
  }
84017
+ //<editor-fold desc="ANIMATION">
84018
+ /** The animation started by {@link playAnimation}, until {@link stopAnimation} or disposal. */
84019
+ get animation() {
84020
+ return this.animationPlayer.animation;
84021
+ }
84022
+ /** Seconds since the animation started, before looping. */
84023
+ get animationTime() {
84024
+ return this.animationPlayer.time;
84025
+ }
84026
+ /**
84027
+ * Poses the object from a keyframe animation of {@link Entities.getAnimations}, replacing the current one.
84028
+ * Parts of every layer are matched by name and their current pose is the default the offsets add to.
84029
+ * The object owns no clock: call {@link advanceAnimation} per frame, or {@link setAnimationTime}.
84030
+ */
84031
+ playAnimation(animation, options) {
84032
+ this.createMeshes();
84033
+ const layers = Object.keys(this.entityLayers).map((name2) => this.getLayerGroup(name2)).filter((layer) => !!layer);
84034
+ this.animationPlayer.play(layers, animation, options);
84035
+ this.notifyDirty();
84036
+ }
84037
+ /** Stops the animation and restores the default pose. */
84038
+ stopAnimation() {
84039
+ if (this.animationPlayer.stop()) this.notifyDirty();
84040
+ }
84041
+ /** Poses the object at an explicit time in seconds. Has no effect without an animation. */
84042
+ setAnimationTime(seconds) {
84043
+ if (this.animationPlayer.setTime(seconds)) this.notifyDirty();
84044
+ }
84045
+ /** Advances the animation by a frame delta in seconds, scaled by its speed. Has no effect without an animation. */
84046
+ advanceAnimation(deltaSeconds) {
84047
+ if (this.animationPlayer.advance(deltaSeconds)) this.notifyDirty();
84048
+ }
84049
+ //</editor-fold>
83796
84050
  getLayerGroup(name2) {
83797
84051
  return super.getGroupByName(`layer:${name2}`);
83798
84052
  }
@@ -83863,6 +84117,7 @@ ${key}` : key;
83863
84117
  depth + growZ * 2,
83864
84118
  uv
83865
84119
  ).clone();
84120
+ this.geometries.add(geometry);
83866
84121
  geometry.translate(cube.origin[0] + width / 2, cube.origin[1] + height / 2, cube.origin[2] + depth / 2);
83867
84122
  if (inward && Math.min(width + growX * 2, height + growY * 2, depth + growZ * 2) > 0) {
83868
84123
  const index = Array.from(geometry.getIndex().array);
@@ -83871,7 +84126,7 @@ ${key}` : key;
83871
84126
  const mesh = this.createMesh(name2, geometry, material);
83872
84127
  mesh.renderOrder = renderOrder;
83873
84128
  anchor.add(mesh);
83874
- if (this.options.wireframe) addWireframeToMesh(geometry, mesh);
84129
+ if (this.options.wireframe) this.disposeWireframes.push(addWireframeToMesh(geometry, mesh));
83875
84130
  }
83876
84131
  for (const [childName, child] of Object.entries(part.children)) {
83877
84132
  this.createPart(childName, child, anchor, size, material, renderOrder, inward);
@@ -83905,11 +84160,8 @@ ${key}` : key;
83905
84160
  this.clearScrollMaterials();
83906
84161
  } else if (this.scrollMaterials.length) {
83907
84162
  this.updateScroll();
83908
- this.scrollTicker = Ticker.add(() => {
83909
- this.age++;
83910
- this.updateScroll();
83911
- });
83912
84163
  }
84164
+ this.updateScrollSubscription();
83913
84165
  this.notifyDirty();
83914
84166
  }
83915
84167
  };
@@ -87400,6 +87652,9 @@ property list uchar int vertex_index
87400
87652
  this._variants = [];
87401
87653
  this._models = [];
87402
87654
  this._cullMask = 0;
87655
+ this._entities = [];
87656
+ this._entityPlacement = new Matrix4();
87657
+ this._entityGeneration = 0;
87403
87658
  this.options = (0, import_ts_deepmerge6.default)({}, _BlockObject.DEFAULT_OPTIONS, options ?? {});
87404
87659
  }
87405
87660
  async init() {
@@ -87419,6 +87674,8 @@ property list uchar int vertex_index
87419
87674
  }
87420
87675
  clearModels() {
87421
87676
  this.removeModels(this._models.splice(0));
87677
+ this._entityGeneration++;
87678
+ this.removeEntities(this._entities.splice(0));
87422
87679
  this._fluidKey = void 0;
87423
87680
  this._fluidModel = void 0;
87424
87681
  this._isInstanced = false;
@@ -87430,6 +87687,57 @@ property list uchar int vertex_index
87430
87687
  if (!isInstanceReference(model)) model.dispose();
87431
87688
  }
87432
87689
  }
87690
+ removeEntities(entities) {
87691
+ for (const entity of entities) {
87692
+ entity.removeFromScene();
87693
+ entity.dispose();
87694
+ }
87695
+ }
87696
+ /** Entity models drawn for this block instead of its block model; they are scene children, unlike the block. */
87697
+ get blockEntities() {
87698
+ return this._entities;
87699
+ }
87700
+ /**
87701
+ * Draws the block through the dataset's block-entity index. Returns false, leaving nothing behind,
87702
+ * when the block is not listed or one of its models cannot be loaded.
87703
+ */
87704
+ async createBlockEntities() {
87705
+ const key = this.blockState.key;
87706
+ if (!key || this.options.displayPosition) return false;
87707
+ const generation = this._entityGeneration;
87708
+ const created = [];
87709
+ let resolved;
87710
+ try {
87711
+ resolved = BlockEntities.resolve(await BlockEntities.getIndex(key.root), key.toNamespacedString(), this.state);
87712
+ if (!resolved) return false;
87713
+ for (const part of resolved.parts) {
87714
+ const [namespace, path] = part.model.includes(":") ? part.model.split(":") : [key.namespace, part.model];
87715
+ const texture = part.textureLocation === void 0 ? void 0 : AssetKey.parse("textures", part.textureLocation.replace(/^([^:]+:)?textures\//, "$1"));
87716
+ if (texture) texture.root = key.root;
87717
+ const entity = await Entities.getEntity(new BasicAssetKey(namespace, path), texture, { layer: part.layer });
87718
+ if (!entity) throw new MineRenderError(`Missing entity model ${part.model}`);
87719
+ created.push(await this.scene.addEntity(entity, { wireframe: this.options.wireframe }));
87720
+ }
87721
+ } catch (error) {
87722
+ console.warn(`Could not draw block entity ${key.toNamespacedString()}, using its block model`, error);
87723
+ this.removeEntities(created);
87724
+ return false;
87725
+ }
87726
+ if (generation !== this._entityGeneration) {
87727
+ this.removeEntities(created);
87728
+ return true;
87729
+ }
87730
+ BlockEntities.matrix(resolved, this._entityPlacement);
87731
+ this._entities = created;
87732
+ this.placeEntities();
87733
+ return true;
87734
+ }
87735
+ placeEntities() {
87736
+ const matrix = new Matrix4().makeTranslation(this.position.x - 8, this.position.y - 8, this.position.z - 8).multiply(this._entityPlacement);
87737
+ for (const entity of this._entities) {
87738
+ matrix.decompose(entity.position, entity.quaternion, entity.scale);
87739
+ }
87740
+ }
87433
87741
  get isOccluding() {
87434
87742
  return this._models.some((model) => {
87435
87743
  const object = isInstanceReference(model) ? model.instanceable : model;
@@ -87541,13 +87849,20 @@ property list uchar int vertex_index
87541
87849
  await this.updateFluid(this._fluidSampler);
87542
87850
  return;
87543
87851
  }
87852
+ const hasEntities = await this.createBlockEntities();
87544
87853
  const variantsToCreate = await this.mapStateToVariant(this.state);
87545
87854
  this._variants = variantsToCreate;
87546
87855
  for (let blockStateVariant of variantsToCreate) {
87856
+ if (hasEntities && !await this.hasGeometry(blockStateVariant)) continue;
87547
87857
  this._models.push(await this.createVariant(blockStateVariant));
87548
87858
  }
87549
87859
  await this.updateFluid(this._fluidSampler);
87550
87860
  }
87861
+ async hasGeometry(variant) {
87862
+ const modelKey = AssetKey.parse("models", variant.model);
87863
+ modelKey.root = this.blockState.key?.root;
87864
+ return !!(await Models.getMerged(modelKey))?.elements?.length;
87865
+ }
87551
87866
  getSingleVariant(variants) {
87552
87867
  return BlockStateResolver.choose(variants);
87553
87868
  }
@@ -87667,6 +87982,7 @@ property list uchar int vertex_index
87667
87982
  for (let model of this._models) {
87668
87983
  model.setPosition(position);
87669
87984
  }
87985
+ this.placeEntities();
87670
87986
  this.notifyDirty();
87671
87987
  }
87672
87988
  //TODO: should override rotation + scale methods
@@ -94553,7 +94869,8 @@ uniform float opacity;varying vec2 vUv;void main(){vec4 texel=texture2D(inputBuf
94553
94869
  this.data.set(index, void 0);
94554
94870
  return void 0;
94555
94871
  }
94556
- const template = getFluidKind(blockState.key, stored.properties) ? void 0 : await this.sectionModels?.get(blockState, stored.properties);
94872
+ const perBlock = !this.sectionModels || getFluidKind(blockState.key, stored.properties) || blockState.key && BlockEntities.entry(await BlockEntities.getIndex(blockState.key.root), blockState.key.toNamespacedString());
94873
+ const template = perBlock ? void 0 : await this.sectionModels.get(blockState, stored.properties);
94557
94874
  if (template) {
94558
94875
  this.sectionBlocks.set(index, { index, template, cullMask: 0 });
94559
94876
  this.meshDirty = true;