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.
@@ -7618,6 +7618,7 @@ __export(index_browser_exports, {
7618
7618
  classicSkinTextureCoordinates: () => classicSkinTextureCoordinates,
7619
7619
  computeMorphedAttributes: () => computeMorphedAttributes,
7620
7620
  computeTangents: () => computeTangents,
7621
+ copySectionLight: () => copySectionLight,
7621
7622
  createCanvas: () => createCanvas,
7622
7623
  createFluidGeometry: () => createFluidGeometry,
7623
7624
  createGuiTextGeometry: () => createGuiTextGeometry,
@@ -7670,6 +7671,7 @@ __export(index_browser_exports, {
7670
7671
  sampleEntityAnimation: () => sampleEntityAnimation,
7671
7672
  sampleEntityKeyframes: () => sampleEntityKeyframes,
7672
7673
  sampleFluid: () => sampleFluid,
7674
+ sampleSectionLight: () => sampleSectionLight,
7673
7675
  sectionGeometryTransferables: () => sectionGeometryTransferables,
7674
7676
  serializeBoxGeometryKey: () => serializeBoxGeometryKey,
7675
7677
  serializeImageKey: () => serializeImageKey,
@@ -16992,11 +16994,20 @@ var AnvilParser = class {
16992
16994
  const { root, expected } = await this.readNBT(data, location, options);
16993
16995
  const level = root.Level?.type === "compound" ? root.Level.value : root;
16994
16996
  const dataVersion = root.DataVersion?.type === "int" ? root.DataVersion.value : void 0;
16997
+ const sections = this.compounds(level.sections ?? level.Sections, "sections");
16998
+ const lightFlagName = level.isLightOn ? "isLightOn" : "LightPopulated";
16999
+ const lightFlag = level[lightFlagName];
17000
+ if (lightFlag && (lightFlag.type !== "byte" || lightFlag.value !== 0 && lightFlag.value !== 1)) {
17001
+ throw new MineRenderError(`Anvil ${lightFlagName} must be a byte containing 0 or 1`);
17002
+ }
17003
+ const modernLighting = root.Level?.type !== "compound" || sections.some((section) => section.Palette || section.BlockStates || section.block_states);
17004
+ const lightValid = lightFlag ? lightFlag.value === 1 : modernLighting ? false : void 0;
16995
17005
  const chunk = {
16996
17006
  x: this.integer(level.xPos, "xPos"),
16997
17007
  z: this.integer(level.zPos, "zPos"),
16998
17008
  dataVersion,
16999
- sections: []
17009
+ sections: [],
17010
+ ...lightValid !== void 0 ? { lightValid } : {}
17000
17011
  };
17001
17012
  if ((chunk.x % 32 + 32) % 32 !== location.x || (chunk.z % 32 + 32) % 32 !== location.z) {
17002
17013
  throw new MineRenderError("Anvil chunk coordinates do not match its region location");
@@ -17006,7 +17017,7 @@ var AnvilParser = class {
17006
17017
  }
17007
17018
  const numericStates = /* @__PURE__ */ new Map();
17008
17019
  let legacyMappings;
17009
- for (const section of this.compounds(level.sections ?? level.Sections, "sections")) {
17020
+ for (const section of sections) {
17010
17021
  const y = this.integer(section.Y, "section Y");
17011
17022
  let data2;
17012
17023
  if (section.Blocks || section.Data || section.Add) {
@@ -17036,7 +17047,8 @@ var AnvilParser = class {
17036
17047
  }
17037
17048
  if (chunk.sections.some((section2) => section2.y === y)) throw new MineRenderError(`Duplicate Anvil section Y ${y}`);
17038
17049
  const biomes = this.biomes(section.biomes);
17039
- chunk.sections.push({ y, data: data2, ...biomes ? { biomes } : {} });
17050
+ const light = this.light(section, y, chunk.lightValid);
17051
+ chunk.sections.push({ y, data: data2, ...biomes ? { biomes } : {}, ...light ? { light } : {} });
17040
17052
  }
17041
17053
  for (const entry of this.compounds(level.block_entities ?? level.TileEntities, "block entities")) {
17042
17054
  const x = this.integer(entry.x, "block entity x") - chunk.x * 16;
@@ -17089,6 +17101,18 @@ var AnvilParser = class {
17089
17101
  return { position: [...position.value.value], nbt: { type: "compound", value } };
17090
17102
  });
17091
17103
  }
17104
+ static light(section, y, valid) {
17105
+ const light = {};
17106
+ for (const [name, channel] of [["BlockLight", "block"], ["SkyLight", "sky"]]) {
17107
+ const tag = section[name];
17108
+ if (!tag) continue;
17109
+ if (tag.type !== "byteArray" || !Array.isArray(tag.value) || tag.value.length !== 2048) {
17110
+ throw new MineRenderError(`Anvil section ${y} ${name} must contain 2048 bytes`);
17111
+ }
17112
+ light[channel] = Uint8Array.from(tag.value);
17113
+ }
17114
+ return light.block || light.sky || valid ? light : void 0;
17115
+ }
17092
17116
  static biomes(tag) {
17093
17117
  if (!tag) return void 0;
17094
17118
  if (tag.type !== "compound") throw new MineRenderError("Anvil biomes must be a compound");
@@ -17666,12 +17690,45 @@ var WorldEntities = class {
17666
17690
 
17667
17691
  // src/world/MineRenderWorld.ts
17668
17692
  init_FluidQuads();
17693
+
17694
+ // src/world/WorldLight.ts
17695
+ function copySectionLight(light) {
17696
+ const copy = {};
17697
+ for (const channel of ["block", "sky"]) {
17698
+ const bytes = light[channel];
17699
+ if (bytes === void 0) continue;
17700
+ if (!(bytes instanceof Uint8Array) || bytes.length !== 2048) {
17701
+ throw new RangeError(`Section ${channel} light must contain 2048 packed bytes`);
17702
+ }
17703
+ copy[channel] = new Uint8Array(bytes);
17704
+ }
17705
+ return copy;
17706
+ }
17707
+ function sampleSectionLight(light, index) {
17708
+ if (!light) return void 0;
17709
+ const shift = (index & 1) * 4;
17710
+ return {
17711
+ block: (light.block?.[index >> 1] ?? 0) >> shift & 15,
17712
+ sky: (light.sky?.[index >> 1] ?? 0) >> shift & 15
17713
+ };
17714
+ }
17715
+
17716
+ // src/world/MineRenderWorld.ts
17669
17717
  var MineRenderWorld = class {
17670
17718
  constructor(scene, options = {}) {
17671
17719
  this._chunks = /* @__PURE__ */ new Map();
17672
17720
  this.biomes = /* @__PURE__ */ new Map();
17721
+ this.light = /* @__PURE__ */ new Map();
17722
+ this.lightValidity = /* @__PURE__ */ new Map();
17723
+ this.highestSkySection = /* @__PURE__ */ new Map();
17673
17724
  this.pendingCulling = /* @__PURE__ */ new Map();
17725
+ if (options.savedLighting && !options.sectionMeshing) throw new TypeError("savedLighting requires sectionMeshing");
17726
+ if (options.ambientLight !== void 0 && (!Number.isFinite(options.ambientLight) || options.ambientLight < 0 || options.ambientLight > 1)) {
17727
+ throw new RangeError("ambientLight must be a number from 0 to 1");
17728
+ }
17674
17729
  this.scene = scene;
17730
+ this.savedLighting = options.savedLighting ?? false;
17731
+ this.ambientLight = options.ambientLight ?? 0;
17675
17732
  if (options.sectionMeshing) this.sectionModels = new SectionModels(options.maxAtlasSize);
17676
17733
  if (options.renderEntities) this.entities = new WorldEntities(scene);
17677
17734
  }
@@ -17693,6 +17750,24 @@ var MineRenderWorld = class {
17693
17750
  const lx = posOrX.x - cx * 16, ly = posOrX.y - cy * 16, lz = posOrX.z - cz * 16;
17694
17751
  return this.biomes.get(`${cx}_${cz}`)?.get(cy)?.[Math.floor(lx / 4) + Math.floor(lz / 4) * 4 + Math.floor(ly / 4) * 16];
17695
17752
  }
17753
+ getLightAt(posOrX, y, z) {
17754
+ if (typeof posOrX === "number") return this.getLightAt(new import_three42.Vector3(posOrX, y, z));
17755
+ if (isTripleArray(posOrX)) return this.getLightAt(new import_three42.Vector3(...posOrX));
17756
+ this.validatePosBounds(posOrX);
17757
+ const { x, y: py, z: pz } = posOrX;
17758
+ const cx = Math.floor(x / 16), cy = Math.floor(py / 16), cz = Math.floor(pz / 16);
17759
+ const key = `${cx}_${cz}`, top = this.highestSkySection.get(key);
17760
+ const sample = sampleSectionLight(this.light.get(key)?.get(cy), (x & 15) + (pz & 15) * 16 + (py & 15) * 256);
17761
+ return top !== void 0 && cy > top ? { block: sample?.block ?? 0, sky: 15 } : sample;
17762
+ }
17763
+ lightLevelAt(x, y, z) {
17764
+ const cx = Math.floor(x / 16), cy = Math.floor(y / 16), cz = Math.floor(z / 16), key = `${cx}_${cz}`;
17765
+ if (this.lightValidity.get(key) === false) return 255;
17766
+ const top = this.highestSkySection.get(key);
17767
+ if (top !== void 0 && cy > top) return 15;
17768
+ const sample = sampleSectionLight(this.light.get(key)?.get(cy), (x & 15) + (z & 15) * 16 + (y & 15) * 256);
17769
+ return sample ? Math.max(sample.block, sample.sky) : 255;
17770
+ }
17696
17771
  async setBlockAt(posOrX, yOrBlock, z, block) {
17697
17772
  if (typeof posOrX == "number") {
17698
17773
  return this.setBlockAt(new import_three42.Vector3(posOrX, yOrBlock, z), block);
@@ -17807,7 +17882,7 @@ var MineRenderWorld = class {
17807
17882
  }
17808
17883
  }
17809
17884
  }
17810
- /** Replaces a chunk column's blocks and biome samples and, with `renderEntities`, its supported saved mobs. */
17885
+ /** Replaces a chunk column's blocks, biomes, and saved light and, with `renderEntities`, its supported saved mobs. */
17811
17886
  async placeChunk(chunk, executor) {
17812
17887
  if (![chunk.x, chunk.z].every(Number.isSafeInteger)) {
17813
17888
  throw new RangeError("Chunk column coordinates must be safe integers");
@@ -17815,6 +17890,11 @@ var MineRenderWorld = class {
17815
17890
  if (!chunk.sections.every((section) => Number.isSafeInteger(section.y))) {
17816
17891
  throw new RangeError("Chunk section coordinates must be safe integers");
17817
17892
  }
17893
+ const lights = /* @__PURE__ */ new Map();
17894
+ for (const section of chunk.sections) {
17895
+ const light = section.light ?? (chunk.lightValid === true ? {} : void 0);
17896
+ if (light) lights.set(section.y, copySectionLight(light));
17897
+ }
17818
17898
  this.entities?.clearColumn(chunk.x, chunk.z);
17819
17899
  const placeEntities = this.entities?.prepare(chunk.entities, [chunk.x, chunk.z]);
17820
17900
  const changes = /* @__PURE__ */ new Map();
@@ -17830,6 +17910,15 @@ var MineRenderWorld = class {
17830
17910
  }
17831
17911
  try {
17832
17912
  await this.clearChunkColumn(chunk.x, chunk.z);
17913
+ const columnKey = `${chunk.x}_${chunk.z}`;
17914
+ if (lights.size) this.light.set(columnKey, lights);
17915
+ for (const [y, light] of lights) {
17916
+ if (light.sky) this.highestSkySection.set(columnKey, Math.max(y, this.highestSkySection.get(columnKey) ?? -Infinity));
17917
+ }
17918
+ if (chunk.lightValid !== void 0) this.lightValidity.set(columnKey, chunk.lightValid);
17919
+ if (this.savedLighting) {
17920
+ for (const y of lights.keys()) this.markSectionChanged({ x: chunk.x, y, z: chunk.z });
17921
+ }
17833
17922
  for (const section of chunk.sections) {
17834
17923
  if (section.biomes) {
17835
17924
  const position = new import_three42.Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
@@ -17899,6 +17988,12 @@ var MineRenderWorld = class {
17899
17988
  }
17900
17989
  async clearChunkColumn(x, z) {
17901
17990
  this.biomes.delete(`${x}_${z}`);
17991
+ if (this.savedLighting) {
17992
+ for (const y of this.light.get(`${x}_${z}`)?.keys() ?? []) this.markSectionChanged({ x, y, z });
17993
+ }
17994
+ this.light.delete(`${x}_${z}`);
17995
+ this.lightValidity.delete(`${x}_${z}`);
17996
+ this.highestSkySection.delete(`${x}_${z}`);
17902
17997
  const previous = [...this._chunks.entries()].filter(([, section]) => section.x === x && section.z === z);
17903
17998
  for (const [key, section] of previous) {
17904
17999
  this._chunks.delete(key);
@@ -17908,7 +18003,8 @@ var MineRenderWorld = class {
17908
18003
  }
17909
18004
  }
17910
18005
  markSectionChanged(chunk) {
17911
- if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingCulling.set(chunk, "all");
18006
+ const current = this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`);
18007
+ if (current && current === chunk) this.pendingCulling.set(current, "all");
17912
18008
  for (let dy = -1; dy <= 1; dy++) {
17913
18009
  for (let dz = -1; dz <= 1; dz++) {
17914
18010
  for (let dx = -1; dx <= 1; dx++) {
@@ -17936,6 +18032,9 @@ var MineRenderWorld = class {
17936
18032
  const chunks = [...this._chunks.values()];
17937
18033
  this._chunks.clear();
17938
18034
  this.biomes.clear();
18035
+ this.light.clear();
18036
+ this.lightValidity.clear();
18037
+ this.highestSkySection.clear();
17939
18038
  for (const chunk of chunks) {
17940
18039
  await chunk.dispose();
17941
18040
  }
@@ -17998,6 +18097,7 @@ var MineRenderWorld = class {
17998
18097
  }
17999
18098
  }
18000
18099
  for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
18100
+ await chunk.refreshLight(indices === "all" ? void 0 : indices);
18001
18101
  }
18002
18102
  await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
18003
18103
  }
@@ -18038,7 +18138,23 @@ var MineRenderWorld = class {
18038
18138
  }
18039
18139
  await chunk.refreshBiomes();
18040
18140
  await this._chunks.get(`${x}_${y + 1}_${z}`)?.refreshBiomes();
18041
- }
18141
+ },
18142
+ this.savedLighting ? (x2, y2, z2) => this.lightLevelAt(x2, y2, z2) : void 0,
18143
+ async () => {
18144
+ if (this._chunks.get(key) !== chunk) return;
18145
+ const columnKey = `${x}_${z}`, column = this.light.get(columnKey);
18146
+ if (!column?.delete(y)) return;
18147
+ if (!column.size) this.light.delete(columnKey);
18148
+ this.highestSkySection.delete(columnKey);
18149
+ for (const [y2, light] of column) {
18150
+ if (light.sky) this.highestSkySection.set(columnKey, Math.max(y2, this.highestSkySection.get(columnKey) ?? -Infinity));
18151
+ }
18152
+ if (this.savedLighting) {
18153
+ this.markSectionChanged(chunk);
18154
+ await this.updateCulling([]);
18155
+ }
18156
+ },
18157
+ this.ambientLight
18042
18158
  );
18043
18159
  this._chunks.set(key, chunk);
18044
18160
  }
@@ -18087,12 +18203,43 @@ init_Materials();
18087
18203
 
18088
18204
  // src/world/SectionGeometry.ts
18089
18205
  init_FluidQuads();
18206
+
18207
+ // src/world/_light/FaceLight.ts
18208
+ function lightBrightness(level, ambient = 0) {
18209
+ if (level === 255) return 1;
18210
+ const curve = level / (60 - 3 * level);
18211
+ return curve + ambient * (1 - curve);
18212
+ }
18213
+ function quadLightIndex(positions, normals, quad) {
18214
+ const start = quad * 12;
18215
+ let axis = 0;
18216
+ if (Math.abs(normals[start + 1]) > Math.abs(normals[start + axis])) axis = 1;
18217
+ if (Math.abs(normals[start + 2]) > Math.abs(normals[start + axis])) axis = 2;
18218
+ const positive2 = normals[start + axis] > 0;
18219
+ const boundary = positive2 ? 8 : -8;
18220
+ for (let vertex = 0; vertex < 4; vertex++) {
18221
+ if (Math.abs(positions[start + vertex * 3 + axis] - boundary) > 16e-4) return 0;
18222
+ }
18223
+ return axis * 2 + (positive2 ? 1 : 2);
18224
+ }
18225
+ function fluidLightLevel(center, neighbor) {
18226
+ return Math.max(center === 255 ? 15 : center, neighbor === 255 ? 15 : neighbor);
18227
+ }
18228
+ var LIGHT_SAMPLE_OFFSETS = [0, 1, -1, 324, -324, 18, -18];
18229
+
18230
+ // src/world/SectionGeometry.ts
18090
18231
  function buildSectionGeometry(input) {
18091
- const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids } = input;
18232
+ const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight } = input;
18092
18233
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
18093
18234
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
18094
18235
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
18095
18236
  if (fluids?.waterTints && fluids.waterTints.length !== 4096 * 3) throw new RangeError("Section water tints must contain RGB for 4096 cells");
18237
+ if (light && (light.length !== 5832 || light.some((level) => level > 15 && level !== 255))) {
18238
+ throw new RangeError("Section light must cover an 18\xD718\xD718 neighborhood with levels from 0 to 15 or 255");
18239
+ }
18240
+ if (ambientLight !== void 0 && (!Number.isFinite(ambientLight) || ambientLight < 0 || ambientLight > 1)) {
18241
+ throw new RangeError("Section ambient light must be a number from 0 to 1");
18242
+ }
18096
18243
  for (const template of templateData) {
18097
18244
  const { quads } = template;
18098
18245
  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) {
@@ -18197,8 +18344,10 @@ function buildSectionGeometry(input) {
18197
18344
  const offsetX = indices[entry] % 16 * 16;
18198
18345
  const offsetY = Math.floor(indices[entry] / 256) * 16;
18199
18346
  const offsetZ = Math.floor(indices[entry] / 16) % 16 * 16;
18347
+ const lightIndex = (offsetY / 16 + 1) * 324 + (offsetZ / 16 + 1) * 18 + offsetX / 16 + 1;
18200
18348
  for (let quad = 0; quad < template.quads; quad++) {
18201
18349
  if (cullMasks[entry] & template.cullFaces[quad]) continue;
18350
+ const brightness = light ? lightBrightness(light[lightIndex + LIGHT_SAMPLE_OFFSETS[quadLightIndex(template.positions, template.normals, quad)]], ambientLight) : 1;
18202
18351
  const start = vertex;
18203
18352
  for (let corner = 0; corner < 4; corner++, vertex++) {
18204
18353
  const source = quad * 4 + corner;
@@ -18214,9 +18363,9 @@ function buildSectionGeometry(input) {
18214
18363
  uvBounds[vertex * 4 + 1] = mapV(template.uvBounds[source * 4 + 1]);
18215
18364
  uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
18216
18365
  uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
18217
- colors[vertex * 3] = template.colors[source * 3];
18218
- colors[vertex * 3 + 1] = template.colors[source * 3 + 1];
18219
- colors[vertex * 3 + 2] = template.colors[source * 3 + 2];
18366
+ colors[vertex * 3] = template.colors[source * 3] * brightness;
18367
+ colors[vertex * 3 + 1] = template.colors[source * 3 + 1] * brightness;
18368
+ colors[vertex * 3 + 2] = template.colors[source * 3 + 2] * brightness;
18220
18369
  }
18221
18370
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
18222
18371
  }
@@ -18226,8 +18375,13 @@ function buildSectionGeometry(input) {
18226
18375
  const placement = page.placements[0];
18227
18376
  const mapU = (u) => (placement.x + u * atlas.width) / page.width;
18228
18377
  const mapV = (v) => 1 - (placement.y + (1 - v) * atlas.height) / page.height;
18378
+ const lightIndex = (y + 1) * 324 + (z + 1) * 18 + x + 1;
18229
18379
  for (let quad = 0; quad < quads.sprites.length; quad++) {
18230
18380
  const rect = quads.sprites[quad] === 0 ? data.still : data.flow;
18381
+ const brightness = light ? lightBrightness(fluidLightLevel(
18382
+ light[lightIndex],
18383
+ light[lightIndex + (quads.normals[quad * 12 + 1] < 0 ? -324 : 324)]
18384
+ ), ambientLight) : 1;
18231
18385
  const start = vertex;
18232
18386
  for (let corner = 0; corner < 4; corner++, vertex++) {
18233
18387
  const source = quad * 4 + corner;
@@ -18245,7 +18399,9 @@ function buildSectionGeometry(input) {
18245
18399
  mapU((rect[0] + rect[2]) / atlas.width),
18246
18400
  mapV(1 - rect[1] / atlas.height)
18247
18401
  ], vertex * 4);
18248
- colors.set(tint, vertex * 3);
18402
+ colors[vertex * 3] = tint[0] * brightness;
18403
+ colors[vertex * 3 + 1] = tint[1] * brightness;
18404
+ colors[vertex * 3 + 2] = tint[2] * brightness;
18249
18405
  }
18250
18406
  outputIndices.set([start, start + 1, start + 2, start, start + 2, start + 3], indexOffset);
18251
18407
  indexOffset += 6;
@@ -18313,7 +18469,7 @@ var SectionWorker = _SectionWorker;
18313
18469
  // src/world/SectionMesh.ts
18314
18470
  var templateCache = /* @__PURE__ */ new WeakMap();
18315
18471
  var WATER_TINT = new import_three43.Color(4159204).toArray();
18316
- function toInput(entries, maxAtlasSize, fluids) {
18472
+ function toInput(entries, maxAtlasSize, fluids, lighting) {
18317
18473
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
18318
18474
  const atlases = [];
18319
18475
  const atlasIds = /* @__PURE__ */ new Map();
@@ -18325,7 +18481,8 @@ function toInput(entries, maxAtlasSize, fluids) {
18325
18481
  cullMasks: new Uint8Array(entries.length),
18326
18482
  templateData: [],
18327
18483
  atlases: [],
18328
- maxAtlasSize
18484
+ maxAtlasSize,
18485
+ ...lighting
18329
18486
  };
18330
18487
  for (let i = 0; i < entries.length; i++) {
18331
18488
  const { index, template, cullMask } = entries[i];
@@ -18413,13 +18570,13 @@ var SectionMesh = class _SectionMesh extends import_three43.Group {
18413
18570
  this.ownedMeshes = [];
18414
18571
  }
18415
18572
  /** Builds section-local meshes from visible quads. `maxAtlasSize` limits each atlas dimension in pixels. */
18416
- static build(entries, maxAtlasSize = 2048, fluids) {
18417
- const { input, atlases } = toInput(entries, maxAtlasSize, fluids);
18573
+ static build(entries, maxAtlasSize = 2048, fluids, lighting) {
18574
+ const { input, atlases } = toInput(entries, maxAtlasSize, fluids, lighting);
18418
18575
  return this.fromPages(buildSectionGeometry(input), atlases);
18419
18576
  }
18420
18577
  /** Builds section-local meshes in a browser worker when available, with synchronous fallback. */
18421
- static async buildAsync(entries, maxAtlasSize = 2048, fluids) {
18422
- const { input, atlases } = toInput(entries, maxAtlasSize, fluids);
18578
+ static async buildAsync(entries, maxAtlasSize = 2048, fluids, lighting) {
18579
+ const { input, atlases } = toInput(entries, maxAtlasSize, fluids, lighting);
18423
18580
  const worker = SectionWorker.shared();
18424
18581
  const pages = worker ? await worker.build(input).catch(() => buildSectionGeometry(input)) : buildSectionGeometry(input);
18425
18582
  return this.fromPages(pages, atlases);
@@ -18489,12 +18646,15 @@ init_FluidQuads();
18489
18646
  init_CubeFace();
18490
18647
  var WATER_TINT2 = new import_three44.Color(4159204).toArray();
18491
18648
  var Chunk = class _Chunk {
18492
- constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged) {
18649
+ constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0) {
18493
18650
  this.onBlocksChanged = onBlocksChanged;
18494
18651
  this.sectionModels = sectionModels;
18495
18652
  this.neighborCell = neighborCell;
18496
18653
  this.biomeAt = biomeAt;
18497
18654
  this.onBiomesChanged = onBiomesChanged;
18655
+ this.lightAt = lightAt;
18656
+ this.onLightCleared = onLightCleared;
18657
+ this.ambientLight = ambientLight;
18498
18658
  this.data = new ChunkData();
18499
18659
  this.renderedBlocks = /* @__PURE__ */ new Map();
18500
18660
  this.sectionBlocks = /* @__PURE__ */ new Map();
@@ -18776,6 +18936,13 @@ var Chunk = class _Chunk {
18776
18936
  positionAt(index) {
18777
18937
  return [this.x * 16 + index % 16, this.y * 16 + Math.floor(index / 256), this.z * 16 + Math.floor(index / 16) % 16];
18778
18938
  }
18939
+ /** Refreshes saved lighting after adjacent columns load or unload, including columns with only light data. */
18940
+ async refreshLight(indices) {
18941
+ if (!this.lightAt) return;
18942
+ for (const index of indices ?? this.renderedBlocks.keys()) {
18943
+ if (this.renderedBlocks.has(index)) this.meshDirty = true;
18944
+ }
18945
+ }
18779
18946
  biomePosition(index, blockState3, stored) {
18780
18947
  const position = this.positionAt(index), name = blockState3.key?.toNamespacedString();
18781
18948
  if (stored.properties?.half === "upper" && (name === "minecraft:tall_grass" || name === "minecraft:large_fern")) position[1]--;
@@ -18953,7 +19120,23 @@ var Chunk = class _Chunk {
18953
19120
  fluids[kind] = await this.sectionModels.fluidAtlas(kind, state?.key?.root);
18954
19121
  }
18955
19122
  }
18956
- const next = entries.length || fluids ? await SectionMesh.buildAsync(entries, this.sectionModels.maxAtlasSize, fluids) : void 0;
19123
+ let light;
19124
+ if (this.lightAt) {
19125
+ light = new Uint8Array(18 * 18 * 18);
19126
+ for (let y = -1; y <= 16; y++) {
19127
+ for (let z = -1; z <= 16; z++) {
19128
+ for (let x = -1; x <= 16; x++) {
19129
+ light[(y + 1) * 324 + (z + 1) * 18 + x + 1] = this.lightAt(this.x * 16 + x, this.y * 16 + y, this.z * 16 + z);
19130
+ }
19131
+ }
19132
+ }
19133
+ }
19134
+ const next = entries.length || fluids ? await SectionMesh.buildAsync(
19135
+ entries,
19136
+ this.sectionModels.maxAtlasSize,
19137
+ fluids,
19138
+ light ? { light, ambientLight: this.ambientLight } : void 0
19139
+ ) : void 0;
18957
19140
  if (generation !== this.meshGeneration) {
18958
19141
  next?.dispose();
18959
19142
  return;
@@ -18985,6 +19168,7 @@ var Chunk = class _Chunk {
18985
19168
  this.biomes = void 0;
18986
19169
  if (notify) {
18987
19170
  await this.onBiomesChanged?.(void 0);
19171
+ await this.onLightCleared?.();
18988
19172
  await this.onBlocksChanged?.(positions);
18989
19173
  }
18990
19174
  }