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.
@@ -7719,6 +7719,7 @@ __export(index_node_exports, {
7719
7719
  classicSkinTextureCoordinates: () => classicSkinTextureCoordinates,
7720
7720
  computeMorphedAttributes: () => computeMorphedAttributes,
7721
7721
  computeTangents: () => computeTangents,
7722
+ copySectionLight: () => copySectionLight,
7722
7723
  createCanvas: () => createCanvas2,
7723
7724
  createFluidGeometry: () => createFluidGeometry,
7724
7725
  createGuiTextGeometry: () => createGuiTextGeometry,
@@ -7770,6 +7771,7 @@ __export(index_node_exports, {
7770
7771
  sampleEntityAnimation: () => sampleEntityAnimation,
7771
7772
  sampleEntityKeyframes: () => sampleEntityKeyframes,
7772
7773
  sampleFluid: () => sampleFluid,
7774
+ sampleSectionLight: () => sampleSectionLight,
7773
7775
  sectionGeometryTransferables: () => sectionGeometryTransferables,
7774
7776
  serializeBoxGeometryKey: () => serializeBoxGeometryKey,
7775
7777
  serializeImageKey: () => serializeImageKey,
@@ -17027,11 +17029,20 @@ var AnvilParser = class {
17027
17029
  const { root, expected } = await this.readNBT(data, location, options);
17028
17030
  const level = root.Level?.type === "compound" ? root.Level.value : root;
17029
17031
  const dataVersion = root.DataVersion?.type === "int" ? root.DataVersion.value : void 0;
17032
+ const sections = this.compounds(level.sections ?? level.Sections, "sections");
17033
+ const lightFlagName = level.isLightOn ? "isLightOn" : "LightPopulated";
17034
+ const lightFlag = level[lightFlagName];
17035
+ if (lightFlag && (lightFlag.type !== "byte" || lightFlag.value !== 0 && lightFlag.value !== 1)) {
17036
+ throw new MineRenderError(`Anvil ${lightFlagName} must be a byte containing 0 or 1`);
17037
+ }
17038
+ const modernLighting = root.Level?.type !== "compound" || sections.some((section) => section.Palette || section.BlockStates || section.block_states);
17039
+ const lightValid = lightFlag ? lightFlag.value === 1 : modernLighting ? false : void 0;
17030
17040
  const chunk = {
17031
17041
  x: this.integer(level.xPos, "xPos"),
17032
17042
  z: this.integer(level.zPos, "zPos"),
17033
17043
  dataVersion,
17034
- sections: []
17044
+ sections: [],
17045
+ ...lightValid !== void 0 ? { lightValid } : {}
17035
17046
  };
17036
17047
  if ((chunk.x % 32 + 32) % 32 !== location.x || (chunk.z % 32 + 32) % 32 !== location.z) {
17037
17048
  throw new MineRenderError("Anvil chunk coordinates do not match its region location");
@@ -17041,7 +17052,7 @@ var AnvilParser = class {
17041
17052
  }
17042
17053
  const numericStates = /* @__PURE__ */ new Map();
17043
17054
  let legacyMappings;
17044
- for (const section of this.compounds(level.sections ?? level.Sections, "sections")) {
17055
+ for (const section of sections) {
17045
17056
  const y = this.integer(section.Y, "section Y");
17046
17057
  let data2;
17047
17058
  if (section.Blocks || section.Data || section.Add) {
@@ -17071,7 +17082,8 @@ var AnvilParser = class {
17071
17082
  }
17072
17083
  if (chunk.sections.some((section2) => section2.y === y)) throw new MineRenderError(`Duplicate Anvil section Y ${y}`);
17073
17084
  const biomes = this.biomes(section.biomes);
17074
- chunk.sections.push({ y, data: data2, ...biomes ? { biomes } : {} });
17085
+ const light = this.light(section, y, chunk.lightValid);
17086
+ chunk.sections.push({ y, data: data2, ...biomes ? { biomes } : {}, ...light ? { light } : {} });
17075
17087
  }
17076
17088
  for (const entry of this.compounds(level.block_entities ?? level.TileEntities, "block entities")) {
17077
17089
  const x = this.integer(entry.x, "block entity x") - chunk.x * 16;
@@ -17124,6 +17136,18 @@ var AnvilParser = class {
17124
17136
  return { position: [...position.value.value], nbt: { type: "compound", value } };
17125
17137
  });
17126
17138
  }
17139
+ static light(section, y, valid) {
17140
+ const light = {};
17141
+ for (const [name, channel] of [["BlockLight", "block"], ["SkyLight", "sky"]]) {
17142
+ const tag = section[name];
17143
+ if (!tag) continue;
17144
+ if (tag.type !== "byteArray" || !Array.isArray(tag.value) || tag.value.length !== 2048) {
17145
+ throw new MineRenderError(`Anvil section ${y} ${name} must contain 2048 bytes`);
17146
+ }
17147
+ light[channel] = Uint8Array.from(tag.value);
17148
+ }
17149
+ return light.block || light.sky || valid ? light : void 0;
17150
+ }
17127
17151
  static biomes(tag) {
17128
17152
  if (!tag) return void 0;
17129
17153
  if (tag.type !== "compound") throw new MineRenderError("Anvil biomes must be a compound");
@@ -17701,12 +17725,45 @@ var WorldEntities = class {
17701
17725
 
17702
17726
  // src/world/MineRenderWorld.ts
17703
17727
  init_FluidQuads();
17728
+
17729
+ // src/world/WorldLight.ts
17730
+ function copySectionLight(light) {
17731
+ const copy = {};
17732
+ for (const channel of ["block", "sky"]) {
17733
+ const bytes = light[channel];
17734
+ if (bytes === void 0) continue;
17735
+ if (!(bytes instanceof Uint8Array) || bytes.length !== 2048) {
17736
+ throw new RangeError(`Section ${channel} light must contain 2048 packed bytes`);
17737
+ }
17738
+ copy[channel] = new Uint8Array(bytes);
17739
+ }
17740
+ return copy;
17741
+ }
17742
+ function sampleSectionLight(light, index) {
17743
+ if (!light) return void 0;
17744
+ const shift = (index & 1) * 4;
17745
+ return {
17746
+ block: (light.block?.[index >> 1] ?? 0) >> shift & 15,
17747
+ sky: (light.sky?.[index >> 1] ?? 0) >> shift & 15
17748
+ };
17749
+ }
17750
+
17751
+ // src/world/MineRenderWorld.ts
17704
17752
  var MineRenderWorld = class {
17705
17753
  constructor(scene, options = {}) {
17706
17754
  this._chunks = /* @__PURE__ */ new Map();
17707
17755
  this.biomes = /* @__PURE__ */ new Map();
17756
+ this.light = /* @__PURE__ */ new Map();
17757
+ this.lightValidity = /* @__PURE__ */ new Map();
17758
+ this.highestSkySection = /* @__PURE__ */ new Map();
17708
17759
  this.pendingCulling = /* @__PURE__ */ new Map();
17760
+ if (options.savedLighting && !options.sectionMeshing) throw new TypeError("savedLighting requires sectionMeshing");
17761
+ if (options.ambientLight !== void 0 && (!Number.isFinite(options.ambientLight) || options.ambientLight < 0 || options.ambientLight > 1)) {
17762
+ throw new RangeError("ambientLight must be a number from 0 to 1");
17763
+ }
17709
17764
  this.scene = scene;
17765
+ this.savedLighting = options.savedLighting ?? false;
17766
+ this.ambientLight = options.ambientLight ?? 0;
17710
17767
  if (options.sectionMeshing) this.sectionModels = new SectionModels(options.maxAtlasSize);
17711
17768
  if (options.renderEntities) this.entities = new WorldEntities(scene);
17712
17769
  }
@@ -17728,6 +17785,24 @@ var MineRenderWorld = class {
17728
17785
  const lx = posOrX.x - cx * 16, ly = posOrX.y - cy * 16, lz = posOrX.z - cz * 16;
17729
17786
  return this.biomes.get(`${cx}_${cz}`)?.get(cy)?.[Math.floor(lx / 4) + Math.floor(lz / 4) * 4 + Math.floor(ly / 4) * 16];
17730
17787
  }
17788
+ getLightAt(posOrX, y, z) {
17789
+ if (typeof posOrX === "number") return this.getLightAt(new import_three42.Vector3(posOrX, y, z));
17790
+ if (isTripleArray(posOrX)) return this.getLightAt(new import_three42.Vector3(...posOrX));
17791
+ this.validatePosBounds(posOrX);
17792
+ const { x, y: py, z: pz } = posOrX;
17793
+ const cx = Math.floor(x / 16), cy = Math.floor(py / 16), cz = Math.floor(pz / 16);
17794
+ const key = `${cx}_${cz}`, top = this.highestSkySection.get(key);
17795
+ const sample = sampleSectionLight(this.light.get(key)?.get(cy), (x & 15) + (pz & 15) * 16 + (py & 15) * 256);
17796
+ return top !== void 0 && cy > top ? { block: sample?.block ?? 0, sky: 15 } : sample;
17797
+ }
17798
+ lightLevelAt(x, y, z) {
17799
+ const cx = Math.floor(x / 16), cy = Math.floor(y / 16), cz = Math.floor(z / 16), key = `${cx}_${cz}`;
17800
+ if (this.lightValidity.get(key) === false) return 255;
17801
+ const top = this.highestSkySection.get(key);
17802
+ if (top !== void 0 && cy > top) return 15;
17803
+ const sample = sampleSectionLight(this.light.get(key)?.get(cy), (x & 15) + (z & 15) * 16 + (y & 15) * 256);
17804
+ return sample ? Math.max(sample.block, sample.sky) : 255;
17805
+ }
17731
17806
  async setBlockAt(posOrX, yOrBlock, z, block) {
17732
17807
  if (typeof posOrX == "number") {
17733
17808
  return this.setBlockAt(new import_three42.Vector3(posOrX, yOrBlock, z), block);
@@ -17842,7 +17917,7 @@ var MineRenderWorld = class {
17842
17917
  }
17843
17918
  }
17844
17919
  }
17845
- /** Replaces a chunk column's blocks and biome samples and, with `renderEntities`, its supported saved mobs. */
17920
+ /** Replaces a chunk column's blocks, biomes, and saved light and, with `renderEntities`, its supported saved mobs. */
17846
17921
  async placeChunk(chunk, executor) {
17847
17922
  if (![chunk.x, chunk.z].every(Number.isSafeInteger)) {
17848
17923
  throw new RangeError("Chunk column coordinates must be safe integers");
@@ -17850,6 +17925,11 @@ var MineRenderWorld = class {
17850
17925
  if (!chunk.sections.every((section) => Number.isSafeInteger(section.y))) {
17851
17926
  throw new RangeError("Chunk section coordinates must be safe integers");
17852
17927
  }
17928
+ const lights = /* @__PURE__ */ new Map();
17929
+ for (const section of chunk.sections) {
17930
+ const light = section.light ?? (chunk.lightValid === true ? {} : void 0);
17931
+ if (light) lights.set(section.y, copySectionLight(light));
17932
+ }
17853
17933
  this.entities?.clearColumn(chunk.x, chunk.z);
17854
17934
  const placeEntities = this.entities?.prepare(chunk.entities, [chunk.x, chunk.z]);
17855
17935
  const changes = /* @__PURE__ */ new Map();
@@ -17865,6 +17945,15 @@ var MineRenderWorld = class {
17865
17945
  }
17866
17946
  try {
17867
17947
  await this.clearChunkColumn(chunk.x, chunk.z);
17948
+ const columnKey = `${chunk.x}_${chunk.z}`;
17949
+ if (lights.size) this.light.set(columnKey, lights);
17950
+ for (const [y, light] of lights) {
17951
+ if (light.sky) this.highestSkySection.set(columnKey, Math.max(y, this.highestSkySection.get(columnKey) ?? -Infinity));
17952
+ }
17953
+ if (chunk.lightValid !== void 0) this.lightValidity.set(columnKey, chunk.lightValid);
17954
+ if (this.savedLighting) {
17955
+ for (const y of lights.keys()) this.markSectionChanged({ x: chunk.x, y, z: chunk.z });
17956
+ }
17868
17957
  for (const section of chunk.sections) {
17869
17958
  if (section.biomes) {
17870
17959
  const position = new import_three42.Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
@@ -17934,6 +18023,12 @@ var MineRenderWorld = class {
17934
18023
  }
17935
18024
  async clearChunkColumn(x, z) {
17936
18025
  this.biomes.delete(`${x}_${z}`);
18026
+ if (this.savedLighting) {
18027
+ for (const y of this.light.get(`${x}_${z}`)?.keys() ?? []) this.markSectionChanged({ x, y, z });
18028
+ }
18029
+ this.light.delete(`${x}_${z}`);
18030
+ this.lightValidity.delete(`${x}_${z}`);
18031
+ this.highestSkySection.delete(`${x}_${z}`);
17937
18032
  const previous = [...this._chunks.entries()].filter(([, section]) => section.x === x && section.z === z);
17938
18033
  for (const [key, section] of previous) {
17939
18034
  this._chunks.delete(key);
@@ -17943,7 +18038,8 @@ var MineRenderWorld = class {
17943
18038
  }
17944
18039
  }
17945
18040
  markSectionChanged(chunk) {
17946
- if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingCulling.set(chunk, "all");
18041
+ const current = this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`);
18042
+ if (current && current === chunk) this.pendingCulling.set(current, "all");
17947
18043
  for (let dy = -1; dy <= 1; dy++) {
17948
18044
  for (let dz = -1; dz <= 1; dz++) {
17949
18045
  for (let dx = -1; dx <= 1; dx++) {
@@ -17971,6 +18067,9 @@ var MineRenderWorld = class {
17971
18067
  const chunks = [...this._chunks.values()];
17972
18068
  this._chunks.clear();
17973
18069
  this.biomes.clear();
18070
+ this.light.clear();
18071
+ this.lightValidity.clear();
18072
+ this.highestSkySection.clear();
17974
18073
  for (const chunk of chunks) {
17975
18074
  await chunk.dispose();
17976
18075
  }
@@ -18033,6 +18132,7 @@ var MineRenderWorld = class {
18033
18132
  }
18034
18133
  }
18035
18134
  for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
18135
+ await chunk.refreshLight(indices === "all" ? void 0 : indices);
18036
18136
  }
18037
18137
  await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
18038
18138
  }
@@ -18073,7 +18173,23 @@ var MineRenderWorld = class {
18073
18173
  }
18074
18174
  await chunk.refreshBiomes();
18075
18175
  await this._chunks.get(`${x}_${y + 1}_${z}`)?.refreshBiomes();
18076
- }
18176
+ },
18177
+ this.savedLighting ? (x2, y2, z2) => this.lightLevelAt(x2, y2, z2) : void 0,
18178
+ async () => {
18179
+ if (this._chunks.get(key) !== chunk) return;
18180
+ const columnKey = `${x}_${z}`, column = this.light.get(columnKey);
18181
+ if (!column?.delete(y)) return;
18182
+ if (!column.size) this.light.delete(columnKey);
18183
+ this.highestSkySection.delete(columnKey);
18184
+ for (const [y2, light] of column) {
18185
+ if (light.sky) this.highestSkySection.set(columnKey, Math.max(y2, this.highestSkySection.get(columnKey) ?? -Infinity));
18186
+ }
18187
+ if (this.savedLighting) {
18188
+ this.markSectionChanged(chunk);
18189
+ await this.updateCulling([]);
18190
+ }
18191
+ },
18192
+ this.ambientLight
18077
18193
  );
18078
18194
  this._chunks.set(key, chunk);
18079
18195
  }
@@ -18122,12 +18238,43 @@ init_Materials();
18122
18238
 
18123
18239
  // src/world/SectionGeometry.ts
18124
18240
  init_FluidQuads();
18241
+
18242
+ // src/world/_light/FaceLight.ts
18243
+ function lightBrightness(level, ambient = 0) {
18244
+ if (level === 255) return 1;
18245
+ const curve = level / (60 - 3 * level);
18246
+ return curve + ambient * (1 - curve);
18247
+ }
18248
+ function quadLightIndex(positions, normals, quad) {
18249
+ const start = quad * 12;
18250
+ let axis = 0;
18251
+ if (Math.abs(normals[start + 1]) > Math.abs(normals[start + axis])) axis = 1;
18252
+ if (Math.abs(normals[start + 2]) > Math.abs(normals[start + axis])) axis = 2;
18253
+ const positive2 = normals[start + axis] > 0;
18254
+ const boundary = positive2 ? 8 : -8;
18255
+ for (let vertex = 0; vertex < 4; vertex++) {
18256
+ if (Math.abs(positions[start + vertex * 3 + axis] - boundary) > 16e-4) return 0;
18257
+ }
18258
+ return axis * 2 + (positive2 ? 1 : 2);
18259
+ }
18260
+ function fluidLightLevel(center, neighbor) {
18261
+ return Math.max(center === 255 ? 15 : center, neighbor === 255 ? 15 : neighbor);
18262
+ }
18263
+ var LIGHT_SAMPLE_OFFSETS = [0, 1, -1, 324, -324, 18, -18];
18264
+
18265
+ // src/world/SectionGeometry.ts
18125
18266
  function buildSectionGeometry(input) {
18126
- const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids } = input;
18267
+ const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight } = input;
18127
18268
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
18128
18269
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
18129
18270
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
18130
18271
  if (fluids?.waterTints && fluids.waterTints.length !== 4096 * 3) throw new RangeError("Section water tints must contain RGB for 4096 cells");
18272
+ if (light && (light.length !== 5832 || light.some((level) => level > 15 && level !== 255))) {
18273
+ throw new RangeError("Section light must cover an 18\xD718\xD718 neighborhood with levels from 0 to 15 or 255");
18274
+ }
18275
+ if (ambientLight !== void 0 && (!Number.isFinite(ambientLight) || ambientLight < 0 || ambientLight > 1)) {
18276
+ throw new RangeError("Section ambient light must be a number from 0 to 1");
18277
+ }
18131
18278
  for (const template of templateData) {
18132
18279
  const { quads } = template;
18133
18280
  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) {
@@ -18232,8 +18379,10 @@ function buildSectionGeometry(input) {
18232
18379
  const offsetX = indices[entry] % 16 * 16;
18233
18380
  const offsetY = Math.floor(indices[entry] / 256) * 16;
18234
18381
  const offsetZ = Math.floor(indices[entry] / 16) % 16 * 16;
18382
+ const lightIndex = (offsetY / 16 + 1) * 324 + (offsetZ / 16 + 1) * 18 + offsetX / 16 + 1;
18235
18383
  for (let quad = 0; quad < template.quads; quad++) {
18236
18384
  if (cullMasks[entry] & template.cullFaces[quad]) continue;
18385
+ const brightness = light ? lightBrightness(light[lightIndex + LIGHT_SAMPLE_OFFSETS[quadLightIndex(template.positions, template.normals, quad)]], ambientLight) : 1;
18237
18386
  const start = vertex;
18238
18387
  for (let corner = 0; corner < 4; corner++, vertex++) {
18239
18388
  const source = quad * 4 + corner;
@@ -18249,9 +18398,9 @@ function buildSectionGeometry(input) {
18249
18398
  uvBounds[vertex * 4 + 1] = mapV(template.uvBounds[source * 4 + 1]);
18250
18399
  uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
18251
18400
  uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
18252
- colors[vertex * 3] = template.colors[source * 3];
18253
- colors[vertex * 3 + 1] = template.colors[source * 3 + 1];
18254
- colors[vertex * 3 + 2] = template.colors[source * 3 + 2];
18401
+ colors[vertex * 3] = template.colors[source * 3] * brightness;
18402
+ colors[vertex * 3 + 1] = template.colors[source * 3 + 1] * brightness;
18403
+ colors[vertex * 3 + 2] = template.colors[source * 3 + 2] * brightness;
18255
18404
  }
18256
18405
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
18257
18406
  }
@@ -18261,8 +18410,13 @@ function buildSectionGeometry(input) {
18261
18410
  const placement = page.placements[0];
18262
18411
  const mapU = (u) => (placement.x + u * atlas.width) / page.width;
18263
18412
  const mapV = (v) => 1 - (placement.y + (1 - v) * atlas.height) / page.height;
18413
+ const lightIndex = (y + 1) * 324 + (z + 1) * 18 + x + 1;
18264
18414
  for (let quad = 0; quad < quads.sprites.length; quad++) {
18265
18415
  const rect = quads.sprites[quad] === 0 ? data.still : data.flow;
18416
+ const brightness = light ? lightBrightness(fluidLightLevel(
18417
+ light[lightIndex],
18418
+ light[lightIndex + (quads.normals[quad * 12 + 1] < 0 ? -324 : 324)]
18419
+ ), ambientLight) : 1;
18266
18420
  const start = vertex;
18267
18421
  for (let corner = 0; corner < 4; corner++, vertex++) {
18268
18422
  const source = quad * 4 + corner;
@@ -18280,7 +18434,9 @@ function buildSectionGeometry(input) {
18280
18434
  mapU((rect[0] + rect[2]) / atlas.width),
18281
18435
  mapV(1 - rect[1] / atlas.height)
18282
18436
  ], vertex * 4);
18283
- colors.set(tint, vertex * 3);
18437
+ colors[vertex * 3] = tint[0] * brightness;
18438
+ colors[vertex * 3 + 1] = tint[1] * brightness;
18439
+ colors[vertex * 3 + 2] = tint[2] * brightness;
18284
18440
  }
18285
18441
  outputIndices.set([start, start + 1, start + 2, start, start + 2, start + 3], indexOffset);
18286
18442
  indexOffset += 6;
@@ -18349,7 +18505,7 @@ var SectionWorker = class _SectionWorker {
18349
18505
  // src/world/SectionMesh.ts
18350
18506
  var templateCache = /* @__PURE__ */ new WeakMap();
18351
18507
  var WATER_TINT = new import_three43.Color(4159204).toArray();
18352
- function toInput(entries, maxAtlasSize, fluids) {
18508
+ function toInput(entries, maxAtlasSize, fluids, lighting) {
18353
18509
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
18354
18510
  const atlases = [];
18355
18511
  const atlasIds = /* @__PURE__ */ new Map();
@@ -18361,7 +18517,8 @@ function toInput(entries, maxAtlasSize, fluids) {
18361
18517
  cullMasks: new Uint8Array(entries.length),
18362
18518
  templateData: [],
18363
18519
  atlases: [],
18364
- maxAtlasSize
18520
+ maxAtlasSize,
18521
+ ...lighting
18365
18522
  };
18366
18523
  for (let i = 0; i < entries.length; i++) {
18367
18524
  const { index, template, cullMask } = entries[i];
@@ -18449,13 +18606,13 @@ var SectionMesh = class _SectionMesh extends import_three43.Group {
18449
18606
  this.ownedMeshes = [];
18450
18607
  }
18451
18608
  /** Builds section-local meshes from visible quads. `maxAtlasSize` limits each atlas dimension in pixels. */
18452
- static build(entries, maxAtlasSize = 2048, fluids) {
18453
- const { input, atlases } = toInput(entries, maxAtlasSize, fluids);
18609
+ static build(entries, maxAtlasSize = 2048, fluids, lighting) {
18610
+ const { input, atlases } = toInput(entries, maxAtlasSize, fluids, lighting);
18454
18611
  return this.fromPages(buildSectionGeometry(input), atlases);
18455
18612
  }
18456
18613
  /** Builds section-local meshes in a browser worker when available, with synchronous fallback. */
18457
- static async buildAsync(entries, maxAtlasSize = 2048, fluids) {
18458
- const { input, atlases } = toInput(entries, maxAtlasSize, fluids);
18614
+ static async buildAsync(entries, maxAtlasSize = 2048, fluids, lighting) {
18615
+ const { input, atlases } = toInput(entries, maxAtlasSize, fluids, lighting);
18459
18616
  const worker = SectionWorker.shared();
18460
18617
  const pages = worker ? await worker.build(input).catch(() => buildSectionGeometry(input)) : buildSectionGeometry(input);
18461
18618
  return this.fromPages(pages, atlases);
@@ -18525,12 +18682,15 @@ init_FluidQuads();
18525
18682
  init_CubeFace();
18526
18683
  var WATER_TINT2 = new import_three44.Color(4159204).toArray();
18527
18684
  var Chunk = class _Chunk {
18528
- constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged) {
18685
+ constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0) {
18529
18686
  this.onBlocksChanged = onBlocksChanged;
18530
18687
  this.sectionModels = sectionModels;
18531
18688
  this.neighborCell = neighborCell;
18532
18689
  this.biomeAt = biomeAt;
18533
18690
  this.onBiomesChanged = onBiomesChanged;
18691
+ this.lightAt = lightAt;
18692
+ this.onLightCleared = onLightCleared;
18693
+ this.ambientLight = ambientLight;
18534
18694
  this.data = new ChunkData();
18535
18695
  this.renderedBlocks = /* @__PURE__ */ new Map();
18536
18696
  this.sectionBlocks = /* @__PURE__ */ new Map();
@@ -18812,6 +18972,13 @@ var Chunk = class _Chunk {
18812
18972
  positionAt(index) {
18813
18973
  return [this.x * 16 + index % 16, this.y * 16 + Math.floor(index / 256), this.z * 16 + Math.floor(index / 16) % 16];
18814
18974
  }
18975
+ /** Refreshes saved lighting after adjacent columns load or unload, including columns with only light data. */
18976
+ async refreshLight(indices) {
18977
+ if (!this.lightAt) return;
18978
+ for (const index of indices ?? this.renderedBlocks.keys()) {
18979
+ if (this.renderedBlocks.has(index)) this.meshDirty = true;
18980
+ }
18981
+ }
18815
18982
  biomePosition(index, blockState3, stored) {
18816
18983
  const position = this.positionAt(index), name = blockState3.key?.toNamespacedString();
18817
18984
  if (stored.properties?.half === "upper" && (name === "minecraft:tall_grass" || name === "minecraft:large_fern")) position[1]--;
@@ -18989,7 +19156,23 @@ var Chunk = class _Chunk {
18989
19156
  fluids[kind] = await this.sectionModels.fluidAtlas(kind, state?.key?.root);
18990
19157
  }
18991
19158
  }
18992
- const next = entries.length || fluids ? await SectionMesh.buildAsync(entries, this.sectionModels.maxAtlasSize, fluids) : void 0;
19159
+ let light;
19160
+ if (this.lightAt) {
19161
+ light = new Uint8Array(18 * 18 * 18);
19162
+ for (let y = -1; y <= 16; y++) {
19163
+ for (let z = -1; z <= 16; z++) {
19164
+ for (let x = -1; x <= 16; x++) {
19165
+ light[(y + 1) * 324 + (z + 1) * 18 + x + 1] = this.lightAt(this.x * 16 + x, this.y * 16 + y, this.z * 16 + z);
19166
+ }
19167
+ }
19168
+ }
19169
+ }
19170
+ const next = entries.length || fluids ? await SectionMesh.buildAsync(
19171
+ entries,
19172
+ this.sectionModels.maxAtlasSize,
19173
+ fluids,
19174
+ light ? { light, ambientLight: this.ambientLight } : void 0
19175
+ ) : void 0;
18993
19176
  if (generation !== this.meshGeneration) {
18994
19177
  next?.dispose();
18995
19178
  return;
@@ -19021,6 +19204,7 @@ var Chunk = class _Chunk {
19021
19204
  this.biomes = void 0;
19022
19205
  if (notify) {
19023
19206
  await this.onBiomesChanged?.(void 0);
19207
+ await this.onLightCleared?.();
19024
19208
  await this.onBlocksChanged?.(positions);
19025
19209
  }
19026
19210
  }
@@ -19530,6 +19714,7 @@ var NodeRenderer = class _NodeRenderer extends Renderer {
19530
19714
  classicSkinTextureCoordinates,
19531
19715
  computeMorphedAttributes,
19532
19716
  computeTangents,
19717
+ copySectionLight,
19533
19718
  createCanvas,
19534
19719
  createFluidGeometry,
19535
19720
  createGuiTextGeometry,
@@ -19581,6 +19766,7 @@ var NodeRenderer = class _NodeRenderer extends Renderer {
19581
19766
  sampleEntityAnimation,
19582
19767
  sampleEntityKeyframes,
19583
19768
  sampleFluid,
19769
+ sampleSectionLight,
19584
19770
  sectionGeometryTransferables,
19585
19771
  serializeBoxGeometryKey,
19586
19772
  serializeImageKey,