minerender 2.1.0-alpha.119 → 2.1.0-alpha.120

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.
@@ -16831,11 +16831,20 @@ var AnvilParser = class {
16831
16831
  const { root, expected } = await this.readNBT(data, location, options);
16832
16832
  const level = root.Level?.type === "compound" ? root.Level.value : root;
16833
16833
  const dataVersion = root.DataVersion?.type === "int" ? root.DataVersion.value : void 0;
16834
+ const sections = this.compounds(level.sections ?? level.Sections, "sections");
16835
+ const lightFlagName = level.isLightOn ? "isLightOn" : "LightPopulated";
16836
+ const lightFlag = level[lightFlagName];
16837
+ if (lightFlag && (lightFlag.type !== "byte" || lightFlag.value !== 0 && lightFlag.value !== 1)) {
16838
+ throw new MineRenderError(`Anvil ${lightFlagName} must be a byte containing 0 or 1`);
16839
+ }
16840
+ const modernLighting = root.Level?.type !== "compound" || sections.some((section) => section.Palette || section.BlockStates || section.block_states);
16841
+ const lightValid = lightFlag ? lightFlag.value === 1 : modernLighting ? false : void 0;
16834
16842
  const chunk = {
16835
16843
  x: this.integer(level.xPos, "xPos"),
16836
16844
  z: this.integer(level.zPos, "zPos"),
16837
16845
  dataVersion,
16838
- sections: []
16846
+ sections: [],
16847
+ ...lightValid !== void 0 ? { lightValid } : {}
16839
16848
  };
16840
16849
  if ((chunk.x % 32 + 32) % 32 !== location.x || (chunk.z % 32 + 32) % 32 !== location.z) {
16841
16850
  throw new MineRenderError("Anvil chunk coordinates do not match its region location");
@@ -16845,7 +16854,7 @@ var AnvilParser = class {
16845
16854
  }
16846
16855
  const numericStates = /* @__PURE__ */ new Map();
16847
16856
  let legacyMappings;
16848
- for (const section of this.compounds(level.sections ?? level.Sections, "sections")) {
16857
+ for (const section of sections) {
16849
16858
  const y = this.integer(section.Y, "section Y");
16850
16859
  let data2;
16851
16860
  if (section.Blocks || section.Data || section.Add) {
@@ -16875,7 +16884,8 @@ var AnvilParser = class {
16875
16884
  }
16876
16885
  if (chunk.sections.some((section2) => section2.y === y)) throw new MineRenderError(`Duplicate Anvil section Y ${y}`);
16877
16886
  const biomes = this.biomes(section.biomes);
16878
- chunk.sections.push({ y, data: data2, ...biomes ? { biomes } : {} });
16887
+ const light = this.light(section, y, chunk.lightValid);
16888
+ chunk.sections.push({ y, data: data2, ...biomes ? { biomes } : {}, ...light ? { light } : {} });
16879
16889
  }
16880
16890
  for (const entry of this.compounds(level.block_entities ?? level.TileEntities, "block entities")) {
16881
16891
  const x = this.integer(entry.x, "block entity x") - chunk.x * 16;
@@ -16928,6 +16938,18 @@ var AnvilParser = class {
16928
16938
  return { position: [...position.value.value], nbt: { type: "compound", value } };
16929
16939
  });
16930
16940
  }
16941
+ static light(section, y, valid) {
16942
+ const light = {};
16943
+ for (const [name, channel] of [["BlockLight", "block"], ["SkyLight", "sky"]]) {
16944
+ const tag = section[name];
16945
+ if (!tag) continue;
16946
+ if (tag.type !== "byteArray" || !Array.isArray(tag.value) || tag.value.length !== 2048) {
16947
+ throw new MineRenderError(`Anvil section ${y} ${name} must contain 2048 bytes`);
16948
+ }
16949
+ light[channel] = Uint8Array.from(tag.value);
16950
+ }
16951
+ return light.block || light.sky || valid ? light : void 0;
16952
+ }
16931
16953
  static biomes(tag) {
16932
16954
  if (!tag) return void 0;
16933
16955
  if (tag.type !== "compound") throw new MineRenderError("Anvil biomes must be a compound");
@@ -17505,12 +17527,45 @@ var WorldEntities = class {
17505
17527
 
17506
17528
  // src/world/MineRenderWorld.ts
17507
17529
  init_FluidQuads();
17530
+
17531
+ // src/world/WorldLight.ts
17532
+ function copySectionLight(light) {
17533
+ const copy = {};
17534
+ for (const channel of ["block", "sky"]) {
17535
+ const bytes = light[channel];
17536
+ if (bytes === void 0) continue;
17537
+ if (!(bytes instanceof Uint8Array) || bytes.length !== 2048) {
17538
+ throw new RangeError(`Section ${channel} light must contain 2048 packed bytes`);
17539
+ }
17540
+ copy[channel] = new Uint8Array(bytes);
17541
+ }
17542
+ return copy;
17543
+ }
17544
+ function sampleSectionLight(light, index) {
17545
+ if (!light) return void 0;
17546
+ const shift = (index & 1) * 4;
17547
+ return {
17548
+ block: (light.block?.[index >> 1] ?? 0) >> shift & 15,
17549
+ sky: (light.sky?.[index >> 1] ?? 0) >> shift & 15
17550
+ };
17551
+ }
17552
+
17553
+ // src/world/MineRenderWorld.ts
17508
17554
  var MineRenderWorld = class {
17509
17555
  constructor(scene, options = {}) {
17510
17556
  this._chunks = /* @__PURE__ */ new Map();
17511
17557
  this.biomes = /* @__PURE__ */ new Map();
17558
+ this.light = /* @__PURE__ */ new Map();
17559
+ this.lightValidity = /* @__PURE__ */ new Map();
17560
+ this.highestSkySection = /* @__PURE__ */ new Map();
17512
17561
  this.pendingCulling = /* @__PURE__ */ new Map();
17562
+ if (options.savedLighting && !options.sectionMeshing) throw new TypeError("savedLighting requires sectionMeshing");
17563
+ if (options.ambientLight !== void 0 && (!Number.isFinite(options.ambientLight) || options.ambientLight < 0 || options.ambientLight > 1)) {
17564
+ throw new RangeError("ambientLight must be a number from 0 to 1");
17565
+ }
17513
17566
  this.scene = scene;
17567
+ this.savedLighting = options.savedLighting ?? false;
17568
+ this.ambientLight = options.ambientLight ?? 0;
17514
17569
  if (options.sectionMeshing) this.sectionModels = new SectionModels(options.maxAtlasSize);
17515
17570
  if (options.renderEntities) this.entities = new WorldEntities(scene);
17516
17571
  }
@@ -17532,6 +17587,24 @@ var MineRenderWorld = class {
17532
17587
  const lx = posOrX.x - cx * 16, ly = posOrX.y - cy * 16, lz = posOrX.z - cz * 16;
17533
17588
  return this.biomes.get(`${cx}_${cz}`)?.get(cy)?.[Math.floor(lx / 4) + Math.floor(lz / 4) * 4 + Math.floor(ly / 4) * 16];
17534
17589
  }
17590
+ getLightAt(posOrX, y, z) {
17591
+ if (typeof posOrX === "number") return this.getLightAt(new Vector317(posOrX, y, z));
17592
+ if (isTripleArray(posOrX)) return this.getLightAt(new Vector317(...posOrX));
17593
+ this.validatePosBounds(posOrX);
17594
+ const { x, y: py, z: pz } = posOrX;
17595
+ const cx = Math.floor(x / 16), cy = Math.floor(py / 16), cz = Math.floor(pz / 16);
17596
+ const key = `${cx}_${cz}`, top = this.highestSkySection.get(key);
17597
+ const sample = sampleSectionLight(this.light.get(key)?.get(cy), (x & 15) + (pz & 15) * 16 + (py & 15) * 256);
17598
+ return top !== void 0 && cy > top ? { block: sample?.block ?? 0, sky: 15 } : sample;
17599
+ }
17600
+ lightLevelAt(x, y, z) {
17601
+ const cx = Math.floor(x / 16), cy = Math.floor(y / 16), cz = Math.floor(z / 16), key = `${cx}_${cz}`;
17602
+ if (this.lightValidity.get(key) === false) return 255;
17603
+ const top = this.highestSkySection.get(key);
17604
+ if (top !== void 0 && cy > top) return 15;
17605
+ const sample = sampleSectionLight(this.light.get(key)?.get(cy), (x & 15) + (z & 15) * 16 + (y & 15) * 256);
17606
+ return sample ? Math.max(sample.block, sample.sky) : 255;
17607
+ }
17535
17608
  async setBlockAt(posOrX, yOrBlock, z, block) {
17536
17609
  if (typeof posOrX == "number") {
17537
17610
  return this.setBlockAt(new Vector317(posOrX, yOrBlock, z), block);
@@ -17646,7 +17719,7 @@ var MineRenderWorld = class {
17646
17719
  }
17647
17720
  }
17648
17721
  }
17649
- /** Replaces a chunk column's blocks and biome samples and, with `renderEntities`, its supported saved mobs. */
17722
+ /** Replaces a chunk column's blocks, biomes, and saved light and, with `renderEntities`, its supported saved mobs. */
17650
17723
  async placeChunk(chunk, executor) {
17651
17724
  if (![chunk.x, chunk.z].every(Number.isSafeInteger)) {
17652
17725
  throw new RangeError("Chunk column coordinates must be safe integers");
@@ -17654,6 +17727,11 @@ var MineRenderWorld = class {
17654
17727
  if (!chunk.sections.every((section) => Number.isSafeInteger(section.y))) {
17655
17728
  throw new RangeError("Chunk section coordinates must be safe integers");
17656
17729
  }
17730
+ const lights = /* @__PURE__ */ new Map();
17731
+ for (const section of chunk.sections) {
17732
+ const light = section.light ?? (chunk.lightValid === true ? {} : void 0);
17733
+ if (light) lights.set(section.y, copySectionLight(light));
17734
+ }
17657
17735
  this.entities?.clearColumn(chunk.x, chunk.z);
17658
17736
  const placeEntities = this.entities?.prepare(chunk.entities, [chunk.x, chunk.z]);
17659
17737
  const changes = /* @__PURE__ */ new Map();
@@ -17669,6 +17747,15 @@ var MineRenderWorld = class {
17669
17747
  }
17670
17748
  try {
17671
17749
  await this.clearChunkColumn(chunk.x, chunk.z);
17750
+ const columnKey = `${chunk.x}_${chunk.z}`;
17751
+ if (lights.size) this.light.set(columnKey, lights);
17752
+ for (const [y, light] of lights) {
17753
+ if (light.sky) this.highestSkySection.set(columnKey, Math.max(y, this.highestSkySection.get(columnKey) ?? -Infinity));
17754
+ }
17755
+ if (chunk.lightValid !== void 0) this.lightValidity.set(columnKey, chunk.lightValid);
17756
+ if (this.savedLighting) {
17757
+ for (const y of lights.keys()) this.markSectionChanged({ x: chunk.x, y, z: chunk.z });
17758
+ }
17672
17759
  for (const section of chunk.sections) {
17673
17760
  if (section.biomes) {
17674
17761
  const position = new Vector317(chunk.x * 16, section.y * 16, chunk.z * 16);
@@ -17738,6 +17825,12 @@ var MineRenderWorld = class {
17738
17825
  }
17739
17826
  async clearChunkColumn(x, z) {
17740
17827
  this.biomes.delete(`${x}_${z}`);
17828
+ if (this.savedLighting) {
17829
+ for (const y of this.light.get(`${x}_${z}`)?.keys() ?? []) this.markSectionChanged({ x, y, z });
17830
+ }
17831
+ this.light.delete(`${x}_${z}`);
17832
+ this.lightValidity.delete(`${x}_${z}`);
17833
+ this.highestSkySection.delete(`${x}_${z}`);
17741
17834
  const previous = [...this._chunks.entries()].filter(([, section]) => section.x === x && section.z === z);
17742
17835
  for (const [key, section] of previous) {
17743
17836
  this._chunks.delete(key);
@@ -17747,7 +17840,8 @@ var MineRenderWorld = class {
17747
17840
  }
17748
17841
  }
17749
17842
  markSectionChanged(chunk) {
17750
- if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingCulling.set(chunk, "all");
17843
+ const current = this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`);
17844
+ if (current && current === chunk) this.pendingCulling.set(current, "all");
17751
17845
  for (let dy = -1; dy <= 1; dy++) {
17752
17846
  for (let dz = -1; dz <= 1; dz++) {
17753
17847
  for (let dx = -1; dx <= 1; dx++) {
@@ -17775,6 +17869,9 @@ var MineRenderWorld = class {
17775
17869
  const chunks = [...this._chunks.values()];
17776
17870
  this._chunks.clear();
17777
17871
  this.biomes.clear();
17872
+ this.light.clear();
17873
+ this.lightValidity.clear();
17874
+ this.highestSkySection.clear();
17778
17875
  for (const chunk of chunks) {
17779
17876
  await chunk.dispose();
17780
17877
  }
@@ -17837,6 +17934,7 @@ var MineRenderWorld = class {
17837
17934
  }
17838
17935
  }
17839
17936
  for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
17937
+ await chunk.refreshLight(indices === "all" ? void 0 : indices);
17840
17938
  }
17841
17939
  await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
17842
17940
  }
@@ -17877,7 +17975,23 @@ var MineRenderWorld = class {
17877
17975
  }
17878
17976
  await chunk.refreshBiomes();
17879
17977
  await this._chunks.get(`${x}_${y + 1}_${z}`)?.refreshBiomes();
17880
- }
17978
+ },
17979
+ this.savedLighting ? (x2, y2, z2) => this.lightLevelAt(x2, y2, z2) : void 0,
17980
+ async () => {
17981
+ if (this._chunks.get(key) !== chunk) return;
17982
+ const columnKey = `${x}_${z}`, column = this.light.get(columnKey);
17983
+ if (!column?.delete(y)) return;
17984
+ if (!column.size) this.light.delete(columnKey);
17985
+ this.highestSkySection.delete(columnKey);
17986
+ for (const [y2, light] of column) {
17987
+ if (light.sky) this.highestSkySection.set(columnKey, Math.max(y2, this.highestSkySection.get(columnKey) ?? -Infinity));
17988
+ }
17989
+ if (this.savedLighting) {
17990
+ this.markSectionChanged(chunk);
17991
+ await this.updateCulling([]);
17992
+ }
17993
+ },
17994
+ this.ambientLight
17881
17995
  );
17882
17996
  this._chunks.set(key, chunk);
17883
17997
  }
@@ -17926,12 +18040,43 @@ import { BufferGeometry as BufferGeometry9, Color as Color12, DoubleSide as Doub
17926
18040
 
17927
18041
  // src/world/SectionGeometry.ts
17928
18042
  init_FluidQuads();
18043
+
18044
+ // src/world/_light/FaceLight.ts
18045
+ function lightBrightness(level, ambient = 0) {
18046
+ if (level === 255) return 1;
18047
+ const curve = level / (60 - 3 * level);
18048
+ return curve + ambient * (1 - curve);
18049
+ }
18050
+ function quadLightIndex(positions, normals, quad) {
18051
+ const start = quad * 12;
18052
+ let axis = 0;
18053
+ if (Math.abs(normals[start + 1]) > Math.abs(normals[start + axis])) axis = 1;
18054
+ if (Math.abs(normals[start + 2]) > Math.abs(normals[start + axis])) axis = 2;
18055
+ const positive2 = normals[start + axis] > 0;
18056
+ const boundary = positive2 ? 8 : -8;
18057
+ for (let vertex = 0; vertex < 4; vertex++) {
18058
+ if (Math.abs(positions[start + vertex * 3 + axis] - boundary) > 16e-4) return 0;
18059
+ }
18060
+ return axis * 2 + (positive2 ? 1 : 2);
18061
+ }
18062
+ function fluidLightLevel(center, neighbor) {
18063
+ return Math.max(center === 255 ? 15 : center, neighbor === 255 ? 15 : neighbor);
18064
+ }
18065
+ var LIGHT_SAMPLE_OFFSETS = [0, 1, -1, 324, -324, 18, -18];
18066
+
18067
+ // src/world/SectionGeometry.ts
17929
18068
  function buildSectionGeometry(input) {
17930
- const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids } = input;
18069
+ const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight } = input;
17931
18070
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
17932
18071
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
17933
18072
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
17934
18073
  if (fluids?.waterTints && fluids.waterTints.length !== 4096 * 3) throw new RangeError("Section water tints must contain RGB for 4096 cells");
18074
+ if (light && (light.length !== 5832 || light.some((level) => level > 15 && level !== 255))) {
18075
+ throw new RangeError("Section light must cover an 18\xD718\xD718 neighborhood with levels from 0 to 15 or 255");
18076
+ }
18077
+ if (ambientLight !== void 0 && (!Number.isFinite(ambientLight) || ambientLight < 0 || ambientLight > 1)) {
18078
+ throw new RangeError("Section ambient light must be a number from 0 to 1");
18079
+ }
17935
18080
  for (const template of templateData) {
17936
18081
  const { quads } = template;
17937
18082
  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) {
@@ -18036,8 +18181,10 @@ function buildSectionGeometry(input) {
18036
18181
  const offsetX = indices[entry] % 16 * 16;
18037
18182
  const offsetY = Math.floor(indices[entry] / 256) * 16;
18038
18183
  const offsetZ = Math.floor(indices[entry] / 16) % 16 * 16;
18184
+ const lightIndex = (offsetY / 16 + 1) * 324 + (offsetZ / 16 + 1) * 18 + offsetX / 16 + 1;
18039
18185
  for (let quad = 0; quad < template.quads; quad++) {
18040
18186
  if (cullMasks[entry] & template.cullFaces[quad]) continue;
18187
+ const brightness = light ? lightBrightness(light[lightIndex + LIGHT_SAMPLE_OFFSETS[quadLightIndex(template.positions, template.normals, quad)]], ambientLight) : 1;
18041
18188
  const start = vertex;
18042
18189
  for (let corner = 0; corner < 4; corner++, vertex++) {
18043
18190
  const source = quad * 4 + corner;
@@ -18053,9 +18200,9 @@ function buildSectionGeometry(input) {
18053
18200
  uvBounds[vertex * 4 + 1] = mapV(template.uvBounds[source * 4 + 1]);
18054
18201
  uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
18055
18202
  uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
18056
- colors[vertex * 3] = template.colors[source * 3];
18057
- colors[vertex * 3 + 1] = template.colors[source * 3 + 1];
18058
- colors[vertex * 3 + 2] = template.colors[source * 3 + 2];
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;
18059
18206
  }
18060
18207
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
18061
18208
  }
@@ -18065,8 +18212,13 @@ function buildSectionGeometry(input) {
18065
18212
  const placement = page.placements[0];
18066
18213
  const mapU = (u) => (placement.x + u * atlas.width) / page.width;
18067
18214
  const mapV = (v) => 1 - (placement.y + (1 - v) * atlas.height) / page.height;
18215
+ const lightIndex = (y + 1) * 324 + (z + 1) * 18 + x + 1;
18068
18216
  for (let quad = 0; quad < quads.sprites.length; quad++) {
18069
18217
  const rect = quads.sprites[quad] === 0 ? data.still : data.flow;
18218
+ const brightness = light ? lightBrightness(fluidLightLevel(
18219
+ light[lightIndex],
18220
+ light[lightIndex + (quads.normals[quad * 12 + 1] < 0 ? -324 : 324)]
18221
+ ), ambientLight) : 1;
18070
18222
  const start = vertex;
18071
18223
  for (let corner = 0; corner < 4; corner++, vertex++) {
18072
18224
  const source = quad * 4 + corner;
@@ -18084,7 +18236,9 @@ function buildSectionGeometry(input) {
18084
18236
  mapU((rect[0] + rect[2]) / atlas.width),
18085
18237
  mapV(1 - rect[1] / atlas.height)
18086
18238
  ], vertex * 4);
18087
- colors.set(tint, vertex * 3);
18239
+ colors[vertex * 3] = tint[0] * brightness;
18240
+ colors[vertex * 3 + 1] = tint[1] * brightness;
18241
+ colors[vertex * 3 + 2] = tint[2] * brightness;
18088
18242
  }
18089
18243
  outputIndices.set([start, start + 1, start + 2, start, start + 2, start + 3], indexOffset);
18090
18244
  indexOffset += 6;
@@ -18152,7 +18306,7 @@ var SectionWorker = _SectionWorker;
18152
18306
  // src/world/SectionMesh.ts
18153
18307
  var templateCache = /* @__PURE__ */ new WeakMap();
18154
18308
  var WATER_TINT = new Color12(4159204).toArray();
18155
- function toInput(entries, maxAtlasSize, fluids) {
18309
+ function toInput(entries, maxAtlasSize, fluids, lighting) {
18156
18310
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
18157
18311
  const atlases = [];
18158
18312
  const atlasIds = /* @__PURE__ */ new Map();
@@ -18164,7 +18318,8 @@ function toInput(entries, maxAtlasSize, fluids) {
18164
18318
  cullMasks: new Uint8Array(entries.length),
18165
18319
  templateData: [],
18166
18320
  atlases: [],
18167
- maxAtlasSize
18321
+ maxAtlasSize,
18322
+ ...lighting
18168
18323
  };
18169
18324
  for (let i = 0; i < entries.length; i++) {
18170
18325
  const { index, template, cullMask } = entries[i];
@@ -18252,13 +18407,13 @@ var SectionMesh = class _SectionMesh extends Group3 {
18252
18407
  this.ownedMeshes = [];
18253
18408
  }
18254
18409
  /** Builds section-local meshes from visible quads. `maxAtlasSize` limits each atlas dimension in pixels. */
18255
- static build(entries, maxAtlasSize = 2048, fluids) {
18256
- const { input, atlases } = toInput(entries, maxAtlasSize, fluids);
18410
+ static build(entries, maxAtlasSize = 2048, fluids, lighting) {
18411
+ const { input, atlases } = toInput(entries, maxAtlasSize, fluids, lighting);
18257
18412
  return this.fromPages(buildSectionGeometry(input), atlases);
18258
18413
  }
18259
18414
  /** Builds section-local meshes in a browser worker when available, with synchronous fallback. */
18260
- static async buildAsync(entries, maxAtlasSize = 2048, fluids) {
18261
- const { input, atlases } = toInput(entries, maxAtlasSize, fluids);
18415
+ static async buildAsync(entries, maxAtlasSize = 2048, fluids, lighting) {
18416
+ const { input, atlases } = toInput(entries, maxAtlasSize, fluids, lighting);
18262
18417
  const worker = SectionWorker.shared();
18263
18418
  const pages = worker ? await worker.build(input).catch(() => buildSectionGeometry(input)) : buildSectionGeometry(input);
18264
18419
  return this.fromPages(pages, atlases);
@@ -18328,12 +18483,15 @@ init_FluidQuads();
18328
18483
  init_CubeFace();
18329
18484
  var WATER_TINT2 = new Color13(4159204).toArray();
18330
18485
  var Chunk = class _Chunk {
18331
- constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged) {
18486
+ constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0) {
18332
18487
  this.onBlocksChanged = onBlocksChanged;
18333
18488
  this.sectionModels = sectionModels;
18334
18489
  this.neighborCell = neighborCell;
18335
18490
  this.biomeAt = biomeAt;
18336
18491
  this.onBiomesChanged = onBiomesChanged;
18492
+ this.lightAt = lightAt;
18493
+ this.onLightCleared = onLightCleared;
18494
+ this.ambientLight = ambientLight;
18337
18495
  this.data = new ChunkData();
18338
18496
  this.renderedBlocks = /* @__PURE__ */ new Map();
18339
18497
  this.sectionBlocks = /* @__PURE__ */ new Map();
@@ -18615,6 +18773,13 @@ var Chunk = class _Chunk {
18615
18773
  positionAt(index) {
18616
18774
  return [this.x * 16 + index % 16, this.y * 16 + Math.floor(index / 256), this.z * 16 + Math.floor(index / 16) % 16];
18617
18775
  }
18776
+ /** Refreshes saved lighting after adjacent columns load or unload, including columns with only light data. */
18777
+ async refreshLight(indices) {
18778
+ if (!this.lightAt) return;
18779
+ for (const index of indices ?? this.renderedBlocks.keys()) {
18780
+ if (this.renderedBlocks.has(index)) this.meshDirty = true;
18781
+ }
18782
+ }
18618
18783
  biomePosition(index, blockState3, stored) {
18619
18784
  const position = this.positionAt(index), name = blockState3.key?.toNamespacedString();
18620
18785
  if (stored.properties?.half === "upper" && (name === "minecraft:tall_grass" || name === "minecraft:large_fern")) position[1]--;
@@ -18792,7 +18957,23 @@ var Chunk = class _Chunk {
18792
18957
  fluids[kind] = await this.sectionModels.fluidAtlas(kind, state?.key?.root);
18793
18958
  }
18794
18959
  }
18795
- const next = entries.length || fluids ? await SectionMesh.buildAsync(entries, this.sectionModels.maxAtlasSize, fluids) : void 0;
18960
+ let light;
18961
+ if (this.lightAt) {
18962
+ light = new Uint8Array(18 * 18 * 18);
18963
+ for (let y = -1; y <= 16; y++) {
18964
+ for (let z = -1; z <= 16; z++) {
18965
+ for (let x = -1; x <= 16; x++) {
18966
+ light[(y + 1) * 324 + (z + 1) * 18 + x + 1] = this.lightAt(this.x * 16 + x, this.y * 16 + y, this.z * 16 + z);
18967
+ }
18968
+ }
18969
+ }
18970
+ }
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;
18796
18977
  if (generation !== this.meshGeneration) {
18797
18978
  next?.dispose();
18798
18979
  return;
@@ -18824,6 +19005,7 @@ var Chunk = class _Chunk {
18824
19005
  this.biomes = void 0;
18825
19006
  if (notify) {
18826
19007
  await this.onBiomesChanged?.(void 0);
19008
+ await this.onLightCleared?.();
18827
19009
  await this.onBlocksChanged?.(positions);
18828
19010
  }
18829
19011
  }
@@ -19167,6 +19349,7 @@ export {
19167
19349
  classicSkinTextureCoordinates,
19168
19350
  computeMorphedAttributes,
19169
19351
  computeTangents,
19352
+ copySectionLight,
19170
19353
  createCanvas,
19171
19354
  createFluidGeometry,
19172
19355
  createGuiTextGeometry,
@@ -19219,6 +19402,7 @@ export {
19219
19402
  sampleEntityAnimation,
19220
19403
  sampleEntityKeyframes,
19221
19404
  sampleFluid,
19405
+ sampleSectionLight,
19222
19406
  sectionGeometryTransferables,
19223
19407
  serializeBoxGeometryKey,
19224
19408
  serializeImageKey,