minerender 2.1.0-alpha.120 → 2.1.0-alpha.122

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.
@@ -12596,6 +12596,10 @@ var _BlockObject = class _BlockObject extends SceneObject {
12596
12596
  get blockEntities() {
12597
12597
  return this._entities;
12598
12598
  }
12599
+ /** Model placements drawn for this block, including its fluid surface. */
12600
+ get models() {
12601
+ return this._models;
12602
+ }
12599
12603
  /** Sets visibility for this block's model instances and block entities. */
12600
12604
  setVisible(visible) {
12601
12605
  this.visible = visible;
@@ -17530,7 +17534,8 @@ var SectionModels = class {
17530
17534
  atlas,
17531
17535
  cullFaces: new Uint8Array(cullFaces),
17532
17536
  occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
17533
- layer: atlas.hasTranslucency ? 1 : 0
17537
+ layer: atlas.hasTranslucency ? 1 : 0,
17538
+ ambientOcclusion: model.ambientocclusion !== false
17534
17539
  };
17535
17540
  this.templates.add(template);
17536
17541
  return template;
@@ -17721,17 +17726,45 @@ var MineRenderWorld = class {
17721
17726
  this.light = /* @__PURE__ */ new Map();
17722
17727
  this.lightValidity = /* @__PURE__ */ new Map();
17723
17728
  this.highestSkySection = /* @__PURE__ */ new Map();
17729
+ this.ambientOcclusionPending = false;
17724
17730
  this.pendingCulling = /* @__PURE__ */ new Map();
17725
17731
  if (options.savedLighting && !options.sectionMeshing) throw new TypeError("savedLighting requires sectionMeshing");
17732
+ if (options.ambientOcclusion && !options.sectionMeshing) throw new TypeError("ambientOcclusion requires sectionMeshing");
17726
17733
  if (options.ambientLight !== void 0 && (!Number.isFinite(options.ambientLight) || options.ambientLight < 0 || options.ambientLight > 1)) {
17727
17734
  throw new RangeError("ambientLight must be a number from 0 to 1");
17728
17735
  }
17729
17736
  this.scene = scene;
17730
17737
  this.savedLighting = options.savedLighting ?? false;
17731
17738
  this.ambientLight = options.ambientLight ?? 0;
17739
+ this._ambientOcclusion = options.ambientOcclusion ?? false;
17732
17740
  if (options.sectionMeshing) this.sectionModels = new SectionModels(options.maxAtlasSize);
17733
17741
  if (options.renderEntities) this.entities = new WorldEntities(scene);
17734
17742
  }
17743
+ /**
17744
+ * Returns the nearest visible block geometry hit, respecting the raycaster's layers and near/far range.
17745
+ * Includes fluids and block entities; texture alpha does not change triangle intersections.
17746
+ */
17747
+ raycast(raycaster) {
17748
+ return Chunk.raycast(this._chunks.values(), raycaster);
17749
+ }
17750
+ /** Whether neighboring opaque cubes shade block-face corners. Defaults to false. */
17751
+ get ambientOcclusion() {
17752
+ return this._ambientOcclusion;
17753
+ }
17754
+ /** Rebuilds loaded blocks with or without corner shading. Block data and saved lighting are preserved. */
17755
+ async setAmbientOcclusion(enabled) {
17756
+ if (typeof enabled !== "boolean") throw new TypeError("Ambient occlusion must be a boolean");
17757
+ if (enabled && !this.sectionModels) throw new TypeError("ambientOcclusion requires sectionMeshing");
17758
+ if (enabled === this._ambientOcclusion && !this.ambientOcclusionPending) {
17759
+ await this.culling;
17760
+ return;
17761
+ }
17762
+ this._ambientOcclusion = enabled;
17763
+ this.ambientOcclusionPending = true;
17764
+ for (const chunk of this._chunks.values()) this.pendingCulling.set(chunk, "all");
17765
+ await this.updateCulling([]);
17766
+ this.ambientOcclusionPending = false;
17767
+ }
17735
17768
  getBlockAt(posOrX, y, z) {
17736
17769
  if (typeof posOrX == "number") {
17737
17770
  return this.getBlockAt(new import_three42.Vector3(posOrX, y, z));
@@ -18055,7 +18088,7 @@ var MineRenderWorld = class {
18055
18088
  const index = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
18056
18089
  let pending = this.pendingCulling.get(section);
18057
18090
  if (pending === "all" || pending?.has(index)) continue;
18058
- if (Math.abs(x) + Math.abs(y) + Math.abs(z) > 1 && !section.isFluidIndex(index)) continue;
18091
+ if (Math.abs(x) + Math.abs(y) + Math.abs(z) > 1 && !this._ambientOcclusion && !section.isFluidIndex(index)) continue;
18059
18092
  if (!pending) this.pendingCulling.set(section, pending = /* @__PURE__ */ new Set());
18060
18093
  pending.add(index);
18061
18094
  }
@@ -18063,6 +18096,7 @@ var MineRenderWorld = class {
18063
18096
  }
18064
18097
  }
18065
18098
  return this.culling ?? (this.culling = Promise.resolve().then(async () => {
18099
+ let sliceStart = performance.now();
18066
18100
  try {
18067
18101
  while (this.pendingCulling.size) {
18068
18102
  const batch = [...this.pendingCulling];
@@ -18098,6 +18132,11 @@ var MineRenderWorld = class {
18098
18132
  }
18099
18133
  for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
18100
18134
  await chunk.refreshLight(indices === "all" ? void 0 : indices);
18135
+ chunk.refreshAmbientOcclusion(this._ambientOcclusion);
18136
+ if ((this._ambientOcclusion || this.ambientOcclusionPending) && performance.now() - sliceStart > 8) {
18137
+ await yieldToEventLoop();
18138
+ sliceStart = performance.now();
18139
+ }
18101
18140
  }
18102
18141
  await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
18103
18142
  }
@@ -18204,6 +18243,50 @@ init_Materials();
18204
18243
  // src/world/SectionGeometry.ts
18205
18244
  init_FluidQuads();
18206
18245
 
18246
+ // src/world/_light/AmbientOcclusion.ts
18247
+ var AXIS_STRIDES = [1, 9, 3];
18248
+ function sectionAmbientOcclusionMask(cells, center) {
18249
+ let mask = 0;
18250
+ for (let y = -1; y <= 1; y++) for (let z = -1; z <= 1; z++) for (let x = -1; x <= 1; x++) {
18251
+ if (Math.abs(x) + Math.abs(y) + Math.abs(z) < 2) continue;
18252
+ if (cells[center + y * 324 + z * 18 + x]) mask |= 1 << (y + 1) * 9 + (z + 1) * 3 + x + 1;
18253
+ }
18254
+ return mask;
18255
+ }
18256
+ function cornerBrightness(mask, center, u, v) {
18257
+ const sideU = mask >>> center + u & 1;
18258
+ const sideV = mask >>> center + v & 1;
18259
+ const corner = sideU && sideV ? 1 : mask >>> center + u + v & 1;
18260
+ return 1 - (sideU + sideV + corner) * 0.2;
18261
+ }
18262
+ function quadAmbientOcclusion(positions, normals, quad, mask, output) {
18263
+ output.fill(1);
18264
+ if (!mask) return;
18265
+ const start = quad * 12;
18266
+ let axis = 0;
18267
+ if (Math.abs(normals[start + 1]) > Math.abs(normals[start + axis])) axis = 1;
18268
+ if (Math.abs(normals[start + 2]) > Math.abs(normals[start + axis])) axis = 2;
18269
+ const uAxis = (axis + 1) % 3, vAxis = (axis + 2) % 3;
18270
+ if (Math.abs(Math.abs(normals[start + axis]) - 1) > 1e-6 || Math.abs(normals[start + uAxis]) > 1e-6 || Math.abs(normals[start + vAxis]) > 1e-6) return;
18271
+ const sign = normals[start + axis] > 0 ? 1 : -1;
18272
+ for (let vertex = 0; vertex < 4; vertex++) {
18273
+ const offset = start + vertex * 3;
18274
+ if (Math.abs(positions[offset + axis] - sign * 8) > 16e-4 || Math.abs(positions[offset + uAxis]) > 8.0016 || Math.abs(positions[offset + vAxis]) > 8.0016) return;
18275
+ }
18276
+ const center = 13 + sign * AXIS_STRIDES[axis];
18277
+ const u = AXIS_STRIDES[uAxis], v = AXIS_STRIDES[vAxis];
18278
+ const lowLow = cornerBrightness(mask, center, -u, -v);
18279
+ const highLow = cornerBrightness(mask, center, u, -v);
18280
+ const lowHigh = cornerBrightness(mask, center, -u, v);
18281
+ const highHigh = cornerBrightness(mask, center, u, v);
18282
+ for (let vertex = 0; vertex < 4; vertex++) {
18283
+ const offset = start + vertex * 3;
18284
+ const a = Math.max(0, Math.min(1, (positions[offset + uAxis] + 8) / 16));
18285
+ const b = Math.max(0, Math.min(1, (positions[offset + vAxis] + 8) / 16));
18286
+ output[vertex] = (lowLow * (1 - a) + highLow * a) * (1 - b) + (lowHigh * (1 - a) + highHigh * a) * b;
18287
+ }
18288
+ }
18289
+
18207
18290
  // src/world/_light/FaceLight.ts
18208
18291
  function lightBrightness(level, ambient = 0) {
18209
18292
  if (level === 255) return 1;
@@ -18229,7 +18312,7 @@ var LIGHT_SAMPLE_OFFSETS = [0, 1, -1, 324, -324, 18, -18];
18229
18312
 
18230
18313
  // src/world/SectionGeometry.ts
18231
18314
  function buildSectionGeometry(input) {
18232
- const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight } = input;
18315
+ const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion } = input;
18233
18316
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
18234
18317
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
18235
18318
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
@@ -18240,6 +18323,10 @@ function buildSectionGeometry(input) {
18240
18323
  if (ambientLight !== void 0 && (!Number.isFinite(ambientLight) || ambientLight < 0 || ambientLight > 1)) {
18241
18324
  throw new RangeError("Section ambient light must be a number from 0 to 1");
18242
18325
  }
18326
+ if (ambientOcclusion && (ambientOcclusion.length !== 5832 || ambientOcclusion.some((value) => value > 1))) {
18327
+ throw new RangeError("Section ambient occlusion must cover an 18\xD718\xD718 neighborhood with zero or one per cell");
18328
+ }
18329
+ const cornerBrightness2 = ambientOcclusion ? new Float32Array(4) : void 0;
18243
18330
  for (const template of templateData) {
18244
18331
  const { quads } = template;
18245
18332
  if (template.positions.length !== quads * 12 || template.normals.length !== quads * 12 || template.uvs.length !== quads * 8 || template.uvBounds.length !== quads * 16 || template.colors.length !== quads * 12 || template.indices.length !== quads * 6 || template.cullFaces.length !== quads) {
@@ -18334,6 +18421,7 @@ function buildSectionGeometry(input) {
18334
18421
  const positions = new Float32Array(faces * 12), normals = new Float32Array(faces * 12);
18335
18422
  const uvs = new Float32Array(faces * 8), uvBounds = new Float32Array(faces * 16), colors = new Float32Array(faces * 12);
18336
18423
  const outputIndices = new Uint32Array(faces * 6);
18424
+ const blockIndices = new Uint16Array(faces);
18337
18425
  let vertex = 0, indexOffset = 0;
18338
18426
  for (const entry of page.entries) {
18339
18427
  const template = templateData[templates[entry]];
@@ -18345,8 +18433,11 @@ function buildSectionGeometry(input) {
18345
18433
  const offsetY = Math.floor(indices[entry] / 256) * 16;
18346
18434
  const offsetZ = Math.floor(indices[entry] / 16) % 16 * 16;
18347
18435
  const lightIndex = (offsetY / 16 + 1) * 324 + (offsetZ / 16 + 1) * 18 + offsetX / 16 + 1;
18436
+ const ambientMask = ambientOcclusion && template.ambientOcclusion !== false ? sectionAmbientOcclusionMask(ambientOcclusion, lightIndex) : 0;
18348
18437
  for (let quad = 0; quad < template.quads; quad++) {
18349
18438
  if (cullMasks[entry] & template.cullFaces[quad]) continue;
18439
+ blockIndices[indexOffset / 6] = indices[entry];
18440
+ if (ambientMask) quadAmbientOcclusion(template.positions, template.normals, quad, ambientMask, cornerBrightness2);
18350
18441
  const brightness = light ? lightBrightness(light[lightIndex + LIGHT_SAMPLE_OFFSETS[quadLightIndex(template.positions, template.normals, quad)]], ambientLight) : 1;
18351
18442
  const start = vertex;
18352
18443
  for (let corner = 0; corner < 4; corner++, vertex++) {
@@ -18363,9 +18454,10 @@ function buildSectionGeometry(input) {
18363
18454
  uvBounds[vertex * 4 + 1] = mapV(template.uvBounds[source * 4 + 1]);
18364
18455
  uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
18365
18456
  uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
18366
- colors[vertex * 3] = template.colors[source * 3] * brightness;
18367
- colors[vertex * 3 + 1] = template.colors[source * 3 + 1] * brightness;
18368
- colors[vertex * 3 + 2] = template.colors[source * 3 + 2] * brightness;
18457
+ const factor = brightness * (ambientMask ? cornerBrightness2[corner] : 1);
18458
+ colors[vertex * 3] = template.colors[source * 3] * factor;
18459
+ colors[vertex * 3 + 1] = template.colors[source * 3 + 1] * factor;
18460
+ colors[vertex * 3 + 2] = template.colors[source * 3 + 2] * factor;
18369
18461
  }
18370
18462
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
18371
18463
  }
@@ -18377,6 +18469,7 @@ function buildSectionGeometry(input) {
18377
18469
  const mapV = (v) => 1 - (placement.y + (1 - v) * atlas.height) / page.height;
18378
18470
  const lightIndex = (y + 1) * 324 + (z + 1) * 18 + x + 1;
18379
18471
  for (let quad = 0; quad < quads.sprites.length; quad++) {
18472
+ blockIndices[indexOffset / 6] = x + z * 16 + y * 256;
18380
18473
  const rect = quads.sprites[quad] === 0 ? data.still : data.flow;
18381
18474
  const brightness = light ? lightBrightness(fluidLightLevel(
18382
18475
  light[lightIndex],
@@ -18407,11 +18500,11 @@ function buildSectionGeometry(input) {
18407
18500
  indexOffset += 6;
18408
18501
  }
18409
18502
  }
18410
- return { layer: page.layer, width: page.width, height: page.height, placements: page.placements, positions, normals, uvs, uvBounds, colors, indices: outputIndices };
18503
+ return { layer: page.layer, width: page.width, height: page.height, placements: page.placements, positions, normals, uvs, uvBounds, colors, indices: outputIndices, blockIndices };
18411
18504
  });
18412
18505
  }
18413
18506
  function sectionGeometryTransferables(pages) {
18414
- return pages.flatMap((page) => [page.positions.buffer, page.normals.buffer, page.uvs.buffer, page.uvBounds.buffer, page.colors.buffer, page.indices.buffer]);
18507
+ return pages.flatMap((page) => [page.positions.buffer, page.normals.buffer, page.uvs.buffer, page.uvBounds.buffer, page.colors.buffer, page.indices.buffer, page.blockIndices.buffer]);
18415
18508
  }
18416
18509
 
18417
18510
  // src/world/SectionWorker.ts
@@ -18513,7 +18606,8 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
18513
18606
  indices: new Uint16Array(quads * 6),
18514
18607
  cullFaces: new Uint8Array(template.cullFaces),
18515
18608
  atlas: 0,
18516
- layer: template.layer
18609
+ layer: template.layer,
18610
+ ambientOcclusion: template.ambientOcclusion
18517
18611
  };
18518
18612
  const normal = template.geometry.getAttribute("normal");
18519
18613
  const uv = template.geometry.getAttribute("uv");
@@ -18568,6 +18662,12 @@ var SectionMesh = class _SectionMesh extends import_three43.Group {
18568
18662
  constructor() {
18569
18663
  super(...arguments);
18570
18664
  this.ownedMeshes = [];
18665
+ this.blockIndices = /* @__PURE__ */ new Map();
18666
+ }
18667
+ /** Returns the owning section cell for a triangle hit on this mesh's merged geometry. */
18668
+ getBlockIndex(hit) {
18669
+ if (hit.faceIndex === void 0 || hit.faceIndex === null) return void 0;
18670
+ return this.blockIndices.get(hit.object)?.[Math.floor(hit.faceIndex / 2)];
18571
18671
  }
18572
18672
  /** Builds section-local meshes from visible quads. `maxAtlasSize` limits each atlas dimension in pixels. */
18573
18673
  static build(entries, maxAtlasSize = 2048, fluids, lighting) {
@@ -18610,6 +18710,7 @@ var SectionMesh = class _SectionMesh extends import_three43.Group {
18610
18710
  const texture = material.uniforms?.map?.value ?? material.map;
18611
18711
  const mesh = new import_three43.Mesh(geometry, material);
18612
18712
  section.add(mesh);
18713
+ section.blockIndices.set(mesh, page.blockIndices);
18613
18714
  const unsubscribe = [];
18614
18715
  for (const { atlas: id, x, y } of page.placements) {
18615
18716
  const atlas = atlases[id];
@@ -18635,6 +18736,7 @@ var SectionMesh = class _SectionMesh extends import_three43.Group {
18635
18736
  mesh.material.dispose();
18636
18737
  texture?.dispose();
18637
18738
  }
18739
+ this.blockIndices.clear();
18638
18740
  this.removeFromParent();
18639
18741
  super.dispose();
18640
18742
  }
@@ -18644,6 +18746,8 @@ var SectionMesh = class _SectionMesh extends import_three43.Group {
18644
18746
  init_FluidGeometry();
18645
18747
  init_FluidQuads();
18646
18748
  init_CubeFace();
18749
+ init_InstanceReference();
18750
+ init_three();
18647
18751
  var WATER_TINT2 = new import_three44.Color(4159204).toArray();
18648
18752
  var Chunk = class _Chunk {
18649
18753
  constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0) {
@@ -18657,6 +18761,7 @@ var Chunk = class _Chunk {
18657
18761
  this.ambientLight = ambientLight;
18658
18762
  this.data = new ChunkData();
18659
18763
  this.renderedBlocks = /* @__PURE__ */ new Map();
18764
+ this.objectBlocks = /* @__PURE__ */ new Set();
18660
18765
  this.sectionBlocks = /* @__PURE__ */ new Map();
18661
18766
  this.hiddenBlocks = /* @__PURE__ */ new Set();
18662
18767
  this.fluidCells = new Uint8Array(4096);
@@ -18668,6 +18773,89 @@ var Chunk = class _Chunk {
18668
18773
  this.y = y;
18669
18774
  this.z = z;
18670
18775
  }
18776
+ /** Finds the nearest owned block hit across sections, testing each shared mesh once. */
18777
+ static raycast(chunks, raycaster) {
18778
+ const roots = /* @__PURE__ */ new Map();
18779
+ const targets = /* @__PURE__ */ new Map();
18780
+ const meshes = (root, scene) => {
18781
+ const cached2 = roots.get(root);
18782
+ if (cached2) return cached2;
18783
+ const result = [];
18784
+ roots.set(root, result);
18785
+ let attached = false;
18786
+ for (let parent = root; parent; parent = parent.parent) {
18787
+ if (!parent.visible) return result;
18788
+ if (parent === scene) attached = true;
18789
+ }
18790
+ if (!attached) return result;
18791
+ root.updateWorldMatrix(true, true);
18792
+ root.traverseVisible((object4) => {
18793
+ if (isMesh(object4) && raycaster.layers.test(object4.layers)) result.push(object4);
18794
+ });
18795
+ return result;
18796
+ };
18797
+ const add = (root, owner, reference) => {
18798
+ for (const mesh of meshes(root, owner.chunk.scene)) {
18799
+ if (reference && reference.instanceable.getInstanceReference(mesh, reference.index) !== reference) continue;
18800
+ let entry = targets.get(mesh);
18801
+ if (!entry) targets.set(mesh, entry = { owners: /* @__PURE__ */ new Map() });
18802
+ entry.owners.set(reference?.index, owner);
18803
+ }
18804
+ };
18805
+ for (const chunk of chunks) {
18806
+ for (const index of chunk.objectBlocks) {
18807
+ const block = chunk.renderedBlocks.get(index).object;
18808
+ if (!block || !block.visible || chunk.hiddenBlocks.has(index)) continue;
18809
+ const owner = { chunk, index };
18810
+ for (const model of block.models) {
18811
+ if (isInstanceReference(model)) {
18812
+ if (model.instanceable.isInstanceActive(model.index, model)) add(model.instanceable, owner, model);
18813
+ } else {
18814
+ add(model, owner);
18815
+ }
18816
+ }
18817
+ for (const entity of block.blockEntities) add(entity, owner);
18818
+ }
18819
+ if (chunk.sectionMesh) {
18820
+ for (const mesh of meshes(chunk.sectionMesh, chunk.scene)) {
18821
+ targets.set(mesh, { owners: /* @__PURE__ */ new Map(), section: { chunk, mesh: chunk.sectionMesh } });
18822
+ }
18823
+ }
18824
+ }
18825
+ for (const hit of raycaster.intersectObjects([...targets.keys()], false)) {
18826
+ if (!hit.face) continue;
18827
+ const mesh = hit.object;
18828
+ const material = Array.isArray(mesh.material) ? mesh.material[hit.face.materialIndex] : mesh.material;
18829
+ if (!material?.visible) continue;
18830
+ const entry = targets.get(mesh);
18831
+ let owner = entry.owners.get(hit.instanceId) ?? entry.owners.get(void 0);
18832
+ if (entry.section) {
18833
+ const index = entry.section.mesh.getBlockIndex(hit);
18834
+ if (index !== void 0) owner = { chunk: entry.section.chunk, index };
18835
+ }
18836
+ if (!owner || owner.chunk.hiddenBlocks.has(owner.index)) continue;
18837
+ const info = owner.chunk.renderedBlocks.get(owner.index);
18838
+ if (!info) continue;
18839
+ const matrix = mesh.matrixWorld.clone();
18840
+ if (isInstancedMesh(mesh) && hit.instanceId !== void 0) {
18841
+ const instance = new import_three44.Matrix4();
18842
+ mesh.getMatrixAt(hit.instanceId, instance);
18843
+ matrix.multiply(instance);
18844
+ }
18845
+ return {
18846
+ position: new import_three44.Vector3(
18847
+ owner.chunk.x * 16 + owner.index % 16,
18848
+ owner.chunk.y * 16 + Math.floor(owner.index / 256),
18849
+ owner.chunk.z * 16 + Math.floor(owner.index / 16) % 16
18850
+ ),
18851
+ block: info.block,
18852
+ point: hit.point.clone(),
18853
+ normal: hit.face.normal.clone().applyNormalMatrix(new import_three44.Matrix3().getNormalMatrix(matrix)),
18854
+ distance: hit.distance
18855
+ };
18856
+ }
18857
+ return void 0;
18858
+ }
18671
18859
  getBlockAt(posOrX, y, z) {
18672
18860
  if (typeof posOrX == "number") {
18673
18861
  return this.getBlockAt(new import_three44.Vector3(posOrX, y, z));
@@ -18788,6 +18976,7 @@ var Chunk = class _Chunk {
18788
18976
  if (this.fluidCells[index]) this.meshDirty = true;
18789
18977
  this.fluidCells[index] = 0;
18790
18978
  this.renderedBlocks.delete(index);
18979
+ this.objectBlocks.delete(index);
18791
18980
  const worldPos = new import_three44.Vector3(
18792
18981
  this.x * 16 + index % 16,
18793
18982
  this.y * 16 + Math.floor(index / 256),
@@ -18830,6 +19019,7 @@ var Chunk = class _Chunk {
18830
19019
  this.renderedBlocks.set(index, { get block() {
18831
19020
  return readBlock();
18832
19021
  }, object: object4 });
19022
+ if (object4) this.objectBlocks.add(index);
18833
19023
  } catch (error) {
18834
19024
  this.data.assign(index, 0);
18835
19025
  this.fluidCells[index] = 0;
@@ -18848,6 +19038,7 @@ var Chunk = class _Chunk {
18848
19038
  this.fluidCells.fill(0);
18849
19039
  this.waterTints = void 0;
18850
19040
  this.renderedBlocks.clear();
19041
+ this.objectBlocks.clear();
18851
19042
  this.hiddenBlocks.clear();
18852
19043
  this.meshDirty = true;
18853
19044
  this.data.copyFrom(data);
@@ -18897,6 +19088,7 @@ var Chunk = class _Chunk {
18897
19088
  this.renderedBlocks.set(index, { get block() {
18898
19089
  return readBlock();
18899
19090
  }, object: void 0 });
19091
+ this.objectBlocks.delete(index);
18900
19092
  } catch (error) {
18901
19093
  this.data.assign(index, 0);
18902
19094
  this.fluidCells[index] = 0;
@@ -18922,10 +19114,12 @@ var Chunk = class _Chunk {
18922
19114
  this.z * 256 + Math.floor(index / 16) % 16 * 16
18923
19115
  ));
18924
19116
  this.renderedBlocks.get(index).object = object4;
19117
+ this.objectBlocks.add(index);
18925
19118
  } catch (error) {
18926
19119
  this.data.assign(index, 0);
18927
19120
  this.fluidCells[index] = 0;
18928
19121
  this.renderedBlocks.delete(index);
19122
+ this.objectBlocks.delete(index);
18929
19123
  object4?.removeFromScene();
18930
19124
  if (!failed) failure = error;
18931
19125
  failed = true;
@@ -18943,6 +19137,25 @@ var Chunk = class _Chunk {
18943
19137
  if (this.renderedBlocks.has(index)) this.meshDirty = true;
18944
19138
  }
18945
19139
  }
19140
+ /** Refreshes baked corner shading after edits or a world setting change. Disabled chunks keep no AO neighborhood. */
19141
+ refreshAmbientOcclusion(enabled) {
19142
+ if (!enabled && !this.ambientOcclusionCells) return;
19143
+ const cells = enabled ? this.buildAmbientOcclusionCells() : void 0;
19144
+ if (cells?.some((value, index) => value !== this.ambientOcclusionCells?.[index]) || !!cells !== !!this.ambientOcclusionCells) this.meshDirty = true;
19145
+ this.ambientOcclusionCells = cells;
19146
+ }
19147
+ buildAmbientOcclusionCells() {
19148
+ const cells = new Uint8Array(18 * 18 * 18);
19149
+ for (let y = -1; y <= 16; y++) {
19150
+ for (let z = -1; z <= 16; z++) {
19151
+ for (let x = -1; x <= 16; x++) {
19152
+ const inner = x >= 0 && x < 16 && y >= 0 && y < 16 && z >= 0 && z < 16;
19153
+ cells[(y + 1) * 324 + (z + 1) * 18 + x + 1] = inner ? Number(this.isOccludingIndex(y * 256 + z * 16 + x)) : Number(!!((this.neighborCell?.(this.x * 16 + x, this.y * 16 + y, this.z * 16 + z) ?? 0) & 64));
19154
+ }
19155
+ }
19156
+ }
19157
+ return cells;
19158
+ }
18946
19159
  biomePosition(index, blockState3, stored) {
18947
19160
  const position = this.positionAt(index), name = blockState3.key?.toNamespacedString();
18948
19161
  if (stored.properties?.half === "upper" && (name === "minecraft:tall_grass" || name === "minecraft:large_fern")) position[1]--;
@@ -19131,12 +19344,18 @@ var Chunk = class _Chunk {
19131
19344
  }
19132
19345
  }
19133
19346
  }
19134
- const next = entries.length || fluids ? await SectionMesh.buildAsync(
19135
- entries,
19136
- this.sectionModels.maxAtlasSize,
19137
- fluids,
19138
- light ? { light, ambientLight: this.ambientLight } : void 0
19139
- ) : void 0;
19347
+ let next;
19348
+ try {
19349
+ next = entries.length || fluids ? await SectionMesh.buildAsync(
19350
+ entries,
19351
+ this.sectionModels.maxAtlasSize,
19352
+ fluids,
19353
+ { light, ambientLight: this.ambientLight, ambientOcclusion: this.ambientOcclusionCells }
19354
+ ) : void 0;
19355
+ } catch (error) {
19356
+ if (generation === this.meshGeneration) this.meshDirty = true;
19357
+ throw error;
19358
+ }
19140
19359
  if (generation !== this.meshGeneration) {
19141
19360
  next?.dispose();
19142
19361
  return;
@@ -19161,9 +19380,11 @@ var Chunk = class _Chunk {
19161
19380
  this.sectionMesh = void 0;
19162
19381
  this.meshDirty = false;
19163
19382
  this.renderedBlocks.clear();
19383
+ this.objectBlocks.clear();
19164
19384
  this.hiddenBlocks.clear();
19165
19385
  this.fluidCells.fill(0);
19166
19386
  this.waterTints = void 0;
19387
+ this.ambientOcclusionCells = void 0;
19167
19388
  this.data.clear();
19168
19389
  this.biomes = void 0;
19169
19390
  if (notify) {