minerender 2.1.0-alpha.33 → 2.1.0-alpha.35

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.
@@ -2832,7 +2832,7 @@ var Entities = class {
2832
2832
  };
2833
2833
 
2834
2834
  // src/renderer/SceneObject.ts
2835
- import { Euler as Euler3, InstancedMesh, MathUtils, Matrix4 as Matrix43, Mesh as Mesh3, Object3D as Object3D2, Quaternion as Quaternion3, Vector3 as Vector35 } from "three";
2835
+ 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";
2836
2836
 
2837
2837
  // src/instance/InstanceReference.ts
2838
2838
  var InstanceReference = class {
@@ -2844,32 +2844,42 @@ var InstanceReference = class {
2844
2844
  nextInstance() {
2845
2845
  return this.instanceable.nextInstance();
2846
2846
  }
2847
+ get activeInstanceable() {
2848
+ if (!this.instanceable.isInstanceActive(this.index, this)) throw new MineRenderError("Instance has been removed");
2849
+ return this.instanceable;
2850
+ }
2851
+ removeFromScene() {
2852
+ if (this.instanceable.isInstanceActive(this.index, this)) this.instanceable.removeInstanceAt(this.index);
2853
+ }
2854
+ dispose() {
2855
+ this.removeFromScene();
2856
+ }
2847
2857
  getMatrix(matrix) {
2848
- return this.instanceable.getMatrixAt(this.index, matrix);
2858
+ return this.activeInstanceable.getMatrixAt(this.index, matrix);
2849
2859
  }
2850
2860
  setMatrix(matrix) {
2851
- this.instanceable.setMatrixAt(this.index, matrix);
2861
+ this.activeInstanceable.setMatrixAt(this.index, matrix);
2852
2862
  }
2853
2863
  setPositionRotationScale(position, rotation, scale) {
2854
- this.instanceable.setPositionRotationScaleAt(this.index, position, rotation, scale);
2864
+ this.activeInstanceable.setPositionRotationScaleAt(this.index, position, rotation, scale);
2855
2865
  }
2856
2866
  setPosition(position) {
2857
- this.instanceable.setPositionAt(this.index, position);
2867
+ this.activeInstanceable.setPositionAt(this.index, position);
2858
2868
  }
2859
2869
  getPosition() {
2860
- return this.instanceable.getPositionAt(this.index);
2870
+ return this.activeInstanceable.getPositionAt(this.index);
2861
2871
  }
2862
2872
  setRotation(rotation) {
2863
- this.instanceable.setRotationAt(this.index, rotation);
2873
+ this.activeInstanceable.setRotationAt(this.index, rotation);
2864
2874
  }
2865
2875
  getRotation() {
2866
- return this.instanceable.getRotationAt(this.index);
2876
+ return this.activeInstanceable.getRotationAt(this.index);
2867
2877
  }
2868
2878
  setScale(scale) {
2869
- this.instanceable.setScaleAt(this.index, scale);
2879
+ this.activeInstanceable.setScaleAt(this.index, scale);
2870
2880
  }
2871
2881
  getScale() {
2872
- return this.instanceable.getScaleAt(this.index);
2882
+ return this.activeInstanceable.getScaleAt(this.index);
2873
2883
  }
2874
2884
  //TODO: support for custom methods e.g. setting block variants
2875
2885
  };
@@ -2887,6 +2897,8 @@ var SceneObject = class _SceneObject extends Object3D2 {
2887
2897
  this.materialCallbacks = {};
2888
2898
  this._isInstanced = false;
2889
2899
  this._instanceCounter = 0;
2900
+ this.instanceReferences = /* @__PURE__ */ new Map();
2901
+ this.freeInstanceIndices = [];
2890
2902
  this.options = (0, import_ts_deepmerge3.default)({}, _SceneObject.DEFAULT_OPTIONS, options ?? {});
2891
2903
  console.log("SceneObject options", this.options);
2892
2904
  }
@@ -2990,6 +3002,8 @@ var SceneObject = class _SceneObject extends Object3D2 {
2990
3002
  createInstancedMesh(name, geometry, material, count) {
2991
3003
  const mesh = new InstancedMesh(geometry, material, count);
2992
3004
  mesh.count = 0;
3005
+ this.instanceMesh = mesh;
3006
+ this._isInstanced = true;
2993
3007
  if (name) {
2994
3008
  mesh.name = `mesh:${name}`;
2995
3009
  }
@@ -3057,40 +3071,70 @@ var SceneObject = class _SceneObject extends Object3D2 {
3057
3071
  return new InstanceReference(this, i);
3058
3072
  }
3059
3073
  nextInstance() {
3060
- if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
3061
- const i = this._instanceCounter++;
3062
- this.setMatrixAt(i, new Matrix43());
3063
- const mesh = this.children[0];
3064
- if (mesh && isInstancedMesh(mesh)) {
3065
- mesh.count = Math.min(this._instanceCounter, mesh.instanceMatrix.count);
3066
- }
3067
- console.debug(p6, "nextInstance " + i);
3068
- if (i === this.options.maxInstanceCount) {
3069
- console.warn(p6, "Max instance count reached for " + this);
3070
- }
3071
- if (this.scene) {
3072
- this.scene.stats.instanceCount++;
3074
+ const mesh = this.instanceMesh;
3075
+ if (!mesh) throw new MineRenderError("Object is not instanced");
3076
+ const index = this.freeInstanceIndices.pop() ?? mesh.count;
3077
+ if (index >= mesh.instanceMatrix.count) this.growInstances(index + 1);
3078
+ const reference = this.constructInstanceReference(index);
3079
+ this.instanceReferences.set(index, reference);
3080
+ this._instanceCounter = this.instanceReferences.size;
3081
+ mesh.count = Math.max(mesh.count, index + 1);
3082
+ if (mesh.instanceColor) {
3083
+ mesh.setColorAt(index, new Color2(16777215));
3084
+ mesh.instanceColor.needsUpdate = true;
3085
+ }
3086
+ if (this._scene) this._scene.stats.instanceCount++;
3087
+ this.setMatrixAt(index, new Matrix43());
3088
+ return reference;
3089
+ }
3090
+ isInstanceActive(index, reference) {
3091
+ return this.instanceReferences.get(index) === reference;
3092
+ }
3093
+ removeInstanceAt(index) {
3094
+ if (!this.instanceReferences.has(index)) return;
3095
+ const mesh = this.instanceMesh;
3096
+ this.setMatrixAt(index, new Matrix43().makeScale(0, 0, 0));
3097
+ this.instanceReferences.delete(index);
3098
+ this.freeInstanceIndices.push(index);
3099
+ this._instanceCounter = this.instanceReferences.size;
3100
+ while (mesh.count > 0 && !this.instanceReferences.has(mesh.count - 1)) mesh.count--;
3101
+ if (this._scene) this._scene.stats.instanceCount--;
3102
+ }
3103
+ growInstances(required) {
3104
+ const mesh = this.instanceMesh;
3105
+ const capacity = Math.max(required, mesh.instanceMatrix.count * 2);
3106
+ const grow = (attribute, fill) => {
3107
+ const array = new Float32Array(capacity * attribute.itemSize).fill(fill);
3108
+ array.set(attribute.array);
3109
+ return new InstancedBufferAttribute(array, attribute.itemSize, attribute.normalized, attribute.meshPerAttribute).setUsage(attribute.usage);
3110
+ };
3111
+ const matrix = grow(mesh.instanceMatrix, 0);
3112
+ const color = mesh.instanceColor ? grow(mesh.instanceColor, 1) : null;
3113
+ mesh.dispose();
3114
+ mesh.instanceMatrix = matrix;
3115
+ mesh.instanceColor = color;
3116
+ }
3117
+ *activeInstanceIndices() {
3118
+ if (this.instanceMesh) {
3119
+ yield* this.instanceReferences.keys();
3120
+ } else {
3121
+ for (let i = 0; i < this.instanceCounter; i++) yield i;
3073
3122
  }
3074
- this.notifyDirty();
3075
- return this.constructInstanceReference(i);
3076
3123
  }
3077
3124
  //</editor-fold>
3078
3125
  //<editor-fold desc="TRANSFORMATION">
3079
3126
  getMatrixAt(index, matrix = new Matrix43()) {
3080
- if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
3081
- const child = this.children[0];
3082
- if (child && isInstancedMesh(child)) {
3083
- child.getMatrixAt(index, matrix);
3084
- }
3127
+ if (!this.instanceReferences.has(index)) throw new MineRenderError("Instance is not active");
3128
+ this.instanceMesh.getMatrixAt(index, matrix);
3085
3129
  return matrix;
3086
3130
  }
3087
3131
  setMatrixAt(index, matrix) {
3088
- if (!this.isInstanced) throw new MineRenderError("Object is not instanced");
3089
- const child = this.children[0];
3090
- if (child && isInstancedMesh(child)) {
3091
- child.setMatrixAt(index, matrix);
3092
- child.instanceMatrix.needsUpdate = true;
3093
- }
3132
+ if (!this.instanceReferences.has(index)) throw new MineRenderError("Instance is not active");
3133
+ const mesh = this.instanceMesh;
3134
+ mesh.setMatrixAt(index, matrix);
3135
+ mesh.instanceMatrix.needsUpdate = true;
3136
+ mesh.boundingBox = null;
3137
+ mesh.boundingSphere = null;
3094
3138
  this.notifyDirty();
3095
3139
  }
3096
3140
  setPositionRotationScaleAt(index, position, rotation, scale) {
@@ -3141,7 +3185,7 @@ var SceneObject = class _SceneObject extends Object3D2 {
3141
3185
  }
3142
3186
  setPositionRotationScale(position, rotation, scale) {
3143
3187
  if (this.isInstanced) {
3144
- for (let i = 0; i < this.instanceCounter; i++) {
3188
+ for (const i of this.activeInstanceIndices()) {
3145
3189
  this.setPositionRotationScaleAt(i, position, rotation, scale);
3146
3190
  }
3147
3191
  } else {
@@ -3159,7 +3203,7 @@ var SceneObject = class _SceneObject extends Object3D2 {
3159
3203
  }
3160
3204
  setPosition(position) {
3161
3205
  if (this.isInstanced) {
3162
- for (let i = 0; i < this.instanceCounter; i++) {
3206
+ for (const i of this.activeInstanceIndices()) {
3163
3207
  this.setPositionRotationScaleAt(i, position);
3164
3208
  }
3165
3209
  } else {
@@ -3169,14 +3213,14 @@ var SceneObject = class _SceneObject extends Object3D2 {
3169
3213
  }
3170
3214
  getPosition() {
3171
3215
  if (this.isInstanced) {
3172
- return this.getPositionAt(0);
3216
+ return this.getPositionAt(this.instanceReferences.keys().next().value ?? 0);
3173
3217
  } else {
3174
3218
  return this.position;
3175
3219
  }
3176
3220
  }
3177
3221
  setRotation(rotation) {
3178
3222
  if (this.isInstanced) {
3179
- for (let i = 0; i < this.instanceCounter; i++) {
3223
+ for (const i of this.activeInstanceIndices()) {
3180
3224
  this.setPositionRotationScaleAt(i, void 0, rotation);
3181
3225
  }
3182
3226
  } else {
@@ -3186,14 +3230,14 @@ var SceneObject = class _SceneObject extends Object3D2 {
3186
3230
  }
3187
3231
  getRotation() {
3188
3232
  if (this.isInstanced) {
3189
- return this.getRotationAt(0);
3233
+ return this.getRotationAt(this.instanceReferences.keys().next().value ?? 0);
3190
3234
  } else {
3191
3235
  return this.rotation;
3192
3236
  }
3193
3237
  }
3194
3238
  setScale(scale) {
3195
3239
  if (this.isInstanced) {
3196
- for (let i = 0; i < this.instanceCounter; i++) {
3240
+ for (const i of this.activeInstanceIndices()) {
3197
3241
  this.setPositionRotationScaleAt(i, void 0, void 0, scale);
3198
3242
  }
3199
3243
  } else {
@@ -3203,7 +3247,7 @@ var SceneObject = class _SceneObject extends Object3D2 {
3203
3247
  }
3204
3248
  getScale() {
3205
3249
  if (this.isInstanced) {
3206
- return this.getScaleAt(0);
3250
+ return this.getScaleAt(this.instanceReferences.keys().next().value ?? 0);
3207
3251
  } else {
3208
3252
  return this.scale;
3209
3253
  }
@@ -3227,6 +3271,17 @@ var SceneObject = class _SceneObject extends Object3D2 {
3227
3271
  this.notifyDirty();
3228
3272
  }
3229
3273
  disposeAndRemoveAllChildren() {
3274
+ if (this.instanceMesh) {
3275
+ if (this._scene) this._scene.stats.instanceCount -= this.instanceReferences.size;
3276
+ this.instanceReferences.clear();
3277
+ this.freeInstanceIndices.length = 0;
3278
+ this._instanceCounter = 0;
3279
+ this.instanceMesh.count = 0;
3280
+ this.instanceMesh.dispose();
3281
+ this.instanceMesh.removeFromParent();
3282
+ this.instanceMesh = void 0;
3283
+ this._isInstanced = false;
3284
+ }
3230
3285
  while (this.children.length > 0) {
3231
3286
  let c = this.children[0];
3232
3287
  if (isDisposable(c)) {
@@ -4449,10 +4504,6 @@ var ModelObject = class _ModelObject extends SceneObject {
4449
4504
  this.createMeshes();
4450
4505
  this.applyTextures();
4451
4506
  }
4452
- dispose() {
4453
- super.dispose();
4454
- this.atlas?.dispose();
4455
- }
4456
4507
  get textureAtlas() {
4457
4508
  return this.atlas;
4458
4509
  }
@@ -4566,7 +4617,7 @@ function isModelObject(obj) {
4566
4617
 
4567
4618
  // src/model/block/scene/BlockObject.ts
4568
4619
  var import_ts_deepmerge6 = __toESM(require_dist());
4569
- import { Euler as Euler5, Matrix4 as Matrix45, Vector3 as Vector37 } from "three";
4620
+ import { Euler as Euler5, Matrix4 as Matrix45 } from "three";
4570
4621
  var p10 = prefix("BlockObject");
4571
4622
  function matchesCondition(condition, state) {
4572
4623
  return Object.entries(condition).every(([key, value]) => {
@@ -4621,18 +4672,21 @@ var BlockObject = class _BlockObject extends SceneObject {
4621
4672
  }
4622
4673
  }
4623
4674
  dispose() {
4675
+ this.clearModels();
4624
4676
  super.dispose();
4625
4677
  }
4626
4678
  removeFromScene() {
4627
- for (let model of this._models) {
4628
- if (isModelObject(model)) {
4629
- model.removeFromScene();
4630
- } else if (isInstanceReference(model)) {
4631
- model.setScale(new Vector37(0, 0, 0));
4632
- }
4633
- }
4679
+ this.clearModels();
4634
4680
  super.removeFromScene();
4635
4681
  }
4682
+ clearModels() {
4683
+ for (const model of this._models.splice(0)) {
4684
+ model.removeFromScene();
4685
+ if (!isInstanceReference(model)) model.dispose();
4686
+ }
4687
+ this._isInstanced = false;
4688
+ this._instanceCounter = 0;
4689
+ }
4636
4690
  get state() {
4637
4691
  return this._state;
4638
4692
  }
@@ -4681,22 +4735,7 @@ var BlockObject = class _BlockObject extends SceneObject {
4681
4735
  return out;
4682
4736
  }
4683
4737
  async recreateModels() {
4684
- const instanceInfo = [];
4685
- if (this.isInstanced) {
4686
- for (let i = 0; i < this.instanceCounter; i++) {
4687
- instanceInfo[i] = this.getMatrixAt(i);
4688
- }
4689
- }
4690
- for (let model of this._models) {
4691
- if (isInstanceReference(model)) {
4692
- model.setScale(new Vector37(0, 0, 0));
4693
- } else {
4694
- model.dispose();
4695
- }
4696
- }
4697
- while (this._models.length > 0) {
4698
- this._models.shift();
4699
- }
4738
+ this.clearModels();
4700
4739
  const variantsToCreate = await this.mapStateToVariant(this.state);
4701
4740
  this._variants = variantsToCreate;
4702
4741
  for (let blockStateVariant of variantsToCreate) {
@@ -4747,6 +4786,7 @@ var BlockObject = class _BlockObject extends SceneObject {
4747
4786
  this._instanceCounter = 1;
4748
4787
  }
4749
4788
  obj.setRotation(rotation);
4789
+ obj.setPosition(this.position);
4750
4790
  return obj;
4751
4791
  }
4752
4792
  async resetState() {
@@ -7203,15 +7243,12 @@ var Skins = class {
7203
7243
  };
7204
7244
 
7205
7245
  // src/world/Chunk.ts
7206
- import { Box3 as Box32, Object3D as Object3D6, Vector3 as Vector311 } from "three";
7246
+ import { Vector3 as Vector311 } from "three";
7207
7247
 
7208
7248
  // src/world/MineRenderWorld.ts
7209
7249
  import { Vector3 as Vector310 } from "three";
7210
7250
  var MineRenderWorld = class {
7211
7251
  constructor(scene) {
7212
- this._size = 4;
7213
- //TODO: expandable
7214
- this._blockSize = this._size * 16;
7215
7252
  this._chunks = /* @__PURE__ */ new Map();
7216
7253
  this.scene = scene;
7217
7254
  }
@@ -7222,12 +7259,8 @@ var MineRenderWorld = class {
7222
7259
  if (isTripleArray(posOrX)) {
7223
7260
  return this.getBlockAt(new Vector310(posOrX[0], posOrX[1], posOrX[2]));
7224
7261
  }
7225
- const chunk = this.getChunkAt(posOrX);
7226
- if (typeof chunk === "undefined") {
7227
- return void 0;
7228
- }
7229
7262
  this.validatePosBounds(posOrX);
7230
- return chunk.getBlockAt(posOrX);
7263
+ return this.getChunkAt(posOrX)?.getBlockAt(posOrX);
7231
7264
  }
7232
7265
  async setBlockAt(posOrX, yOrBlock, z, block) {
7233
7266
  if (typeof posOrX == "number") {
@@ -7237,8 +7270,9 @@ var MineRenderWorld = class {
7237
7270
  return this.setBlockAt(new Vector310(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
7238
7271
  }
7239
7272
  this.validatePosBounds(posOrX);
7240
- const chunk = this.getOrCreateChunkAt(posOrX);
7241
- return chunk.setBlockAt(posOrX, yOrBlock);
7273
+ const value = yOrBlock;
7274
+ const chunk = Chunk.isAir(value) ? this.getChunkAt(posOrX) : this.getOrCreateChunkAt(posOrX);
7275
+ return chunk?.setBlockAt(posOrX, value);
7242
7276
  }
7243
7277
  async placeMultiBlock(multiblock, useBatches = true, executor = new BatchedExecutor()) {
7244
7278
  const keys = new Set(multiblock.blocks.map((block) => AssetKey.parse("blockstates", block.type)));
@@ -7259,10 +7293,8 @@ var MineRenderWorld = class {
7259
7293
  }
7260
7294
  }
7261
7295
  async clear() {
7262
- console.log("CHUNKS", this._chunks);
7263
- for (let chunk of this._chunks.values()) {
7264
- console.log("dispose chunk", chunk);
7265
- await chunk?.dispose();
7296
+ for (const chunk of this._chunks.values()) {
7297
+ await chunk.dispose();
7266
7298
  }
7267
7299
  this._chunks.clear();
7268
7300
  }
@@ -7276,8 +7308,7 @@ var MineRenderWorld = class {
7276
7308
  return chunk;
7277
7309
  }
7278
7310
  getChunkAt(pos) {
7279
- const index = this.worldPosToChunkIndex(pos);
7280
- return this._chunks[index];
7311
+ return this._chunks.get(this.worldPosToChunkKey(pos));
7281
7312
  }
7282
7313
  static worldToScenePosition(pos) {
7283
7314
  return new Vector310(
@@ -7293,12 +7324,6 @@ var MineRenderWorld = class {
7293
7324
  pos.z / 16
7294
7325
  );
7295
7326
  }
7296
- worldPosToChunkIndex(pos) {
7297
- const chunkX = Math.floor(pos.x / 16);
7298
- const chunkY = Math.floor(pos.y / 16);
7299
- const chunkZ = Math.floor(pos.z / 16);
7300
- return chunkZ * this._size * this._size + chunkY * this._size + chunkX;
7301
- }
7302
7327
  worldPosToChunkKey(pos) {
7303
7328
  const chunkX = Math.floor(pos.x / 16);
7304
7329
  const chunkY = Math.floor(pos.y / 16);
@@ -7306,12 +7331,9 @@ var MineRenderWorld = class {
7306
7331
  return `${chunkX}_${chunkY}_${chunkZ}`;
7307
7332
  }
7308
7333
  validatePosBounds(pos) {
7309
- if (pos.x < 0) throw new Error("x<0");
7310
- if (pos.y < 0) throw new Error("y<0");
7311
- if (pos.z < 0) throw new Error("z<0");
7312
- if (pos.x > this._blockSize) throw new Error("x>" + this._blockSize);
7313
- if (pos.y > this._blockSize) throw new Error("y>" + this._blockSize);
7314
- if (pos.z > this._blockSize) throw new Error("z>" + this._blockSize);
7334
+ if (![pos.x, pos.y, pos.z].every(Number.isInteger)) {
7335
+ throw new RangeError("Block coordinates must be integers");
7336
+ }
7315
7337
  }
7316
7338
  };
7317
7339
 
@@ -7324,16 +7346,6 @@ var Chunk = class _Chunk {
7324
7346
  this.x = x;
7325
7347
  this.y = y;
7326
7348
  this.z = z;
7327
- let anchor = new Object3D6();
7328
- this._anchor = anchor;
7329
- scene.add(anchor);
7330
- anchor.position.set(this.x * 256 + 128 - 8, this.y * 256 + 128 - 8, this.z * 256 + 128 - 8);
7331
- addBox3WireframeToObject(
7332
- new Box32(new Vector311(0, 0, 0), new Vector311(256, 256, 256)),
7333
- anchor,
7334
- 16776960,
7335
- 4
7336
- );
7337
7349
  }
7338
7350
  getBlockAt(posOrX, y, z) {
7339
7351
  if (typeof posOrX == "number") {
@@ -7359,7 +7371,7 @@ var Chunk = class _Chunk {
7359
7371
  return this.setBlockInChunkAt(pos, block, worldPos);
7360
7372
  }
7361
7373
  /**
7362
- * Set block at a _chunk_ position (0-16)
7374
+ * Set a block at integer chunk-local coordinates from 0 to 15.
7363
7375
  */
7364
7376
  async setBlockInChunkAt(pos, block, worldPos) {
7365
7377
  if (typeof worldPos === "undefined") {
@@ -7372,15 +7384,13 @@ var Chunk = class _Chunk {
7372
7384
  current.object.removeFromScene();
7373
7385
  delete this._blocks[index];
7374
7386
  }
7375
- if (typeof block === "undefined" || typeof block.type === "undefined" || block.type === "air") return void 0;
7387
+ if (!block || _Chunk.isAir(block)) return void 0;
7376
7388
  const blockState = await BlockStates.get(AssetKey.parse("blockstates", block.type));
7377
7389
  if (blockState) {
7378
7390
  const blockObject = await this.scene.addBlock(blockState, {
7379
7391
  mergeMeshes: true,
7380
7392
  instanceMeshes: true,
7381
- wireframe: true,
7382
7393
  maxInstanceCount: 2e3
7383
- //TODO: setting this to high kills FPS; increase on demand somehow?
7384
7394
  });
7385
7395
  if (block.properties) {
7386
7396
  await blockObject.setState(block.properties);
@@ -7396,23 +7406,21 @@ var Chunk = class _Chunk {
7396
7406
  return void 0;
7397
7407
  }
7398
7408
  async clear() {
7399
- let promises = [];
7400
- let pos = new Vector311();
7401
- for (let x = 0; x < 16; x++) {
7402
- for (let z = 0; z < 16; z++) {
7403
- for (let y = 0; y < 16; y++) {
7404
- promises.push(this.setBlockInChunkAt(pos.set(x, y, z), void 0));
7405
- }
7406
- }
7407
- }
7408
- return Promise.all(promises).then((ignored) => {
7409
- });
7409
+ this._blocks.forEach((block) => block.object.removeFromScene());
7410
+ this._blocks.length = 0;
7410
7411
  }
7411
7412
  async dispose() {
7412
7413
  await this.clear();
7413
- this.scene.remove(this._anchor);
7414
+ }
7415
+ static isAir(block) {
7416
+ if (typeof block === "undefined" || typeof block.type === "undefined") return true;
7417
+ const key = AssetKey.parse("blockstates", block.type);
7418
+ return key.namespace === "minecraft" && !key.type && ["air", "cave_air", "void_air"].includes(key.path);
7414
7419
  }
7415
7420
  static chunkPosToBlockIndex(pos) {
7421
+ if ([pos.x, pos.y, pos.z].some((value) => !Number.isInteger(value) || value < 0 || value >= 16)) {
7422
+ throw new RangeError("Block coordinates must be integers from 0 to 15 within a chunk");
7423
+ }
7416
7424
  return pos.y * 16 * 16 + pos.z * 16 + pos.x;
7417
7425
  }
7418
7426
  worldPosToChunkPos(pos) {