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.
@@ -12425,6 +12425,10 @@ var _BlockObject = class _BlockObject extends SceneObject {
12425
12425
  get blockEntities() {
12426
12426
  return this._entities;
12427
12427
  }
12428
+ /** Model placements drawn for this block, including its fluid surface. */
12429
+ get models() {
12430
+ return this._models;
12431
+ }
12428
12432
  /** Sets visibility for this block's model instances and block entities. */
12429
12433
  setVisible(visible) {
12430
12434
  this.visible = visible;
@@ -17188,7 +17192,7 @@ var AnvilWorldSource = class {
17188
17192
  };
17189
17193
 
17190
17194
  // src/world/Chunk.ts
17191
- import { Color as Color13, Vector3 as Vector318 } from "three";
17195
+ import { Color as Color13, Matrix3 as Matrix33, Matrix4 as Matrix413, Vector3 as Vector318 } from "three";
17192
17196
  init_BlockStates();
17193
17197
  init_AssetKey();
17194
17198
 
@@ -17367,7 +17371,8 @@ var SectionModels = class {
17367
17371
  atlas,
17368
17372
  cullFaces: new Uint8Array(cullFaces),
17369
17373
  occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
17370
- layer: atlas.hasTranslucency ? 1 : 0
17374
+ layer: atlas.hasTranslucency ? 1 : 0,
17375
+ ambientOcclusion: model.ambientocclusion !== false
17371
17376
  };
17372
17377
  this.templates.add(template);
17373
17378
  return template;
@@ -17558,17 +17563,45 @@ var MineRenderWorld = class {
17558
17563
  this.light = /* @__PURE__ */ new Map();
17559
17564
  this.lightValidity = /* @__PURE__ */ new Map();
17560
17565
  this.highestSkySection = /* @__PURE__ */ new Map();
17566
+ this.ambientOcclusionPending = false;
17561
17567
  this.pendingCulling = /* @__PURE__ */ new Map();
17562
17568
  if (options.savedLighting && !options.sectionMeshing) throw new TypeError("savedLighting requires sectionMeshing");
17569
+ if (options.ambientOcclusion && !options.sectionMeshing) throw new TypeError("ambientOcclusion requires sectionMeshing");
17563
17570
  if (options.ambientLight !== void 0 && (!Number.isFinite(options.ambientLight) || options.ambientLight < 0 || options.ambientLight > 1)) {
17564
17571
  throw new RangeError("ambientLight must be a number from 0 to 1");
17565
17572
  }
17566
17573
  this.scene = scene;
17567
17574
  this.savedLighting = options.savedLighting ?? false;
17568
17575
  this.ambientLight = options.ambientLight ?? 0;
17576
+ this._ambientOcclusion = options.ambientOcclusion ?? false;
17569
17577
  if (options.sectionMeshing) this.sectionModels = new SectionModels(options.maxAtlasSize);
17570
17578
  if (options.renderEntities) this.entities = new WorldEntities(scene);
17571
17579
  }
17580
+ /**
17581
+ * Returns the nearest visible block geometry hit, respecting the raycaster's layers and near/far range.
17582
+ * Includes fluids and block entities; texture alpha does not change triangle intersections.
17583
+ */
17584
+ raycast(raycaster) {
17585
+ return Chunk.raycast(this._chunks.values(), raycaster);
17586
+ }
17587
+ /** Whether neighboring opaque cubes shade block-face corners. Defaults to false. */
17588
+ get ambientOcclusion() {
17589
+ return this._ambientOcclusion;
17590
+ }
17591
+ /** Rebuilds loaded blocks with or without corner shading. Block data and saved lighting are preserved. */
17592
+ async setAmbientOcclusion(enabled) {
17593
+ if (typeof enabled !== "boolean") throw new TypeError("Ambient occlusion must be a boolean");
17594
+ if (enabled && !this.sectionModels) throw new TypeError("ambientOcclusion requires sectionMeshing");
17595
+ if (enabled === this._ambientOcclusion && !this.ambientOcclusionPending) {
17596
+ await this.culling;
17597
+ return;
17598
+ }
17599
+ this._ambientOcclusion = enabled;
17600
+ this.ambientOcclusionPending = true;
17601
+ for (const chunk of this._chunks.values()) this.pendingCulling.set(chunk, "all");
17602
+ await this.updateCulling([]);
17603
+ this.ambientOcclusionPending = false;
17604
+ }
17572
17605
  getBlockAt(posOrX, y, z) {
17573
17606
  if (typeof posOrX == "number") {
17574
17607
  return this.getBlockAt(new Vector317(posOrX, y, z));
@@ -17892,7 +17925,7 @@ var MineRenderWorld = class {
17892
17925
  const index = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
17893
17926
  let pending = this.pendingCulling.get(section);
17894
17927
  if (pending === "all" || pending?.has(index)) continue;
17895
- if (Math.abs(x) + Math.abs(y) + Math.abs(z) > 1 && !section.isFluidIndex(index)) continue;
17928
+ if (Math.abs(x) + Math.abs(y) + Math.abs(z) > 1 && !this._ambientOcclusion && !section.isFluidIndex(index)) continue;
17896
17929
  if (!pending) this.pendingCulling.set(section, pending = /* @__PURE__ */ new Set());
17897
17930
  pending.add(index);
17898
17931
  }
@@ -17900,6 +17933,7 @@ var MineRenderWorld = class {
17900
17933
  }
17901
17934
  }
17902
17935
  return this.culling ?? (this.culling = Promise.resolve().then(async () => {
17936
+ let sliceStart = performance.now();
17903
17937
  try {
17904
17938
  while (this.pendingCulling.size) {
17905
17939
  const batch = [...this.pendingCulling];
@@ -17935,6 +17969,11 @@ var MineRenderWorld = class {
17935
17969
  }
17936
17970
  for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
17937
17971
  await chunk.refreshLight(indices === "all" ? void 0 : indices);
17972
+ chunk.refreshAmbientOcclusion(this._ambientOcclusion);
17973
+ if ((this._ambientOcclusion || this.ambientOcclusionPending) && performance.now() - sliceStart > 8) {
17974
+ await yieldToEventLoop();
17975
+ sliceStart = performance.now();
17976
+ }
17938
17977
  }
17939
17978
  await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
17940
17979
  }
@@ -18041,6 +18080,50 @@ import { BufferGeometry as BufferGeometry9, Color as Color12, DoubleSide as Doub
18041
18080
  // src/world/SectionGeometry.ts
18042
18081
  init_FluidQuads();
18043
18082
 
18083
+ // src/world/_light/AmbientOcclusion.ts
18084
+ var AXIS_STRIDES = [1, 9, 3];
18085
+ function sectionAmbientOcclusionMask(cells, center) {
18086
+ let mask = 0;
18087
+ for (let y = -1; y <= 1; y++) for (let z = -1; z <= 1; z++) for (let x = -1; x <= 1; x++) {
18088
+ if (Math.abs(x) + Math.abs(y) + Math.abs(z) < 2) continue;
18089
+ if (cells[center + y * 324 + z * 18 + x]) mask |= 1 << (y + 1) * 9 + (z + 1) * 3 + x + 1;
18090
+ }
18091
+ return mask;
18092
+ }
18093
+ function cornerBrightness(mask, center, u, v) {
18094
+ const sideU = mask >>> center + u & 1;
18095
+ const sideV = mask >>> center + v & 1;
18096
+ const corner = sideU && sideV ? 1 : mask >>> center + u + v & 1;
18097
+ return 1 - (sideU + sideV + corner) * 0.2;
18098
+ }
18099
+ function quadAmbientOcclusion(positions, normals, quad, mask, output) {
18100
+ output.fill(1);
18101
+ if (!mask) return;
18102
+ const start = quad * 12;
18103
+ let axis = 0;
18104
+ if (Math.abs(normals[start + 1]) > Math.abs(normals[start + axis])) axis = 1;
18105
+ if (Math.abs(normals[start + 2]) > Math.abs(normals[start + axis])) axis = 2;
18106
+ const uAxis = (axis + 1) % 3, vAxis = (axis + 2) % 3;
18107
+ 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;
18108
+ const sign = normals[start + axis] > 0 ? 1 : -1;
18109
+ for (let vertex = 0; vertex < 4; vertex++) {
18110
+ const offset = start + vertex * 3;
18111
+ 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;
18112
+ }
18113
+ const center = 13 + sign * AXIS_STRIDES[axis];
18114
+ const u = AXIS_STRIDES[uAxis], v = AXIS_STRIDES[vAxis];
18115
+ const lowLow = cornerBrightness(mask, center, -u, -v);
18116
+ const highLow = cornerBrightness(mask, center, u, -v);
18117
+ const lowHigh = cornerBrightness(mask, center, -u, v);
18118
+ const highHigh = cornerBrightness(mask, center, u, v);
18119
+ for (let vertex = 0; vertex < 4; vertex++) {
18120
+ const offset = start + vertex * 3;
18121
+ const a = Math.max(0, Math.min(1, (positions[offset + uAxis] + 8) / 16));
18122
+ const b = Math.max(0, Math.min(1, (positions[offset + vAxis] + 8) / 16));
18123
+ output[vertex] = (lowLow * (1 - a) + highLow * a) * (1 - b) + (lowHigh * (1 - a) + highHigh * a) * b;
18124
+ }
18125
+ }
18126
+
18044
18127
  // src/world/_light/FaceLight.ts
18045
18128
  function lightBrightness(level, ambient = 0) {
18046
18129
  if (level === 255) return 1;
@@ -18066,7 +18149,7 @@ var LIGHT_SAMPLE_OFFSETS = [0, 1, -1, 324, -324, 18, -18];
18066
18149
 
18067
18150
  // src/world/SectionGeometry.ts
18068
18151
  function buildSectionGeometry(input) {
18069
- const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight } = input;
18152
+ const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion } = input;
18070
18153
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
18071
18154
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
18072
18155
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
@@ -18077,6 +18160,10 @@ function buildSectionGeometry(input) {
18077
18160
  if (ambientLight !== void 0 && (!Number.isFinite(ambientLight) || ambientLight < 0 || ambientLight > 1)) {
18078
18161
  throw new RangeError("Section ambient light must be a number from 0 to 1");
18079
18162
  }
18163
+ if (ambientOcclusion && (ambientOcclusion.length !== 5832 || ambientOcclusion.some((value) => value > 1))) {
18164
+ throw new RangeError("Section ambient occlusion must cover an 18\xD718\xD718 neighborhood with zero or one per cell");
18165
+ }
18166
+ const cornerBrightness2 = ambientOcclusion ? new Float32Array(4) : void 0;
18080
18167
  for (const template of templateData) {
18081
18168
  const { quads } = template;
18082
18169
  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) {
@@ -18171,6 +18258,7 @@ function buildSectionGeometry(input) {
18171
18258
  const positions = new Float32Array(faces * 12), normals = new Float32Array(faces * 12);
18172
18259
  const uvs = new Float32Array(faces * 8), uvBounds = new Float32Array(faces * 16), colors = new Float32Array(faces * 12);
18173
18260
  const outputIndices = new Uint32Array(faces * 6);
18261
+ const blockIndices = new Uint16Array(faces);
18174
18262
  let vertex = 0, indexOffset = 0;
18175
18263
  for (const entry of page.entries) {
18176
18264
  const template = templateData[templates[entry]];
@@ -18182,8 +18270,11 @@ function buildSectionGeometry(input) {
18182
18270
  const offsetY = Math.floor(indices[entry] / 256) * 16;
18183
18271
  const offsetZ = Math.floor(indices[entry] / 16) % 16 * 16;
18184
18272
  const lightIndex = (offsetY / 16 + 1) * 324 + (offsetZ / 16 + 1) * 18 + offsetX / 16 + 1;
18273
+ const ambientMask = ambientOcclusion && template.ambientOcclusion !== false ? sectionAmbientOcclusionMask(ambientOcclusion, lightIndex) : 0;
18185
18274
  for (let quad = 0; quad < template.quads; quad++) {
18186
18275
  if (cullMasks[entry] & template.cullFaces[quad]) continue;
18276
+ blockIndices[indexOffset / 6] = indices[entry];
18277
+ if (ambientMask) quadAmbientOcclusion(template.positions, template.normals, quad, ambientMask, cornerBrightness2);
18187
18278
  const brightness = light ? lightBrightness(light[lightIndex + LIGHT_SAMPLE_OFFSETS[quadLightIndex(template.positions, template.normals, quad)]], ambientLight) : 1;
18188
18279
  const start = vertex;
18189
18280
  for (let corner = 0; corner < 4; corner++, vertex++) {
@@ -18200,9 +18291,10 @@ function buildSectionGeometry(input) {
18200
18291
  uvBounds[vertex * 4 + 1] = mapV(template.uvBounds[source * 4 + 1]);
18201
18292
  uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
18202
18293
  uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
18203
- colors[vertex * 3] = template.colors[source * 3] * brightness;
18204
- colors[vertex * 3 + 1] = template.colors[source * 3 + 1] * brightness;
18205
- colors[vertex * 3 + 2] = template.colors[source * 3 + 2] * brightness;
18294
+ const factor = brightness * (ambientMask ? cornerBrightness2[corner] : 1);
18295
+ colors[vertex * 3] = template.colors[source * 3] * factor;
18296
+ colors[vertex * 3 + 1] = template.colors[source * 3 + 1] * factor;
18297
+ colors[vertex * 3 + 2] = template.colors[source * 3 + 2] * factor;
18206
18298
  }
18207
18299
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
18208
18300
  }
@@ -18214,6 +18306,7 @@ function buildSectionGeometry(input) {
18214
18306
  const mapV = (v) => 1 - (placement.y + (1 - v) * atlas.height) / page.height;
18215
18307
  const lightIndex = (y + 1) * 324 + (z + 1) * 18 + x + 1;
18216
18308
  for (let quad = 0; quad < quads.sprites.length; quad++) {
18309
+ blockIndices[indexOffset / 6] = x + z * 16 + y * 256;
18217
18310
  const rect = quads.sprites[quad] === 0 ? data.still : data.flow;
18218
18311
  const brightness = light ? lightBrightness(fluidLightLevel(
18219
18312
  light[lightIndex],
@@ -18244,11 +18337,11 @@ function buildSectionGeometry(input) {
18244
18337
  indexOffset += 6;
18245
18338
  }
18246
18339
  }
18247
- return { layer: page.layer, width: page.width, height: page.height, placements: page.placements, positions, normals, uvs, uvBounds, colors, indices: outputIndices };
18340
+ return { layer: page.layer, width: page.width, height: page.height, placements: page.placements, positions, normals, uvs, uvBounds, colors, indices: outputIndices, blockIndices };
18248
18341
  });
18249
18342
  }
18250
18343
  function sectionGeometryTransferables(pages) {
18251
- return pages.flatMap((page) => [page.positions.buffer, page.normals.buffer, page.uvs.buffer, page.uvBounds.buffer, page.colors.buffer, page.indices.buffer]);
18344
+ 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]);
18252
18345
  }
18253
18346
 
18254
18347
  // src/world/SectionWorker.ts
@@ -18350,7 +18443,8 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
18350
18443
  indices: new Uint16Array(quads * 6),
18351
18444
  cullFaces: new Uint8Array(template.cullFaces),
18352
18445
  atlas: 0,
18353
- layer: template.layer
18446
+ layer: template.layer,
18447
+ ambientOcclusion: template.ambientOcclusion
18354
18448
  };
18355
18449
  const normal = template.geometry.getAttribute("normal");
18356
18450
  const uv = template.geometry.getAttribute("uv");
@@ -18405,6 +18499,12 @@ var SectionMesh = class _SectionMesh extends Group3 {
18405
18499
  constructor() {
18406
18500
  super(...arguments);
18407
18501
  this.ownedMeshes = [];
18502
+ this.blockIndices = /* @__PURE__ */ new Map();
18503
+ }
18504
+ /** Returns the owning section cell for a triangle hit on this mesh's merged geometry. */
18505
+ getBlockIndex(hit) {
18506
+ if (hit.faceIndex === void 0 || hit.faceIndex === null) return void 0;
18507
+ return this.blockIndices.get(hit.object)?.[Math.floor(hit.faceIndex / 2)];
18408
18508
  }
18409
18509
  /** Builds section-local meshes from visible quads. `maxAtlasSize` limits each atlas dimension in pixels. */
18410
18510
  static build(entries, maxAtlasSize = 2048, fluids, lighting) {
@@ -18447,6 +18547,7 @@ var SectionMesh = class _SectionMesh extends Group3 {
18447
18547
  const texture = material.uniforms?.map?.value ?? material.map;
18448
18548
  const mesh = new Mesh10(geometry, material);
18449
18549
  section.add(mesh);
18550
+ section.blockIndices.set(mesh, page.blockIndices);
18450
18551
  const unsubscribe = [];
18451
18552
  for (const { atlas: id, x, y } of page.placements) {
18452
18553
  const atlas = atlases[id];
@@ -18472,6 +18573,7 @@ var SectionMesh = class _SectionMesh extends Group3 {
18472
18573
  mesh.material.dispose();
18473
18574
  texture?.dispose();
18474
18575
  }
18576
+ this.blockIndices.clear();
18475
18577
  this.removeFromParent();
18476
18578
  super.dispose();
18477
18579
  }
@@ -18481,6 +18583,8 @@ var SectionMesh = class _SectionMesh extends Group3 {
18481
18583
  init_FluidGeometry();
18482
18584
  init_FluidQuads();
18483
18585
  init_CubeFace();
18586
+ init_InstanceReference();
18587
+ init_three();
18484
18588
  var WATER_TINT2 = new Color13(4159204).toArray();
18485
18589
  var Chunk = class _Chunk {
18486
18590
  constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0) {
@@ -18494,6 +18598,7 @@ var Chunk = class _Chunk {
18494
18598
  this.ambientLight = ambientLight;
18495
18599
  this.data = new ChunkData();
18496
18600
  this.renderedBlocks = /* @__PURE__ */ new Map();
18601
+ this.objectBlocks = /* @__PURE__ */ new Set();
18497
18602
  this.sectionBlocks = /* @__PURE__ */ new Map();
18498
18603
  this.hiddenBlocks = /* @__PURE__ */ new Set();
18499
18604
  this.fluidCells = new Uint8Array(4096);
@@ -18505,6 +18610,89 @@ var Chunk = class _Chunk {
18505
18610
  this.y = y;
18506
18611
  this.z = z;
18507
18612
  }
18613
+ /** Finds the nearest owned block hit across sections, testing each shared mesh once. */
18614
+ static raycast(chunks, raycaster) {
18615
+ const roots = /* @__PURE__ */ new Map();
18616
+ const targets = /* @__PURE__ */ new Map();
18617
+ const meshes = (root, scene) => {
18618
+ const cached2 = roots.get(root);
18619
+ if (cached2) return cached2;
18620
+ const result = [];
18621
+ roots.set(root, result);
18622
+ let attached = false;
18623
+ for (let parent = root; parent; parent = parent.parent) {
18624
+ if (!parent.visible) return result;
18625
+ if (parent === scene) attached = true;
18626
+ }
18627
+ if (!attached) return result;
18628
+ root.updateWorldMatrix(true, true);
18629
+ root.traverseVisible((object4) => {
18630
+ if (isMesh(object4) && raycaster.layers.test(object4.layers)) result.push(object4);
18631
+ });
18632
+ return result;
18633
+ };
18634
+ const add = (root, owner, reference) => {
18635
+ for (const mesh of meshes(root, owner.chunk.scene)) {
18636
+ if (reference && reference.instanceable.getInstanceReference(mesh, reference.index) !== reference) continue;
18637
+ let entry = targets.get(mesh);
18638
+ if (!entry) targets.set(mesh, entry = { owners: /* @__PURE__ */ new Map() });
18639
+ entry.owners.set(reference?.index, owner);
18640
+ }
18641
+ };
18642
+ for (const chunk of chunks) {
18643
+ for (const index of chunk.objectBlocks) {
18644
+ const block = chunk.renderedBlocks.get(index).object;
18645
+ if (!block || !block.visible || chunk.hiddenBlocks.has(index)) continue;
18646
+ const owner = { chunk, index };
18647
+ for (const model of block.models) {
18648
+ if (isInstanceReference(model)) {
18649
+ if (model.instanceable.isInstanceActive(model.index, model)) add(model.instanceable, owner, model);
18650
+ } else {
18651
+ add(model, owner);
18652
+ }
18653
+ }
18654
+ for (const entity of block.blockEntities) add(entity, owner);
18655
+ }
18656
+ if (chunk.sectionMesh) {
18657
+ for (const mesh of meshes(chunk.sectionMesh, chunk.scene)) {
18658
+ targets.set(mesh, { owners: /* @__PURE__ */ new Map(), section: { chunk, mesh: chunk.sectionMesh } });
18659
+ }
18660
+ }
18661
+ }
18662
+ for (const hit of raycaster.intersectObjects([...targets.keys()], false)) {
18663
+ if (!hit.face) continue;
18664
+ const mesh = hit.object;
18665
+ const material = Array.isArray(mesh.material) ? mesh.material[hit.face.materialIndex] : mesh.material;
18666
+ if (!material?.visible) continue;
18667
+ const entry = targets.get(mesh);
18668
+ let owner = entry.owners.get(hit.instanceId) ?? entry.owners.get(void 0);
18669
+ if (entry.section) {
18670
+ const index = entry.section.mesh.getBlockIndex(hit);
18671
+ if (index !== void 0) owner = { chunk: entry.section.chunk, index };
18672
+ }
18673
+ if (!owner || owner.chunk.hiddenBlocks.has(owner.index)) continue;
18674
+ const info = owner.chunk.renderedBlocks.get(owner.index);
18675
+ if (!info) continue;
18676
+ const matrix = mesh.matrixWorld.clone();
18677
+ if (isInstancedMesh(mesh) && hit.instanceId !== void 0) {
18678
+ const instance = new Matrix413();
18679
+ mesh.getMatrixAt(hit.instanceId, instance);
18680
+ matrix.multiply(instance);
18681
+ }
18682
+ return {
18683
+ position: new Vector318(
18684
+ owner.chunk.x * 16 + owner.index % 16,
18685
+ owner.chunk.y * 16 + Math.floor(owner.index / 256),
18686
+ owner.chunk.z * 16 + Math.floor(owner.index / 16) % 16
18687
+ ),
18688
+ block: info.block,
18689
+ point: hit.point.clone(),
18690
+ normal: hit.face.normal.clone().applyNormalMatrix(new Matrix33().getNormalMatrix(matrix)),
18691
+ distance: hit.distance
18692
+ };
18693
+ }
18694
+ return void 0;
18695
+ }
18508
18696
  getBlockAt(posOrX, y, z) {
18509
18697
  if (typeof posOrX == "number") {
18510
18698
  return this.getBlockAt(new Vector318(posOrX, y, z));
@@ -18625,6 +18813,7 @@ var Chunk = class _Chunk {
18625
18813
  if (this.fluidCells[index]) this.meshDirty = true;
18626
18814
  this.fluidCells[index] = 0;
18627
18815
  this.renderedBlocks.delete(index);
18816
+ this.objectBlocks.delete(index);
18628
18817
  const worldPos = new Vector318(
18629
18818
  this.x * 16 + index % 16,
18630
18819
  this.y * 16 + Math.floor(index / 256),
@@ -18667,6 +18856,7 @@ var Chunk = class _Chunk {
18667
18856
  this.renderedBlocks.set(index, { get block() {
18668
18857
  return readBlock();
18669
18858
  }, object: object4 });
18859
+ if (object4) this.objectBlocks.add(index);
18670
18860
  } catch (error) {
18671
18861
  this.data.assign(index, 0);
18672
18862
  this.fluidCells[index] = 0;
@@ -18685,6 +18875,7 @@ var Chunk = class _Chunk {
18685
18875
  this.fluidCells.fill(0);
18686
18876
  this.waterTints = void 0;
18687
18877
  this.renderedBlocks.clear();
18878
+ this.objectBlocks.clear();
18688
18879
  this.hiddenBlocks.clear();
18689
18880
  this.meshDirty = true;
18690
18881
  this.data.copyFrom(data);
@@ -18734,6 +18925,7 @@ var Chunk = class _Chunk {
18734
18925
  this.renderedBlocks.set(index, { get block() {
18735
18926
  return readBlock();
18736
18927
  }, object: void 0 });
18928
+ this.objectBlocks.delete(index);
18737
18929
  } catch (error) {
18738
18930
  this.data.assign(index, 0);
18739
18931
  this.fluidCells[index] = 0;
@@ -18759,10 +18951,12 @@ var Chunk = class _Chunk {
18759
18951
  this.z * 256 + Math.floor(index / 16) % 16 * 16
18760
18952
  ));
18761
18953
  this.renderedBlocks.get(index).object = object4;
18954
+ this.objectBlocks.add(index);
18762
18955
  } catch (error) {
18763
18956
  this.data.assign(index, 0);
18764
18957
  this.fluidCells[index] = 0;
18765
18958
  this.renderedBlocks.delete(index);
18959
+ this.objectBlocks.delete(index);
18766
18960
  object4?.removeFromScene();
18767
18961
  if (!failed) failure = error;
18768
18962
  failed = true;
@@ -18780,6 +18974,25 @@ var Chunk = class _Chunk {
18780
18974
  if (this.renderedBlocks.has(index)) this.meshDirty = true;
18781
18975
  }
18782
18976
  }
18977
+ /** Refreshes baked corner shading after edits or a world setting change. Disabled chunks keep no AO neighborhood. */
18978
+ refreshAmbientOcclusion(enabled) {
18979
+ if (!enabled && !this.ambientOcclusionCells) return;
18980
+ const cells = enabled ? this.buildAmbientOcclusionCells() : void 0;
18981
+ if (cells?.some((value, index) => value !== this.ambientOcclusionCells?.[index]) || !!cells !== !!this.ambientOcclusionCells) this.meshDirty = true;
18982
+ this.ambientOcclusionCells = cells;
18983
+ }
18984
+ buildAmbientOcclusionCells() {
18985
+ const cells = new Uint8Array(18 * 18 * 18);
18986
+ for (let y = -1; y <= 16; y++) {
18987
+ for (let z = -1; z <= 16; z++) {
18988
+ for (let x = -1; x <= 16; x++) {
18989
+ const inner = x >= 0 && x < 16 && y >= 0 && y < 16 && z >= 0 && z < 16;
18990
+ 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));
18991
+ }
18992
+ }
18993
+ }
18994
+ return cells;
18995
+ }
18783
18996
  biomePosition(index, blockState3, stored) {
18784
18997
  const position = this.positionAt(index), name = blockState3.key?.toNamespacedString();
18785
18998
  if (stored.properties?.half === "upper" && (name === "minecraft:tall_grass" || name === "minecraft:large_fern")) position[1]--;
@@ -18968,12 +19181,18 @@ var Chunk = class _Chunk {
18968
19181
  }
18969
19182
  }
18970
19183
  }
18971
- const next = entries.length || fluids ? await SectionMesh.buildAsync(
18972
- entries,
18973
- this.sectionModels.maxAtlasSize,
18974
- fluids,
18975
- light ? { light, ambientLight: this.ambientLight } : void 0
18976
- ) : void 0;
19184
+ let next;
19185
+ try {
19186
+ next = entries.length || fluids ? await SectionMesh.buildAsync(
19187
+ entries,
19188
+ this.sectionModels.maxAtlasSize,
19189
+ fluids,
19190
+ { light, ambientLight: this.ambientLight, ambientOcclusion: this.ambientOcclusionCells }
19191
+ ) : void 0;
19192
+ } catch (error) {
19193
+ if (generation === this.meshGeneration) this.meshDirty = true;
19194
+ throw error;
19195
+ }
18977
19196
  if (generation !== this.meshGeneration) {
18978
19197
  next?.dispose();
18979
19198
  return;
@@ -18998,9 +19217,11 @@ var Chunk = class _Chunk {
18998
19217
  this.sectionMesh = void 0;
18999
19218
  this.meshDirty = false;
19000
19219
  this.renderedBlocks.clear();
19220
+ this.objectBlocks.clear();
19001
19221
  this.hiddenBlocks.clear();
19002
19222
  this.fluidCells.fill(0);
19003
19223
  this.waterTints = void 0;
19224
+ this.ambientOcclusionCells = void 0;
19004
19225
  this.data.clear();
19005
19226
  this.biomes = void 0;
19006
19227
  if (notify) {