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.
@@ -16867,11 +16867,20 @@ var AnvilParser = class {
16867
16867
  const { root, expected } = await this.readNBT(data, location, options);
16868
16868
  const level = root.Level?.type === "compound" ? root.Level.value : root;
16869
16869
  const dataVersion = root.DataVersion?.type === "int" ? root.DataVersion.value : void 0;
16870
+ const sections = this.compounds(level.sections ?? level.Sections, "sections");
16871
+ const lightFlagName = level.isLightOn ? "isLightOn" : "LightPopulated";
16872
+ const lightFlag = level[lightFlagName];
16873
+ if (lightFlag && (lightFlag.type !== "byte" || lightFlag.value !== 0 && lightFlag.value !== 1)) {
16874
+ throw new MineRenderError(`Anvil ${lightFlagName} must be a byte containing 0 or 1`);
16875
+ }
16876
+ const modernLighting = root.Level?.type !== "compound" || sections.some((section) => section.Palette || section.BlockStates || section.block_states);
16877
+ const lightValid = lightFlag ? lightFlag.value === 1 : modernLighting ? false : void 0;
16870
16878
  const chunk = {
16871
16879
  x: this.integer(level.xPos, "xPos"),
16872
16880
  z: this.integer(level.zPos, "zPos"),
16873
16881
  dataVersion,
16874
- sections: []
16882
+ sections: [],
16883
+ ...lightValid !== void 0 ? { lightValid } : {}
16875
16884
  };
16876
16885
  if ((chunk.x % 32 + 32) % 32 !== location.x || (chunk.z % 32 + 32) % 32 !== location.z) {
16877
16886
  throw new MineRenderError("Anvil chunk coordinates do not match its region location");
@@ -16881,7 +16890,7 @@ var AnvilParser = class {
16881
16890
  }
16882
16891
  const numericStates = /* @__PURE__ */ new Map();
16883
16892
  let legacyMappings;
16884
- for (const section of this.compounds(level.sections ?? level.Sections, "sections")) {
16893
+ for (const section of sections) {
16885
16894
  const y = this.integer(section.Y, "section Y");
16886
16895
  let data2;
16887
16896
  if (section.Blocks || section.Data || section.Add) {
@@ -16911,7 +16920,8 @@ var AnvilParser = class {
16911
16920
  }
16912
16921
  if (chunk.sections.some((section2) => section2.y === y)) throw new MineRenderError(`Duplicate Anvil section Y ${y}`);
16913
16922
  const biomes = this.biomes(section.biomes);
16914
- chunk.sections.push({ y, data: data2, ...biomes ? { biomes } : {} });
16923
+ const light = this.light(section, y, chunk.lightValid);
16924
+ chunk.sections.push({ y, data: data2, ...biomes ? { biomes } : {}, ...light ? { light } : {} });
16915
16925
  }
16916
16926
  for (const entry of this.compounds(level.block_entities ?? level.TileEntities, "block entities")) {
16917
16927
  const x = this.integer(entry.x, "block entity x") - chunk.x * 16;
@@ -16964,6 +16974,18 @@ var AnvilParser = class {
16964
16974
  return { position: [...position.value.value], nbt: { type: "compound", value } };
16965
16975
  });
16966
16976
  }
16977
+ static light(section, y, valid) {
16978
+ const light = {};
16979
+ for (const [name, channel] of [["BlockLight", "block"], ["SkyLight", "sky"]]) {
16980
+ const tag = section[name];
16981
+ if (!tag) continue;
16982
+ if (tag.type !== "byteArray" || !Array.isArray(tag.value) || tag.value.length !== 2048) {
16983
+ throw new MineRenderError(`Anvil section ${y} ${name} must contain 2048 bytes`);
16984
+ }
16985
+ light[channel] = Uint8Array.from(tag.value);
16986
+ }
16987
+ return light.block || light.sky || valid ? light : void 0;
16988
+ }
16967
16989
  static biomes(tag) {
16968
16990
  if (!tag) return void 0;
16969
16991
  if (tag.type !== "compound") throw new MineRenderError("Anvil biomes must be a compound");
@@ -17541,12 +17563,45 @@ var WorldEntities = class {
17541
17563
 
17542
17564
  // src/world/MineRenderWorld.ts
17543
17565
  init_FluidQuads();
17566
+
17567
+ // src/world/WorldLight.ts
17568
+ function copySectionLight(light) {
17569
+ const copy = {};
17570
+ for (const channel of ["block", "sky"]) {
17571
+ const bytes = light[channel];
17572
+ if (bytes === void 0) continue;
17573
+ if (!(bytes instanceof Uint8Array) || bytes.length !== 2048) {
17574
+ throw new RangeError(`Section ${channel} light must contain 2048 packed bytes`);
17575
+ }
17576
+ copy[channel] = new Uint8Array(bytes);
17577
+ }
17578
+ return copy;
17579
+ }
17580
+ function sampleSectionLight(light, index) {
17581
+ if (!light) return void 0;
17582
+ const shift = (index & 1) * 4;
17583
+ return {
17584
+ block: (light.block?.[index >> 1] ?? 0) >> shift & 15,
17585
+ sky: (light.sky?.[index >> 1] ?? 0) >> shift & 15
17586
+ };
17587
+ }
17588
+
17589
+ // src/world/MineRenderWorld.ts
17544
17590
  var MineRenderWorld = class {
17545
17591
  constructor(scene, options = {}) {
17546
17592
  this._chunks = /* @__PURE__ */ new Map();
17547
17593
  this.biomes = /* @__PURE__ */ new Map();
17594
+ this.light = /* @__PURE__ */ new Map();
17595
+ this.lightValidity = /* @__PURE__ */ new Map();
17596
+ this.highestSkySection = /* @__PURE__ */ new Map();
17548
17597
  this.pendingCulling = /* @__PURE__ */ new Map();
17598
+ if (options.savedLighting && !options.sectionMeshing) throw new TypeError("savedLighting requires sectionMeshing");
17599
+ if (options.ambientLight !== void 0 && (!Number.isFinite(options.ambientLight) || options.ambientLight < 0 || options.ambientLight > 1)) {
17600
+ throw new RangeError("ambientLight must be a number from 0 to 1");
17601
+ }
17549
17602
  this.scene = scene;
17603
+ this.savedLighting = options.savedLighting ?? false;
17604
+ this.ambientLight = options.ambientLight ?? 0;
17550
17605
  if (options.sectionMeshing) this.sectionModels = new SectionModels(options.maxAtlasSize);
17551
17606
  if (options.renderEntities) this.entities = new WorldEntities(scene);
17552
17607
  }
@@ -17568,6 +17623,24 @@ var MineRenderWorld = class {
17568
17623
  const lx = posOrX.x - cx * 16, ly = posOrX.y - cy * 16, lz = posOrX.z - cz * 16;
17569
17624
  return this.biomes.get(`${cx}_${cz}`)?.get(cy)?.[Math.floor(lx / 4) + Math.floor(lz / 4) * 4 + Math.floor(ly / 4) * 16];
17570
17625
  }
17626
+ getLightAt(posOrX, y, z) {
17627
+ if (typeof posOrX === "number") return this.getLightAt(new Vector317(posOrX, y, z));
17628
+ if (isTripleArray(posOrX)) return this.getLightAt(new Vector317(...posOrX));
17629
+ this.validatePosBounds(posOrX);
17630
+ const { x, y: py, z: pz } = posOrX;
17631
+ const cx = Math.floor(x / 16), cy = Math.floor(py / 16), cz = Math.floor(pz / 16);
17632
+ const key = `${cx}_${cz}`, top = this.highestSkySection.get(key);
17633
+ const sample = sampleSectionLight(this.light.get(key)?.get(cy), (x & 15) + (pz & 15) * 16 + (py & 15) * 256);
17634
+ return top !== void 0 && cy > top ? { block: sample?.block ?? 0, sky: 15 } : sample;
17635
+ }
17636
+ lightLevelAt(x, y, z) {
17637
+ const cx = Math.floor(x / 16), cy = Math.floor(y / 16), cz = Math.floor(z / 16), key = `${cx}_${cz}`;
17638
+ if (this.lightValidity.get(key) === false) return 255;
17639
+ const top = this.highestSkySection.get(key);
17640
+ if (top !== void 0 && cy > top) return 15;
17641
+ const sample = sampleSectionLight(this.light.get(key)?.get(cy), (x & 15) + (z & 15) * 16 + (y & 15) * 256);
17642
+ return sample ? Math.max(sample.block, sample.sky) : 255;
17643
+ }
17571
17644
  async setBlockAt(posOrX, yOrBlock, z, block) {
17572
17645
  if (typeof posOrX == "number") {
17573
17646
  return this.setBlockAt(new Vector317(posOrX, yOrBlock, z), block);
@@ -17682,7 +17755,7 @@ var MineRenderWorld = class {
17682
17755
  }
17683
17756
  }
17684
17757
  }
17685
- /** Replaces a chunk column's blocks and biome samples and, with `renderEntities`, its supported saved mobs. */
17758
+ /** Replaces a chunk column's blocks, biomes, and saved light and, with `renderEntities`, its supported saved mobs. */
17686
17759
  async placeChunk(chunk, executor) {
17687
17760
  if (![chunk.x, chunk.z].every(Number.isSafeInteger)) {
17688
17761
  throw new RangeError("Chunk column coordinates must be safe integers");
@@ -17690,6 +17763,11 @@ var MineRenderWorld = class {
17690
17763
  if (!chunk.sections.every((section) => Number.isSafeInteger(section.y))) {
17691
17764
  throw new RangeError("Chunk section coordinates must be safe integers");
17692
17765
  }
17766
+ const lights = /* @__PURE__ */ new Map();
17767
+ for (const section of chunk.sections) {
17768
+ const light = section.light ?? (chunk.lightValid === true ? {} : void 0);
17769
+ if (light) lights.set(section.y, copySectionLight(light));
17770
+ }
17693
17771
  this.entities?.clearColumn(chunk.x, chunk.z);
17694
17772
  const placeEntities = this.entities?.prepare(chunk.entities, [chunk.x, chunk.z]);
17695
17773
  const changes = /* @__PURE__ */ new Map();
@@ -17705,6 +17783,15 @@ var MineRenderWorld = class {
17705
17783
  }
17706
17784
  try {
17707
17785
  await this.clearChunkColumn(chunk.x, chunk.z);
17786
+ const columnKey = `${chunk.x}_${chunk.z}`;
17787
+ if (lights.size) this.light.set(columnKey, lights);
17788
+ for (const [y, light] of lights) {
17789
+ if (light.sky) this.highestSkySection.set(columnKey, Math.max(y, this.highestSkySection.get(columnKey) ?? -Infinity));
17790
+ }
17791
+ if (chunk.lightValid !== void 0) this.lightValidity.set(columnKey, chunk.lightValid);
17792
+ if (this.savedLighting) {
17793
+ for (const y of lights.keys()) this.markSectionChanged({ x: chunk.x, y, z: chunk.z });
17794
+ }
17708
17795
  for (const section of chunk.sections) {
17709
17796
  if (section.biomes) {
17710
17797
  const position = new Vector317(chunk.x * 16, section.y * 16, chunk.z * 16);
@@ -17774,6 +17861,12 @@ var MineRenderWorld = class {
17774
17861
  }
17775
17862
  async clearChunkColumn(x, z) {
17776
17863
  this.biomes.delete(`${x}_${z}`);
17864
+ if (this.savedLighting) {
17865
+ for (const y of this.light.get(`${x}_${z}`)?.keys() ?? []) this.markSectionChanged({ x, y, z });
17866
+ }
17867
+ this.light.delete(`${x}_${z}`);
17868
+ this.lightValidity.delete(`${x}_${z}`);
17869
+ this.highestSkySection.delete(`${x}_${z}`);
17777
17870
  const previous = [...this._chunks.entries()].filter(([, section]) => section.x === x && section.z === z);
17778
17871
  for (const [key, section] of previous) {
17779
17872
  this._chunks.delete(key);
@@ -17783,7 +17876,8 @@ var MineRenderWorld = class {
17783
17876
  }
17784
17877
  }
17785
17878
  markSectionChanged(chunk) {
17786
- if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingCulling.set(chunk, "all");
17879
+ const current = this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`);
17880
+ if (current && current === chunk) this.pendingCulling.set(current, "all");
17787
17881
  for (let dy = -1; dy <= 1; dy++) {
17788
17882
  for (let dz = -1; dz <= 1; dz++) {
17789
17883
  for (let dx = -1; dx <= 1; dx++) {
@@ -17811,6 +17905,9 @@ var MineRenderWorld = class {
17811
17905
  const chunks = [...this._chunks.values()];
17812
17906
  this._chunks.clear();
17813
17907
  this.biomes.clear();
17908
+ this.light.clear();
17909
+ this.lightValidity.clear();
17910
+ this.highestSkySection.clear();
17814
17911
  for (const chunk of chunks) {
17815
17912
  await chunk.dispose();
17816
17913
  }
@@ -17873,6 +17970,7 @@ var MineRenderWorld = class {
17873
17970
  }
17874
17971
  }
17875
17972
  for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
17973
+ await chunk.refreshLight(indices === "all" ? void 0 : indices);
17876
17974
  }
17877
17975
  await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
17878
17976
  }
@@ -17913,7 +18011,23 @@ var MineRenderWorld = class {
17913
18011
  }
17914
18012
  await chunk.refreshBiomes();
17915
18013
  await this._chunks.get(`${x}_${y + 1}_${z}`)?.refreshBiomes();
17916
- }
18014
+ },
18015
+ this.savedLighting ? (x2, y2, z2) => this.lightLevelAt(x2, y2, z2) : void 0,
18016
+ async () => {
18017
+ if (this._chunks.get(key) !== chunk) return;
18018
+ const columnKey = `${x}_${z}`, column = this.light.get(columnKey);
18019
+ if (!column?.delete(y)) return;
18020
+ if (!column.size) this.light.delete(columnKey);
18021
+ this.highestSkySection.delete(columnKey);
18022
+ for (const [y2, light] of column) {
18023
+ if (light.sky) this.highestSkySection.set(columnKey, Math.max(y2, this.highestSkySection.get(columnKey) ?? -Infinity));
18024
+ }
18025
+ if (this.savedLighting) {
18026
+ this.markSectionChanged(chunk);
18027
+ await this.updateCulling([]);
18028
+ }
18029
+ },
18030
+ this.ambientLight
17917
18031
  );
17918
18032
  this._chunks.set(key, chunk);
17919
18033
  }
@@ -17962,12 +18076,43 @@ import { BufferGeometry as BufferGeometry9, Color as Color12, DoubleSide as Doub
17962
18076
 
17963
18077
  // src/world/SectionGeometry.ts
17964
18078
  init_FluidQuads();
18079
+
18080
+ // src/world/_light/FaceLight.ts
18081
+ function lightBrightness(level, ambient = 0) {
18082
+ if (level === 255) return 1;
18083
+ const curve = level / (60 - 3 * level);
18084
+ return curve + ambient * (1 - curve);
18085
+ }
18086
+ function quadLightIndex(positions, normals, quad) {
18087
+ const start = quad * 12;
18088
+ let axis = 0;
18089
+ if (Math.abs(normals[start + 1]) > Math.abs(normals[start + axis])) axis = 1;
18090
+ if (Math.abs(normals[start + 2]) > Math.abs(normals[start + axis])) axis = 2;
18091
+ const positive2 = normals[start + axis] > 0;
18092
+ const boundary = positive2 ? 8 : -8;
18093
+ for (let vertex = 0; vertex < 4; vertex++) {
18094
+ if (Math.abs(positions[start + vertex * 3 + axis] - boundary) > 16e-4) return 0;
18095
+ }
18096
+ return axis * 2 + (positive2 ? 1 : 2);
18097
+ }
18098
+ function fluidLightLevel(center, neighbor) {
18099
+ return Math.max(center === 255 ? 15 : center, neighbor === 255 ? 15 : neighbor);
18100
+ }
18101
+ var LIGHT_SAMPLE_OFFSETS = [0, 1, -1, 324, -324, 18, -18];
18102
+
18103
+ // src/world/SectionGeometry.ts
17965
18104
  function buildSectionGeometry(input) {
17966
- const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids } = input;
18105
+ const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight } = input;
17967
18106
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
17968
18107
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
17969
18108
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
17970
18109
  if (fluids?.waterTints && fluids.waterTints.length !== 4096 * 3) throw new RangeError("Section water tints must contain RGB for 4096 cells");
18110
+ if (light && (light.length !== 5832 || light.some((level) => level > 15 && level !== 255))) {
18111
+ throw new RangeError("Section light must cover an 18\xD718\xD718 neighborhood with levels from 0 to 15 or 255");
18112
+ }
18113
+ if (ambientLight !== void 0 && (!Number.isFinite(ambientLight) || ambientLight < 0 || ambientLight > 1)) {
18114
+ throw new RangeError("Section ambient light must be a number from 0 to 1");
18115
+ }
17971
18116
  for (const template of templateData) {
17972
18117
  const { quads } = template;
17973
18118
  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) {
@@ -18072,8 +18217,10 @@ function buildSectionGeometry(input) {
18072
18217
  const offsetX = indices[entry] % 16 * 16;
18073
18218
  const offsetY = Math.floor(indices[entry] / 256) * 16;
18074
18219
  const offsetZ = Math.floor(indices[entry] / 16) % 16 * 16;
18220
+ const lightIndex = (offsetY / 16 + 1) * 324 + (offsetZ / 16 + 1) * 18 + offsetX / 16 + 1;
18075
18221
  for (let quad = 0; quad < template.quads; quad++) {
18076
18222
  if (cullMasks[entry] & template.cullFaces[quad]) continue;
18223
+ const brightness = light ? lightBrightness(light[lightIndex + LIGHT_SAMPLE_OFFSETS[quadLightIndex(template.positions, template.normals, quad)]], ambientLight) : 1;
18077
18224
  const start = vertex;
18078
18225
  for (let corner = 0; corner < 4; corner++, vertex++) {
18079
18226
  const source = quad * 4 + corner;
@@ -18089,9 +18236,9 @@ function buildSectionGeometry(input) {
18089
18236
  uvBounds[vertex * 4 + 1] = mapV(template.uvBounds[source * 4 + 1]);
18090
18237
  uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
18091
18238
  uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
18092
- colors[vertex * 3] = template.colors[source * 3];
18093
- colors[vertex * 3 + 1] = template.colors[source * 3 + 1];
18094
- colors[vertex * 3 + 2] = template.colors[source * 3 + 2];
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;
18095
18242
  }
18096
18243
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
18097
18244
  }
@@ -18101,8 +18248,13 @@ function buildSectionGeometry(input) {
18101
18248
  const placement = page.placements[0];
18102
18249
  const mapU = (u) => (placement.x + u * atlas.width) / page.width;
18103
18250
  const mapV = (v) => 1 - (placement.y + (1 - v) * atlas.height) / page.height;
18251
+ const lightIndex = (y + 1) * 324 + (z + 1) * 18 + x + 1;
18104
18252
  for (let quad = 0; quad < quads.sprites.length; quad++) {
18105
18253
  const rect = quads.sprites[quad] === 0 ? data.still : data.flow;
18254
+ const brightness = light ? lightBrightness(fluidLightLevel(
18255
+ light[lightIndex],
18256
+ light[lightIndex + (quads.normals[quad * 12 + 1] < 0 ? -324 : 324)]
18257
+ ), ambientLight) : 1;
18106
18258
  const start = vertex;
18107
18259
  for (let corner = 0; corner < 4; corner++, vertex++) {
18108
18260
  const source = quad * 4 + corner;
@@ -18120,7 +18272,9 @@ function buildSectionGeometry(input) {
18120
18272
  mapU((rect[0] + rect[2]) / atlas.width),
18121
18273
  mapV(1 - rect[1] / atlas.height)
18122
18274
  ], vertex * 4);
18123
- colors.set(tint, vertex * 3);
18275
+ colors[vertex * 3] = tint[0] * brightness;
18276
+ colors[vertex * 3 + 1] = tint[1] * brightness;
18277
+ colors[vertex * 3 + 2] = tint[2] * brightness;
18124
18278
  }
18125
18279
  outputIndices.set([start, start + 1, start + 2, start, start + 2, start + 3], indexOffset);
18126
18280
  indexOffset += 6;
@@ -18189,7 +18343,7 @@ var SectionWorker = class _SectionWorker {
18189
18343
  // src/world/SectionMesh.ts
18190
18344
  var templateCache = /* @__PURE__ */ new WeakMap();
18191
18345
  var WATER_TINT = new Color12(4159204).toArray();
18192
- function toInput(entries, maxAtlasSize, fluids) {
18346
+ function toInput(entries, maxAtlasSize, fluids, lighting) {
18193
18347
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
18194
18348
  const atlases = [];
18195
18349
  const atlasIds = /* @__PURE__ */ new Map();
@@ -18201,7 +18355,8 @@ function toInput(entries, maxAtlasSize, fluids) {
18201
18355
  cullMasks: new Uint8Array(entries.length),
18202
18356
  templateData: [],
18203
18357
  atlases: [],
18204
- maxAtlasSize
18358
+ maxAtlasSize,
18359
+ ...lighting
18205
18360
  };
18206
18361
  for (let i = 0; i < entries.length; i++) {
18207
18362
  const { index, template, cullMask } = entries[i];
@@ -18289,13 +18444,13 @@ var SectionMesh = class _SectionMesh extends Group3 {
18289
18444
  this.ownedMeshes = [];
18290
18445
  }
18291
18446
  /** Builds section-local meshes from visible quads. `maxAtlasSize` limits each atlas dimension in pixels. */
18292
- static build(entries, maxAtlasSize = 2048, fluids) {
18293
- const { input, atlases } = toInput(entries, maxAtlasSize, fluids);
18447
+ static build(entries, maxAtlasSize = 2048, fluids, lighting) {
18448
+ const { input, atlases } = toInput(entries, maxAtlasSize, fluids, lighting);
18294
18449
  return this.fromPages(buildSectionGeometry(input), atlases);
18295
18450
  }
18296
18451
  /** Builds section-local meshes in a browser worker when available, with synchronous fallback. */
18297
- static async buildAsync(entries, maxAtlasSize = 2048, fluids) {
18298
- const { input, atlases } = toInput(entries, maxAtlasSize, fluids);
18452
+ static async buildAsync(entries, maxAtlasSize = 2048, fluids, lighting) {
18453
+ const { input, atlases } = toInput(entries, maxAtlasSize, fluids, lighting);
18299
18454
  const worker = SectionWorker.shared();
18300
18455
  const pages = worker ? await worker.build(input).catch(() => buildSectionGeometry(input)) : buildSectionGeometry(input);
18301
18456
  return this.fromPages(pages, atlases);
@@ -18365,12 +18520,15 @@ init_FluidQuads();
18365
18520
  init_CubeFace();
18366
18521
  var WATER_TINT2 = new Color13(4159204).toArray();
18367
18522
  var Chunk = class _Chunk {
18368
- constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged) {
18523
+ constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0) {
18369
18524
  this.onBlocksChanged = onBlocksChanged;
18370
18525
  this.sectionModels = sectionModels;
18371
18526
  this.neighborCell = neighborCell;
18372
18527
  this.biomeAt = biomeAt;
18373
18528
  this.onBiomesChanged = onBiomesChanged;
18529
+ this.lightAt = lightAt;
18530
+ this.onLightCleared = onLightCleared;
18531
+ this.ambientLight = ambientLight;
18374
18532
  this.data = new ChunkData();
18375
18533
  this.renderedBlocks = /* @__PURE__ */ new Map();
18376
18534
  this.sectionBlocks = /* @__PURE__ */ new Map();
@@ -18652,6 +18810,13 @@ var Chunk = class _Chunk {
18652
18810
  positionAt(index) {
18653
18811
  return [this.x * 16 + index % 16, this.y * 16 + Math.floor(index / 256), this.z * 16 + Math.floor(index / 16) % 16];
18654
18812
  }
18813
+ /** Refreshes saved lighting after adjacent columns load or unload, including columns with only light data. */
18814
+ async refreshLight(indices) {
18815
+ if (!this.lightAt) return;
18816
+ for (const index of indices ?? this.renderedBlocks.keys()) {
18817
+ if (this.renderedBlocks.has(index)) this.meshDirty = true;
18818
+ }
18819
+ }
18655
18820
  biomePosition(index, blockState3, stored) {
18656
18821
  const position = this.positionAt(index), name = blockState3.key?.toNamespacedString();
18657
18822
  if (stored.properties?.half === "upper" && (name === "minecraft:tall_grass" || name === "minecraft:large_fern")) position[1]--;
@@ -18829,7 +18994,23 @@ var Chunk = class _Chunk {
18829
18994
  fluids[kind] = await this.sectionModels.fluidAtlas(kind, state?.key?.root);
18830
18995
  }
18831
18996
  }
18832
- const next = entries.length || fluids ? await SectionMesh.buildAsync(entries, this.sectionModels.maxAtlasSize, fluids) : void 0;
18997
+ let light;
18998
+ if (this.lightAt) {
18999
+ light = new Uint8Array(18 * 18 * 18);
19000
+ for (let y = -1; y <= 16; y++) {
19001
+ for (let z = -1; z <= 16; z++) {
19002
+ for (let x = -1; x <= 16; x++) {
19003
+ light[(y + 1) * 324 + (z + 1) * 18 + x + 1] = this.lightAt(this.x * 16 + x, this.y * 16 + y, this.z * 16 + z);
19004
+ }
19005
+ }
19006
+ }
19007
+ }
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;
18833
19014
  if (generation !== this.meshGeneration) {
18834
19015
  next?.dispose();
18835
19016
  return;
@@ -18861,6 +19042,7 @@ var Chunk = class _Chunk {
18861
19042
  this.biomes = void 0;
18862
19043
  if (notify) {
18863
19044
  await this.onBiomesChanged?.(void 0);
19045
+ await this.onLightCleared?.();
18864
19046
  await this.onBlocksChanged?.(positions);
18865
19047
  }
18866
19048
  }
@@ -19369,6 +19551,7 @@ export {
19369
19551
  classicSkinTextureCoordinates,
19370
19552
  computeMorphedAttributes,
19371
19553
  computeTangents,
19554
+ copySectionLight,
19372
19555
  createCanvas2 as createCanvas,
19373
19556
  createFluidGeometry,
19374
19557
  createGuiTextGeometry,
@@ -19420,6 +19603,7 @@ export {
19420
19603
  sampleEntityAnimation,
19421
19604
  sampleEntityKeyframes,
19422
19605
  sampleFluid,
19606
+ sampleSectionLight,
19423
19607
  sectionGeometryTransferables,
19424
19608
  serializeBoxGeometryKey,
19425
19609
  serializeImageKey,