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.
@@ -12463,6 +12463,10 @@ var BlockObject = class _BlockObject extends SceneObject {
12463
12463
  get blockEntities() {
12464
12464
  return this._entities;
12465
12465
  }
12466
+ /** Model placements drawn for this block, including its fluid surface. */
12467
+ get models() {
12468
+ return this._models;
12469
+ }
12466
12470
  /** Sets visibility for this block's model instances and block entities. */
12467
12471
  setVisible(visible) {
12468
12472
  this.visible = visible;
@@ -17224,7 +17228,7 @@ var AnvilWorldSource = class {
17224
17228
  };
17225
17229
 
17226
17230
  // src/world/Chunk.ts
17227
- import { Color as Color13, Vector3 as Vector318 } from "three";
17231
+ import { Color as Color13, Matrix3 as Matrix33, Matrix4 as Matrix413, Vector3 as Vector318 } from "three";
17228
17232
  init_BlockStates();
17229
17233
  init_AssetKey();
17230
17234
 
@@ -17403,7 +17407,8 @@ var SectionModels = class {
17403
17407
  atlas,
17404
17408
  cullFaces: new Uint8Array(cullFaces),
17405
17409
  occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
17406
- layer: atlas.hasTranslucency ? 1 : 0
17410
+ layer: atlas.hasTranslucency ? 1 : 0,
17411
+ ambientOcclusion: model.ambientocclusion !== false
17407
17412
  };
17408
17413
  this.templates.add(template);
17409
17414
  return template;
@@ -17594,17 +17599,45 @@ var MineRenderWorld = class {
17594
17599
  this.light = /* @__PURE__ */ new Map();
17595
17600
  this.lightValidity = /* @__PURE__ */ new Map();
17596
17601
  this.highestSkySection = /* @__PURE__ */ new Map();
17602
+ this.ambientOcclusionPending = false;
17597
17603
  this.pendingCulling = /* @__PURE__ */ new Map();
17598
17604
  if (options.savedLighting && !options.sectionMeshing) throw new TypeError("savedLighting requires sectionMeshing");
17605
+ if (options.ambientOcclusion && !options.sectionMeshing) throw new TypeError("ambientOcclusion requires sectionMeshing");
17599
17606
  if (options.ambientLight !== void 0 && (!Number.isFinite(options.ambientLight) || options.ambientLight < 0 || options.ambientLight > 1)) {
17600
17607
  throw new RangeError("ambientLight must be a number from 0 to 1");
17601
17608
  }
17602
17609
  this.scene = scene;
17603
17610
  this.savedLighting = options.savedLighting ?? false;
17604
17611
  this.ambientLight = options.ambientLight ?? 0;
17612
+ this._ambientOcclusion = options.ambientOcclusion ?? false;
17605
17613
  if (options.sectionMeshing) this.sectionModels = new SectionModels(options.maxAtlasSize);
17606
17614
  if (options.renderEntities) this.entities = new WorldEntities(scene);
17607
17615
  }
17616
+ /**
17617
+ * Returns the nearest visible block geometry hit, respecting the raycaster's layers and near/far range.
17618
+ * Includes fluids and block entities; texture alpha does not change triangle intersections.
17619
+ */
17620
+ raycast(raycaster) {
17621
+ return Chunk.raycast(this._chunks.values(), raycaster);
17622
+ }
17623
+ /** Whether neighboring opaque cubes shade block-face corners. Defaults to false. */
17624
+ get ambientOcclusion() {
17625
+ return this._ambientOcclusion;
17626
+ }
17627
+ /** Rebuilds loaded blocks with or without corner shading. Block data and saved lighting are preserved. */
17628
+ async setAmbientOcclusion(enabled) {
17629
+ if (typeof enabled !== "boolean") throw new TypeError("Ambient occlusion must be a boolean");
17630
+ if (enabled && !this.sectionModels) throw new TypeError("ambientOcclusion requires sectionMeshing");
17631
+ if (enabled === this._ambientOcclusion && !this.ambientOcclusionPending) {
17632
+ await this.culling;
17633
+ return;
17634
+ }
17635
+ this._ambientOcclusion = enabled;
17636
+ this.ambientOcclusionPending = true;
17637
+ for (const chunk of this._chunks.values()) this.pendingCulling.set(chunk, "all");
17638
+ await this.updateCulling([]);
17639
+ this.ambientOcclusionPending = false;
17640
+ }
17608
17641
  getBlockAt(posOrX, y, z) {
17609
17642
  if (typeof posOrX == "number") {
17610
17643
  return this.getBlockAt(new Vector317(posOrX, y, z));
@@ -17928,7 +17961,7 @@ var MineRenderWorld = class {
17928
17961
  const index = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
17929
17962
  let pending = this.pendingCulling.get(section);
17930
17963
  if (pending === "all" || pending?.has(index)) continue;
17931
- if (Math.abs(x) + Math.abs(y) + Math.abs(z) > 1 && !section.isFluidIndex(index)) continue;
17964
+ if (Math.abs(x) + Math.abs(y) + Math.abs(z) > 1 && !this._ambientOcclusion && !section.isFluidIndex(index)) continue;
17932
17965
  if (!pending) this.pendingCulling.set(section, pending = /* @__PURE__ */ new Set());
17933
17966
  pending.add(index);
17934
17967
  }
@@ -17936,6 +17969,7 @@ var MineRenderWorld = class {
17936
17969
  }
17937
17970
  }
17938
17971
  return this.culling ??= Promise.resolve().then(async () => {
17972
+ let sliceStart = performance.now();
17939
17973
  try {
17940
17974
  while (this.pendingCulling.size) {
17941
17975
  const batch = [...this.pendingCulling];
@@ -17971,6 +18005,11 @@ var MineRenderWorld = class {
17971
18005
  }
17972
18006
  for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
17973
18007
  await chunk.refreshLight(indices === "all" ? void 0 : indices);
18008
+ chunk.refreshAmbientOcclusion(this._ambientOcclusion);
18009
+ if ((this._ambientOcclusion || this.ambientOcclusionPending) && performance.now() - sliceStart > 8) {
18010
+ await yieldToEventLoop();
18011
+ sliceStart = performance.now();
18012
+ }
17974
18013
  }
17975
18014
  await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
17976
18015
  }
@@ -18077,6 +18116,50 @@ import { BufferGeometry as BufferGeometry9, Color as Color12, DoubleSide as Doub
18077
18116
  // src/world/SectionGeometry.ts
18078
18117
  init_FluidQuads();
18079
18118
 
18119
+ // src/world/_light/AmbientOcclusion.ts
18120
+ var AXIS_STRIDES = [1, 9, 3];
18121
+ function sectionAmbientOcclusionMask(cells, center) {
18122
+ let mask = 0;
18123
+ for (let y = -1; y <= 1; y++) for (let z = -1; z <= 1; z++) for (let x = -1; x <= 1; x++) {
18124
+ if (Math.abs(x) + Math.abs(y) + Math.abs(z) < 2) continue;
18125
+ if (cells[center + y * 324 + z * 18 + x]) mask |= 1 << (y + 1) * 9 + (z + 1) * 3 + x + 1;
18126
+ }
18127
+ return mask;
18128
+ }
18129
+ function cornerBrightness(mask, center, u, v) {
18130
+ const sideU = mask >>> center + u & 1;
18131
+ const sideV = mask >>> center + v & 1;
18132
+ const corner = sideU && sideV ? 1 : mask >>> center + u + v & 1;
18133
+ return 1 - (sideU + sideV + corner) * 0.2;
18134
+ }
18135
+ function quadAmbientOcclusion(positions, normals, quad, mask, output) {
18136
+ output.fill(1);
18137
+ if (!mask) return;
18138
+ const start = quad * 12;
18139
+ let axis = 0;
18140
+ if (Math.abs(normals[start + 1]) > Math.abs(normals[start + axis])) axis = 1;
18141
+ if (Math.abs(normals[start + 2]) > Math.abs(normals[start + axis])) axis = 2;
18142
+ const uAxis = (axis + 1) % 3, vAxis = (axis + 2) % 3;
18143
+ 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;
18144
+ const sign = normals[start + axis] > 0 ? 1 : -1;
18145
+ for (let vertex = 0; vertex < 4; vertex++) {
18146
+ const offset = start + vertex * 3;
18147
+ 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;
18148
+ }
18149
+ const center = 13 + sign * AXIS_STRIDES[axis];
18150
+ const u = AXIS_STRIDES[uAxis], v = AXIS_STRIDES[vAxis];
18151
+ const lowLow = cornerBrightness(mask, center, -u, -v);
18152
+ const highLow = cornerBrightness(mask, center, u, -v);
18153
+ const lowHigh = cornerBrightness(mask, center, -u, v);
18154
+ const highHigh = cornerBrightness(mask, center, u, v);
18155
+ for (let vertex = 0; vertex < 4; vertex++) {
18156
+ const offset = start + vertex * 3;
18157
+ const a = Math.max(0, Math.min(1, (positions[offset + uAxis] + 8) / 16));
18158
+ const b = Math.max(0, Math.min(1, (positions[offset + vAxis] + 8) / 16));
18159
+ output[vertex] = (lowLow * (1 - a) + highLow * a) * (1 - b) + (lowHigh * (1 - a) + highHigh * a) * b;
18160
+ }
18161
+ }
18162
+
18080
18163
  // src/world/_light/FaceLight.ts
18081
18164
  function lightBrightness(level, ambient = 0) {
18082
18165
  if (level === 255) return 1;
@@ -18102,7 +18185,7 @@ var LIGHT_SAMPLE_OFFSETS = [0, 1, -1, 324, -324, 18, -18];
18102
18185
 
18103
18186
  // src/world/SectionGeometry.ts
18104
18187
  function buildSectionGeometry(input) {
18105
- const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight } = input;
18188
+ const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion } = input;
18106
18189
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
18107
18190
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
18108
18191
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
@@ -18113,6 +18196,10 @@ function buildSectionGeometry(input) {
18113
18196
  if (ambientLight !== void 0 && (!Number.isFinite(ambientLight) || ambientLight < 0 || ambientLight > 1)) {
18114
18197
  throw new RangeError("Section ambient light must be a number from 0 to 1");
18115
18198
  }
18199
+ if (ambientOcclusion && (ambientOcclusion.length !== 5832 || ambientOcclusion.some((value) => value > 1))) {
18200
+ throw new RangeError("Section ambient occlusion must cover an 18\xD718\xD718 neighborhood with zero or one per cell");
18201
+ }
18202
+ const cornerBrightness2 = ambientOcclusion ? new Float32Array(4) : void 0;
18116
18203
  for (const template of templateData) {
18117
18204
  const { quads } = template;
18118
18205
  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) {
@@ -18207,6 +18294,7 @@ function buildSectionGeometry(input) {
18207
18294
  const positions = new Float32Array(faces * 12), normals = new Float32Array(faces * 12);
18208
18295
  const uvs = new Float32Array(faces * 8), uvBounds = new Float32Array(faces * 16), colors = new Float32Array(faces * 12);
18209
18296
  const outputIndices = new Uint32Array(faces * 6);
18297
+ const blockIndices = new Uint16Array(faces);
18210
18298
  let vertex = 0, indexOffset = 0;
18211
18299
  for (const entry of page.entries) {
18212
18300
  const template = templateData[templates[entry]];
@@ -18218,8 +18306,11 @@ function buildSectionGeometry(input) {
18218
18306
  const offsetY = Math.floor(indices[entry] / 256) * 16;
18219
18307
  const offsetZ = Math.floor(indices[entry] / 16) % 16 * 16;
18220
18308
  const lightIndex = (offsetY / 16 + 1) * 324 + (offsetZ / 16 + 1) * 18 + offsetX / 16 + 1;
18309
+ const ambientMask = ambientOcclusion && template.ambientOcclusion !== false ? sectionAmbientOcclusionMask(ambientOcclusion, lightIndex) : 0;
18221
18310
  for (let quad = 0; quad < template.quads; quad++) {
18222
18311
  if (cullMasks[entry] & template.cullFaces[quad]) continue;
18312
+ blockIndices[indexOffset / 6] = indices[entry];
18313
+ if (ambientMask) quadAmbientOcclusion(template.positions, template.normals, quad, ambientMask, cornerBrightness2);
18223
18314
  const brightness = light ? lightBrightness(light[lightIndex + LIGHT_SAMPLE_OFFSETS[quadLightIndex(template.positions, template.normals, quad)]], ambientLight) : 1;
18224
18315
  const start = vertex;
18225
18316
  for (let corner = 0; corner < 4; corner++, vertex++) {
@@ -18236,9 +18327,10 @@ function buildSectionGeometry(input) {
18236
18327
  uvBounds[vertex * 4 + 1] = mapV(template.uvBounds[source * 4 + 1]);
18237
18328
  uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
18238
18329
  uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
18239
- colors[vertex * 3] = template.colors[source * 3] * brightness;
18240
- colors[vertex * 3 + 1] = template.colors[source * 3 + 1] * brightness;
18241
- colors[vertex * 3 + 2] = template.colors[source * 3 + 2] * brightness;
18330
+ const factor = brightness * (ambientMask ? cornerBrightness2[corner] : 1);
18331
+ colors[vertex * 3] = template.colors[source * 3] * factor;
18332
+ colors[vertex * 3 + 1] = template.colors[source * 3 + 1] * factor;
18333
+ colors[vertex * 3 + 2] = template.colors[source * 3 + 2] * factor;
18242
18334
  }
18243
18335
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
18244
18336
  }
@@ -18250,6 +18342,7 @@ function buildSectionGeometry(input) {
18250
18342
  const mapV = (v) => 1 - (placement.y + (1 - v) * atlas.height) / page.height;
18251
18343
  const lightIndex = (y + 1) * 324 + (z + 1) * 18 + x + 1;
18252
18344
  for (let quad = 0; quad < quads.sprites.length; quad++) {
18345
+ blockIndices[indexOffset / 6] = x + z * 16 + y * 256;
18253
18346
  const rect = quads.sprites[quad] === 0 ? data.still : data.flow;
18254
18347
  const brightness = light ? lightBrightness(fluidLightLevel(
18255
18348
  light[lightIndex],
@@ -18280,11 +18373,11 @@ function buildSectionGeometry(input) {
18280
18373
  indexOffset += 6;
18281
18374
  }
18282
18375
  }
18283
- return { layer: page.layer, width: page.width, height: page.height, placements: page.placements, positions, normals, uvs, uvBounds, colors, indices: outputIndices };
18376
+ return { layer: page.layer, width: page.width, height: page.height, placements: page.placements, positions, normals, uvs, uvBounds, colors, indices: outputIndices, blockIndices };
18284
18377
  });
18285
18378
  }
18286
18379
  function sectionGeometryTransferables(pages) {
18287
- return pages.flatMap((page) => [page.positions.buffer, page.normals.buffer, page.uvs.buffer, page.uvBounds.buffer, page.colors.buffer, page.indices.buffer]);
18380
+ 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]);
18288
18381
  }
18289
18382
 
18290
18383
  // src/world/SectionWorker.ts
@@ -18387,7 +18480,8 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
18387
18480
  indices: new Uint16Array(quads * 6),
18388
18481
  cullFaces: new Uint8Array(template.cullFaces),
18389
18482
  atlas: 0,
18390
- layer: template.layer
18483
+ layer: template.layer,
18484
+ ambientOcclusion: template.ambientOcclusion
18391
18485
  };
18392
18486
  const normal = template.geometry.getAttribute("normal");
18393
18487
  const uv = template.geometry.getAttribute("uv");
@@ -18442,6 +18536,12 @@ var SectionMesh = class _SectionMesh extends Group3 {
18442
18536
  constructor() {
18443
18537
  super(...arguments);
18444
18538
  this.ownedMeshes = [];
18539
+ this.blockIndices = /* @__PURE__ */ new Map();
18540
+ }
18541
+ /** Returns the owning section cell for a triangle hit on this mesh's merged geometry. */
18542
+ getBlockIndex(hit) {
18543
+ if (hit.faceIndex === void 0 || hit.faceIndex === null) return void 0;
18544
+ return this.blockIndices.get(hit.object)?.[Math.floor(hit.faceIndex / 2)];
18445
18545
  }
18446
18546
  /** Builds section-local meshes from visible quads. `maxAtlasSize` limits each atlas dimension in pixels. */
18447
18547
  static build(entries, maxAtlasSize = 2048, fluids, lighting) {
@@ -18484,6 +18584,7 @@ var SectionMesh = class _SectionMesh extends Group3 {
18484
18584
  const texture = material.uniforms?.map?.value ?? material.map;
18485
18585
  const mesh = new Mesh10(geometry, material);
18486
18586
  section.add(mesh);
18587
+ section.blockIndices.set(mesh, page.blockIndices);
18487
18588
  const unsubscribe = [];
18488
18589
  for (const { atlas: id, x, y } of page.placements) {
18489
18590
  const atlas = atlases[id];
@@ -18509,6 +18610,7 @@ var SectionMesh = class _SectionMesh extends Group3 {
18509
18610
  mesh.material.dispose();
18510
18611
  texture?.dispose();
18511
18612
  }
18613
+ this.blockIndices.clear();
18512
18614
  this.removeFromParent();
18513
18615
  super.dispose();
18514
18616
  }
@@ -18518,6 +18620,8 @@ var SectionMesh = class _SectionMesh extends Group3 {
18518
18620
  init_FluidGeometry();
18519
18621
  init_FluidQuads();
18520
18622
  init_CubeFace();
18623
+ init_InstanceReference();
18624
+ init_three();
18521
18625
  var WATER_TINT2 = new Color13(4159204).toArray();
18522
18626
  var Chunk = class _Chunk {
18523
18627
  constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0) {
@@ -18531,6 +18635,7 @@ var Chunk = class _Chunk {
18531
18635
  this.ambientLight = ambientLight;
18532
18636
  this.data = new ChunkData();
18533
18637
  this.renderedBlocks = /* @__PURE__ */ new Map();
18638
+ this.objectBlocks = /* @__PURE__ */ new Set();
18534
18639
  this.sectionBlocks = /* @__PURE__ */ new Map();
18535
18640
  this.hiddenBlocks = /* @__PURE__ */ new Set();
18536
18641
  this.fluidCells = new Uint8Array(4096);
@@ -18542,6 +18647,89 @@ var Chunk = class _Chunk {
18542
18647
  this.y = y;
18543
18648
  this.z = z;
18544
18649
  }
18650
+ /** Finds the nearest owned block hit across sections, testing each shared mesh once. */
18651
+ static raycast(chunks, raycaster) {
18652
+ const roots = /* @__PURE__ */ new Map();
18653
+ const targets = /* @__PURE__ */ new Map();
18654
+ const meshes = (root, scene) => {
18655
+ const cached2 = roots.get(root);
18656
+ if (cached2) return cached2;
18657
+ const result = [];
18658
+ roots.set(root, result);
18659
+ let attached = false;
18660
+ for (let parent = root; parent; parent = parent.parent) {
18661
+ if (!parent.visible) return result;
18662
+ if (parent === scene) attached = true;
18663
+ }
18664
+ if (!attached) return result;
18665
+ root.updateWorldMatrix(true, true);
18666
+ root.traverseVisible((object4) => {
18667
+ if (isMesh(object4) && raycaster.layers.test(object4.layers)) result.push(object4);
18668
+ });
18669
+ return result;
18670
+ };
18671
+ const add = (root, owner, reference) => {
18672
+ for (const mesh of meshes(root, owner.chunk.scene)) {
18673
+ if (reference && reference.instanceable.getInstanceReference(mesh, reference.index) !== reference) continue;
18674
+ let entry = targets.get(mesh);
18675
+ if (!entry) targets.set(mesh, entry = { owners: /* @__PURE__ */ new Map() });
18676
+ entry.owners.set(reference?.index, owner);
18677
+ }
18678
+ };
18679
+ for (const chunk of chunks) {
18680
+ for (const index of chunk.objectBlocks) {
18681
+ const block = chunk.renderedBlocks.get(index).object;
18682
+ if (!block || !block.visible || chunk.hiddenBlocks.has(index)) continue;
18683
+ const owner = { chunk, index };
18684
+ for (const model of block.models) {
18685
+ if (isInstanceReference(model)) {
18686
+ if (model.instanceable.isInstanceActive(model.index, model)) add(model.instanceable, owner, model);
18687
+ } else {
18688
+ add(model, owner);
18689
+ }
18690
+ }
18691
+ for (const entity of block.blockEntities) add(entity, owner);
18692
+ }
18693
+ if (chunk.sectionMesh) {
18694
+ for (const mesh of meshes(chunk.sectionMesh, chunk.scene)) {
18695
+ targets.set(mesh, { owners: /* @__PURE__ */ new Map(), section: { chunk, mesh: chunk.sectionMesh } });
18696
+ }
18697
+ }
18698
+ }
18699
+ for (const hit of raycaster.intersectObjects([...targets.keys()], false)) {
18700
+ if (!hit.face) continue;
18701
+ const mesh = hit.object;
18702
+ const material = Array.isArray(mesh.material) ? mesh.material[hit.face.materialIndex] : mesh.material;
18703
+ if (!material?.visible) continue;
18704
+ const entry = targets.get(mesh);
18705
+ let owner = entry.owners.get(hit.instanceId) ?? entry.owners.get(void 0);
18706
+ if (entry.section) {
18707
+ const index = entry.section.mesh.getBlockIndex(hit);
18708
+ if (index !== void 0) owner = { chunk: entry.section.chunk, index };
18709
+ }
18710
+ if (!owner || owner.chunk.hiddenBlocks.has(owner.index)) continue;
18711
+ const info = owner.chunk.renderedBlocks.get(owner.index);
18712
+ if (!info) continue;
18713
+ const matrix = mesh.matrixWorld.clone();
18714
+ if (isInstancedMesh(mesh) && hit.instanceId !== void 0) {
18715
+ const instance = new Matrix413();
18716
+ mesh.getMatrixAt(hit.instanceId, instance);
18717
+ matrix.multiply(instance);
18718
+ }
18719
+ return {
18720
+ position: new Vector318(
18721
+ owner.chunk.x * 16 + owner.index % 16,
18722
+ owner.chunk.y * 16 + Math.floor(owner.index / 256),
18723
+ owner.chunk.z * 16 + Math.floor(owner.index / 16) % 16
18724
+ ),
18725
+ block: info.block,
18726
+ point: hit.point.clone(),
18727
+ normal: hit.face.normal.clone().applyNormalMatrix(new Matrix33().getNormalMatrix(matrix)),
18728
+ distance: hit.distance
18729
+ };
18730
+ }
18731
+ return void 0;
18732
+ }
18545
18733
  getBlockAt(posOrX, y, z) {
18546
18734
  if (typeof posOrX == "number") {
18547
18735
  return this.getBlockAt(new Vector318(posOrX, y, z));
@@ -18662,6 +18850,7 @@ var Chunk = class _Chunk {
18662
18850
  if (this.fluidCells[index]) this.meshDirty = true;
18663
18851
  this.fluidCells[index] = 0;
18664
18852
  this.renderedBlocks.delete(index);
18853
+ this.objectBlocks.delete(index);
18665
18854
  const worldPos = new Vector318(
18666
18855
  this.x * 16 + index % 16,
18667
18856
  this.y * 16 + Math.floor(index / 256),
@@ -18704,6 +18893,7 @@ var Chunk = class _Chunk {
18704
18893
  this.renderedBlocks.set(index, { get block() {
18705
18894
  return readBlock();
18706
18895
  }, object: object4 });
18896
+ if (object4) this.objectBlocks.add(index);
18707
18897
  } catch (error) {
18708
18898
  this.data.assign(index, 0);
18709
18899
  this.fluidCells[index] = 0;
@@ -18722,6 +18912,7 @@ var Chunk = class _Chunk {
18722
18912
  this.fluidCells.fill(0);
18723
18913
  this.waterTints = void 0;
18724
18914
  this.renderedBlocks.clear();
18915
+ this.objectBlocks.clear();
18725
18916
  this.hiddenBlocks.clear();
18726
18917
  this.meshDirty = true;
18727
18918
  this.data.copyFrom(data);
@@ -18771,6 +18962,7 @@ var Chunk = class _Chunk {
18771
18962
  this.renderedBlocks.set(index, { get block() {
18772
18963
  return readBlock();
18773
18964
  }, object: void 0 });
18965
+ this.objectBlocks.delete(index);
18774
18966
  } catch (error) {
18775
18967
  this.data.assign(index, 0);
18776
18968
  this.fluidCells[index] = 0;
@@ -18796,10 +18988,12 @@ var Chunk = class _Chunk {
18796
18988
  this.z * 256 + Math.floor(index / 16) % 16 * 16
18797
18989
  ));
18798
18990
  this.renderedBlocks.get(index).object = object4;
18991
+ this.objectBlocks.add(index);
18799
18992
  } catch (error) {
18800
18993
  this.data.assign(index, 0);
18801
18994
  this.fluidCells[index] = 0;
18802
18995
  this.renderedBlocks.delete(index);
18996
+ this.objectBlocks.delete(index);
18803
18997
  object4?.removeFromScene();
18804
18998
  if (!failed) failure = error;
18805
18999
  failed = true;
@@ -18817,6 +19011,25 @@ var Chunk = class _Chunk {
18817
19011
  if (this.renderedBlocks.has(index)) this.meshDirty = true;
18818
19012
  }
18819
19013
  }
19014
+ /** Refreshes baked corner shading after edits or a world setting change. Disabled chunks keep no AO neighborhood. */
19015
+ refreshAmbientOcclusion(enabled) {
19016
+ if (!enabled && !this.ambientOcclusionCells) return;
19017
+ const cells = enabled ? this.buildAmbientOcclusionCells() : void 0;
19018
+ if (cells?.some((value, index) => value !== this.ambientOcclusionCells?.[index]) || !!cells !== !!this.ambientOcclusionCells) this.meshDirty = true;
19019
+ this.ambientOcclusionCells = cells;
19020
+ }
19021
+ buildAmbientOcclusionCells() {
19022
+ const cells = new Uint8Array(18 * 18 * 18);
19023
+ for (let y = -1; y <= 16; y++) {
19024
+ for (let z = -1; z <= 16; z++) {
19025
+ for (let x = -1; x <= 16; x++) {
19026
+ const inner = x >= 0 && x < 16 && y >= 0 && y < 16 && z >= 0 && z < 16;
19027
+ 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));
19028
+ }
19029
+ }
19030
+ }
19031
+ return cells;
19032
+ }
18820
19033
  biomePosition(index, blockState3, stored) {
18821
19034
  const position = this.positionAt(index), name = blockState3.key?.toNamespacedString();
18822
19035
  if (stored.properties?.half === "upper" && (name === "minecraft:tall_grass" || name === "minecraft:large_fern")) position[1]--;
@@ -19005,12 +19218,18 @@ var Chunk = class _Chunk {
19005
19218
  }
19006
19219
  }
19007
19220
  }
19008
- const next = entries.length || fluids ? await SectionMesh.buildAsync(
19009
- entries,
19010
- this.sectionModels.maxAtlasSize,
19011
- fluids,
19012
- light ? { light, ambientLight: this.ambientLight } : void 0
19013
- ) : void 0;
19221
+ let next;
19222
+ try {
19223
+ next = entries.length || fluids ? await SectionMesh.buildAsync(
19224
+ entries,
19225
+ this.sectionModels.maxAtlasSize,
19226
+ fluids,
19227
+ { light, ambientLight: this.ambientLight, ambientOcclusion: this.ambientOcclusionCells }
19228
+ ) : void 0;
19229
+ } catch (error) {
19230
+ if (generation === this.meshGeneration) this.meshDirty = true;
19231
+ throw error;
19232
+ }
19014
19233
  if (generation !== this.meshGeneration) {
19015
19234
  next?.dispose();
19016
19235
  return;
@@ -19035,9 +19254,11 @@ var Chunk = class _Chunk {
19035
19254
  this.sectionMesh = void 0;
19036
19255
  this.meshDirty = false;
19037
19256
  this.renderedBlocks.clear();
19257
+ this.objectBlocks.clear();
19038
19258
  this.hiddenBlocks.clear();
19039
19259
  this.fluidCells.fill(0);
19040
19260
  this.waterTints = void 0;
19261
+ this.ambientOcclusionCells = void 0;
19041
19262
  this.data.clear();
19042
19263
  this.biomes = void 0;
19043
19264
  if (notify) {