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
  };
@@ -315,6 +315,14 @@ var CubeFaceIndex = /* @__PURE__ */ ((CubeFaceIndex2) => {
315
315
  return CubeFaceIndex2;
316
316
  })(CubeFaceIndex || {});
317
317
  var CUBE_FACES = Object.values(CubeFace);
318
+ var CUBE_FACE_OFFSETS = [
319
+ [1, 0, 0],
320
+ [-1, 0, 0],
321
+ [0, 1, 0],
322
+ [0, -1, 0],
323
+ [0, 0, 1],
324
+ [0, 0, -1]
325
+ ];
318
326
 
319
327
  // src/Disposable.ts
320
328
  function isDisposable(obj) {
@@ -1165,12 +1173,12 @@ var AssetKey = class _AssetKey extends BasicAssetKey {
1165
1173
  return a.join("/");
1166
1174
  }
1167
1175
  getFullPath() {
1168
- let p12 = [];
1176
+ let p11 = [];
1169
1177
  if (this.type) {
1170
- p12.push(this.type);
1178
+ p11.push(this.type);
1171
1179
  }
1172
- p12.push(this.path);
1173
- return p12.join("/");
1180
+ p11.push(this.path);
1181
+ return p11.join("/");
1174
1182
  }
1175
1183
  toNamespacedString() {
1176
1184
  return this.namespace + ":" + this.getFullPath();
@@ -1257,7 +1265,7 @@ var ModelMerger = class {
1257
1265
  merged.display = { ...inheritedDisplay, ...DisplayTransforms.withHandFallbacks(source.display) };
1258
1266
  }
1259
1267
  }
1260
- merged.hierarchy = models.map((m) => m.parent).filter((p12) => `${p12}`);
1268
+ merged.hierarchy = models.map((m) => m.parent).filter((p11) => `${p11}`);
1261
1269
  merged.hierarchy.push(`${merged.parent}`);
1262
1270
  return merged;
1263
1271
  }
@@ -3803,6 +3811,42 @@ function isQuadArray(obj) {
3803
3811
  return Array.isArray(obj) && obj.length === 4;
3804
3812
  }
3805
3813
 
3814
+ // src/model/ModelCulling.ts
3815
+ import { Vector3 as Vector36 } from "three";
3816
+ var ModelCulling = class {
3817
+ static toLocalMask(worldMask, rotation) {
3818
+ let localMask = 0;
3819
+ for (const [localFace, offset] of CUBE_FACE_OFFSETS.entries()) {
3820
+ const normal = new Vector36(...offset).applyEuler(rotation);
3821
+ 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);
3822
+ if (worldFace < 0) return 0;
3823
+ if (worldMask & 1 << worldFace) localMask |= 1 << localFace;
3824
+ }
3825
+ return localMask;
3826
+ }
3827
+ static isOpaqueFullCube(atlas) {
3828
+ if (!atlas || atlas.hasTransparency !== false || atlas.model.elements?.length !== 1) return false;
3829
+ const element = atlas.model.elements[0];
3830
+ 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;
3831
+ if (!(atlas.image.width > 0 && atlas.image.height > 0)) return false;
3832
+ return CUBE_FACES.every((face, faceIndex) => {
3833
+ const texture = element.faces[face]?.texture;
3834
+ if (!texture?.startsWith("#")) return false;
3835
+ const key = texture.slice(1);
3836
+ const position = atlas.positions[key];
3837
+ const size = atlas.sizes[key];
3838
+ if (position?.length !== 2 || !position.every(Number.isFinite) || size?.length !== 2 || !size.every((value) => value > 0 && Number.isFinite(value))) return false;
3839
+ for (let vertex = 0; vertex < 4; vertex++) {
3840
+ const uvIndex = (faceIndex * 4 + vertex) * 2;
3841
+ const x = element.mappedUv[uvIndex] * atlas.image.width;
3842
+ const y = (1 - element.mappedUv[uvIndex + 1]) * atlas.image.height;
3843
+ 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;
3844
+ }
3845
+ return true;
3846
+ });
3847
+ }
3848
+ };
3849
+
3806
3850
  // src/model/block/blockTints.json
3807
3851
  var blockTints_default = {
3808
3852
  "minecraft:large_fern": { source: "grass" },
@@ -3889,7 +3933,7 @@ var BlockTints = class {
3889
3933
  };
3890
3934
 
3891
3935
  // src/model/scene/ModelObject.ts
3892
- import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute4, Color as Color3, Euler as Euler4, Matrix4 as Matrix44 } from "three";
3936
+ import { BoxGeometry as BoxGeometry5, BufferAttribute as BufferAttribute4, Color as Color3, Euler as Euler5, Matrix4 as Matrix44 } from "three";
3893
3937
 
3894
3938
  // src/three/BufferGeometryUtils.js
3895
3939
  import {
@@ -3901,7 +3945,7 @@ import {
3901
3945
  TriangleFanDrawMode,
3902
3946
  TriangleStripDrawMode,
3903
3947
  TrianglesDrawMode,
3904
- Vector3 as Vector36
3948
+ Vector3 as Vector37
3905
3949
  } from "three";
3906
3950
  function computeTangents(geometry) {
3907
3951
  geometry.computeTangents();
@@ -4220,15 +4264,15 @@ function computeMorphedAttributes(object) {
4220
4264
  console.error("THREE.BufferGeometryUtils: Geometry is not of type BufferGeometry.");
4221
4265
  return null;
4222
4266
  }
4223
- const _vA = new Vector36();
4224
- const _vB = new Vector36();
4225
- const _vC = new Vector36();
4226
- const _tempA = new Vector36();
4227
- const _tempB = new Vector36();
4228
- const _tempC = new Vector36();
4229
- const _morphA = new Vector36();
4230
- const _morphB = new Vector36();
4231
- const _morphC = new Vector36();
4267
+ const _vA = new Vector37();
4268
+ const _vB = new Vector37();
4269
+ const _vC = new Vector37();
4270
+ const _tempA = new Vector37();
4271
+ const _tempB = new Vector37();
4272
+ const _tempC = new Vector37();
4273
+ const _morphA = new Vector37();
4274
+ const _morphB = new Vector37();
4275
+ const _morphC = new Vector37();
4232
4276
  function _calculateMorphedAttributeData(object2, material2, attribute, morphAttribute, morphTargetsRelative2, a2, b2, c2, modifiedAttributeArray) {
4233
4277
  _vA.fromBufferAttribute(attribute, a2);
4234
4278
  _vB.fromBufferAttribute(attribute, b2);
@@ -4454,6 +4498,9 @@ var _ModelObject = class _ModelObject extends SceneObject {
4454
4498
  get textureAtlas() {
4455
4499
  return this.atlas;
4456
4500
  }
4501
+ get isOpaqueFullCube() {
4502
+ return !this.options.displayPosition && ModelCulling.isOpaqueFullCube(this.atlas);
4503
+ }
4457
4504
  //TODO: support for replacing textures
4458
4505
  async loadTextures() {
4459
4506
  this.atlas = await UVMapper.getAtlas(this.originalModel);
@@ -4467,8 +4514,17 @@ var _ModelObject = class _ModelObject extends SceneObject {
4467
4514
  if (this.atlas.model.elements) {
4468
4515
  this.atlas.model.elements?.forEach((el) => {
4469
4516
  const elGeo = this._getBoxGeometryFromElement(el).clone();
4517
+ if (this.options.cullMask) {
4518
+ const indices = Array.from(elGeo.getIndex().array);
4519
+ elGeo.setIndex(indices.filter((_, index) => {
4520
+ const face = CUBE_FACES[Math.floor(index / 6)];
4521
+ const direction = CUBE_FACES.findIndex((name) => name === el.faces[face]?.cullface);
4522
+ return direction < 0 || !(this.options.cullMask & 1 << direction);
4523
+ }));
4524
+ elGeo.clearGroups();
4525
+ }
4470
4526
  if (this.options.uvLockRotation) {
4471
- UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler4(...this.options.uvLockRotation));
4527
+ UVMapper.lockUvs(elGeo, el.faces, this.atlas, new Euler5(...this.options.uvLockRotation));
4472
4528
  }
4473
4529
  if (this.options.tints) {
4474
4530
  const colors = new Float32Array(elGeo.getAttribute("position").count * 3).fill(1);
@@ -4566,7 +4622,7 @@ function isModelObject(obj) {
4566
4622
 
4567
4623
  // src/model/block/scene/BlockObject.ts
4568
4624
  var import_ts_deepmerge6 = __toESM(require_dist());
4569
- import { Euler as Euler5, Matrix4 as Matrix45 } from "three";
4625
+ import { Euler as Euler6, Matrix4 as Matrix45, Quaternion as Quaternion4, Vector3 as Vector38 } from "three";
4570
4626
  var p10 = prefix("BlockObject");
4571
4627
  function matchesCondition(condition, state) {
4572
4628
  return Object.entries(condition).every(([key, value]) => {
@@ -4591,6 +4647,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
4591
4647
  this._instanceModel = [];
4592
4648
  this._variants = [];
4593
4649
  this._models = [];
4650
+ this._cullMask = 0;
4594
4651
  this.options = (0, import_ts_deepmerge6.default)({}, _BlockObject.DEFAULT_OPTIONS, options ?? {});
4595
4652
  }
4596
4653
  async init() {
@@ -4624,12 +4681,69 @@ var _BlockObject = class _BlockObject extends SceneObject {
4624
4681
  super.removeFromScene();
4625
4682
  }
4626
4683
  clearModels() {
4627
- for (const model of this._models.splice(0)) {
4684
+ this.removeModels(this._models.splice(0));
4685
+ this._isInstanced = false;
4686
+ this._instanceCounter = 0;
4687
+ }
4688
+ removeModels(models) {
4689
+ for (const model of models) {
4628
4690
  model.removeFromScene();
4629
4691
  if (!isInstanceReference(model)) model.dispose();
4630
4692
  }
4631
- this._isInstanced = false;
4632
- this._instanceCounter = 0;
4693
+ }
4694
+ get isOccluding() {
4695
+ return this._models.some((model) => {
4696
+ const object = isInstanceReference(model) ? model.instanceable : model;
4697
+ return object.isOpaqueFullCube && this.getModelCullMask(model, 63) === 63;
4698
+ });
4699
+ }
4700
+ getModelMatrix(model) {
4701
+ if (isInstanceReference(model)) return model.getMatrix();
4702
+ if (model.matrixAutoUpdate) model.updateMatrix();
4703
+ return model.matrix.clone();
4704
+ }
4705
+ getModelCullMask(model, worldMask) {
4706
+ const object = isInstanceReference(model) ? model.instanceable : model;
4707
+ if (object.options.displayPosition) return 0;
4708
+ const position = new Vector38();
4709
+ const rotation = new Quaternion4();
4710
+ const scale = new Vector38();
4711
+ this.getModelMatrix(model).decompose(position, rotation, scale);
4712
+ if (position.distanceToSquared(this.position) > 1e-10 || scale.distanceToSquared(new Vector38(1, 1, 1)) > 1e-10) return 0;
4713
+ return ModelCulling.toLocalMask(worldMask, new Euler6().setFromQuaternion(rotation));
4714
+ }
4715
+ async setCullMask(worldMask) {
4716
+ worldMask &= 63;
4717
+ const replacements = [];
4718
+ try {
4719
+ for (const model of this._models) {
4720
+ const object = isInstanceReference(model) ? model.instanceable : model;
4721
+ const cullMask = this.getModelCullMask(model, worldMask);
4722
+ if (cullMask === (object.options.cullMask ?? 0)) {
4723
+ replacements.push(model);
4724
+ continue;
4725
+ }
4726
+ const matrix = this.getModelMatrix(model);
4727
+ const replacement = await this.scene.addModel(object.originalModel, { ...object.options, cullMask });
4728
+ replacements.push(replacement);
4729
+ if (isInstanceReference(replacement)) {
4730
+ replacement.setMatrix(matrix);
4731
+ } else {
4732
+ replacement.matrix.copy(matrix);
4733
+ matrix.decompose(replacement.position, replacement.quaternion, replacement.scale);
4734
+ replacement.matrixAutoUpdate = object.matrixAutoUpdate;
4735
+ replacement.visible = object.visible;
4736
+ }
4737
+ }
4738
+ } catch (error) {
4739
+ this.removeModels(replacements.filter((model) => !this._models.includes(model)));
4740
+ throw error;
4741
+ }
4742
+ const previous = this._models;
4743
+ this._models = replacements;
4744
+ this._cullMask = worldMask;
4745
+ this.removeModels(previous.filter((model) => !replacements.includes(model)));
4746
+ this.notifyDirty();
4633
4747
  }
4634
4748
  get state() {
4635
4749
  return this._state;
@@ -4709,7 +4823,7 @@ var _BlockObject = class _BlockObject extends SceneObject {
4709
4823
  await this.createVariant(variant);
4710
4824
  }
4711
4825
  async createVariant(variant) {
4712
- const rotation = new Euler5();
4826
+ const rotation = new Euler6();
4713
4827
  if (typeof variant.x !== "undefined") {
4714
4828
  rotation.x = toRadians(clampRotationDegrees(variant.x));
4715
4829
  }
@@ -4722,7 +4836,8 @@ var _BlockObject = class _BlockObject extends SceneObject {
4722
4836
  const options = {
4723
4837
  ...this.options,
4724
4838
  tints: model ? await BlockTints.get(this.blockState.key, this.state, model, this.options.tints) : this.options.tints,
4725
- uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
4839
+ uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0,
4840
+ cullMask: this.options.displayPosition ? 0 : ModelCulling.toLocalMask(this._cullMask, rotation)
4726
4841
  };
4727
4842
  const obj = await this.scene.addModel(model, options);
4728
4843
  if (isInstanceReference(obj) || obj.isInstanced) {
@@ -5984,11 +6099,12 @@ var _MineRenderScene = class _MineRenderScene extends Scene2 {
5984
6099
  console.log("instanceMeshes + key");
5985
6100
  let key = asset.key.serialize();
5986
6101
  if (isModelObject(obj)) {
5987
- const { displayPosition, uvLockRotation, tints } = obj.options;
6102
+ const { displayPosition, uvLockRotation, tints, cullMask } = obj.options;
5988
6103
  if (displayPosition) key += `|display:${displayPosition}`;
5989
6104
  if (uvLockRotation) key += `|uvlock:${uvLockRotation.join(",")}`;
5990
6105
  const palette = Object.entries(tints ?? {}).sort(([a], [b]) => Number(a) - Number(b));
5991
6106
  if (palette.length) key += `|tints:${JSON.stringify(palette)}`;
6107
+ if (cullMask) key += `|cull:${cullMask}`;
5992
6108
  }
5993
6109
  return this.instanceManager.getOrCreate(key, async () => {
5994
6110
  obj.scene = this;
@@ -6034,7 +6150,7 @@ function isMineRenderScene(obj) {
6034
6150
  }
6035
6151
 
6036
6152
  // src/renderer/Renderer.ts
6037
- import { AxesHelper as AxesHelper2, GridHelper, OrthographicCamera, PCFSoftShadowMap, PerspectiveCamera, SRGBColorSpace as SRGBColorSpace4, Vector3 as Vector39, WebGLRenderer } from "three";
6153
+ import { AxesHelper as AxesHelper2, GridHelper, OrthographicCamera, PCFSoftShadowMap, PerspectiveCamera, SRGBColorSpace as SRGBColorSpace4, Vector3 as Vector310, WebGLRenderer } from "three";
6038
6154
  var import_ts_deepmerge9 = __toESM(require_dist());
6039
6155
  import Stats from "stats.js";
6040
6156
  import { EffectComposer, RenderPass } from "postprocessing";
@@ -6043,11 +6159,11 @@ import { EffectComposer, RenderPass } from "postprocessing";
6043
6159
  import {
6044
6160
  EventDispatcher,
6045
6161
  MOUSE,
6046
- Quaternion as Quaternion4,
6162
+ Quaternion as Quaternion5,
6047
6163
  Spherical,
6048
6164
  TOUCH,
6049
6165
  Vector2 as Vector23,
6050
- Vector3 as Vector38
6166
+ Vector3 as Vector39
6051
6167
  } from "three";
6052
6168
  var _changeEvent = { type: "change" };
6053
6169
  var _startEvent = { type: "start" };
@@ -6061,7 +6177,7 @@ var OrbitControls = class extends EventDispatcher {
6061
6177
  this.domElement = domElement;
6062
6178
  this.domElement.style.touchAction = "none";
6063
6179
  this.enabled = true;
6064
- this.target = new Vector38();
6180
+ this.target = new Vector39();
6065
6181
  this.minDistance = 0;
6066
6182
  this.maxDistance = Infinity;
6067
6183
  this.minZoom = 0;
@@ -6113,11 +6229,11 @@ var OrbitControls = class extends EventDispatcher {
6113
6229
  state = STATE.NONE;
6114
6230
  };
6115
6231
  this.update = function() {
6116
- const offset = new Vector38();
6117
- const quat = new Quaternion4().setFromUnitVectors(object.up, new Vector38(0, 1, 0));
6232
+ const offset = new Vector39();
6233
+ const quat = new Quaternion5().setFromUnitVectors(object.up, new Vector39(0, 1, 0));
6118
6234
  const quatInverse = quat.clone().invert();
6119
- const lastPosition = new Vector38();
6120
- const lastQuaternion = new Quaternion4();
6235
+ const lastPosition = new Vector39();
6236
+ const lastQuaternion = new Quaternion5();
6121
6237
  const twoPI = 2 * Math.PI;
6122
6238
  return function update() {
6123
6239
  const position = scope.object.position;
@@ -6206,7 +6322,7 @@ var OrbitControls = class extends EventDispatcher {
6206
6322
  const spherical = new Spherical();
6207
6323
  const sphericalDelta = new Spherical();
6208
6324
  let scale = 1;
6209
- const panOffset = new Vector38();
6325
+ const panOffset = new Vector39();
6210
6326
  let zoomChanged = false;
6211
6327
  const rotateStart = new Vector23();
6212
6328
  const rotateEnd = new Vector23();
@@ -6232,7 +6348,7 @@ var OrbitControls = class extends EventDispatcher {
6232
6348
  sphericalDelta.phi -= angle;
6233
6349
  }
6234
6350
  const panLeft = function() {
6235
- const v = new Vector38();
6351
+ const v = new Vector39();
6236
6352
  return function panLeft2(distance, objectMatrix) {
6237
6353
  v.setFromMatrixColumn(objectMatrix, 0);
6238
6354
  v.multiplyScalar(-distance);
@@ -6240,7 +6356,7 @@ var OrbitControls = class extends EventDispatcher {
6240
6356
  };
6241
6357
  }();
6242
6358
  const panUp = function() {
6243
- const v = new Vector38();
6359
+ const v = new Vector39();
6244
6360
  return function panUp2(distance, objectMatrix) {
6245
6361
  if (scope.screenSpacePanning === true) {
6246
6362
  v.setFromMatrixColumn(objectMatrix, 1);
@@ -6253,7 +6369,7 @@ var OrbitControls = class extends EventDispatcher {
6253
6369
  };
6254
6370
  }();
6255
6371
  const pan = function() {
6256
- const offset = new Vector38();
6372
+ const offset = new Vector39();
6257
6373
  return function pan2(deltaX, deltaY) {
6258
6374
  const element = scope.domElement;
6259
6375
  if (scope.object.isPerspectiveCamera) {
@@ -7000,8 +7116,8 @@ _Renderer.DEFAULT_OPTIONS = (0, import_ts_deepmerge9.default)({}, {
7000
7116
  top: void 0,
7001
7117
  bottom: void 0
7002
7118
  },
7003
- position: new Vector39(50, 50, 50),
7004
- lookingAt: new Vector39(0, 0, 0)
7119
+ position: new Vector310(50, 50, 50),
7120
+ lookingAt: new Vector310(0, 0, 0)
7005
7121
  },
7006
7122
  render: {
7007
7123
  fpsLimit: 60,
@@ -7189,31 +7305,113 @@ var Skins = class {
7189
7305
  };
7190
7306
 
7191
7307
  // src/world/Chunk.ts
7192
- import { Vector3 as Vector311 } from "three";
7308
+ import { Vector3 as Vector312 } from "three";
7309
+
7310
+ // src/world/ChunkData.ts
7311
+ var ChunkData = class _ChunkData {
7312
+ constructor() {
7313
+ this.indices = new Uint16Array(4096);
7314
+ this.palette = [void 0];
7315
+ this.paletteIds = /* @__PURE__ */ new Map();
7316
+ this.freeIds = [];
7317
+ this.nbt = /* @__PURE__ */ new Map();
7318
+ }
7319
+ get(index) {
7320
+ _ChunkData.validateIndex(index);
7321
+ const entry = this.palette[this.indices[index]];
7322
+ return entry ? _ChunkData.copyBlock(entry.state, this.nbt.get(index)) : void 0;
7323
+ }
7324
+ snapshot(index) {
7325
+ _ChunkData.validateIndex(index);
7326
+ const entry = this.palette[this.indices[index]];
7327
+ return entry ? _ChunkData.copyBlock.bind(void 0, entry.state, this.nbt.get(index)) : void 0;
7328
+ }
7329
+ set(index, block) {
7330
+ _ChunkData.validateIndex(index);
7331
+ let state;
7332
+ let key;
7333
+ let nbt;
7334
+ if (block && !_ChunkData.isAir(block)) {
7335
+ const type = AssetKey.parse("blockstates", block.type).toNamespacedString();
7336
+ const properties = Object.fromEntries(Object.keys(block.properties ?? {}).sort().map((name) => [name, block.properties[name]]));
7337
+ state = Object.freeze(Object.keys(properties).length ? { type, properties: Object.freeze(properties) } : { type });
7338
+ key = JSON.stringify([type, properties]);
7339
+ if (block.nbt !== void 0) nbt = structuredClone(block.nbt);
7340
+ }
7341
+ const previousId = this.indices[index];
7342
+ if (previousId !== 0) {
7343
+ const previous = this.palette[previousId];
7344
+ if (--previous.references === 0) {
7345
+ this.paletteIds.delete(previous.key);
7346
+ this.palette[previousId] = void 0;
7347
+ this.freeIds.push(previousId);
7348
+ }
7349
+ }
7350
+ let id = 0;
7351
+ if (state && key !== void 0) {
7352
+ const existingId = this.paletteIds.get(key);
7353
+ if (existingId !== void 0) {
7354
+ id = existingId;
7355
+ this.palette[id].references++;
7356
+ } else {
7357
+ id = this.freeIds.pop() ?? this.palette.length;
7358
+ this.palette[id] = { key, state, references: 1 };
7359
+ this.paletteIds.set(key, id);
7360
+ }
7361
+ }
7362
+ this.indices[index] = id;
7363
+ if (nbt !== void 0) this.nbt.set(index, nbt);
7364
+ else this.nbt.delete(index);
7365
+ }
7366
+ clear() {
7367
+ this.indices.fill(0);
7368
+ this.palette.length = 1;
7369
+ this.paletteIds.clear();
7370
+ this.freeIds.length = 0;
7371
+ this.nbt.clear();
7372
+ }
7373
+ static isAir(block) {
7374
+ if (typeof block === "undefined" || typeof block.type === "undefined") return true;
7375
+ const key = AssetKey.parse("blockstates", block.type);
7376
+ return key.namespace === "minecraft" && !key.type && ["air", "cave_air", "void_air"].includes(key.path);
7377
+ }
7378
+ static copyBlock(state, nbt) {
7379
+ const block = { type: state.type };
7380
+ if (state.properties) block.properties = { ...state.properties };
7381
+ if (nbt !== void 0) block.nbt = structuredClone(nbt);
7382
+ return block;
7383
+ }
7384
+ static validateIndex(index) {
7385
+ if (!Number.isInteger(index) || index < 0 || index >= 4096) {
7386
+ throw new RangeError("Block index must be an integer from 0 to 4095");
7387
+ }
7388
+ }
7389
+ };
7193
7390
 
7194
7391
  // src/world/MineRenderWorld.ts
7195
- import { Vector3 as Vector310 } from "three";
7392
+ import { Vector3 as Vector311 } from "three";
7196
7393
  var MineRenderWorld = class {
7197
7394
  constructor(scene) {
7198
7395
  this._chunks = /* @__PURE__ */ new Map();
7396
+ this.pendingCulling = /* @__PURE__ */ new Map();
7199
7397
  this.scene = scene;
7200
7398
  }
7201
7399
  getBlockAt(posOrX, y, z) {
7202
7400
  if (typeof posOrX == "number") {
7203
- return this.getBlockAt(new Vector310(posOrX, y, z));
7401
+ return this.getBlockAt(new Vector311(posOrX, y, z));
7204
7402
  }
7205
7403
  if (isTripleArray(posOrX)) {
7206
- return this.getBlockAt(new Vector310(posOrX[0], posOrX[1], posOrX[2]));
7404
+ return this.getBlockAt(new Vector311(posOrX[0], posOrX[1], posOrX[2]));
7207
7405
  }
7208
7406
  this.validatePosBounds(posOrX);
7209
7407
  return this.getChunkAt(posOrX)?.getBlockAt(posOrX);
7210
7408
  }
7211
7409
  async setBlockAt(posOrX, yOrBlock, z, block) {
7212
7410
  if (typeof posOrX == "number") {
7213
- return this.setBlockAt(new Vector310(posOrX, yOrBlock, z), block);
7411
+ return this.setBlockAt(new Vector311(posOrX, yOrBlock, z), block);
7214
7412
  }
7215
7413
  if (isTripleArray(posOrX)) {
7216
- return this.setBlockAt(new Vector310(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
7414
+ return this.setBlockAt(new Vector311(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
7217
7415
  }
7218
7416
  this.validatePosBounds(posOrX);
7219
7417
  const value = yOrBlock;
@@ -7256,16 +7454,54 @@ var MineRenderWorld = class {
7256
7454
  }
7257
7455
  }
7258
7456
  async clear() {
7259
- for (const chunk of this._chunks.values()) {
7457
+ await this.culling;
7458
+ const chunks = [...this._chunks.values()];
7459
+ this._chunks.clear();
7460
+ for (const chunk of chunks) {
7260
7461
  await chunk.dispose();
7261
7462
  }
7262
- this._chunks.clear();
7463
+ }
7464
+ updateCulling(positions) {
7465
+ for (const pos of positions) {
7466
+ this.pendingCulling.set(pos.toArray().join(","), pos.clone());
7467
+ for (const offset of CUBE_FACE_OFFSETS) {
7468
+ const neighbor = pos.clone().add(new Vector311(...offset));
7469
+ this.pendingCulling.set(neighbor.toArray().join(","), neighbor);
7470
+ }
7471
+ }
7472
+ return this.culling ?? (this.culling = Promise.resolve().then(async () => {
7473
+ try {
7474
+ while (this.pendingCulling.size) {
7475
+ const batch = [...this.pendingCulling.values()];
7476
+ this.pendingCulling.clear();
7477
+ for (const pos of batch) {
7478
+ const block = this.getBlockAt(pos)?.object;
7479
+ if (!block) continue;
7480
+ let mask = 0;
7481
+ for (const [face, offset] of CUBE_FACE_OFFSETS.entries()) {
7482
+ if (this.getBlockAt(pos.clone().add(new Vector311(...offset)))?.object.isOccluding) {
7483
+ mask |= 1 << face;
7484
+ }
7485
+ }
7486
+ await block.setCullMask(mask);
7487
+ }
7488
+ }
7489
+ } finally {
7490
+ this.culling = void 0;
7491
+ }
7492
+ }));
7263
7493
  }
7264
7494
  getOrCreateChunkAt(pos) {
7265
7495
  const key = this.worldPosToChunkKey(pos);
7266
7496
  let chunk = this._chunks.get(key);
7267
7497
  if (typeof chunk === "undefined") {
7268
- chunk = new Chunk(this.scene, Math.floor(pos.x / 16), Math.floor(pos.y / 16), Math.floor(pos.z / 16));
7498
+ chunk = new Chunk(
7499
+ this.scene,
7500
+ Math.floor(pos.x / 16),
7501
+ Math.floor(pos.y / 16),
7502
+ Math.floor(pos.z / 16),
7503
+ (positions) => this.updateCulling(positions)
7504
+ );
7269
7505
  this._chunks.set(key, chunk);
7270
7506
  }
7271
7507
  return chunk;
@@ -7274,14 +7510,14 @@ var MineRenderWorld = class {
7274
7510
  return this._chunks.get(this.worldPosToChunkKey(pos));
7275
7511
  }
7276
7512
  static worldToScenePosition(pos) {
7277
- return new Vector310(
7513
+ return new Vector311(
7278
7514
  pos.x * 16,
7279
7515
  pos.y * 16,
7280
7516
  pos.z * 16
7281
7517
  );
7282
7518
  }
7283
7519
  static sceneToWorldPosition(pos) {
7284
- return new Vector310(
7520
+ return new Vector311(
7285
7521
  pos.x / 16,
7286
7522
  pos.y / 16,
7287
7523
  pos.z / 16
@@ -7301,10 +7537,11 @@ var MineRenderWorld = class {
7301
7537
  };
7302
7538
 
7303
7539
  // src/world/Chunk.ts
7304
- var p11 = prefix("Chunk");
7305
7540
  var Chunk = class _Chunk {
7306
- constructor(scene, x, y, z) {
7307
- this._blocks = [];
7541
+ constructor(scene, x, y, z, onBlocksChanged) {
7542
+ this.onBlocksChanged = onBlocksChanged;
7543
+ this.data = new ChunkData();
7544
+ this.renderedBlocks = /* @__PURE__ */ new Map();
7308
7545
  this.scene = scene;
7309
7546
  this.x = x;
7310
7547
  this.y = y;
@@ -7312,21 +7549,21 @@ var Chunk = class _Chunk {
7312
7549
  }
7313
7550
  getBlockAt(posOrX, y, z) {
7314
7551
  if (typeof posOrX == "number") {
7315
- return this.getBlockAt(new Vector311(posOrX, y, z));
7552
+ return this.getBlockAt(new Vector312(posOrX, y, z));
7316
7553
  }
7317
7554
  if (isTripleArray(posOrX)) {
7318
- return this.getBlockAt(new Vector311(posOrX[0], posOrX[1], posOrX[2]));
7555
+ return this.getBlockAt(new Vector312(posOrX[0], posOrX[1], posOrX[2]));
7319
7556
  }
7320
7557
  const pos = this.worldPosToChunkPos(posOrX);
7321
7558
  const index = _Chunk.chunkPosToBlockIndex(pos);
7322
- return this._blocks[index];
7559
+ return this.renderedBlocks.get(index);
7323
7560
  }
7324
7561
  async setBlockAt(posOrX, yOrBlock, z, block) {
7325
7562
  if (typeof posOrX == "number") {
7326
- return this.setBlockAt(new Vector311(posOrX, yOrBlock, z), block);
7563
+ return this.setBlockAt(new Vector312(posOrX, yOrBlock, z), block);
7327
7564
  }
7328
7565
  if (isTripleArray(posOrX)) {
7329
- return this.setBlockAt(new Vector311(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
7566
+ return this.setBlockAt(new Vector312(posOrX[0], posOrX[1], posOrX[2]), yOrBlock);
7330
7567
  }
7331
7568
  const worldPos = posOrX;
7332
7569
  const pos = this.worldPosToChunkPos(worldPos);
@@ -7341,44 +7578,53 @@ var Chunk = class _Chunk {
7341
7578
  worldPos = this.chunkPosToWorldPos(pos);
7342
7579
  }
7343
7580
  const index = _Chunk.chunkPosToBlockIndex(pos);
7344
- const current = this._blocks[index];
7345
- if (typeof current !== "undefined") {
7346
- console.debug(p11, "deleting existing block at", pos, worldPos, index);
7347
- current.object.removeFromScene();
7348
- delete this._blocks[index];
7349
- }
7350
- if (!block || _Chunk.isAir(block)) return void 0;
7351
- const blockState = await BlockStates.get(AssetKey.parse("blockstates", block.type));
7352
- if (blockState) {
7353
- const blockObject = await this.scene.addBlock(blockState, {
7581
+ this.data.set(index, block);
7582
+ const readBlock = this.data.snapshot(index);
7583
+ this.renderedBlocks.get(index)?.object.removeFromScene();
7584
+ this.renderedBlocks.delete(index);
7585
+ let object;
7586
+ try {
7587
+ if (!readBlock) return void 0;
7588
+ const stored = readBlock();
7589
+ const blockState = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
7590
+ if (!blockState) {
7591
+ this.data.set(index, void 0);
7592
+ return void 0;
7593
+ }
7594
+ object = await this.scene.addBlock(blockState, {
7354
7595
  mergeMeshes: true,
7355
7596
  instanceMeshes: true,
7356
7597
  maxInstanceCount: 2e3
7357
7598
  });
7358
- if (block.properties) {
7359
- await blockObject.setState(block.properties);
7360
- }
7361
- const scenePos = MineRenderWorld.worldToScenePosition(worldPos);
7362
- blockObject.setPosition(scenePos);
7363
- this._blocks[index] = {
7364
- block,
7365
- object: blockObject
7366
- };
7367
- return this._blocks[index];
7599
+ if (stored.properties) await object.setState(stored.properties);
7600
+ object.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
7601
+ const info = { get block() {
7602
+ return readBlock();
7603
+ }, object };
7604
+ this.renderedBlocks.set(index, info);
7605
+ return info;
7606
+ } catch (error) {
7607
+ this.data.set(index, void 0);
7608
+ object?.removeFromScene();
7609
+ throw error;
7610
+ } finally {
7611
+ await this.onBlocksChanged?.([worldPos]);
7368
7612
  }
7369
- return void 0;
7370
7613
  }
7371
7614
  async clear() {
7372
- this._blocks.forEach((block) => block.object.removeFromScene());
7373
- this._blocks.length = 0;
7615
+ const positions = [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
7616
+ new Vector312(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
7617
+ ));
7618
+ for (const info of this.renderedBlocks.values()) info.object.removeFromScene();
7619
+ this.renderedBlocks.clear();
7620
+ this.data.clear();
7621
+ await this.onBlocksChanged?.(positions);
7374
7622
  }
7375
7623
  async dispose() {
7376
7624
  await this.clear();
7377
7625
  }
7378
7626
  static isAir(block) {
7379
- if (typeof block === "undefined" || typeof block.type === "undefined") return true;
7380
- const key = AssetKey.parse("blockstates", block.type);
7381
- return key.namespace === "minecraft" && !key.type && ["air", "cave_air", "void_air"].includes(key.path);
7627
+ return ChunkData.isAir(block);
7382
7628
  }
7383
7629
  static chunkPosToBlockIndex(pos) {
7384
7630
  if ([pos.x, pos.y, pos.z].some((value) => !Number.isInteger(value) || value < 0 || value >= 16)) {
@@ -7387,14 +7633,14 @@ var Chunk = class _Chunk {
7387
7633
  return pos.y * 16 * 16 + pos.z * 16 + pos.x;
7388
7634
  }
7389
7635
  worldPosToChunkPos(pos) {
7390
- return new Vector311(
7636
+ return new Vector312(
7391
7637
  pos.x - this.x * 16,
7392
7638
  pos.y - this.y * 16,
7393
7639
  pos.z - this.z * 16
7394
7640
  );
7395
7641
  }
7396
7642
  chunkPosToWorldPos(pos) {
7397
- return new Vector311(
7643
+ return new Vector312(
7398
7644
  this.x * 16 + pos.x,
7399
7645
  this.y * 16 + pos.y,
7400
7646
  this.z * 16 + pos.z
@@ -7422,9 +7668,11 @@ export {
7422
7668
  BrowserCache,
7423
7669
  BrowserEnv,
7424
7670
  CUBE_FACES,
7671
+ CUBE_FACE_OFFSETS,
7425
7672
  Caching,
7426
7673
  CanvasImage,
7427
7674
  Chunk,
7675
+ ChunkData,
7428
7676
  CubeFace,
7429
7677
  CubeFaceIndex,
7430
7678
  DEFAULT_ELEMENTS,
@@ -7454,6 +7702,7 @@ export {
7454
7702
  MineRenderWorld,
7455
7703
  MinecraftCubeTexture,
7456
7704
  MinecraftFaceUV,
7705
+ ModelCulling,
7457
7706
  ModelGenerator,
7458
7707
  ModelMerger,
7459
7708
  ModelObject,