minerender 2.1.0-alpha.120 → 2.1.0-alpha.121

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.
@@ -17403,7 +17403,8 @@ var SectionModels = class {
17403
17403
  atlas,
17404
17404
  cullFaces: new Uint8Array(cullFaces),
17405
17405
  occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
17406
- layer: atlas.hasTranslucency ? 1 : 0
17406
+ layer: atlas.hasTranslucency ? 1 : 0,
17407
+ ambientOcclusion: model.ambientocclusion !== false
17407
17408
  };
17408
17409
  this.templates.add(template);
17409
17410
  return template;
@@ -17594,17 +17595,38 @@ var MineRenderWorld = class {
17594
17595
  this.light = /* @__PURE__ */ new Map();
17595
17596
  this.lightValidity = /* @__PURE__ */ new Map();
17596
17597
  this.highestSkySection = /* @__PURE__ */ new Map();
17598
+ this.ambientOcclusionPending = false;
17597
17599
  this.pendingCulling = /* @__PURE__ */ new Map();
17598
17600
  if (options.savedLighting && !options.sectionMeshing) throw new TypeError("savedLighting requires sectionMeshing");
17601
+ if (options.ambientOcclusion && !options.sectionMeshing) throw new TypeError("ambientOcclusion requires sectionMeshing");
17599
17602
  if (options.ambientLight !== void 0 && (!Number.isFinite(options.ambientLight) || options.ambientLight < 0 || options.ambientLight > 1)) {
17600
17603
  throw new RangeError("ambientLight must be a number from 0 to 1");
17601
17604
  }
17602
17605
  this.scene = scene;
17603
17606
  this.savedLighting = options.savedLighting ?? false;
17604
17607
  this.ambientLight = options.ambientLight ?? 0;
17608
+ this._ambientOcclusion = options.ambientOcclusion ?? false;
17605
17609
  if (options.sectionMeshing) this.sectionModels = new SectionModels(options.maxAtlasSize);
17606
17610
  if (options.renderEntities) this.entities = new WorldEntities(scene);
17607
17611
  }
17612
+ /** Whether neighboring opaque cubes shade block-face corners. Defaults to false. */
17613
+ get ambientOcclusion() {
17614
+ return this._ambientOcclusion;
17615
+ }
17616
+ /** Rebuilds loaded blocks with or without corner shading. Block data and saved lighting are preserved. */
17617
+ async setAmbientOcclusion(enabled) {
17618
+ if (typeof enabled !== "boolean") throw new TypeError("Ambient occlusion must be a boolean");
17619
+ if (enabled && !this.sectionModels) throw new TypeError("ambientOcclusion requires sectionMeshing");
17620
+ if (enabled === this._ambientOcclusion && !this.ambientOcclusionPending) {
17621
+ await this.culling;
17622
+ return;
17623
+ }
17624
+ this._ambientOcclusion = enabled;
17625
+ this.ambientOcclusionPending = true;
17626
+ for (const chunk of this._chunks.values()) this.pendingCulling.set(chunk, "all");
17627
+ await this.updateCulling([]);
17628
+ this.ambientOcclusionPending = false;
17629
+ }
17608
17630
  getBlockAt(posOrX, y, z) {
17609
17631
  if (typeof posOrX == "number") {
17610
17632
  return this.getBlockAt(new Vector317(posOrX, y, z));
@@ -17928,7 +17950,7 @@ var MineRenderWorld = class {
17928
17950
  const index = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
17929
17951
  let pending = this.pendingCulling.get(section);
17930
17952
  if (pending === "all" || pending?.has(index)) continue;
17931
- if (Math.abs(x) + Math.abs(y) + Math.abs(z) > 1 && !section.isFluidIndex(index)) continue;
17953
+ if (Math.abs(x) + Math.abs(y) + Math.abs(z) > 1 && !this._ambientOcclusion && !section.isFluidIndex(index)) continue;
17932
17954
  if (!pending) this.pendingCulling.set(section, pending = /* @__PURE__ */ new Set());
17933
17955
  pending.add(index);
17934
17956
  }
@@ -17936,6 +17958,7 @@ var MineRenderWorld = class {
17936
17958
  }
17937
17959
  }
17938
17960
  return this.culling ??= Promise.resolve().then(async () => {
17961
+ let sliceStart = performance.now();
17939
17962
  try {
17940
17963
  while (this.pendingCulling.size) {
17941
17964
  const batch = [...this.pendingCulling];
@@ -17971,6 +17994,11 @@ var MineRenderWorld = class {
17971
17994
  }
17972
17995
  for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
17973
17996
  await chunk.refreshLight(indices === "all" ? void 0 : indices);
17997
+ chunk.refreshAmbientOcclusion(this._ambientOcclusion);
17998
+ if ((this._ambientOcclusion || this.ambientOcclusionPending) && performance.now() - sliceStart > 8) {
17999
+ await yieldToEventLoop();
18000
+ sliceStart = performance.now();
18001
+ }
17974
18002
  }
17975
18003
  await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
17976
18004
  }
@@ -18077,6 +18105,50 @@ import { BufferGeometry as BufferGeometry9, Color as Color12, DoubleSide as Doub
18077
18105
  // src/world/SectionGeometry.ts
18078
18106
  init_FluidQuads();
18079
18107
 
18108
+ // src/world/_light/AmbientOcclusion.ts
18109
+ var AXIS_STRIDES = [1, 9, 3];
18110
+ function sectionAmbientOcclusionMask(cells, center) {
18111
+ let mask = 0;
18112
+ for (let y = -1; y <= 1; y++) for (let z = -1; z <= 1; z++) for (let x = -1; x <= 1; x++) {
18113
+ if (Math.abs(x) + Math.abs(y) + Math.abs(z) < 2) continue;
18114
+ if (cells[center + y * 324 + z * 18 + x]) mask |= 1 << (y + 1) * 9 + (z + 1) * 3 + x + 1;
18115
+ }
18116
+ return mask;
18117
+ }
18118
+ function cornerBrightness(mask, center, u, v) {
18119
+ const sideU = mask >>> center + u & 1;
18120
+ const sideV = mask >>> center + v & 1;
18121
+ const corner = sideU && sideV ? 1 : mask >>> center + u + v & 1;
18122
+ return 1 - (sideU + sideV + corner) * 0.2;
18123
+ }
18124
+ function quadAmbientOcclusion(positions, normals, quad, mask, output) {
18125
+ output.fill(1);
18126
+ if (!mask) return;
18127
+ const start = quad * 12;
18128
+ let axis = 0;
18129
+ if (Math.abs(normals[start + 1]) > Math.abs(normals[start + axis])) axis = 1;
18130
+ if (Math.abs(normals[start + 2]) > Math.abs(normals[start + axis])) axis = 2;
18131
+ const uAxis = (axis + 1) % 3, vAxis = (axis + 2) % 3;
18132
+ 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;
18133
+ const sign = normals[start + axis] > 0 ? 1 : -1;
18134
+ for (let vertex = 0; vertex < 4; vertex++) {
18135
+ const offset = start + vertex * 3;
18136
+ 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;
18137
+ }
18138
+ const center = 13 + sign * AXIS_STRIDES[axis];
18139
+ const u = AXIS_STRIDES[uAxis], v = AXIS_STRIDES[vAxis];
18140
+ const lowLow = cornerBrightness(mask, center, -u, -v);
18141
+ const highLow = cornerBrightness(mask, center, u, -v);
18142
+ const lowHigh = cornerBrightness(mask, center, -u, v);
18143
+ const highHigh = cornerBrightness(mask, center, u, v);
18144
+ for (let vertex = 0; vertex < 4; vertex++) {
18145
+ const offset = start + vertex * 3;
18146
+ const a = Math.max(0, Math.min(1, (positions[offset + uAxis] + 8) / 16));
18147
+ const b = Math.max(0, Math.min(1, (positions[offset + vAxis] + 8) / 16));
18148
+ output[vertex] = (lowLow * (1 - a) + highLow * a) * (1 - b) + (lowHigh * (1 - a) + highHigh * a) * b;
18149
+ }
18150
+ }
18151
+
18080
18152
  // src/world/_light/FaceLight.ts
18081
18153
  function lightBrightness(level, ambient = 0) {
18082
18154
  if (level === 255) return 1;
@@ -18102,7 +18174,7 @@ var LIGHT_SAMPLE_OFFSETS = [0, 1, -1, 324, -324, 18, -18];
18102
18174
 
18103
18175
  // src/world/SectionGeometry.ts
18104
18176
  function buildSectionGeometry(input) {
18105
- const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight } = input;
18177
+ const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion } = input;
18106
18178
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
18107
18179
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
18108
18180
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
@@ -18113,6 +18185,10 @@ function buildSectionGeometry(input) {
18113
18185
  if (ambientLight !== void 0 && (!Number.isFinite(ambientLight) || ambientLight < 0 || ambientLight > 1)) {
18114
18186
  throw new RangeError("Section ambient light must be a number from 0 to 1");
18115
18187
  }
18188
+ if (ambientOcclusion && (ambientOcclusion.length !== 5832 || ambientOcclusion.some((value) => value > 1))) {
18189
+ throw new RangeError("Section ambient occlusion must cover an 18\xD718\xD718 neighborhood with zero or one per cell");
18190
+ }
18191
+ const cornerBrightness2 = ambientOcclusion ? new Float32Array(4) : void 0;
18116
18192
  for (const template of templateData) {
18117
18193
  const { quads } = template;
18118
18194
  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) {
@@ -18218,8 +18294,10 @@ function buildSectionGeometry(input) {
18218
18294
  const offsetY = Math.floor(indices[entry] / 256) * 16;
18219
18295
  const offsetZ = Math.floor(indices[entry] / 16) % 16 * 16;
18220
18296
  const lightIndex = (offsetY / 16 + 1) * 324 + (offsetZ / 16 + 1) * 18 + offsetX / 16 + 1;
18297
+ const ambientMask = ambientOcclusion && template.ambientOcclusion !== false ? sectionAmbientOcclusionMask(ambientOcclusion, lightIndex) : 0;
18221
18298
  for (let quad = 0; quad < template.quads; quad++) {
18222
18299
  if (cullMasks[entry] & template.cullFaces[quad]) continue;
18300
+ if (ambientMask) quadAmbientOcclusion(template.positions, template.normals, quad, ambientMask, cornerBrightness2);
18223
18301
  const brightness = light ? lightBrightness(light[lightIndex + LIGHT_SAMPLE_OFFSETS[quadLightIndex(template.positions, template.normals, quad)]], ambientLight) : 1;
18224
18302
  const start = vertex;
18225
18303
  for (let corner = 0; corner < 4; corner++, vertex++) {
@@ -18236,9 +18314,10 @@ function buildSectionGeometry(input) {
18236
18314
  uvBounds[vertex * 4 + 1] = mapV(template.uvBounds[source * 4 + 1]);
18237
18315
  uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
18238
18316
  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;
18317
+ const factor = brightness * (ambientMask ? cornerBrightness2[corner] : 1);
18318
+ colors[vertex * 3] = template.colors[source * 3] * factor;
18319
+ colors[vertex * 3 + 1] = template.colors[source * 3 + 1] * factor;
18320
+ colors[vertex * 3 + 2] = template.colors[source * 3 + 2] * factor;
18242
18321
  }
18243
18322
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
18244
18323
  }
@@ -18387,7 +18466,8 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
18387
18466
  indices: new Uint16Array(quads * 6),
18388
18467
  cullFaces: new Uint8Array(template.cullFaces),
18389
18468
  atlas: 0,
18390
- layer: template.layer
18469
+ layer: template.layer,
18470
+ ambientOcclusion: template.ambientOcclusion
18391
18471
  };
18392
18472
  const normal = template.geometry.getAttribute("normal");
18393
18473
  const uv = template.geometry.getAttribute("uv");
@@ -18817,6 +18897,25 @@ var Chunk = class _Chunk {
18817
18897
  if (this.renderedBlocks.has(index)) this.meshDirty = true;
18818
18898
  }
18819
18899
  }
18900
+ /** Refreshes baked corner shading after edits or a world setting change. Disabled chunks keep no AO neighborhood. */
18901
+ refreshAmbientOcclusion(enabled) {
18902
+ if (!enabled && !this.ambientOcclusionCells) return;
18903
+ const cells = enabled ? this.buildAmbientOcclusionCells() : void 0;
18904
+ if (cells?.some((value, index) => value !== this.ambientOcclusionCells?.[index]) || !!cells !== !!this.ambientOcclusionCells) this.meshDirty = true;
18905
+ this.ambientOcclusionCells = cells;
18906
+ }
18907
+ buildAmbientOcclusionCells() {
18908
+ const cells = new Uint8Array(18 * 18 * 18);
18909
+ for (let y = -1; y <= 16; y++) {
18910
+ for (let z = -1; z <= 16; z++) {
18911
+ for (let x = -1; x <= 16; x++) {
18912
+ const inner = x >= 0 && x < 16 && y >= 0 && y < 16 && z >= 0 && z < 16;
18913
+ 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));
18914
+ }
18915
+ }
18916
+ }
18917
+ return cells;
18918
+ }
18820
18919
  biomePosition(index, blockState3, stored) {
18821
18920
  const position = this.positionAt(index), name = blockState3.key?.toNamespacedString();
18822
18921
  if (stored.properties?.half === "upper" && (name === "minecraft:tall_grass" || name === "minecraft:large_fern")) position[1]--;
@@ -19005,12 +19104,18 @@ var Chunk = class _Chunk {
19005
19104
  }
19006
19105
  }
19007
19106
  }
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;
19107
+ let next;
19108
+ try {
19109
+ next = entries.length || fluids ? await SectionMesh.buildAsync(
19110
+ entries,
19111
+ this.sectionModels.maxAtlasSize,
19112
+ fluids,
19113
+ { light, ambientLight: this.ambientLight, ambientOcclusion: this.ambientOcclusionCells }
19114
+ ) : void 0;
19115
+ } catch (error) {
19116
+ if (generation === this.meshGeneration) this.meshDirty = true;
19117
+ throw error;
19118
+ }
19014
19119
  if (generation !== this.meshGeneration) {
19015
19120
  next?.dispose();
19016
19121
  return;
@@ -19038,6 +19143,7 @@ var Chunk = class _Chunk {
19038
19143
  this.hiddenBlocks.clear();
19039
19144
  this.fluidCells.fill(0);
19040
19145
  this.waterTints = void 0;
19146
+ this.ambientOcclusionCells = void 0;
19041
19147
  this.data.clear();
19042
19148
  this.biomes = void 0;
19043
19149
  if (notify) {