minerender 2.1.0-alpha.36 → 2.1.0-alpha.38

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.
@@ -32,8 +32,8 @@ var require_dist = __commonJS({
32
32
  __assign = Object.assign || function(t) {
33
33
  for (var s, i = 1, n = arguments.length; i < n; i++) {
34
34
  s = arguments[i];
35
- for (var p12 in s) if (Object.prototype.hasOwnProperty.call(s, p12))
36
- t[p12] = s[p12];
35
+ for (var p11 in s) if (Object.prototype.hasOwnProperty.call(s, p11))
36
+ t[p11] = s[p11];
37
37
  }
38
38
  return t;
39
39
  };
@@ -278,6 +278,14 @@ var CubeFaceIndex = /* @__PURE__ */ ((CubeFaceIndex2) => {
278
278
  return CubeFaceIndex2;
279
279
  })(CubeFaceIndex || {});
280
280
  var CUBE_FACES = Object.values(CubeFace);
281
+ var CUBE_FACE_OFFSETS = [
282
+ [1, 0, 0],
283
+ [-1, 0, 0],
284
+ [0, 1, 0],
285
+ [0, -1, 0],
286
+ [0, 0, 1],
287
+ [0, 0, -1]
288
+ ];
281
289
 
282
290
  // src/Disposable.ts
283
291
  function isDisposable(obj) {
@@ -1172,12 +1180,12 @@ var AssetKey = class _AssetKey extends BasicAssetKey {
1172
1180
  return a.join("/");
1173
1181
  }
1174
1182
  getFullPath() {
1175
- let p12 = [];
1183
+ let p11 = [];
1176
1184
  if (this.type) {
1177
- p12.push(this.type);
1185
+ p11.push(this.type);
1178
1186
  }
1179
- p12.push(this.path);
1180
- return p12.join("/");
1187
+ p11.push(this.path);
1188
+ return p11.join("/");
1181
1189
  }
1182
1190
  toNamespacedString() {
1183
1191
  return this.namespace + ":" + this.getFullPath();
@@ -1264,7 +1272,7 @@ var ModelMerger = class {
1264
1272
  merged.display = { ...inheritedDisplay, ...DisplayTransforms.withHandFallbacks(source.display) };
1265
1273
  }
1266
1274
  }
1267
- merged.hierarchy = models.map((m) => m.parent).filter((p12) => `${p12}`);
1275
+ merged.hierarchy = models.map((m) => m.parent).filter((p11) => `${p11}`);
1268
1276
  merged.hierarchy.push(`${merged.parent}`);
1269
1277
  return merged;
1270
1278
  }
@@ -3849,6 +3857,42 @@ function isQuadArray(obj) {
3849
3857
  return Array.isArray(obj) && obj.length === 4;
3850
3858
  }
3851
3859
 
3860
+ // src/model/ModelCulling.ts
3861
+ import { Vector3 as Vector36 } from "three";
3862
+ var ModelCulling = class {
3863
+ static toLocalMask(worldMask, rotation) {
3864
+ let localMask = 0;
3865
+ for (const [localFace, offset] of CUBE_FACE_OFFSETS.entries()) {
3866
+ const normal = new Vector36(...offset).applyEuler(rotation);
3867
+ const worldFace = CUBE_FACE_OFFSETS.findIndex(([x, y, z]) => Math.abs(normal.x - x) < 1e-6 && Math.abs(normal.y - y) < 1e-6 && Math.abs(normal.z - z) < 1e-6);
3868
+ if (worldFace < 0) return 0;
3869
+ if (worldMask & 1 << worldFace) localMask |= 1 << localFace;
3870
+ }
3871
+ return localMask;
3872
+ }
3873
+ static isOpaqueFullCube(atlas) {
3874
+ if (!atlas || atlas.hasTransparency !== false || atlas.model.elements?.length !== 1) return false;
3875
+ const element = atlas.model.elements[0];
3876
+ if (element.from.length !== 3 || !element.from.every((value) => value === 0) || element.to.length !== 3 || !element.to.every((value) => value === 16) || element.rotation && element.rotation.angle !== 0 || element.mappedUv?.length !== 48 || !element.mappedUv.every(Number.isFinite)) return false;
3877
+ if (!(atlas.image.width > 0 && atlas.image.height > 0)) return false;
3878
+ return CUBE_FACES.every((face, faceIndex) => {
3879
+ const texture = element.faces[face]?.texture;
3880
+ if (!texture?.startsWith("#")) return false;
3881
+ const key = texture.slice(1);
3882
+ const position = atlas.positions[key];
3883
+ const size = atlas.sizes[key];
3884
+ if (position?.length !== 2 || !position.every(Number.isFinite) || size?.length !== 2 || !size.every((value) => value > 0 && Number.isFinite(value))) return false;
3885
+ for (let vertex = 0; vertex < 4; vertex++) {
3886
+ const uvIndex = (faceIndex * 4 + vertex) * 2;
3887
+ const x = element.mappedUv[uvIndex] * atlas.image.width;
3888
+ const y = (1 - element.mappedUv[uvIndex + 1]) * atlas.image.height;
3889
+ if (x < position[0] - 1e-6 || x > position[0] + size[0] + 1e-6 || y < position[1] - 1e-6 || y > position[1] + size[1] + 1e-6) return false;
3890
+ }
3891
+ return true;
3892
+ });
3893
+ }
3894
+ };
3895
+
3852
3896
  // src/model/block/blockTints.json
3853
3897
  var blockTints_default = {
3854
3898
  "minecraft:large_fern": { source: "grass" },
@@ -3935,7 +3979,7 @@ var BlockTints = class {
3935
3979
  };
3936
3980
 
3937
3981
  // src/model/scene/ModelObject.ts
3938
- import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute4, Color as Color3, Euler as Euler4, Matrix4 as Matrix44 } from "three";
3982
+ import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute4, Color as Color3, Euler as Euler5, Matrix4 as Matrix44 } from "three";
3939
3983
 
3940
3984
  // src/three/BufferGeometryUtils.js
3941
3985
  import {
@@ -3947,7 +3991,7 @@ import {
3947
3991
  TriangleFanDrawMode,
3948
3992
  TriangleStripDrawMode,
3949
3993
  TrianglesDrawMode,
3950
- Vector3 as Vector36
3994
+ Vector3 as Vector37
3951
3995
  } from "three";
3952
3996
  function computeTangents(geometry) {
3953
3997
  geometry.computeTangents();
@@ -4266,15 +4310,15 @@ function computeMorphedAttributes(object) {
4266
4310
  console.error("THREE.BufferGeometryUtils: Geometry is not of type BufferGeometry.");
4267
4311
  return null;
4268
4312
  }
4269
- const _vA = new Vector36();
4270
- const _vB = new Vector36();
4271
- const _vC = new Vector36();
4272
- const _tempA = new Vector36();
4273
- const _tempB = new Vector36();
4274
- const _tempC = new Vector36();
4275
- const _morphA = new Vector36();
4276
- const _morphB = new Vector36();
4277
- const _morphC = new Vector36();
4313
+ const _vA = new Vector37();
4314
+ const _vB = new Vector37();
4315
+ const _vC = new Vector37();
4316
+ const _tempA = new Vector37();
4317
+ const _tempB = new Vector37();
4318
+ const _tempC = new Vector37();
4319
+ const _morphA = new Vector37();
4320
+ const _morphB = new Vector37();
4321
+ const _morphC = new Vector37();
4278
4322
  function _calculateMorphedAttributeData(object2, material2, attribute, morphAttribute, morphTargetsRelative2, a2, b2, c2, modifiedAttributeArray) {
4279
4323
  _vA.fromBufferAttribute(attribute, a2);
4280
4324
  _vB.fromBufferAttribute(attribute, b2);
@@ -4503,6 +4547,9 @@ var ModelObject = class _ModelObject extends SceneObject {
4503
4547
  get textureAtlas() {
4504
4548
  return this.atlas;
4505
4549
  }
4550
+ get isOpaqueFullCube() {
4551
+ return !this.options.displayPosition && ModelCulling.isOpaqueFullCube(this.atlas);
4552
+ }
4506
4553
  //TODO: support for replacing textures
4507
4554
  async loadTextures() {
4508
4555
  this.atlas = await UVMapper.getAtlas(this.originalModel);
@@ -4516,8 +4563,17 @@ var ModelObject = class _ModelObject extends SceneObject {
4516
4563
  if (this.atlas.model.elements) {
4517
4564
  this.atlas.model.elements?.forEach((el) => {
4518
4565
  const elGeo = this._getBoxGeometryFromElement(el).clone();
4566
+ if (this.options.cullMask) {
4567
+ const indices = Array.from(elGeo.getIndex().array);
4568
+ elGeo.setIndex(indices.filter((_, index) => {
4569
+ const face = CUBE_FACES[Math.floor(index / 6)];
4570
+ const direction = CUBE_FACES.findIndex((name) => name === el.faces[face]?.cullface);
4571
+ return direction < 0 || !(this.options.cullMask & 1 << direction);
4572
+ }));
4573
+ elGeo.clearGroups();
4574
+ }
4519
4575
  if (this.options.uvLockRotation) {
4520
- UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler4(...this.options.uvLockRotation));
4576
+ UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler5(...this.options.uvLockRotation));
4521
4577
  }
4522
4578
  if (this.options.tints) {
4523
4579
  const colors = new Float32Array(elGeo.getAttribute("position").count * 3).fill(1);
@@ -4613,7 +4669,7 @@ function isModelObject(obj) {
4613
4669
 
4614
4670
  // src/model/block/scene/BlockObject.ts
4615
4671
  var import_ts_deepmerge6 = __toESM(require_dist());
4616
- import { Euler as Euler5, Matrix4 as Matrix45 } from "three";
4672
+ import { Euler as Euler6, Matrix4 as Matrix45, Quaternion as Quaternion4, Vector3 as Vector38 } from "three";
4617
4673
  var p10 = prefix("BlockObject");
4618
4674
  function matchesCondition(condition, state) {
4619
4675
  return Object.entries(condition).every(([key, value]) => {
@@ -4638,6 +4694,7 @@ var BlockObject = class _BlockObject extends SceneObject {
4638
4694
  this._instanceModel = [];
4639
4695
  this._variants = [];
4640
4696
  this._models = [];
4697
+ this._cullMask = 0;
4641
4698
  this.options = (0, import_ts_deepmerge6.default)({}, _BlockObject.DEFAULT_OPTIONS, options ?? {});
4642
4699
  }
4643
4700
  static {
@@ -4676,12 +4733,69 @@ var BlockObject = class _BlockObject extends SceneObject {
4676
4733
  super.removeFromScene();
4677
4734
  }
4678
4735
  clearModels() {
4679
- for (const model of this._models.splice(0)) {
4736
+ this.removeModels(this._models.splice(0));
4737
+ this._isInstanced = false;
4738
+ this._instanceCounter = 0;
4739
+ }
4740
+ removeModels(models) {
4741
+ for (const model of models) {
4680
4742
  model.removeFromScene();
4681
4743
  if (!isInstanceReference(model)) model.dispose();
4682
4744
  }
4683
- this._isInstanced = false;
4684
- this._instanceCounter = 0;
4745
+ }
4746
+ get isOccluding() {
4747
+ return this._models.some((model) => {
4748
+ const object = isInstanceReference(model) ? model.instanceable : model;
4749
+ return object.isOpaqueFullCube && this.getModelCullMask(model, 63) === 63;
4750
+ });
4751
+ }
4752
+ getModelMatrix(model) {
4753
+ if (isInstanceReference(model)) return model.getMatrix();
4754
+ if (model.matrixAutoUpdate) model.updateMatrix();
4755
+ return model.matrix.clone();
4756
+ }
4757
+ getModelCullMask(model, worldMask) {
4758
+ const object = isInstanceReference(model) ? model.instanceable : model;
4759
+ if (object.options.displayPosition) return 0;
4760
+ const position = new Vector38();
4761
+ const rotation = new Quaternion4();
4762
+ const scale = new Vector38();
4763
+ this.getModelMatrix(model).decompose(position, rotation, scale);
4764
+ if (position.distanceToSquared(this.position) > 1e-10 || scale.distanceToSquared(new Vector38(1, 1, 1)) > 1e-10) return 0;
4765
+ return ModelCulling.toLocalMask(worldMask, new Euler6().setFromQuaternion(rotation));
4766
+ }
4767
+ async setCullMask(worldMask) {
4768
+ worldMask &= 63;
4769
+ const replacements = [];
4770
+ try {
4771
+ for (const model of this._models) {
4772
+ const object = isInstanceReference(model) ? model.instanceable : model;
4773
+ const cullMask = this.getModelCullMask(model, worldMask);
4774
+ if (cullMask === (object.options.cullMask ?? 0)) {
4775
+ replacements.push(model);
4776
+ continue;
4777
+ }
4778
+ const matrix = this.getModelMatrix(model);
4779
+ const replacement = await this.scene.addModel(object.originalModel, { ...object.options, cullMask });
4780
+ replacements.push(replacement);
4781
+ if (isInstanceReference(replacement)) {
4782
+ replacement.setMatrix(matrix);
4783
+ } else {
4784
+ replacement.matrix.copy(matrix);
4785
+ matrix.decompose(replacement.position, replacement.quaternion, replacement.scale);
4786
+ replacement.matrixAutoUpdate = object.matrixAutoUpdate;
4787
+ replacement.visible = object.visible;
4788
+ }
4789
+ }
4790
+ } catch (error) {
4791
+ this.removeModels(replacements.filter((model) => !this._models.includes(model)));
4792
+ throw error;
4793
+ }
4794
+ const previous = this._models;
4795
+ this._models = replacements;
4796
+ this._cullMask = worldMask;
4797
+ this.removeModels(previous.filter((model) => !replacements.includes(model)));
4798
+ this.notifyDirty();
4685
4799
  }
4686
4800
  get state() {
4687
4801
  return this._state;
@@ -4761,7 +4875,7 @@ var BlockObject = class _BlockObject extends SceneObject {
4761
4875
  await this.createVariant(variant);
4762
4876
  }
4763
4877
  async createVariant(variant) {
4764
- const rotation = new Euler5();
4878
+ const rotation = new Euler6();
4765
4879
  if (typeof variant.x !== "undefined") {
4766
4880
  rotation.x = toRadians(clampRotationDegrees(variant.x));
4767
4881
  }
@@ -4774,7 +4888,8 @@ var BlockObject = class _BlockObject extends SceneObject {
4774
4888
  const options = {
4775
4889
  ...this.options,
4776
4890
  tints: model ? await BlockTints.get(this.blockState.key, this.state, model, this.options.tints) : this.options.tints,
4777
- uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
4891
+ uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0,
4892
+ cullMask: this.options.displayPosition ? 0 : ModelCulling.toLocalMask(this._cullMask, rotation)
4778
4893
  };
4779
4894
  const obj = await this.scene.addModel(model, options);
4780
4895
  if (isInstanceReference(obj) || obj.isInstanced) {
@@ -6035,11 +6150,12 @@ var MineRenderScene = class _MineRenderScene extends Scene2 {
6035
6150
  console.log("instanceMeshes + key");
6036
6151
  let key = asset.key.serialize();
6037
6152
  if (isModelObject(obj)) {
6038
- const { displayPosition, uvLockRotation, tints } = obj.options;
6153
+ const { displayPosition, uvLockRotation, tints, cullMask } = obj.options;
6039
6154
  if (displayPosition) key += `|display:${displayPosition}`;
6040
6155
  if (uvLockRotation) key += `|uvlock:${uvLockRotation.join(",")}`;
6041
6156
  const palette = Object.entries(tints ?? {}).sort(([a], [b]) => Number(a) - Number(b));
6042
6157
  if (palette.length) key += `|tints:${JSON.stringify(palette)}`;
6158
+ if (cullMask) key += `|cull:${cullMask}`;
6043
6159
  }
6044
6160
  return this.instanceManager.getOrCreate(key, async () => {
6045
6161
  obj.scene = this;
@@ -6083,7 +6199,7 @@ function isMineRenderScene(obj) {
6083
6199
  }
6084
6200
 
6085
6201
  // src/renderer/Renderer.ts
6086
- import { AxesHelper as AxesHelper2, GridHelper, OrthographicCamera, PCFSoftShadowMap, PerspectiveCamera, SRGBColorSpace as SRGBColorSpace4, Vector3 as Vector39, WebGLRenderer } from "three";
6202
+ import { AxesHelper as AxesHelper2, GridHelper, OrthographicCamera, PCFSoftShadowMap, PerspectiveCamera, SRGBColorSpace as SRGBColorSpace4, Vector3 as Vector310, WebGLRenderer } from "three";
6087
6203
  var import_ts_deepmerge9 = __toESM(require_dist());
6088
6204
  import Stats from "stats.js";
6089
6205
  import { EffectComposer, RenderPass } from "postprocessing";
@@ -6092,11 +6208,11 @@ import { EffectComposer, RenderPass } from "postprocessing";
6092
6208
  import {
6093
6209
  EventDispatcher,
6094
6210
  MOUSE,
6095
- Quaternion as Quaternion4,
6211
+ Quaternion as Quaternion5,
6096
6212
  Spherical,
6097
6213
  TOUCH,
6098
6214
  Vector2 as Vector23,
6099
- Vector3 as Vector38
6215
+ Vector3 as Vector39
6100
6216
  } from "three";
6101
6217
  var _changeEvent = { type: "change" };
6102
6218
  var _startEvent = { type: "start" };
@@ -6110,7 +6226,7 @@ var OrbitControls = class extends EventDispatcher {
6110
6226
  this.domElement = domElement;
6111
6227
  this.domElement.style.touchAction = "none";
6112
6228
  this.enabled = true;
6113
- this.target = new Vector38();
6229
+ this.target = new Vector39();
6114
6230
  this.minDistance = 0;
6115
6231
  this.maxDistance = Infinity;
6116
6232
  this.minZoom = 0;
@@ -6162,11 +6278,11 @@ var OrbitControls = class extends EventDispatcher {
6162
6278
  state = STATE.NONE;
6163
6279
  };
6164
6280
  this.update = function() {
6165
- const offset = new Vector38();
6166
- const quat = new Quaternion4().setFromUnitVectors(object.up, new Vector38(0, 1, 0));
6281
+ const offset = new Vector39();
6282
+ const quat = new Quaternion5().setFromUnitVectors(object.up, new Vector39(0, 1, 0));
6167
6283
  const quatInverse = quat.clone().invert();
6168
- const lastPosition = new Vector38();
6169
- const lastQuaternion = new Quaternion4();
6284
+ const lastPosition = new Vector39();
6285
+ const lastQuaternion = new Quaternion5();
6170
6286
  const twoPI = 2 * Math.PI;
6171
6287
  return function update() {
6172
6288
  const position = scope.object.position;
@@ -6255,7 +6371,7 @@ var OrbitControls = class extends EventDispatcher {
6255
6371
  const spherical = new Spherical();
6256
6372
  const sphericalDelta = new Spherical();
6257
6373
  let scale = 1;
6258
- const panOffset = new Vector38();
6374
+ const panOffset = new Vector39();
6259
6375
  let zoomChanged = false;
6260
6376
  const rotateStart = new Vector23();
6261
6377
  const rotateEnd = new Vector23();
@@ -6281,7 +6397,7 @@ var OrbitControls = class extends EventDispatcher {
6281
6397
  sphericalDelta.phi -= angle;
6282
6398
  }
6283
6399
  const panLeft = function() {
6284
- const v = new Vector38();
6400
+ const v = new Vector39();
6285
6401
  return function panLeft2(distance, objectMatrix) {
6286
6402
  v.setFromMatrixColumn(objectMatrix, 0);
6287
6403
  v.multiplyScalar(-distance);
@@ -6289,7 +6405,7 @@ var OrbitControls = class extends EventDispatcher {
6289
6405
  };
6290
6406
  }();
6291
6407
  const panUp = function() {
6292
- const v = new Vector38();
6408
+ const v = new Vector39();
6293
6409
  return function panUp2(distance, objectMatrix) {
6294
6410
  if (scope.screenSpacePanning === true) {
6295
6411
  v.setFromMatrixColumn(objectMatrix, 1);
@@ -6302,7 +6418,7 @@ var OrbitControls = class extends EventDispatcher {
6302
6418
  };
6303
6419
  }();
6304
6420
  const pan = function() {
6305
- const offset = new Vector38();
6421
+ const offset = new Vector39();
6306
6422
  return function pan2(deltaX, deltaY) {
6307
6423
  const element = scope.domElement;
6308
6424
  if (scope.object.isPerspectiveCamera) {
@@ -6779,8 +6895,8 @@ var Renderer = class _Renderer {
6779
6895
  top: void 0,
6780
6896
  bottom: void 0
6781
6897
  },
6782
- position: new Vector39(50, 50, 50),
6783
- lookingAt: new Vector39(0, 0, 0)
6898
+ position: new Vector310(50, 50, 50),
6899
+ lookingAt: new Vector310(0, 0, 0)
6784
6900
  },
6785
6901
  render: {
6786
6902
  fpsLimit: 60,
@@ -7239,31 +7355,113 @@ var Skins = class {
7239
7355
  };
7240
7356
 
7241
7357
  // src/world/Chunk.ts
7242
- import { Vector3 as Vector311 } from "three";
7358
+ import { Vector3 as Vector312 } from "three";
7359
+
7360
+ // src/world/ChunkData.ts
7361
+ var ChunkData = class _ChunkData {
7362
+ constructor() {
7363
+ this.indices = new Uint16Array(4096);
7364
+ this.palette = [void 0];
7365
+ this.paletteIds = /* @__PURE__ */ new Map();
7366
+ this.freeIds = [];
7367
+ this.nbt = /* @__PURE__ */ new Map();
7368
+ }
7369
+ get(index) {
7370
+ _ChunkData.validateIndex(index);
7371
+ const entry = this.palette[this.indices[index]];
7372
+ return entry ? _ChunkData.copyBlock(entry.state, this.nbt.get(index)) : void 0;
7373
+ }
7374
+ snapshot(index) {
7375
+ _ChunkData.validateIndex(index);
7376
+ const entry = this.palette[this.indices[index]];
7377
+ return entry ? _ChunkData.copyBlock.bind(void 0, entry.state, this.nbt.get(index)) : void 0;
7378
+ }
7379
+ set(index, block) {
7380
+ _ChunkData.validateIndex(index);
7381
+ let state;
7382
+ let key;
7383
+ let nbt;
7384
+ if (block && !_ChunkData.isAir(block)) {
7385
+ const type = AssetKey.parse("blockstates", block.type).toNamespacedString();
7386
+ const properties = Object.fromEntries(Object.keys(block.properties ?? {}).sort().map((name) => [name, block.properties[name]]));
7387
+ state = Object.freeze(Object.keys(properties).length ? { type, properties: Object.freeze(properties) } : { type });
7388
+ key = JSON.stringify([type, properties]);
7389
+ if (block.nbt !== void 0) nbt = structuredClone(block.nbt);
7390
+ }
7391
+ const previousId = this.indices[index];
7392
+ if (previousId !== 0) {
7393
+ const previous = this.palette[previousId];
7394
+ if (--previous.references === 0) {
7395
+ this.paletteIds.delete(previous.key);
7396
+ this.palette[previousId] = void 0;
7397
+ this.freeIds.push(previousId);
7398
+ }
7399
+ }
7400
+ let id = 0;
7401
+ if (state && key !== void 0) {
7402
+ const existingId = this.paletteIds.get(key);
7403
+ if (existingId !== void 0) {
7404
+ id = existingId;
7405
+ this.palette[id].references++;
7406
+ } else {
7407
+ id = this.freeIds.pop() ?? this.palette.length;
7408
+ this.palette[id] = { key, state, references: 1 };
7409
+ this.paletteIds.set(key, id);
7410
+ }
7411
+ }
7412
+ this.indices[index] = id;
7413
+ if (nbt !== void 0) this.nbt.set(index, nbt);
7414
+ else this.nbt.delete(index);
7415
+ }
7416
+ clear() {
7417
+ this.indices.fill(0);
7418
+ this.palette.length = 1;
7419
+ this.paletteIds.clear();
7420
+ this.freeIds.length = 0;
7421
+ this.nbt.clear();
7422
+ }
7423
+ static isAir(block) {
7424
+ if (typeof block === "undefined" || typeof block.type === "undefined") return true;
7425
+ const key = AssetKey.parse("blockstates", block.type);
7426
+ return key.namespace === "minecraft" && !key.type && ["air", "cave_air", "void_air"].includes(key.path);
7427
+ }
7428
+ static copyBlock(state, nbt) {
7429
+ const block = { type: state.type };
7430
+ if (state.properties) block.properties = { ...state.properties };
7431
+ if (nbt !== void 0) block.nbt = structuredClone(nbt);
7432
+ return block;
7433
+ }
7434
+ static validateIndex(index) {
7435
+ if (!Number.isInteger(index) || index < 0 || index >= 4096) {
7436
+ throw new RangeError("Block index must be an integer from 0 to 4095");
7437
+ }
7438
+ }
7439
+ };
7243
7440
 
7244
7441
  // src/world/MineRenderWorld.ts
7245
- import { Vector3 as Vector310 } from "three";
7442
+ import { Vector3 as Vector311 } from "three";
7246
7443
  var MineRenderWorld = class {
7247
7444
  constructor(scene) {
7248
7445
  this._chunks = /* @__PURE__ */ new Map();
7446
+ this.pendingCulling = /* @__PURE__ */ new Map();
7249
7447
  this.scene = scene;
7250
7448
  }
7251
7449
  getBlockAt(posOrX, y, z) {
7252
7450
  if (typeof posOrX == "number") {
7253
- return this.getBlockAt(new Vector310(posOrX, y, z));
7451
+ return this.getBlockAt(new Vector311(posOrX, y, z));
7254
7452
  }
7255
7453
  if (isTripleArray(posOrX)) {
7256
- return this.getBlockAt(new Vector310(posOrX[0], posOrX[1], posOrX[2]));
7454
+ return this.getBlockAt(new Vector311(posOrX[0], posOrX[1], posOrX[2]));
7257
7455
  }
7258
7456
  this.validatePosBounds(posOrX);
7259
7457
  return this.getChunkAt(posOrX)?.getBlockAt(posOrX);
7260
7458
  }
7261
7459
  async setBlockAt(posOrX, yOrBlock, z, block) {
7262
7460
  if (typeof posOrX == "number") {
7263
- return this.setBlockAt(new Vector310(posOrX, yOrBlock, z), block);
7461
+ return this.setBlockAt(new Vector311(posOrX, yOrBlock, z), block);
7264
7462
  }
7265
7463
  if (isTripleArray(posOrX)) {
7266
- return this.setBlockAt(new Vector310(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
7464
+ return this.setBlockAt(new Vector311(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
7267
7465
  }
7268
7466
  this.validatePosBounds(posOrX);
7269
7467
  const value = yOrBlock;
@@ -7306,16 +7504,54 @@ var MineRenderWorld = class {
7306
7504
  }
7307
7505
  }
7308
7506
  async clear() {
7309
- for (const chunk of this._chunks.values()) {
7507
+ await this.culling;
7508
+ const chunks = [...this._chunks.values()];
7509
+ this._chunks.clear();
7510
+ for (const chunk of chunks) {
7310
7511
  await chunk.dispose();
7311
7512
  }
7312
- this._chunks.clear();
7513
+ }
7514
+ updateCulling(positions) {
7515
+ for (const pos of positions) {
7516
+ this.pendingCulling.set(pos.toArray().join(","), pos.clone());
7517
+ for (const offset of CUBE_FACE_OFFSETS) {
7518
+ const neighbor = pos.clone().add(new Vector311(...offset));
7519
+ this.pendingCulling.set(neighbor.toArray().join(","), neighbor);
7520
+ }
7521
+ }
7522
+ return this.culling ??= Promise.resolve().then(async () => {
7523
+ try {
7524
+ while (this.pendingCulling.size) {
7525
+ const batch = [...this.pendingCulling.values()];
7526
+ this.pendingCulling.clear();
7527
+ for (const pos of batch) {
7528
+ const block = this.getBlockAt(pos)?.object;
7529
+ if (!block) continue;
7530
+ let mask = 0;
7531
+ for (const [face, offset] of CUBE_FACE_OFFSETS.entries()) {
7532
+ if (this.getBlockAt(pos.clone().add(new Vector311(...offset)))?.object.isOccluding) {
7533
+ mask |= 1 << face;
7534
+ }
7535
+ }
7536
+ await block.setCullMask(mask);
7537
+ }
7538
+ }
7539
+ } finally {
7540
+ this.culling = void 0;
7541
+ }
7542
+ });
7313
7543
  }
7314
7544
  getOrCreateChunkAt(pos) {
7315
7545
  const key = this.worldPosToChunkKey(pos);
7316
7546
  let chunk = this._chunks.get(key);
7317
7547
  if (typeof chunk === "undefined") {
7318
- chunk = new Chunk(this.scene, Math.floor(pos.x / 16), Math.floor(pos.y / 16), Math.floor(pos.z / 16));
7548
+ chunk = new Chunk(
7549
+ this.scene,
7550
+ Math.floor(pos.x / 16),
7551
+ Math.floor(pos.y / 16),
7552
+ Math.floor(pos.z / 16),
7553
+ (positions) => this.updateCulling(positions)
7554
+ );
7319
7555
  this._chunks.set(key, chunk);
7320
7556
  }
7321
7557
  return chunk;
@@ -7324,14 +7560,14 @@ var MineRenderWorld = class {
7324
7560
  return this._chunks.get(this.worldPosToChunkKey(pos));
7325
7561
  }
7326
7562
  static worldToScenePosition(pos) {
7327
- return new Vector310(
7563
+ return new Vector311(
7328
7564
  pos.x * 16,
7329
7565
  pos.y * 16,
7330
7566
  pos.z * 16
7331
7567
  );
7332
7568
  }
7333
7569
  static sceneToWorldPosition(pos) {
7334
- return new Vector310(
7570
+ return new Vector311(
7335
7571
  pos.x / 16,
7336
7572
  pos.y / 16,
7337
7573
  pos.z / 16
@@ -7351,10 +7587,11 @@ var MineRenderWorld = class {
7351
7587
  };
7352
7588
 
7353
7589
  // src/world/Chunk.ts
7354
- var p11 = prefix("Chunk");
7355
7590
  var Chunk = class _Chunk {
7356
- constructor(scene, x, y, z) {
7357
- this._blocks = [];
7591
+ constructor(scene, x, y, z, onBlocksChanged) {
7592
+ this.onBlocksChanged = onBlocksChanged;
7593
+ this.data = new ChunkData();
7594
+ this.renderedBlocks = /* @__PURE__ */ new Map();
7358
7595
  this.scene = scene;
7359
7596
  this.x = x;
7360
7597
  this.y = y;
@@ -7362,21 +7599,21 @@ var Chunk = class _Chunk {
7362
7599
  }
7363
7600
  getBlockAt(posOrX, y, z) {
7364
7601
  if (typeof posOrX == "number") {
7365
- return this.getBlockAt(new Vector311(posOrX, y, z));
7602
+ return this.getBlockAt(new Vector312(posOrX, y, z));
7366
7603
  }
7367
7604
  if (isTripleArray(posOrX)) {
7368
- return this.getBlockAt(new Vector311(posOrX[0], posOrX[1], posOrX[2]));
7605
+ return this.getBlockAt(new Vector312(posOrX[0], posOrX[1], posOrX[2]));
7369
7606
  }
7370
7607
  const pos = this.worldPosToChunkPos(posOrX);
7371
7608
  const index = _Chunk.chunkPosToBlockIndex(pos);
7372
- return this._blocks[index];
7609
+ return this.renderedBlocks.get(index);
7373
7610
  }
7374
7611
  async setBlockAt(posOrX, yOrBlock, z, block) {
7375
7612
  if (typeof posOrX == "number") {
7376
- return this.setBlockAt(new Vector311(posOrX, yOrBlock, z), block);
7613
+ return this.setBlockAt(new Vector312(posOrX, yOrBlock, z), block);
7377
7614
  }
7378
7615
  if (isTripleArray(posOrX)) {
7379
- return this.setBlockAt(new Vector311(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
7616
+ return this.setBlockAt(new Vector312(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
7380
7617
  }
7381
7618
  const worldPos = posOrX;
7382
7619
  const pos = this.worldPosToChunkPos(worldPos);
@@ -7391,44 +7628,53 @@ var Chunk = class _Chunk {
7391
7628
  worldPos = this.chunkPosToWorldPos(pos);
7392
7629
  }
7393
7630
  const index = _Chunk.chunkPosToBlockIndex(pos);
7394
- const current = this._blocks[index];
7395
- if (typeof current !== "undefined") {
7396
- console.debug(p11, "deleting existing block at", pos, worldPos, index);
7397
- current.object.removeFromScene();
7398
- delete this._blocks[index];
7399
- }
7400
- if (!block || _Chunk.isAir(block)) return void 0;
7401
- const blockState = await BlockStates.get(AssetKey.parse("blockstates", block.type));
7402
- if (blockState) {
7403
- const blockObject = await this.scene.addBlock(blockState, {
7631
+ this.data.set(index, block);
7632
+ const readBlock = this.data.snapshot(index);
7633
+ this.renderedBlocks.get(index)?.object.removeFromScene();
7634
+ this.renderedBlocks.delete(index);
7635
+ let object;
7636
+ try {
7637
+ if (!readBlock) return void 0;
7638
+ const stored = readBlock();
7639
+ const blockState = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
7640
+ if (!blockState) {
7641
+ this.data.set(index, void 0);
7642
+ return void 0;
7643
+ }
7644
+ object = await this.scene.addBlock(blockState, {
7404
7645
  mergeMeshes: true,
7405
7646
  instanceMeshes: true,
7406
7647
  maxInstanceCount: 2e3
7407
7648
  });
7408
- if (block.properties) {
7409
- await blockObject.setState(block.properties);
7410
- }
7411
- const scenePos = MineRenderWorld.worldToScenePosition(worldPos);
7412
- blockObject.setPosition(scenePos);
7413
- this._blocks[index] = {
7414
- block,
7415
- object: blockObject
7416
- };
7417
- return this._blocks[index];
7649
+ if (stored.properties) await object.setState(stored.properties);
7650
+ object.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
7651
+ const info = { get block() {
7652
+ return readBlock();
7653
+ }, object };
7654
+ this.renderedBlocks.set(index, info);
7655
+ return info;
7656
+ } catch (error) {
7657
+ this.data.set(index, void 0);
7658
+ object?.removeFromScene();
7659
+ throw error;
7660
+ } finally {
7661
+ await this.onBlocksChanged?.([worldPos]);
7418
7662
  }
7419
- return void 0;
7420
7663
  }
7421
7664
  async clear() {
7422
- this._blocks.forEach((block) => block.object.removeFromScene());
7423
- this._blocks.length = 0;
7665
+ const positions = [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
7666
+ new Vector312(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
7667
+ ));
7668
+ for (const info of this.renderedBlocks.values()) info.object.removeFromScene();
7669
+ this.renderedBlocks.clear();
7670
+ this.data.clear();
7671
+ await this.onBlocksChanged?.(positions);
7424
7672
  }
7425
7673
  async dispose() {
7426
7674
  await this.clear();
7427
7675
  }
7428
7676
  static isAir(block) {
7429
- if (typeof block === "undefined" || typeof block.type === "undefined") return true;
7430
- const key = AssetKey.parse("blockstates", block.type);
7431
- return key.namespace === "minecraft" && !key.type && ["air", "cave_air", "void_air"].includes(key.path);
7677
+ return ChunkData.isAir(block);
7432
7678
  }
7433
7679
  static chunkPosToBlockIndex(pos) {
7434
7680
  if ([pos.x, pos.y, pos.z].some((value) => !Number.isInteger(value) || value < 0 || value >= 16)) {
@@ -7437,14 +7683,14 @@ var Chunk = class _Chunk {
7437
7683
  return pos.y * 16 * 16 + pos.z * 16 + pos.x;
7438
7684
  }
7439
7685
  worldPosToChunkPos(pos) {
7440
- return new Vector311(
7686
+ return new Vector312(
7441
7687
  pos.x - this.x * 16,
7442
7688
  pos.y - this.y * 16,
7443
7689
  pos.z - this.z * 16
7444
7690
  );
7445
7691
  }
7446
7692
  chunkPosToWorldPos(pos) {
7447
- return new Vector311(
7693
+ return new Vector312(
7448
7694
  this.x * 16 + pos.x,
7449
7695
  this.y * 16 + pos.y,
7450
7696
  this.z * 16 + pos.z
@@ -7470,9 +7716,11 @@ export {
7470
7716
  BlockTints,
7471
7717
  BrowserArchiveProxy,
7472
7718
  CUBE_FACES,
7719
+ CUBE_FACE_OFFSETS,
7473
7720
  Caching,
7474
7721
  CanvasImage,
7475
7722
  Chunk,
7723
+ ChunkData,
7476
7724
  CubeFace,
7477
7725
  CubeFaceIndex,
7478
7726
  DEFAULT_ELEMENTS,
@@ -7502,6 +7750,7 @@ export {
7502
7750
  MineRenderWorld,
7503
7751
  MinecraftCubeTexture,
7504
7752
  MinecraftFaceUV,
7753
+ ModelCulling,
7505
7754
  ModelGenerator,
7506
7755
  ModelMerger,
7507
7756
  ModelObject,