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.
@@ -12633,6 +12633,10 @@ var BlockObject = class _BlockObject extends SceneObject {
12633
12633
  get blockEntities() {
12634
12634
  return this._entities;
12635
12635
  }
12636
+ /** Model placements drawn for this block, including its fluid surface. */
12637
+ get models() {
12638
+ return this._models;
12639
+ }
12636
12640
  /** Sets visibility for this block's model instances and block entities. */
12637
12641
  setVisible(visible) {
12638
12642
  this.visible = visible;
@@ -17565,7 +17569,8 @@ var SectionModels = class {
17565
17569
  atlas,
17566
17570
  cullFaces: new Uint8Array(cullFaces),
17567
17571
  occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
17568
- layer: atlas.hasTranslucency ? 1 : 0
17572
+ layer: atlas.hasTranslucency ? 1 : 0,
17573
+ ambientOcclusion: model.ambientocclusion !== false
17569
17574
  };
17570
17575
  this.templates.add(template);
17571
17576
  return template;
@@ -17756,17 +17761,45 @@ var MineRenderWorld = class {
17756
17761
  this.light = /* @__PURE__ */ new Map();
17757
17762
  this.lightValidity = /* @__PURE__ */ new Map();
17758
17763
  this.highestSkySection = /* @__PURE__ */ new Map();
17764
+ this.ambientOcclusionPending = false;
17759
17765
  this.pendingCulling = /* @__PURE__ */ new Map();
17760
17766
  if (options.savedLighting && !options.sectionMeshing) throw new TypeError("savedLighting requires sectionMeshing");
17767
+ if (options.ambientOcclusion && !options.sectionMeshing) throw new TypeError("ambientOcclusion requires sectionMeshing");
17761
17768
  if (options.ambientLight !== void 0 && (!Number.isFinite(options.ambientLight) || options.ambientLight < 0 || options.ambientLight > 1)) {
17762
17769
  throw new RangeError("ambientLight must be a number from 0 to 1");
17763
17770
  }
17764
17771
  this.scene = scene;
17765
17772
  this.savedLighting = options.savedLighting ?? false;
17766
17773
  this.ambientLight = options.ambientLight ?? 0;
17774
+ this._ambientOcclusion = options.ambientOcclusion ?? false;
17767
17775
  if (options.sectionMeshing) this.sectionModels = new SectionModels(options.maxAtlasSize);
17768
17776
  if (options.renderEntities) this.entities = new WorldEntities(scene);
17769
17777
  }
17778
+ /**
17779
+ * Returns the nearest visible block geometry hit, respecting the raycaster's layers and near/far range.
17780
+ * Includes fluids and block entities; texture alpha does not change triangle intersections.
17781
+ */
17782
+ raycast(raycaster) {
17783
+ return Chunk.raycast(this._chunks.values(), raycaster);
17784
+ }
17785
+ /** Whether neighboring opaque cubes shade block-face corners. Defaults to false. */
17786
+ get ambientOcclusion() {
17787
+ return this._ambientOcclusion;
17788
+ }
17789
+ /** Rebuilds loaded blocks with or without corner shading. Block data and saved lighting are preserved. */
17790
+ async setAmbientOcclusion(enabled) {
17791
+ if (typeof enabled !== "boolean") throw new TypeError("Ambient occlusion must be a boolean");
17792
+ if (enabled && !this.sectionModels) throw new TypeError("ambientOcclusion requires sectionMeshing");
17793
+ if (enabled === this._ambientOcclusion && !this.ambientOcclusionPending) {
17794
+ await this.culling;
17795
+ return;
17796
+ }
17797
+ this._ambientOcclusion = enabled;
17798
+ this.ambientOcclusionPending = true;
17799
+ for (const chunk of this._chunks.values()) this.pendingCulling.set(chunk, "all");
17800
+ await this.updateCulling([]);
17801
+ this.ambientOcclusionPending = false;
17802
+ }
17770
17803
  getBlockAt(posOrX, y, z) {
17771
17804
  if (typeof posOrX == "number") {
17772
17805
  return this.getBlockAt(new import_three42.Vector3(posOrX, y, z));
@@ -18090,7 +18123,7 @@ var MineRenderWorld = class {
18090
18123
  const index = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
18091
18124
  let pending = this.pendingCulling.get(section);
18092
18125
  if (pending === "all" || pending?.has(index)) continue;
18093
- if (Math.abs(x) + Math.abs(y) + Math.abs(z) > 1 && !section.isFluidIndex(index)) continue;
18126
+ if (Math.abs(x) + Math.abs(y) + Math.abs(z) > 1 && !this._ambientOcclusion && !section.isFluidIndex(index)) continue;
18094
18127
  if (!pending) this.pendingCulling.set(section, pending = /* @__PURE__ */ new Set());
18095
18128
  pending.add(index);
18096
18129
  }
@@ -18098,6 +18131,7 @@ var MineRenderWorld = class {
18098
18131
  }
18099
18132
  }
18100
18133
  return this.culling ??= Promise.resolve().then(async () => {
18134
+ let sliceStart = performance.now();
18101
18135
  try {
18102
18136
  while (this.pendingCulling.size) {
18103
18137
  const batch = [...this.pendingCulling];
@@ -18133,6 +18167,11 @@ var MineRenderWorld = class {
18133
18167
  }
18134
18168
  for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
18135
18169
  await chunk.refreshLight(indices === "all" ? void 0 : indices);
18170
+ chunk.refreshAmbientOcclusion(this._ambientOcclusion);
18171
+ if ((this._ambientOcclusion || this.ambientOcclusionPending) && performance.now() - sliceStart > 8) {
18172
+ await yieldToEventLoop();
18173
+ sliceStart = performance.now();
18174
+ }
18136
18175
  }
18137
18176
  await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
18138
18177
  }
@@ -18239,6 +18278,50 @@ init_Materials();
18239
18278
  // src/world/SectionGeometry.ts
18240
18279
  init_FluidQuads();
18241
18280
 
18281
+ // src/world/_light/AmbientOcclusion.ts
18282
+ var AXIS_STRIDES = [1, 9, 3];
18283
+ function sectionAmbientOcclusionMask(cells, center) {
18284
+ let mask = 0;
18285
+ for (let y = -1; y <= 1; y++) for (let z = -1; z <= 1; z++) for (let x = -1; x <= 1; x++) {
18286
+ if (Math.abs(x) + Math.abs(y) + Math.abs(z) < 2) continue;
18287
+ if (cells[center + y * 324 + z * 18 + x]) mask |= 1 << (y + 1) * 9 + (z + 1) * 3 + x + 1;
18288
+ }
18289
+ return mask;
18290
+ }
18291
+ function cornerBrightness(mask, center, u, v) {
18292
+ const sideU = mask >>> center + u & 1;
18293
+ const sideV = mask >>> center + v & 1;
18294
+ const corner = sideU && sideV ? 1 : mask >>> center + u + v & 1;
18295
+ return 1 - (sideU + sideV + corner) * 0.2;
18296
+ }
18297
+ function quadAmbientOcclusion(positions, normals, quad, mask, output) {
18298
+ output.fill(1);
18299
+ if (!mask) return;
18300
+ const start = quad * 12;
18301
+ let axis = 0;
18302
+ if (Math.abs(normals[start + 1]) > Math.abs(normals[start + axis])) axis = 1;
18303
+ if (Math.abs(normals[start + 2]) > Math.abs(normals[start + axis])) axis = 2;
18304
+ const uAxis = (axis + 1) % 3, vAxis = (axis + 2) % 3;
18305
+ 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;
18306
+ const sign = normals[start + axis] > 0 ? 1 : -1;
18307
+ for (let vertex = 0; vertex < 4; vertex++) {
18308
+ const offset = start + vertex * 3;
18309
+ 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;
18310
+ }
18311
+ const center = 13 + sign * AXIS_STRIDES[axis];
18312
+ const u = AXIS_STRIDES[uAxis], v = AXIS_STRIDES[vAxis];
18313
+ const lowLow = cornerBrightness(mask, center, -u, -v);
18314
+ const highLow = cornerBrightness(mask, center, u, -v);
18315
+ const lowHigh = cornerBrightness(mask, center, -u, v);
18316
+ const highHigh = cornerBrightness(mask, center, u, v);
18317
+ for (let vertex = 0; vertex < 4; vertex++) {
18318
+ const offset = start + vertex * 3;
18319
+ const a = Math.max(0, Math.min(1, (positions[offset + uAxis] + 8) / 16));
18320
+ const b = Math.max(0, Math.min(1, (positions[offset + vAxis] + 8) / 16));
18321
+ output[vertex] = (lowLow * (1 - a) + highLow * a) * (1 - b) + (lowHigh * (1 - a) + highHigh * a) * b;
18322
+ }
18323
+ }
18324
+
18242
18325
  // src/world/_light/FaceLight.ts
18243
18326
  function lightBrightness(level, ambient = 0) {
18244
18327
  if (level === 255) return 1;
@@ -18264,7 +18347,7 @@ var LIGHT_SAMPLE_OFFSETS = [0, 1, -1, 324, -324, 18, -18];
18264
18347
 
18265
18348
  // src/world/SectionGeometry.ts
18266
18349
  function buildSectionGeometry(input) {
18267
- const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight } = input;
18350
+ const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion } = input;
18268
18351
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
18269
18352
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
18270
18353
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
@@ -18275,6 +18358,10 @@ function buildSectionGeometry(input) {
18275
18358
  if (ambientLight !== void 0 && (!Number.isFinite(ambientLight) || ambientLight < 0 || ambientLight > 1)) {
18276
18359
  throw new RangeError("Section ambient light must be a number from 0 to 1");
18277
18360
  }
18361
+ if (ambientOcclusion && (ambientOcclusion.length !== 5832 || ambientOcclusion.some((value) => value > 1))) {
18362
+ throw new RangeError("Section ambient occlusion must cover an 18\xD718\xD718 neighborhood with zero or one per cell");
18363
+ }
18364
+ const cornerBrightness2 = ambientOcclusion ? new Float32Array(4) : void 0;
18278
18365
  for (const template of templateData) {
18279
18366
  const { quads } = template;
18280
18367
  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) {
@@ -18369,6 +18456,7 @@ function buildSectionGeometry(input) {
18369
18456
  const positions = new Float32Array(faces * 12), normals = new Float32Array(faces * 12);
18370
18457
  const uvs = new Float32Array(faces * 8), uvBounds = new Float32Array(faces * 16), colors = new Float32Array(faces * 12);
18371
18458
  const outputIndices = new Uint32Array(faces * 6);
18459
+ const blockIndices = new Uint16Array(faces);
18372
18460
  let vertex = 0, indexOffset = 0;
18373
18461
  for (const entry of page.entries) {
18374
18462
  const template = templateData[templates[entry]];
@@ -18380,8 +18468,11 @@ function buildSectionGeometry(input) {
18380
18468
  const offsetY = Math.floor(indices[entry] / 256) * 16;
18381
18469
  const offsetZ = Math.floor(indices[entry] / 16) % 16 * 16;
18382
18470
  const lightIndex = (offsetY / 16 + 1) * 324 + (offsetZ / 16 + 1) * 18 + offsetX / 16 + 1;
18471
+ const ambientMask = ambientOcclusion && template.ambientOcclusion !== false ? sectionAmbientOcclusionMask(ambientOcclusion, lightIndex) : 0;
18383
18472
  for (let quad = 0; quad < template.quads; quad++) {
18384
18473
  if (cullMasks[entry] & template.cullFaces[quad]) continue;
18474
+ blockIndices[indexOffset / 6] = indices[entry];
18475
+ if (ambientMask) quadAmbientOcclusion(template.positions, template.normals, quad, ambientMask, cornerBrightness2);
18385
18476
  const brightness = light ? lightBrightness(light[lightIndex + LIGHT_SAMPLE_OFFSETS[quadLightIndex(template.positions, template.normals, quad)]], ambientLight) : 1;
18386
18477
  const start = vertex;
18387
18478
  for (let corner = 0; corner < 4; corner++, vertex++) {
@@ -18398,9 +18489,10 @@ function buildSectionGeometry(input) {
18398
18489
  uvBounds[vertex * 4 + 1] = mapV(template.uvBounds[source * 4 + 1]);
18399
18490
  uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
18400
18491
  uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
18401
- colors[vertex * 3] = template.colors[source * 3] * brightness;
18402
- colors[vertex * 3 + 1] = template.colors[source * 3 + 1] * brightness;
18403
- colors[vertex * 3 + 2] = template.colors[source * 3 + 2] * brightness;
18492
+ const factor = brightness * (ambientMask ? cornerBrightness2[corner] : 1);
18493
+ colors[vertex * 3] = template.colors[source * 3] * factor;
18494
+ colors[vertex * 3 + 1] = template.colors[source * 3 + 1] * factor;
18495
+ colors[vertex * 3 + 2] = template.colors[source * 3 + 2] * factor;
18404
18496
  }
18405
18497
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
18406
18498
  }
@@ -18412,6 +18504,7 @@ function buildSectionGeometry(input) {
18412
18504
  const mapV = (v) => 1 - (placement.y + (1 - v) * atlas.height) / page.height;
18413
18505
  const lightIndex = (y + 1) * 324 + (z + 1) * 18 + x + 1;
18414
18506
  for (let quad = 0; quad < quads.sprites.length; quad++) {
18507
+ blockIndices[indexOffset / 6] = x + z * 16 + y * 256;
18415
18508
  const rect = quads.sprites[quad] === 0 ? data.still : data.flow;
18416
18509
  const brightness = light ? lightBrightness(fluidLightLevel(
18417
18510
  light[lightIndex],
@@ -18442,11 +18535,11 @@ function buildSectionGeometry(input) {
18442
18535
  indexOffset += 6;
18443
18536
  }
18444
18537
  }
18445
- return { layer: page.layer, width: page.width, height: page.height, placements: page.placements, positions, normals, uvs, uvBounds, colors, indices: outputIndices };
18538
+ return { layer: page.layer, width: page.width, height: page.height, placements: page.placements, positions, normals, uvs, uvBounds, colors, indices: outputIndices, blockIndices };
18446
18539
  });
18447
18540
  }
18448
18541
  function sectionGeometryTransferables(pages) {
18449
- return pages.flatMap((page) => [page.positions.buffer, page.normals.buffer, page.uvs.buffer, page.uvBounds.buffer, page.colors.buffer, page.indices.buffer]);
18542
+ 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]);
18450
18543
  }
18451
18544
 
18452
18545
  // src/world/SectionWorker.ts
@@ -18549,7 +18642,8 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
18549
18642
  indices: new Uint16Array(quads * 6),
18550
18643
  cullFaces: new Uint8Array(template.cullFaces),
18551
18644
  atlas: 0,
18552
- layer: template.layer
18645
+ layer: template.layer,
18646
+ ambientOcclusion: template.ambientOcclusion
18553
18647
  };
18554
18648
  const normal = template.geometry.getAttribute("normal");
18555
18649
  const uv = template.geometry.getAttribute("uv");
@@ -18604,6 +18698,12 @@ var SectionMesh = class _SectionMesh extends import_three43.Group {
18604
18698
  constructor() {
18605
18699
  super(...arguments);
18606
18700
  this.ownedMeshes = [];
18701
+ this.blockIndices = /* @__PURE__ */ new Map();
18702
+ }
18703
+ /** Returns the owning section cell for a triangle hit on this mesh's merged geometry. */
18704
+ getBlockIndex(hit) {
18705
+ if (hit.faceIndex === void 0 || hit.faceIndex === null) return void 0;
18706
+ return this.blockIndices.get(hit.object)?.[Math.floor(hit.faceIndex / 2)];
18607
18707
  }
18608
18708
  /** Builds section-local meshes from visible quads. `maxAtlasSize` limits each atlas dimension in pixels. */
18609
18709
  static build(entries, maxAtlasSize = 2048, fluids, lighting) {
@@ -18646,6 +18746,7 @@ var SectionMesh = class _SectionMesh extends import_three43.Group {
18646
18746
  const texture = material.uniforms?.map?.value ?? material.map;
18647
18747
  const mesh = new import_three43.Mesh(geometry, material);
18648
18748
  section.add(mesh);
18749
+ section.blockIndices.set(mesh, page.blockIndices);
18649
18750
  const unsubscribe = [];
18650
18751
  for (const { atlas: id, x, y } of page.placements) {
18651
18752
  const atlas = atlases[id];
@@ -18671,6 +18772,7 @@ var SectionMesh = class _SectionMesh extends import_three43.Group {
18671
18772
  mesh.material.dispose();
18672
18773
  texture?.dispose();
18673
18774
  }
18775
+ this.blockIndices.clear();
18674
18776
  this.removeFromParent();
18675
18777
  super.dispose();
18676
18778
  }
@@ -18680,6 +18782,8 @@ var SectionMesh = class _SectionMesh extends import_three43.Group {
18680
18782
  init_FluidGeometry();
18681
18783
  init_FluidQuads();
18682
18784
  init_CubeFace();
18785
+ init_InstanceReference();
18786
+ init_three();
18683
18787
  var WATER_TINT2 = new import_three44.Color(4159204).toArray();
18684
18788
  var Chunk = class _Chunk {
18685
18789
  constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0) {
@@ -18693,6 +18797,7 @@ var Chunk = class _Chunk {
18693
18797
  this.ambientLight = ambientLight;
18694
18798
  this.data = new ChunkData();
18695
18799
  this.renderedBlocks = /* @__PURE__ */ new Map();
18800
+ this.objectBlocks = /* @__PURE__ */ new Set();
18696
18801
  this.sectionBlocks = /* @__PURE__ */ new Map();
18697
18802
  this.hiddenBlocks = /* @__PURE__ */ new Set();
18698
18803
  this.fluidCells = new Uint8Array(4096);
@@ -18704,6 +18809,89 @@ var Chunk = class _Chunk {
18704
18809
  this.y = y;
18705
18810
  this.z = z;
18706
18811
  }
18812
+ /** Finds the nearest owned block hit across sections, testing each shared mesh once. */
18813
+ static raycast(chunks, raycaster) {
18814
+ const roots = /* @__PURE__ */ new Map();
18815
+ const targets = /* @__PURE__ */ new Map();
18816
+ const meshes = (root, scene) => {
18817
+ const cached2 = roots.get(root);
18818
+ if (cached2) return cached2;
18819
+ const result = [];
18820
+ roots.set(root, result);
18821
+ let attached = false;
18822
+ for (let parent = root; parent; parent = parent.parent) {
18823
+ if (!parent.visible) return result;
18824
+ if (parent === scene) attached = true;
18825
+ }
18826
+ if (!attached) return result;
18827
+ root.updateWorldMatrix(true, true);
18828
+ root.traverseVisible((object4) => {
18829
+ if (isMesh(object4) && raycaster.layers.test(object4.layers)) result.push(object4);
18830
+ });
18831
+ return result;
18832
+ };
18833
+ const add = (root, owner, reference) => {
18834
+ for (const mesh of meshes(root, owner.chunk.scene)) {
18835
+ if (reference && reference.instanceable.getInstanceReference(mesh, reference.index) !== reference) continue;
18836
+ let entry = targets.get(mesh);
18837
+ if (!entry) targets.set(mesh, entry = { owners: /* @__PURE__ */ new Map() });
18838
+ entry.owners.set(reference?.index, owner);
18839
+ }
18840
+ };
18841
+ for (const chunk of chunks) {
18842
+ for (const index of chunk.objectBlocks) {
18843
+ const block = chunk.renderedBlocks.get(index).object;
18844
+ if (!block || !block.visible || chunk.hiddenBlocks.has(index)) continue;
18845
+ const owner = { chunk, index };
18846
+ for (const model of block.models) {
18847
+ if (isInstanceReference(model)) {
18848
+ if (model.instanceable.isInstanceActive(model.index, model)) add(model.instanceable, owner, model);
18849
+ } else {
18850
+ add(model, owner);
18851
+ }
18852
+ }
18853
+ for (const entity of block.blockEntities) add(entity, owner);
18854
+ }
18855
+ if (chunk.sectionMesh) {
18856
+ for (const mesh of meshes(chunk.sectionMesh, chunk.scene)) {
18857
+ targets.set(mesh, { owners: /* @__PURE__ */ new Map(), section: { chunk, mesh: chunk.sectionMesh } });
18858
+ }
18859
+ }
18860
+ }
18861
+ for (const hit of raycaster.intersectObjects([...targets.keys()], false)) {
18862
+ if (!hit.face) continue;
18863
+ const mesh = hit.object;
18864
+ const material = Array.isArray(mesh.material) ? mesh.material[hit.face.materialIndex] : mesh.material;
18865
+ if (!material?.visible) continue;
18866
+ const entry = targets.get(mesh);
18867
+ let owner = entry.owners.get(hit.instanceId) ?? entry.owners.get(void 0);
18868
+ if (entry.section) {
18869
+ const index = entry.section.mesh.getBlockIndex(hit);
18870
+ if (index !== void 0) owner = { chunk: entry.section.chunk, index };
18871
+ }
18872
+ if (!owner || owner.chunk.hiddenBlocks.has(owner.index)) continue;
18873
+ const info = owner.chunk.renderedBlocks.get(owner.index);
18874
+ if (!info) continue;
18875
+ const matrix = mesh.matrixWorld.clone();
18876
+ if (isInstancedMesh(mesh) && hit.instanceId !== void 0) {
18877
+ const instance = new import_three44.Matrix4();
18878
+ mesh.getMatrixAt(hit.instanceId, instance);
18879
+ matrix.multiply(instance);
18880
+ }
18881
+ return {
18882
+ position: new import_three44.Vector3(
18883
+ owner.chunk.x * 16 + owner.index % 16,
18884
+ owner.chunk.y * 16 + Math.floor(owner.index / 256),
18885
+ owner.chunk.z * 16 + Math.floor(owner.index / 16) % 16
18886
+ ),
18887
+ block: info.block,
18888
+ point: hit.point.clone(),
18889
+ normal: hit.face.normal.clone().applyNormalMatrix(new import_three44.Matrix3().getNormalMatrix(matrix)),
18890
+ distance: hit.distance
18891
+ };
18892
+ }
18893
+ return void 0;
18894
+ }
18707
18895
  getBlockAt(posOrX, y, z) {
18708
18896
  if (typeof posOrX == "number") {
18709
18897
  return this.getBlockAt(new import_three44.Vector3(posOrX, y, z));
@@ -18824,6 +19012,7 @@ var Chunk = class _Chunk {
18824
19012
  if (this.fluidCells[index]) this.meshDirty = true;
18825
19013
  this.fluidCells[index] = 0;
18826
19014
  this.renderedBlocks.delete(index);
19015
+ this.objectBlocks.delete(index);
18827
19016
  const worldPos = new import_three44.Vector3(
18828
19017
  this.x * 16 + index % 16,
18829
19018
  this.y * 16 + Math.floor(index / 256),
@@ -18866,6 +19055,7 @@ var Chunk = class _Chunk {
18866
19055
  this.renderedBlocks.set(index, { get block() {
18867
19056
  return readBlock();
18868
19057
  }, object: object4 });
19058
+ if (object4) this.objectBlocks.add(index);
18869
19059
  } catch (error) {
18870
19060
  this.data.assign(index, 0);
18871
19061
  this.fluidCells[index] = 0;
@@ -18884,6 +19074,7 @@ var Chunk = class _Chunk {
18884
19074
  this.fluidCells.fill(0);
18885
19075
  this.waterTints = void 0;
18886
19076
  this.renderedBlocks.clear();
19077
+ this.objectBlocks.clear();
18887
19078
  this.hiddenBlocks.clear();
18888
19079
  this.meshDirty = true;
18889
19080
  this.data.copyFrom(data);
@@ -18933,6 +19124,7 @@ var Chunk = class _Chunk {
18933
19124
  this.renderedBlocks.set(index, { get block() {
18934
19125
  return readBlock();
18935
19126
  }, object: void 0 });
19127
+ this.objectBlocks.delete(index);
18936
19128
  } catch (error) {
18937
19129
  this.data.assign(index, 0);
18938
19130
  this.fluidCells[index] = 0;
@@ -18958,10 +19150,12 @@ var Chunk = class _Chunk {
18958
19150
  this.z * 256 + Math.floor(index / 16) % 16 * 16
18959
19151
  ));
18960
19152
  this.renderedBlocks.get(index).object = object4;
19153
+ this.objectBlocks.add(index);
18961
19154
  } catch (error) {
18962
19155
  this.data.assign(index, 0);
18963
19156
  this.fluidCells[index] = 0;
18964
19157
  this.renderedBlocks.delete(index);
19158
+ this.objectBlocks.delete(index);
18965
19159
  object4?.removeFromScene();
18966
19160
  if (!failed) failure = error;
18967
19161
  failed = true;
@@ -18979,6 +19173,25 @@ var Chunk = class _Chunk {
18979
19173
  if (this.renderedBlocks.has(index)) this.meshDirty = true;
18980
19174
  }
18981
19175
  }
19176
+ /** Refreshes baked corner shading after edits or a world setting change. Disabled chunks keep no AO neighborhood. */
19177
+ refreshAmbientOcclusion(enabled) {
19178
+ if (!enabled && !this.ambientOcclusionCells) return;
19179
+ const cells = enabled ? this.buildAmbientOcclusionCells() : void 0;
19180
+ if (cells?.some((value, index) => value !== this.ambientOcclusionCells?.[index]) || !!cells !== !!this.ambientOcclusionCells) this.meshDirty = true;
19181
+ this.ambientOcclusionCells = cells;
19182
+ }
19183
+ buildAmbientOcclusionCells() {
19184
+ const cells = new Uint8Array(18 * 18 * 18);
19185
+ for (let y = -1; y <= 16; y++) {
19186
+ for (let z = -1; z <= 16; z++) {
19187
+ for (let x = -1; x <= 16; x++) {
19188
+ const inner = x >= 0 && x < 16 && y >= 0 && y < 16 && z >= 0 && z < 16;
19189
+ 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));
19190
+ }
19191
+ }
19192
+ }
19193
+ return cells;
19194
+ }
18982
19195
  biomePosition(index, blockState3, stored) {
18983
19196
  const position = this.positionAt(index), name = blockState3.key?.toNamespacedString();
18984
19197
  if (stored.properties?.half === "upper" && (name === "minecraft:tall_grass" || name === "minecraft:large_fern")) position[1]--;
@@ -19167,12 +19380,18 @@ var Chunk = class _Chunk {
19167
19380
  }
19168
19381
  }
19169
19382
  }
19170
- const next = entries.length || fluids ? await SectionMesh.buildAsync(
19171
- entries,
19172
- this.sectionModels.maxAtlasSize,
19173
- fluids,
19174
- light ? { light, ambientLight: this.ambientLight } : void 0
19175
- ) : void 0;
19383
+ let next;
19384
+ try {
19385
+ next = entries.length || fluids ? await SectionMesh.buildAsync(
19386
+ entries,
19387
+ this.sectionModels.maxAtlasSize,
19388
+ fluids,
19389
+ { light, ambientLight: this.ambientLight, ambientOcclusion: this.ambientOcclusionCells }
19390
+ ) : void 0;
19391
+ } catch (error) {
19392
+ if (generation === this.meshGeneration) this.meshDirty = true;
19393
+ throw error;
19394
+ }
19176
19395
  if (generation !== this.meshGeneration) {
19177
19396
  next?.dispose();
19178
19397
  return;
@@ -19197,9 +19416,11 @@ var Chunk = class _Chunk {
19197
19416
  this.sectionMesh = void 0;
19198
19417
  this.meshDirty = false;
19199
19418
  this.renderedBlocks.clear();
19419
+ this.objectBlocks.clear();
19200
19420
  this.hiddenBlocks.clear();
19201
19421
  this.fluidCells.fill(0);
19202
19422
  this.waterTints = void 0;
19423
+ this.ambientOcclusionCells = void 0;
19203
19424
  this.data.clear();
19204
19425
  this.biomes = void 0;
19205
19426
  if (notify) {