minerender 2.1.0-alpha.115 → 2.1.0-alpha.116

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.
package/dist/bundle.js CHANGED
@@ -20101,8 +20101,8 @@ var MineRender = (() => {
20101
20101
  width *= view.width / fullWidth;
20102
20102
  height *= view.height / fullHeight;
20103
20103
  }
20104
- const skew = this.filmOffset;
20105
- if (skew !== 0) left += near * skew / this.getFilmWidth();
20104
+ const skew2 = this.filmOffset;
20105
+ if (skew2 !== 0) left += near * skew2 / this.getFilmWidth();
20106
20106
  this.projectionMatrix.makePerspective(left, left + width, top, top - height, near, this.far, this.coordinateSystem, this.reversedDepth);
20107
20107
  this.projectionMatrixInverse.copy(this.projectionMatrix).invert();
20108
20108
  }
@@ -41489,7 +41489,8 @@ void main() {
41489
41489
  this.fontCache,
41490
41490
  this.bannerPatternCache,
41491
41491
  this.mineRenderDataCache,
41492
- this.usernameUuidCache
41492
+ this.usernameUuidCache,
41493
+ this.biomeCache
41493
41494
  ];
41494
41495
  }
41495
41496
  /** Invalidates in-memory entries so later lookups reload them. Persisted entries are retained. */
@@ -41540,6 +41541,7 @@ void main() {
41540
41541
  _Caching.bannerPatternCache = _Caching.createAssetCache();
41541
41542
  _Caching.mineRenderDataCache = _Caching.createAssetCache();
41542
41543
  _Caching.usernameUuidCache = Caches.builder().expireAfterWrite(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
41544
+ _Caching.biomeCache = _Caching.createAssetCache();
41543
41545
  Caching = _Caching;
41544
41546
  }
41545
41547
  });
@@ -43305,7 +43307,7 @@ ${key}` : key, async () => {
43305
43307
  case "blockStates":
43306
43308
  return Object.values(value).every((property) => object(property) && "default" in property && Array.isArray(property.values));
43307
43309
  case "blockTints":
43308
- return ["constant", "grass", "stem", "redstone"].includes(value.source) && (value.source !== "constant" || Number.isInteger(value.color));
43310
+ return ["constant", "grass", "stem", "redstone"].includes(value.source) && (value.source !== "constant" || Number.isInteger(value.color)) && (value.biome === void 0 || ["grass", "foliage", "dry_foliage", "water"].includes(value.biome));
43309
43311
  case "fluids":
43310
43312
  return ["water", "lava"].includes(value.kind) && typeof value.renderModel === "boolean" && (value.levelProperty === void 0 || typeof value.levelProperty === "string");
43311
43313
  case "dyes":
@@ -43461,15 +43463,19 @@ ${key}` : key, async () => {
43461
43463
  Colormaps = class {
43462
43464
  /** Samples the selected resource pack's grass colormap using Minecraft's climate coordinates. */
43463
43465
  static async grassColor(origin, temperature = 0.5, downfall = 1) {
43464
- const key = AssetKey.parse("textures", "minecraft:colormap/grass", origin);
43466
+ return this.getColor("grass", origin, temperature, downfall);
43467
+ }
43468
+ /** Samples a resource-pack colormap at the supplied climate coordinates. */
43469
+ static async getColor(kind, origin, temperature = 0.5, downfall = 1) {
43470
+ const key = AssetKey.parse("textures", `minecraft:colormap/${kind}`, origin);
43465
43471
  const temp = Math.fround(temperature);
43466
43472
  const rain = Math.fround(downfall) * temp;
43467
43473
  const x4 = Math.floor((1 - temp) * 255), y5 = Math.floor((1 - rain) * 255);
43468
43474
  return await Caching.blockTintCache.get(`${key.serialize()}|${x4},${y5}`, async () => {
43469
43475
  const image = await ModelTextures.get(key);
43470
- if (!image) throw new MineRenderError(`Missing grass colormap: ${key.toNamespacedString()}`);
43476
+ if (!image) throw new MineRenderError(`Missing ${kind} colormap: ${key.toNamespacedString()}`);
43471
43477
  if (image.width !== 256 || image.height !== 256) {
43472
- throw new MineRenderError("Grass colormaps must be 256 \xD7 256 pixels");
43478
+ throw new MineRenderError(`${kind} colormaps must be 256 \xD7 256 pixels`);
43473
43479
  }
43474
43480
  const [red, green, blue] = image.data.getImageData(x4, y5, 1, 1).data;
43475
43481
  return red << 16 | green << 8 | blue;
@@ -57531,7 +57537,7 @@ ${key}` : key, async () => {
57531
57537
  var base = 36;
57532
57538
  var tMin = 1;
57533
57539
  var tMax = 26;
57534
- var skew = 38;
57540
+ var skew2 = 38;
57535
57541
  var damp2 = 700;
57536
57542
  var initialBias = 72;
57537
57543
  var initialN = 128;
@@ -57620,7 +57626,7 @@ ${key}` : key, async () => {
57620
57626
  ) {
57621
57627
  delta = floor(delta / baseMinusTMin);
57622
57628
  }
57623
- return floor(k4 + (baseMinusTMin + 1) * delta / (delta + skew));
57629
+ return floor(k4 + (baseMinusTMin + 1) * delta / (delta + skew2));
57624
57630
  };
57625
57631
  var decode2 = function decode3(input) {
57626
57632
  var output = [];
@@ -95379,6 +95385,7 @@ ${shader.fragmentShader}`.replace(
95379
95385
  BannerPatterns: () => BannerPatterns,
95380
95386
  BasicAssetKey: () => BasicAssetKey,
95381
95387
  BatchedExecutor: () => BatchedExecutor,
95388
+ Biomes: () => Biomes,
95382
95389
  BlockEntities: () => BlockEntities,
95383
95390
  BlockObject: () => BlockObject,
95384
95391
  BlockStateResolver: () => BlockStateResolver,
@@ -95841,6 +95848,122 @@ ${shader.fragmentShader}`.replace(
95841
95848
  init_Assets();
95842
95849
  init_BannerPatterns();
95843
95850
 
95851
+ // src/assets/Biomes.ts
95852
+ init_dirname();
95853
+ init_buffer2();
95854
+ init_process2();
95855
+ init_AssetKey();
95856
+ init_AssetLoader();
95857
+ init_AssetParsers();
95858
+ init_Caching();
95859
+ init_MineRenderError();
95860
+ init_Colormaps();
95861
+
95862
+ // src/assets/_biomes/SwampGrassColor.ts
95863
+ init_dirname();
95864
+ init_buffer2();
95865
+ init_process2();
95866
+ var permutation;
95867
+ var gradients = [[1, 1], [-1, 1], [1, -1], [-1, -1], [1, 0], [-1, 0], [1, 0], [-1, 0], [0, 1], [0, -1], [0, 1], [0, -1]];
95868
+ var skew = (Math.sqrt(3) - 1) / 2;
95869
+ var unskew = (3 - Math.sqrt(3)) / 6;
95870
+ function createPermutation() {
95871
+ const multiplier = 0x5deece66dn, mask = (1n << 48n) - 1n;
95872
+ let seed = 2345n ^ multiplier;
95873
+ const next = (bits2) => {
95874
+ seed = seed * multiplier + 11n & mask;
95875
+ return Number(seed >> BigInt(48 - bits2));
95876
+ };
95877
+ for (let i4 = 0; i4 < 3; i4++) {
95878
+ next(26);
95879
+ next(27);
95880
+ }
95881
+ const values = Uint8Array.from({ length: 256 }, (_4, i4) => i4);
95882
+ for (let i4 = 0; i4 < 256; i4++) {
95883
+ const bound = 256 - i4;
95884
+ let offset;
95885
+ if ((bound & bound - 1) === 0) offset = Math.floor(bound * next(31) / 2147483648);
95886
+ else {
95887
+ let bits2;
95888
+ do {
95889
+ bits2 = next(31);
95890
+ offset = bits2 % bound;
95891
+ } while (bits2 - offset + bound - 1 > 2147483647);
95892
+ }
95893
+ const other = i4 + offset;
95894
+ [values[i4], values[other]] = [values[other], values[i4]];
95895
+ }
95896
+ return values;
95897
+ }
95898
+ function swampGrassColor(x4, z4) {
95899
+ const p13 = permutation ?? (permutation = createPermutation());
95900
+ x4 *= 0.0225;
95901
+ z4 *= 0.0225;
95902
+ const s4 = (x4 + z4) * skew;
95903
+ const i4 = Math.floor(x4 + s4), j4 = Math.floor(z4 + s4);
95904
+ const t5 = (i4 + j4) * unskew;
95905
+ const dx = x4 - (i4 - t5), dz = z4 - (j4 - t5);
95906
+ const stepX = dx > dz ? 1 : 0, stepZ = 1 - stepX;
95907
+ const corner = (gx, gz, a4, b4) => {
95908
+ let falloff = 0.5 - a4 * a4 - b4 * b4;
95909
+ if (falloff < 0) return 0;
95910
+ const gradient = gradients[p13[gx + p13[gz & 255] & 255] % 12];
95911
+ falloff *= falloff;
95912
+ return falloff * falloff * (gradient[0] * a4 + gradient[1] * b4);
95913
+ };
95914
+ const noise = 70 * (corner(i4, j4, dx, dz) + corner(i4 + stepX, j4 + stepZ, dx - stepX + unskew, dz - stepZ + unskew) + corner(i4 + 1, j4 + 1, dx - 1 + 2 * unskew, dz - 1 + 2 * unskew));
95915
+ return noise < -0.1 ? 5011004 : 6975545;
95916
+ }
95917
+
95918
+ // src/assets/Biomes.ts
95919
+ var Biomes = class {
95920
+ /** Loads a biome registry entry, or returns `undefined` when no source provides it. */
95921
+ static async get(id, origin) {
95922
+ if (!/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(id)) throw new MineRenderError(`Invalid biome identifier: ${id}`);
95923
+ const [namespace, path] = id.includes(":") ? id.split(":") : ["minecraft", id];
95924
+ const key = new AssetKey(namespace, path, "worldgen", "biome", "data", ".json", origin?.root);
95925
+ return Caching.biomeCache.get(key.serialize(), async () => {
95926
+ const asset2 = await AssetLoader.get(key, "json" /* JSON */);
95927
+ if (asset2 === void 0) return void 0;
95928
+ if (!asset2 || typeof asset2 !== "object" || Array.isArray(asset2) || !asset2.effects || typeof asset2.effects !== "object" || Array.isArray(asset2.effects)) {
95929
+ throw new MineRenderError(`Invalid biome effects: ${id}`);
95930
+ }
95931
+ if (typeof asset2.temperature !== "number" || !Number.isFinite(asset2.temperature) || typeof asset2.downfall !== "number" || !Number.isFinite(asset2.downfall)) {
95932
+ throw new MineRenderError(`Invalid biome climate: ${id}`);
95933
+ }
95934
+ for (const kind of ["grass", "foliage", "dry_foliage", "water"]) {
95935
+ const color3 = asset2.effects[`${kind}_color`];
95936
+ if (color3 !== void 0) this.parseColor(color3, id, kind);
95937
+ }
95938
+ const modifier = asset2.effects.grass_color_modifier;
95939
+ if (modifier !== void 0 && modifier !== "none" && modifier !== "dark_forest" && modifier !== "swamp") {
95940
+ throw new MineRenderError(`Invalid biome grass color modifier: ${id}`);
95941
+ }
95942
+ return asset2;
95943
+ });
95944
+ }
95945
+ /** Returns packed sRGB, or `undefined` when the biome or requested water color is missing. */
95946
+ static async getColor(id, kind, origin, x4 = 0, z4 = 0) {
95947
+ const biome = await this.get(id, origin);
95948
+ if (!biome) return void 0;
95949
+ const rawColor = biome.effects[`${kind}_color`];
95950
+ let color3 = rawColor === void 0 ? void 0 : this.parseColor(rawColor, id, kind);
95951
+ if (kind === "water") return color3;
95952
+ const modifier = kind === "grass" ? biome.effects.grass_color_modifier : void 0;
95953
+ if (modifier === "swamp") return swampGrassColor(x4, z4);
95954
+ if (color3 === void 0) {
95955
+ const clamp2 = (value) => Math.max(0, Math.min(1, Math.fround(value)));
95956
+ color3 = await Colormaps.getColor(kind, origin, clamp2(biome.temperature), clamp2(biome.downfall));
95957
+ }
95958
+ return modifier === "dark_forest" ? (color3 & 16711422) + 2634762 >> 1 : color3;
95959
+ }
95960
+ static parseColor(value, id, kind) {
95961
+ if (typeof value === "string" && /^#[0-9a-fA-F]{6}$/.test(value)) return parseInt(value.slice(1), 16);
95962
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 16777215) return value;
95963
+ throw new MineRenderError(`Invalid biome ${kind} color: ${id}`);
95964
+ }
95965
+ };
95966
+
95844
95967
  // src/assets/BlockEntities.ts
95845
95968
  init_dirname();
95846
95969
  init_buffer2();
@@ -100445,8 +100568,20 @@ property list uchar int vertex_index
100445
100568
  init_Colormaps();
100446
100569
  init_MineRenderData();
100447
100570
  var BlockTints = class {
100448
- /** Resolves block preview colors without biome context; explicit tints take precedence. */
100449
- static async get(key, state, model, tints) {
100571
+ /** Returns a block's versioned biome color source, leaving fixed-color blocks unchanged. */
100572
+ static async biomeSource(key) {
100573
+ const rule = key && (await MineRenderData.get("blockTints", key.root))[key.toNamespacedString()];
100574
+ return rule ? rule.biome ?? (rule.source === "grass" ? "grass" : void 0) : void 0;
100575
+ }
100576
+ /** Resolves one block's biome tint at world block coordinates, without biome blending. */
100577
+ static async getBiomeColor(key, biome, position) {
100578
+ if (!biome) return void 0;
100579
+ const x4 = position?.[0], z4 = position?.[2];
100580
+ const source = await this.biomeSource(key);
100581
+ return source ? Biomes.getColor(biome, source, key, x4, z4) : void 0;
100582
+ }
100583
+ /** Resolves used tint indices from biome or preview colors; explicit tints take precedence. */
100584
+ static async get(key, state, model, tints, biomeColor) {
100450
100585
  if (!key) return tints;
100451
100586
  const candidates = (model.elements ?? []).flatMap((element) => Object.values(element.faces)).some((face) => face?.tintindex !== void 0 && face.tintindex >= 0 && tints?.[face.tintindex] === void 0);
100452
100587
  if (!candidates) return tints;
@@ -100462,8 +100597,12 @@ property list uchar int vertex_index
100462
100597
  }
100463
100598
  }
100464
100599
  if (!indices.size) return tints;
100600
+ const source = rule.biome ?? (rule.source === "grass" ? "grass" : void 0);
100601
+ const biome = source ? typeof biomeColor === "function" ? await biomeColor() : biomeColor : void 0;
100465
100602
  let color3;
100466
- switch (rule.source) {
100603
+ if (biome !== void 0) {
100604
+ color3 = biome;
100605
+ } else switch (rule.source) {
100467
100606
  case "constant":
100468
100607
  color3 = rule.color;
100469
100608
  break;
@@ -100530,6 +100669,7 @@ property list uchar int vertex_index
100530
100669
  this._entityGeneration = 0;
100531
100670
  this.options = (0, import_ts_deepmerge6.default)({}, _BlockObject.DEFAULT_OPTIONS, options ?? {});
100532
100671
  this.variantPosition = options?.variantPosition ? [...options.variantPosition] : void 0;
100672
+ this.biomePosition = options?.biomePosition ? [...options.biomePosition] : void 0;
100533
100673
  }
100534
100674
  async init() {
100535
100675
  const root = AssetLoader.ROOT, scope = AssetLoader.persistentScope;
@@ -100722,11 +100862,16 @@ property list uchar int vertex_index
100722
100862
  const { FluidModelObject: FluidModelObject2, sampleFluid: sampleFluid2 } = await Promise.resolve().then(() => (init_FluidModelObject(), FluidModelObject_exports));
100723
100863
  this._fluidSampler = sample;
100724
100864
  const surface = sampleFluid2(kind, (x4, y5, z4) => x4 === 0 && y5 === 0 && z4 === 0 ? { fluid: kind, level: this.fluidLevel } : sample?.(x4, y5, z4) ?? {});
100725
- if (this._fluidKey === surface.key) return;
100865
+ const tint = this.options.tints?.[0] ?? this.waterTint;
100866
+ const fluidKey = `${surface.key}|${tint ?? "default"}`;
100867
+ if (this._fluidKey === fluidKey) return;
100726
100868
  const key = new AssetKey("minecraft", `${kind}/${surface.key}`, "models", "fluid", "assets", ".json", this.blockState.key?.root);
100727
100869
  const replacement = await this.scene.addSceneObject(
100728
100870
  { key },
100729
- () => new FluidModelObject2(kind, surface.sample, this.blockState.key, this.options)
100871
+ () => new FluidModelObject2(kind, surface.sample, this.blockState.key, {
100872
+ ...this.options,
100873
+ tints: tint === void 0 ? this.options.tints : { ...this.options.tints, 0: tint }
100874
+ })
100730
100875
  );
100731
100876
  const previous = this._fluidModel;
100732
100877
  const matrix = previous ? this.getModelMatrix(previous) : new Matrix4().makeTranslation(...this.position.toArray());
@@ -100735,7 +100880,7 @@ property list uchar int vertex_index
100735
100880
  this._models = this._models.filter((model) => model !== previous);
100736
100881
  this._models.push(replacement);
100737
100882
  this._fluidModel = replacement;
100738
- this._fluidKey = surface.key;
100883
+ this._fluidKey = fluidKey;
100739
100884
  this._isInstanced || (this._isInstanced = isInstanceReference(replacement));
100740
100885
  this._instanceCounter = this._isInstanced ? 1 : 0;
100741
100886
  if (previous) this.removeModels([previous]);
@@ -100758,6 +100903,7 @@ property list uchar int vertex_index
100758
100903
  }
100759
100904
  async recreateModels() {
100760
100905
  this.clearModels();
100906
+ this.waterTint = this.fluidKind === "water" && this.options.biome && this.options.tints?.[0] === void 0 ? await Biomes.getColor(this.options.biome, "water", this.blockState.key) : void 0;
100761
100907
  if (resolveBlockFluidState(this.blockState.key, this.state, this._fluidRules)?.renderModel === false) {
100762
100908
  await this.updateFluid(this._fluidSampler);
100763
100909
  return;
@@ -100792,7 +100938,13 @@ property list uchar int vertex_index
100792
100938
  const model = await Models.getMerged(modelKey);
100793
100939
  const options = {
100794
100940
  ...this.options,
100795
- tints: model ? await BlockTints.get(this.blockState.key, this.state, model, this.options.tints) : this.options.tints,
100941
+ tints: model ? await BlockTints.get(
100942
+ this.blockState.key,
100943
+ this.state,
100944
+ model,
100945
+ this.options.tints,
100946
+ () => BlockTints.getBiomeColor(this.blockState.key, this.options.biome, this.biomePosition)
100947
+ ) : this.options.tints,
100796
100948
  uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0,
100797
100949
  cullMask: this.options.displayPosition ? 0 : ModelCulling.toLocalMask(this._cullMask, rotation)
100798
100950
  };
@@ -107376,24 +107528,48 @@ gl_FragDepth=readDepth(vUv);
107376
107528
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("maxAtlasSize must be a positive integer");
107377
107529
  }
107378
107530
  /** Returns templates for the matched parts, or `undefined` when the block needs an individual render object. */
107379
- async get(blockState3, properties = {}, position) {
107380
- const variantPosition = position ? [...position] : void 0;
107381
- return (await this.prepareState(blockState3, properties))?.pick(variantPosition);
107531
+ async get(blockState3, properties = {}, position, biome) {
107532
+ const biomePosition = position ? [...position] : void 0;
107533
+ const prepared = await this.state(blockState3, properties);
107534
+ if (!prepared) return void 0;
107535
+ return Promise.all(prepared.groups.map(async (group) => {
107536
+ const variant = BlockStateResolver.choose(group, biomePosition);
107537
+ const choice = prepared.choices.get(variant);
107538
+ const tints = await BlockTints.get(
107539
+ blockState3.key,
107540
+ prepared.state,
107541
+ choice.model,
107542
+ void 0,
107543
+ () => BlockTints.getBiomeColor(blockState3.key, biome, biomePosition)
107544
+ );
107545
+ return this.template(variant, choice, tints);
107546
+ }));
107547
+ }
107548
+ /** Resolves section templates for one tint palette, with a synchronous weighted choice for each cell. */
107549
+ async prepareState(blockState3, properties = {}, biomeColor) {
107550
+ const prepared = await this.state(blockState3, properties);
107551
+ if (!prepared) return void 0;
107552
+ let color3;
107553
+ const resolveColor = typeof biomeColor === "function" ? () => color3 ?? (color3 = biomeColor()) : biomeColor;
107554
+ const templates = /* @__PURE__ */ new Map();
107555
+ await Promise.all([...prepared.choices].map(async ([variant, choice]) => {
107556
+ const tints = await BlockTints.get(blockState3.key, prepared.state, choice.model, void 0, resolveColor);
107557
+ templates.set(variant, await this.template(variant, choice, tints));
107558
+ }));
107559
+ return { pick: (position) => prepared.groups.map((group) => templates.get(BlockStateResolver.choose(group, position))) };
107382
107560
  }
107383
- /** Resolves section templates once, with a synchronous weighted choice for each cell. */
107384
- async prepareState(blockState3, properties = {}) {
107561
+ async state(blockState3, properties) {
107385
107562
  let states = this.states.get(blockState3);
107386
107563
  if (!states) this.states.set(blockState3, states = /* @__PURE__ */ new Map());
107387
107564
  const key = JSON.stringify(Object.entries(properties).sort(([a4], [b4]) => a4.localeCompare(b4)));
107388
- const prepared = await cached(states, key, () => this.prepare(blockState3, properties));
107389
- if (!prepared) return void 0;
107390
- return { pick: (position) => prepared.groups.map((group) => prepared.templates.get(BlockStateResolver.choose(group, position))) };
107565
+ return cached(states, key, () => this.prepare(blockState3, properties));
107391
107566
  }
107392
107567
  /** Returns the shared still/flow atlas used by per-block fluid objects. */
107393
107568
  fluidAtlas(kind, root) {
107394
- return cached(this.fluidAtlases, kind, async () => {
107569
+ const key = new AssetKey("minecraft", kind, "models", "fluid", "assets", ".json", root);
107570
+ return cached(this.fluidAtlases, key.serialize(), async () => {
107395
107571
  const atlas = await UVMapper.getAtlas({
107396
- key: new AssetKey("minecraft", kind, "models", "fluid", "assets", ".json", root),
107572
+ key,
107397
107573
  textures: { still: `minecraft:block/${kind}_still`, flow: `minecraft:block/${kind}_flow` },
107398
107574
  elements: []
107399
107575
  });
@@ -107405,64 +107581,67 @@ gl_FragDepth=readDepth(vUv);
107405
107581
  const state = { ...await BlockStateResolver.defaults(blockState3), ...properties };
107406
107582
  const groups = BlockStateResolver.matching(blockState3, state);
107407
107583
  if (!groups.length) return void 0;
107408
- const templates = /* @__PURE__ */ new Map();
107584
+ const prepared = /* @__PURE__ */ new Map();
107409
107585
  for (const group of groups) {
107410
107586
  const choices = Array.isArray(group) ? group : [group];
107411
107587
  if (!choices.length) return void 0;
107412
107588
  for (const variant of choices) {
107413
107589
  if (!variant.model) return void 0;
107414
- const model = await Models.getMerged(AssetKey.parse("models", variant.model));
107590
+ const modelKey = AssetKey.parse("models", variant.model);
107591
+ modelKey.root = blockState3.key?.root;
107592
+ const model = await Models.getMerged(modelKey);
107415
107593
  if (!model?.elements?.length) return void 0;
107416
107594
  const atlas = await UVMapper.getAtlas(model);
107417
107595
  const rotation = BlockStateResolver.rotation(variant);
107418
107596
  if (!atlas || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return void 0;
107419
- const tints = await BlockTints.get(blockState3.key, state, model);
107420
- let models = this.models.get(model);
107421
- if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
107422
- const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
107423
- const template = await cached(models, key, async () => {
107424
- const object4 = new SectionModel(model, {
107425
- instanceMeshes: false,
107426
- mergeMeshes: true,
107427
- tints,
107428
- uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
107429
- });
107430
- await object4.init();
107431
- let geometry = object4.children[0].geometry;
107432
- geometry.applyMatrix4(new Matrix4().makeRotationFromEuler(rotation));
107433
- object4.clear();
107434
- const keep = [];
107435
- const cullFaces = [];
107436
- for (const element of atlas.model.elements) {
107437
- for (const face of CUBE_FACES) {
107438
- const data = element.faces[face];
107439
- keep.push(data !== void 0);
107440
- if (!data) continue;
107441
- const local = CUBE_FACES.findIndex((name2) => name2 === data.cullface);
107442
- if (local < 0) {
107443
- cullFaces.push(0);
107444
- continue;
107445
- }
107446
- const normal = new Vector3(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);
107447
- const world = CUBE_FACE_OFFSETS.findIndex(([x4, y5, z4]) => Math.abs(normal.x - x4) < 1e-6 && Math.abs(normal.y - y5) < 1e-6 && Math.abs(normal.z - z4) < 1e-6);
107448
- cullFaces.push(world < 0 ? 0 : 1 << world);
107449
- }
107450
- }
107451
- geometry = compactQuads(geometry, keep);
107452
- const template2 = {
107453
- geometry,
107454
- atlas,
107455
- cullFaces: new Uint8Array(cullFaces),
107456
- occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
107457
- layer: atlas.hasTranslucency ? 1 : 0
107458
- };
107459
- this.templates.add(template2);
107460
- return template2;
107461
- });
107462
- templates.set(variant, template);
107597
+ prepared.set(variant, { model, atlas, rotation });
107463
107598
  }
107464
107599
  }
107465
- return { groups, templates };
107600
+ return { state, groups, choices: prepared };
107601
+ }
107602
+ async template(variant, { model, atlas, rotation }, tints) {
107603
+ let models = this.models.get(model);
107604
+ if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
107605
+ const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
107606
+ return cached(models, key, async () => {
107607
+ const object4 = new SectionModel(model, {
107608
+ instanceMeshes: false,
107609
+ mergeMeshes: true,
107610
+ tints,
107611
+ uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
107612
+ });
107613
+ await object4.init();
107614
+ let geometry = object4.children[0].geometry;
107615
+ geometry.applyMatrix4(new Matrix4().makeRotationFromEuler(rotation));
107616
+ object4.clear();
107617
+ const keep = [];
107618
+ const cullFaces = [];
107619
+ for (const element of atlas.model.elements) {
107620
+ for (const face of CUBE_FACES) {
107621
+ const data = element.faces[face];
107622
+ keep.push(data !== void 0);
107623
+ if (!data) continue;
107624
+ const local = CUBE_FACES.findIndex((name2) => name2 === data.cullface);
107625
+ if (local < 0) {
107626
+ cullFaces.push(0);
107627
+ continue;
107628
+ }
107629
+ const normal = new Vector3(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);
107630
+ const world = CUBE_FACE_OFFSETS.findIndex(([x4, y5, z4]) => Math.abs(normal.x - x4) < 1e-6 && Math.abs(normal.y - y5) < 1e-6 && Math.abs(normal.z - z4) < 1e-6);
107631
+ cullFaces.push(world < 0 ? 0 : 1 << world);
107632
+ }
107633
+ }
107634
+ geometry = compactQuads(geometry, keep);
107635
+ const template = {
107636
+ geometry,
107637
+ atlas,
107638
+ cullFaces: new Uint8Array(cullFaces),
107639
+ occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
107640
+ layer: atlas.hasTranslucency ? 1 : 0
107641
+ };
107642
+ this.templates.add(template);
107643
+ return template;
107644
+ });
107466
107645
  }
107467
107646
  /** Disposes prepared geometry and clears this world's template caches. */
107468
107647
  clear() {
@@ -107798,7 +107977,6 @@ gl_FragDepth=readDepth(vUv);
107798
107977
  }
107799
107978
  try {
107800
107979
  await this.clearChunkColumn(chunk.x, chunk.z);
107801
- let sliceStart = performance.now();
107802
107980
  for (const section of chunk.sections) {
107803
107981
  if (section.biomes) {
107804
107982
  const position = new Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
@@ -107812,6 +107990,9 @@ gl_FragDepth=readDepth(vUv);
107812
107990
  if (!column) this.biomes.set(key, column = /* @__PURE__ */ new Map());
107813
107991
  column.set(section.y, copy);
107814
107992
  }
107993
+ }
107994
+ let sliceStart = performance.now();
107995
+ for (const section of chunk.sections) {
107815
107996
  if (!executor) {
107816
107997
  if (!occupiedSections.has(section.y)) continue;
107817
107998
  const position = new Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
@@ -107976,18 +108157,34 @@ gl_FragDepth=readDepth(vUv);
107976
108157
  const key = this.worldPosToChunkKey(pos);
107977
108158
  let chunk = this._chunks.get(key);
107978
108159
  if (typeof chunk === "undefined") {
108160
+ const x4 = Math.floor(pos.x / 16), y5 = Math.floor(pos.y / 16), z4 = Math.floor(pos.z / 16);
107979
108161
  chunk = new Chunk(
107980
108162
  this.scene,
107981
- Math.floor(pos.x / 16),
107982
- Math.floor(pos.y / 16),
107983
- Math.floor(pos.z / 16),
108163
+ x4,
108164
+ y5,
108165
+ z4,
107984
108166
  (positions) => this.updateCulling(positions),
107985
108167
  this.sectionModels,
107986
- (x4, y5, z4) => {
107987
- const cx = Math.floor(x4 / 16), cy = Math.floor(y5 / 16), cz = Math.floor(z4 / 16);
108168
+ (x5, y6, z5) => {
108169
+ const cx = Math.floor(x5 / 16), cy = Math.floor(y6 / 16), cz = Math.floor(z5 / 16);
107988
108170
  const section = this._chunks.get(`${cx}_${cy}_${cz}`);
107989
- const index = (y5 - cy * 16) * 256 + (z4 - cz * 16) * 16 + x4 - cx * 16;
108171
+ const index = (y6 - cy * 16) * 256 + (z5 - cz * 16) * 16 + x5 - cx * 16;
107990
108172
  return (section?.fluidByteIndex(index) ?? 0) | (section?.isOccludingIndex(index) ? 64 : 0);
108173
+ },
108174
+ (x5, y6, z5) => this.getBiomeAt(x5, y6, z5),
108175
+ async (biomes) => {
108176
+ if (this._chunks.get(key) !== chunk) return;
108177
+ const columnKey = `${x4}_${z4}`;
108178
+ let column = this.biomes.get(columnKey);
108179
+ if (biomes) {
108180
+ if (!column) this.biomes.set(columnKey, column = /* @__PURE__ */ new Map());
108181
+ column.set(y5, biomes);
108182
+ } else {
108183
+ column?.delete(y5);
108184
+ if (!column?.size) this.biomes.delete(columnKey);
108185
+ }
108186
+ await chunk.refreshBiomes();
108187
+ await this._chunks.get(`${x4}_${y5 + 1}_${z4}`)?.refreshBiomes();
107991
108188
  }
107992
108189
  );
107993
108190
  this._chunks.set(key, chunk);
@@ -108048,6 +108245,7 @@ gl_FragDepth=readDepth(vUv);
108048
108245
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
108049
108246
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
108050
108247
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
108248
+ if (fluids?.waterTints && fluids.waterTints.length !== 4096 * 3) throw new RangeError("Section water tints must contain RGB for 4096 cells");
108051
108249
  for (const template of templateData) {
108052
108250
  const { quads } = template;
108053
108251
  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) {
@@ -108125,7 +108323,9 @@ gl_FragDepth=readDepth(vUv);
108125
108323
  height
108126
108324
  });
108127
108325
  }
108128
- fluidPages.get(fluid).fluids.push({ quads, x: x4, y: y5, z: z4, data });
108326
+ const tintIndex = (x4 + z4 * 16 + y5 * 256) * 3;
108327
+ const tint = fluid === "water" && fluids.waterTints ? fluids.waterTints.subarray(tintIndex, tintIndex + 3) : data.tint;
108328
+ fluidPages.get(fluid).fluids.push({ quads, x: x4, y: y5, z: z4, data, tint });
108129
108329
  }
108130
108330
  pages.push(...[...fluidPages.values()].sort((a4, b4) => Number(atlases[a4.placements[0].atlas].animated) - Number(atlases[b4.placements[0].atlas].animated) || a4.placements[0].atlas - b4.placements[0].atlas));
108131
108331
  }
@@ -108174,7 +108374,7 @@ gl_FragDepth=readDepth(vUv);
108174
108374
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
108175
108375
  }
108176
108376
  }
108177
- for (const { quads, x: x4, y: y5, z: z4, data } of page.fluids) {
108377
+ for (const { quads, x: x4, y: y5, z: z4, data, tint } of page.fluids) {
108178
108378
  const atlas = atlases[data.atlas];
108179
108379
  const placement = page.placements[0];
108180
108380
  const mapU = (u4) => (placement.x + u4 * atlas.width) / page.width;
@@ -108198,7 +108398,7 @@ gl_FragDepth=readDepth(vUv);
108198
108398
  mapU((rect[0] + rect[2]) / atlas.width),
108199
108399
  mapV(1 - rect[1] / atlas.height)
108200
108400
  ], vertex2 * 4);
108201
- colors.set(data.tint, vertex2 * 3);
108401
+ colors.set(tint, vertex2 * 3);
108202
108402
  }
108203
108403
  outputIndices.set([start, start + 1, start + 2, start, start + 2, start + 3], indexOffset);
108204
108404
  indexOffset += 6;
@@ -108342,7 +108542,7 @@ gl_FragDepth=readDepth(vUv);
108342
108542
  input.cullMasks[i4] = cullMask;
108343
108543
  }
108344
108544
  if (fluids) {
108345
- input.fluids = { cells: fluids.cells };
108545
+ input.fluids = { cells: fluids.cells, waterTints: fluids.waterTints };
108346
108546
  for (const kind of ["water", "lava"]) {
108347
108547
  const atlas = fluids[kind];
108348
108548
  if (!atlas) continue;
@@ -108443,11 +108643,14 @@ gl_FragDepth=readDepth(vUv);
108443
108643
  init_FluidGeometry();
108444
108644
  init_FluidQuads();
108445
108645
  init_CubeFace();
108646
+ var WATER_TINT2 = new Color(4159204).toArray();
108446
108647
  var Chunk = class _Chunk {
108447
- constructor(scene, x4, y5, z4, onBlocksChanged, sectionModels, neighborCell) {
108648
+ constructor(scene, x4, y5, z4, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged) {
108448
108649
  this.onBlocksChanged = onBlocksChanged;
108449
108650
  this.sectionModels = sectionModels;
108450
108651
  this.neighborCell = neighborCell;
108652
+ this.biomeAt = biomeAt;
108653
+ this.onBiomesChanged = onBiomesChanged;
108451
108654
  this.data = new ChunkData();
108452
108655
  this.renderedBlocks = /* @__PURE__ */ new Map();
108453
108656
  this.sectionBlocks = /* @__PURE__ */ new Map();
@@ -108472,6 +108675,56 @@ gl_FragDepth=readDepth(vUv);
108472
108675
  const index = _Chunk.chunkPosToBlockIndex(pos);
108473
108676
  return this.renderedBlocks.get(index);
108474
108677
  }
108678
+ getBiomeAt(posOrX, y5, z4) {
108679
+ if (typeof posOrX === "number") return this.getBiomeAt(new Vector3(posOrX, y5, z4));
108680
+ if (isTripleArray(posOrX)) return this.getBiomeAt(new Vector3(...posOrX));
108681
+ const pos = this.worldPosToChunkPos(posOrX);
108682
+ _Chunk.chunkPosToBlockIndex(pos);
108683
+ return this.biomeAt ? this.biomeAt(posOrX.x, posOrX.y, posOrX.z) : this.biomes?.[Math.floor(pos.x / 4) + Math.floor(pos.z / 4) * 4 + Math.floor(pos.y / 4) * 16];
108684
+ }
108685
+ /** Copies 64 biome IDs in x + z * 4 + y * 16 order and refreshes placed blocks. Omit the array to clear biome data. */
108686
+ setBiomes(biomes) {
108687
+ const copy = biomes ? [...biomes] : void 0;
108688
+ if (copy && (copy.length !== 64 || !copy.every((id) => typeof id === "string" && id.length > 0))) {
108689
+ throw new RangeError("Section biomes must contain 64 nonempty biome IDs");
108690
+ }
108691
+ if (this.onBiomesChanged) return this.onBiomesChanged(copy);
108692
+ this.biomes = copy;
108693
+ return this.refreshBiomes();
108694
+ }
108695
+ /** Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects. */
108696
+ async refreshBiomes() {
108697
+ const affected = /* @__PURE__ */ new Map();
108698
+ const blocks = [];
108699
+ for (const index of this.renderedBlocks.keys()) {
108700
+ const block = this.data.get(index);
108701
+ const stateKey = ChunkData.paletteKey(block);
108702
+ let matches = affected.get(stateKey);
108703
+ if (!matches) {
108704
+ const key = AssetKey.parse("blockstates", block.type);
108705
+ matches = BlockStates.get(key).then(async (state) => {
108706
+ const origin = state?.key ?? key;
108707
+ const [source, fluid] = await Promise.all([BlockTints.biomeSource(origin), getBlockFluidState(origin, block.properties)]);
108708
+ return !!source || fluid?.kind === "water";
108709
+ });
108710
+ affected.set(stateKey, matches);
108711
+ }
108712
+ if (await matches) blocks.push({ index, block });
108713
+ }
108714
+ if (!blocks.length) return;
108715
+ const hidden = new Set(this.hiddenBlocks);
108716
+ try {
108717
+ await this.placeBlocks(blocks);
108718
+ } finally {
108719
+ for (const index of hidden) {
108720
+ this.hiddenBlocks.add(index);
108721
+ this.renderedBlocks.get(index)?.object?.setVisible(false);
108722
+ }
108723
+ const positions = blocks.map(({ index }) => new Vector3(...this.positionAt(index)));
108724
+ if (this.onBlocksChanged) await this.onBlocksChanged(positions);
108725
+ else await this.rebuildSectionMesh();
108726
+ }
108727
+ }
108475
108728
  async setBlockAt(posOrX, yOrBlock, z4, block) {
108476
108729
  if (typeof posOrX == "number") {
108477
108730
  return this.setBlockAt(new Vector3(posOrX, yOrBlock, z4), block);
@@ -108510,14 +108763,15 @@ gl_FragDepth=readDepth(vUv);
108510
108763
  nbt: block.nbt === void 0 ? void 0 : structuredClone(block.nbt)
108511
108764
  } : void 0
108512
108765
  }));
108513
- const states = new Map(placements.filter(({ key }) => key !== void 0).map(({ key, block }) => [key, block]));
108514
- const settled = await Promise.allSettled([...states.values()].map(async (stored) => {
108515
- const blockState3 = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
108516
- const perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
108517
- const fluid = await getBlockFluidState(blockState3?.key, stored.properties);
108518
- const prepared = perBlock || fluid?.renderModel === false ? void 0 : await this.sectionModels.prepareState(blockState3, stored.properties);
108519
- return { blockState: blockState3, perBlock, fluid, prepared, stored };
108520
- }));
108766
+ const states = /* @__PURE__ */ new Map();
108767
+ for (const { index, key, block } of placements) {
108768
+ if (key === void 0) continue;
108769
+ let state = states.get(key);
108770
+ if (!state) states.set(key, state = { stored: block, indices: [] });
108771
+ state.indices.push(index);
108772
+ }
108773
+ const biomes = /* @__PURE__ */ new Map();
108774
+ const settled = await Promise.allSettled([...states.values()].map(({ stored, indices }) => this.resolveState(stored, indices, biomes)));
108521
108775
  const resolutions = new Map([...states.keys()].map((key, i4) => [key, settled[i4]]));
108522
108776
  const positions = [];
108523
108777
  let failed = false, failure;
@@ -108541,7 +108795,8 @@ gl_FragDepth=readDepth(vUv);
108541
108795
  if (!readBlock) continue;
108542
108796
  const resolved = resolutions.get(key);
108543
108797
  if (resolved.status === "rejected") throw resolved.reason;
108544
- const { blockState: blockState3, perBlock, fluid, prepared, stored } = resolved.value;
108798
+ const { blockState: blockState3, perBlock, fluid, prepared, contexts, stored } = resolved.value;
108799
+ const context = contexts.get(index);
108545
108800
  if (!blockState3) {
108546
108801
  this.data.assign(index, 0);
108547
108802
  continue;
@@ -108549,9 +108804,10 @@ gl_FragDepth=readDepth(vUv);
108549
108804
  const variantPosition = worldPos.toArray();
108550
108805
  if (fluid) {
108551
108806
  this.fluidCells[index] = (fluid.kind === "water" ? 16 : 32) | Math.min(fluid.level, 8);
108807
+ if (fluid.kind === "water") this.setWaterTint(index, context?.waterTint);
108552
108808
  this.meshDirty = true;
108553
108809
  }
108554
- const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : prepared?.pick(variantPosition);
108810
+ const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
108555
108811
  if (templates) {
108556
108812
  this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
108557
108813
  this.meshDirty = true;
@@ -108561,7 +108817,9 @@ gl_FragDepth=readDepth(vUv);
108561
108817
  instanceMeshes: true,
108562
108818
  maxInstanceCount: 2e3,
108563
108819
  initialState: stored.properties,
108564
- variantPosition
108820
+ variantPosition,
108821
+ biome: context?.biome,
108822
+ biomePosition: this.biomePosition(index, blockState3, stored)
108565
108823
  });
108566
108824
  object4.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
108567
108825
  }
@@ -108584,24 +108842,21 @@ gl_FragDepth=readDepth(vUv);
108584
108842
  for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
108585
108843
  this.sectionBlocks.clear();
108586
108844
  this.fluidCells.fill(0);
108845
+ this.waterTints = void 0;
108587
108846
  this.renderedBlocks.clear();
108588
108847
  this.hiddenBlocks.clear();
108589
108848
  this.meshDirty = true;
108590
108849
  this.data.copyFrom(data);
108591
- const ids = /* @__PURE__ */ new Set();
108850
+ const ids = /* @__PURE__ */ new Map();
108592
108851
  for (let index = 0; index < 4096; index++) {
108593
108852
  const id = this.data.idAt(index);
108594
- if (id) ids.add(id);
108595
- }
108596
- const palette = [...ids];
108597
- const settled = await Promise.allSettled(palette.map(async (id) => {
108598
- const state = this.data.stateAt(id);
108599
- const blockState3 = await BlockStates.get(AssetKey.parse("blockstates", state.type));
108600
- const perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
108601
- const fluid = await getBlockFluidState(blockState3?.key, state.properties);
108602
- const prepared = perBlock || fluid?.renderModel === false ? void 0 : await this.sectionModels.prepareState(blockState3, state.properties);
108603
- return { blockState: blockState3, perBlock, fluid, prepared };
108604
- }));
108853
+ if (!id) continue;
108854
+ let indices = ids.get(id);
108855
+ if (!indices) ids.set(id, indices = []);
108856
+ indices.push(index);
108857
+ }
108858
+ const palette = [...ids.keys()], biomes = /* @__PURE__ */ new Map();
108859
+ const settled = await Promise.allSettled(palette.map((id) => this.resolveState(this.data.stateAt(id), ids.get(id), biomes)));
108605
108860
  const resolutions = new Map(palette.map((id, i4) => [id, settled[i4]]));
108606
108861
  const perBlocks = [];
108607
108862
  let failed = false, failure;
@@ -108611,24 +108866,28 @@ gl_FragDepth=readDepth(vUv);
108611
108866
  try {
108612
108867
  const resolved = resolutions.get(id);
108613
108868
  if (resolved.status === "rejected") throw resolved.reason;
108614
- const { blockState: blockState3, perBlock, fluid, prepared } = resolved.value;
108869
+ const { blockState: blockState3, perBlock, fluid, prepared, contexts } = resolved.value;
108870
+ const context = contexts.get(index);
108615
108871
  if (!blockState3) {
108616
108872
  this.data.assign(index, 0);
108617
108873
  this.fluidCells[index] = 0;
108618
108874
  continue;
108619
108875
  }
108620
- if (fluid) this.fluidCells[index] = (fluid.kind === "water" ? 16 : 32) | Math.min(fluid.level, 8);
108876
+ if (fluid) {
108877
+ this.fluidCells[index] = (fluid.kind === "water" ? 16 : 32) | Math.min(fluid.level, 8);
108878
+ if (fluid.kind === "water") this.setWaterTint(index, context?.waterTint);
108879
+ }
108621
108880
  const variantPosition = [
108622
108881
  this.x * 16 + index % 16,
108623
108882
  this.y * 16 + Math.floor(index / 256),
108624
108883
  this.z * 16 + Math.floor(index / 16) % 16
108625
108884
  ];
108626
- const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : prepared?.pick(variantPosition);
108885
+ const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
108627
108886
  if (templates) {
108628
108887
  this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
108629
108888
  this.meshDirty = true;
108630
108889
  } else {
108631
- perBlocks.push({ index, blockState: blockState3 });
108890
+ perBlocks.push({ index, blockState: blockState3, biome: context?.biome });
108632
108891
  }
108633
108892
  const readBlock = this.data.snapshot(index);
108634
108893
  this.renderedBlocks.set(index, { get block() {
@@ -108641,7 +108900,7 @@ gl_FragDepth=readDepth(vUv);
108641
108900
  failed = true;
108642
108901
  }
108643
108902
  }
108644
- for (const { index, blockState: blockState3 } of perBlocks) {
108903
+ for (const { index, blockState: blockState3, biome } of perBlocks) {
108645
108904
  let object4;
108646
108905
  try {
108647
108906
  object4 = await this.scene.addBlock(blockState3, {
@@ -108649,11 +108908,9 @@ gl_FragDepth=readDepth(vUv);
108649
108908
  instanceMeshes: true,
108650
108909
  maxInstanceCount: 2e3,
108651
108910
  initialState: this.data.stateAt(this.data.idAt(index)).properties,
108652
- variantPosition: [
108653
- this.x * 16 + index % 16,
108654
- this.y * 16 + Math.floor(index / 256),
108655
- this.z * 16 + Math.floor(index / 16) % 16
108656
- ]
108911
+ variantPosition: this.positionAt(index),
108912
+ biome,
108913
+ biomePosition: this.biomePosition(index, blockState3, this.data.stateAt(this.data.idAt(index)))
108657
108914
  });
108658
108915
  object4.setPosition(new Vector3(
108659
108916
  this.x * 256 + index % 16 * 16,
@@ -108672,6 +108929,68 @@ gl_FragDepth=readDepth(vUv);
108672
108929
  }
108673
108930
  if (failed) throw failure;
108674
108931
  }
108932
+ positionAt(index) {
108933
+ return [this.x * 16 + index % 16, this.y * 16 + Math.floor(index / 256), this.z * 16 + Math.floor(index / 16) % 16];
108934
+ }
108935
+ biomePosition(index, blockState3, stored) {
108936
+ const position = this.positionAt(index), name2 = blockState3.key?.toNamespacedString();
108937
+ if (stored.properties?.half === "upper" && (name2 === "minecraft:tall_grass" || name2 === "minecraft:large_fern")) position[1]--;
108938
+ return position;
108939
+ }
108940
+ async resolveState(stored, indices, biomes) {
108941
+ const blockState3 = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
108942
+ const perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
108943
+ const fluid = await getBlockFluidState(blockState3?.key, stored.properties);
108944
+ const hasBiomes = this.getBiomeAt(this.x * 16, this.y * 16, this.z * 16) !== void 0 || this.biomeAt?.(this.x * 16, this.y * 16 - 1, this.z * 16) !== void 0;
108945
+ const source = hasBiomes && blockState3 ? await BlockTints.biomeSource(blockState3.key) : void 0;
108946
+ const prepare = (color3) => perBlock || fluid?.renderModel === false ? void 0 : this.sectionModels.prepareState(blockState3, stored.properties, color3);
108947
+ const contexts = /* @__PURE__ */ new Map();
108948
+ const groups = /* @__PURE__ */ new Map();
108949
+ if (blockState3 && (source || fluid?.kind === "water")) {
108950
+ for (const index of indices) {
108951
+ const position = this.biomePosition(index, blockState3, stored);
108952
+ const biome = this.biomeAt ? this.biomeAt(...position) : position[1] >= this.y * 16 ? this.getBiomeAt(position) : void 0;
108953
+ if (!biome) continue;
108954
+ const key = JSON.stringify(source === "grass" ? [biome, position[0], position[2]] : [biome]);
108955
+ let group = groups.get(key);
108956
+ if (!group) groups.set(key, group = { biome, position, indices: [] });
108957
+ group.indices.push(index);
108958
+ }
108959
+ }
108960
+ const groupedCount = [...groups.values()].reduce((sum, group) => sum + group.indices.length, 0);
108961
+ const prepared = groupedCount < indices.length ? await prepare() : void 0;
108962
+ await Promise.all([...groups.values()].map(async ({ biome, position, indices: indices2 }) => {
108963
+ const available = () => {
108964
+ const key = JSON.stringify([biome, blockState3?.key?.root]);
108965
+ let found = biomes.get(key);
108966
+ if (!found) {
108967
+ found = Biomes.get(biome, blockState3?.key).then((value) => value !== void 0);
108968
+ biomes.set(key, found);
108969
+ }
108970
+ return found;
108971
+ };
108972
+ const context = { biome };
108973
+ if (perBlock) {
108974
+ if (!await available()) context.biome = void 0;
108975
+ } else {
108976
+ context.prepared = await prepare(async () => await available() ? BlockTints.getBiomeColor(blockState3?.key, biome, position) : void 0);
108977
+ if (fluid?.kind === "water" && await available()) {
108978
+ const color3 = await Biomes.getColor(biome, "water", blockState3?.key);
108979
+ if (color3 !== void 0 && color3 !== 4159204) context.waterTint = new Color(color3).toArray();
108980
+ }
108981
+ }
108982
+ for (const index of indices2) contexts.set(index, context);
108983
+ }));
108984
+ return { blockState: blockState3, perBlock, fluid, prepared, contexts, stored };
108985
+ }
108986
+ setWaterTint(index, tint) {
108987
+ if (!this.sectionModels || !this.waterTints && tint === void 0) return;
108988
+ if (!this.waterTints) {
108989
+ this.waterTints = new Float32Array(4096 * 3);
108990
+ for (let cell = 0; cell < 4096; cell++) this.waterTints.set(WATER_TINT2, cell * 3);
108991
+ }
108992
+ this.waterTints.set(tint ?? WATER_TINT2, index * 3);
108993
+ }
108675
108994
  /** Changes visibility at world block coordinates while preserving the block's data. */
108676
108995
  async setBlockVisibleAt(pos, visible) {
108677
108996
  const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
@@ -108775,7 +109094,7 @@ gl_FragDepth=readDepth(vUv);
108775
109094
  if (block && !block.object) origins.set(kind, block.block.type);
108776
109095
  }
108777
109096
  if (origins.size) {
108778
- fluids = { cells: new Uint8Array(18 * 18 * 18) };
109097
+ fluids = { cells: new Uint8Array(18 * 18 * 18), waterTints: this.waterTints?.slice() };
108779
109098
  for (let y5 = -1; y5 <= 16; y5++) {
108780
109099
  for (let z4 = -1; z4 <= 16; z4++) {
108781
109100
  for (let x4 = -1; x4 <= 16; x4++) {
@@ -108817,8 +109136,13 @@ gl_FragDepth=readDepth(vUv);
108817
109136
  this.renderedBlocks.clear();
108818
109137
  this.hiddenBlocks.clear();
108819
109138
  this.fluidCells.fill(0);
109139
+ this.waterTints = void 0;
108820
109140
  this.data.clear();
108821
- if (notify) await this.onBlocksChanged?.(positions);
109141
+ this.biomes = void 0;
109142
+ if (notify) {
109143
+ await this.onBiomesChanged?.(void 0);
109144
+ await this.onBlocksChanged?.(positions);
109145
+ }
108822
109146
  }
108823
109147
  async dispose() {
108824
109148
  await this.clear();