minerender 2.1.0-alpha.115 → 2.1.0-alpha.117

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
  });
@@ -43233,7 +43235,8 @@ void main() {
43233
43235
  }
43234
43236
  /**
43235
43237
  * Uses the exact version when published. Otherwise, selects the nearest patch in the same
43236
- * release line (older on ties), then the latest earlier release. Unpublished snapshots reject.
43238
+ * release line (older on ties), then the latest earlier release or the oldest published release.
43239
+ * Unpublished snapshots reject.
43237
43240
  */
43238
43241
  static async resolve(root) {
43239
43242
  const requestRoot = root?.replace(/\/+$/, "");
@@ -43242,11 +43245,11 @@ void main() {
43242
43245
  const scope = AssetLoader.persistentScope;
43243
43246
  return this.cached(`resolve:${requestRoot === void 0 ? "sources" : "explicit"}:${root}`, scope, defaultRoot, async () => {
43244
43247
  const requestedVersion = root.slice(root.lastIndexOf("/") + 1);
43245
- let manifest = await this.manifest(root, release(requestedVersion) ? requestedVersion : void 0, requestRoot);
43248
+ let manifest = await this.manifest(root, requestRoot !== void 0 && release(requestedVersion) ? requestedVersion : void 0, requestRoot);
43246
43249
  if (manifest) return { requestedVersion, version: manifest.minecraftVersion, root, manifest };
43247
43250
  const requested = release(requestedVersion);
43248
- if (!requested || compare(requested, [1, 16, 5]) < 0) {
43249
- throw new Error(`No MineRender data is published for ${requestedVersion}; automatic fallback requires a release from 1.16.5 onward`);
43251
+ if (!requested) {
43252
+ throw new Error(`No MineRender data is published for ${requestedVersion}; automatic fallback requires a release`);
43250
43253
  }
43251
43254
  const base = root.slice(0, root.lastIndexOf("/"));
43252
43255
  const index = await this.cached(`versions:${base}`, scope, defaultRoot, async () => {
@@ -43256,9 +43259,9 @@ void main() {
43256
43259
  }
43257
43260
  return value.versions;
43258
43261
  });
43259
- const versions2 = index.map((entry) => ({ name: entry.name, number: release(entry.name) })).filter((entry) => !!entry.number && compare(entry.number, [1, 16, 5]) >= 0).sort((left, right) => compare(left.number, right.number));
43262
+ const versions2 = index.map((entry) => ({ name: entry.name, number: release(entry.name) })).filter((entry) => !!entry.number).sort((left, right) => compare(left.number, right.number));
43260
43263
  const sameLine = versions2.filter((entry) => entry.number[0] === requested[0] && entry.number[1] === requested[1]);
43261
- const selected = sameLine.sort((left, right) => Math.abs((left.number[2] ?? 0) - (requested[2] ?? 0)) - Math.abs((right.number[2] ?? 0) - (requested[2] ?? 0)) || compare(left.number, right.number))[0] ?? versions2.filter((entry) => compare(entry.number, requested) <= 0).pop();
43264
+ const selected = sameLine.sort((left, right) => Math.abs((left.number[2] ?? 0) - (requested[2] ?? 0)) - Math.abs((right.number[2] ?? 0) - (requested[2] ?? 0)) || compare(left.number, right.number))[0] ?? versions2.filter((entry) => compare(entry.number, requested) <= 0).pop() ?? versions2[0];
43262
43265
  if (!selected) throw new Error(`No compatible MineRender data is published for ${requestedVersion}`);
43263
43266
  const resolvedRoot = `${base}/${selected.name}`;
43264
43267
  manifest = await this.manifest(resolvedRoot, selected.name, resolvedRoot);
@@ -43305,7 +43308,7 @@ ${key}` : key, async () => {
43305
43308
  case "blockStates":
43306
43309
  return Object.values(value).every((property) => object(property) && "default" in property && Array.isArray(property.values));
43307
43310
  case "blockTints":
43308
- return ["constant", "grass", "stem", "redstone"].includes(value.source) && (value.source !== "constant" || Number.isInteger(value.color));
43311
+ 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
43312
  case "fluids":
43310
43313
  return ["water", "lava"].includes(value.kind) && typeof value.renderModel === "boolean" && (value.levelProperty === void 0 || typeof value.levelProperty === "string");
43311
43314
  case "dyes":
@@ -43377,12 +43380,7 @@ ${key}` : key, async () => {
43377
43380
  static async getLayers(value, target, root) {
43378
43381
  if (value === void 0) return [];
43379
43382
  if (!Array.isArray(value)) throw new Error("banner_patterns must be an array");
43380
- const activeRoot = AssetLoader.ROOT, scope = AssetLoader.persistentScope;
43381
- const checkSources = () => {
43382
- if (activeRoot !== AssetLoader.ROOT || scope !== AssetLoader.persistentScope) throw new Error("Asset sources changed while loading banner patterns; retry the request");
43383
- };
43384
43383
  const colors = value.length ? await this.getColors(root) : void 0;
43385
- checkSources();
43386
43384
  return Promise.all(value.slice(0, 16).map(async (layer) => {
43387
43385
  if (!layer || typeof layer !== "object" || Array.isArray(layer)) throw new Error("Each banner pattern requires a pattern and dye color");
43388
43386
  if (typeof layer.color !== "string" || !Object.prototype.hasOwnProperty.call(colors, layer.color)) throw new Error(`Unsupported dye color ${layer.color}`);
@@ -43392,7 +43390,6 @@ ${key}` : key, async () => {
43392
43390
  const id = this.identifier(pattern2);
43393
43391
  const key = new AssetKey(id.namespace, id.getFullPath(), "banner_pattern", void 0, "data", ".json", root);
43394
43392
  pattern2 = await Caching.bannerPatternCache.get(key.serialize(), () => AssetLoader.get(key, "json" /* JSON */));
43395
- checkSources();
43396
43393
  if (!pattern2) throw new Error(`Missing banner pattern ${layer.pattern}`);
43397
43394
  }
43398
43395
  if (!pattern2 || typeof pattern2 !== "object" || Array.isArray(pattern2) || typeof pattern2.translation_key !== "string") throw new Error("Banner patterns require asset_id and translation_key");
@@ -43461,15 +43458,19 @@ ${key}` : key, async () => {
43461
43458
  Colormaps = class {
43462
43459
  /** Samples the selected resource pack's grass colormap using Minecraft's climate coordinates. */
43463
43460
  static async grassColor(origin, temperature = 0.5, downfall = 1) {
43464
- const key = AssetKey.parse("textures", "minecraft:colormap/grass", origin);
43461
+ return this.getColor("grass", origin, temperature, downfall);
43462
+ }
43463
+ /** Samples a resource-pack colormap at the supplied climate coordinates. */
43464
+ static async getColor(kind, origin, temperature = 0.5, downfall = 1) {
43465
+ const key = AssetKey.parse("textures", `minecraft:colormap/${kind}`, origin);
43465
43466
  const temp = Math.fround(temperature);
43466
43467
  const rain = Math.fround(downfall) * temp;
43467
43468
  const x4 = Math.floor((1 - temp) * 255), y5 = Math.floor((1 - rain) * 255);
43468
43469
  return await Caching.blockTintCache.get(`${key.serialize()}|${x4},${y5}`, async () => {
43469
43470
  const image = await ModelTextures.get(key);
43470
- if (!image) throw new MineRenderError(`Missing grass colormap: ${key.toNamespacedString()}`);
43471
+ if (!image) throw new MineRenderError(`Missing ${kind} colormap: ${key.toNamespacedString()}`);
43471
43472
  if (image.width !== 256 || image.height !== 256) {
43472
- throw new MineRenderError("Grass colormaps must be 256 \xD7 256 pixels");
43473
+ throw new MineRenderError(`${kind} colormaps must be 256 \xD7 256 pixels`);
43473
43474
  }
43474
43475
  const [red, green, blue] = image.data.getImageData(x4, y5, 1, 1).data;
43475
43476
  return red << 16 | green << 8 | blue;
@@ -43688,30 +43689,24 @@ ${key}` : key, async () => {
43688
43689
  }).then((r5) => r5?.files ?? []);
43689
43690
  }
43690
43691
  static async loadAndMerge(key, context = {}) {
43691
- const checkSources = this.sourceCheck();
43692
43692
  const model = key.type === "item" ? await this.getItemModel(key, context) : await this.getRaw(key);
43693
43693
  if (!model) {
43694
43694
  return void 0;
43695
43695
  }
43696
43696
  const merged = await ModelMerger.mergeWithParents(model);
43697
- checkSources();
43698
43697
  return merged;
43699
43698
  }
43700
43699
  static async getItemModel(key, context = {}) {
43701
- const checkSources = this.sourceCheck();
43702
43700
  const preview = this.snapshotContext(key, context);
43703
43701
  const dataKey = await this.applyDefaults(key, preview);
43704
- checkSources();
43705
43702
  return this.getPreparedItemModel(key, preview, dataKey);
43706
43703
  }
43707
43704
  static async getPreparedItemModel(key, preview, dataKey) {
43708
- const checkSources = this.sourceCheck();
43709
43705
  const itemId = `${key.namespace}:${key.path}`;
43710
43706
  const itemKey2 = new AssetKey(key.namespace, key.path, "items", void 0, key.rootType, ".json", key.root);
43711
43707
  const cacheKey = itemKey2.serialize() + this.contextKey(preview) + dataKey;
43712
43708
  const model = await this.PERSISTENT_CACHE.getOrLoad(`${this.ITEM_CACHE_VERSION}:${AssetLoader.persistentKey(cacheKey)}`, async () => {
43713
43709
  const result = await AssetLoader.getFirst([itemKey2, key], "json" /* JSON */);
43714
- checkSources();
43715
43710
  if (!result) return void 0;
43716
43711
  if (result.key.assetType !== "items") return { ...result.asset, key, itemId, components: preview.components };
43717
43712
  const load = async (selected) => {
@@ -43730,10 +43725,8 @@ ${key}` : key, async () => {
43730
43725
  return { ...model3, itemId, components: preview.components, ...selected.special && { special: selected.special }, ...selected.tints && { tints: selected.tints } };
43731
43726
  };
43732
43727
  const model2 = await load(this.selectItemModel(result.asset.model, key, preview));
43733
- checkSources();
43734
43728
  return model2;
43735
43729
  });
43736
- checkSources();
43737
43730
  const restore = (model2) => ({
43738
43731
  ...model2,
43739
43732
  key: Object.assign(new AssetKey("", ""), model2.key),
@@ -43797,22 +43790,14 @@ ${key}` : key, async () => {
43797
43790
  }
43798
43791
  static async applyDefaults(key, preview) {
43799
43792
  if (key.type !== "item") return "";
43800
- const root = AssetLoader.ROOT, scope = AssetLoader.persistentScope;
43801
43793
  const resolved = await MineRenderData.resolve(key.root);
43802
43794
  const defaults = await ItemDefaults.get(`${key.namespace}:${key.path}`, key.root);
43803
43795
  preview.components = { ...defaults, ...preview.components };
43804
43796
  if (resolved.manifest.dataVersion < 3337 && key.namespace === DEFAULT_NAMESPACE && ["potion", "splash_potion", "lingering_potion"].includes(key.path) && !Object.prototype.hasOwnProperty.call(preview.components, "minecraft:enchantment_glint_override") && await ItemTints.hasPotionEffects(preview.components["minecraft:potion_contents"], key.root)) {
43805
43797
  preview.components["minecraft:enchantment_glint_override"] = true;
43806
43798
  }
43807
- if (AssetLoader.ROOT !== root || AssetLoader.persistentScope !== scope) throw new Error("Asset sources changed while loading item defaults; retry the request");
43808
43799
  return `|data:${resolved.root}:${resolved.manifest.datasets.itemDefaults?.sha256}:${resolved.manifest.datasets.potionColors?.sha256}`;
43809
43800
  }
43810
- static sourceCheck() {
43811
- const root = AssetLoader.ROOT, scope = AssetLoader.persistentScope;
43812
- return () => {
43813
- if (AssetLoader.ROOT !== root || AssetLoader.persistentScope !== scope) throw new Error("Asset sources changed while loading item models; retry the request");
43814
- };
43815
- }
43816
43801
  static contextKey(context) {
43817
43802
  const itemReferences = Object.fromEntries(Object.entries(context.itemReferences).map(([id, key]) => [id, key.serialize()]));
43818
43803
  return `|${this.ITEM_CACHE_VERSION}:${JSON.stringify(this.snapshotJson({ ...context, itemReferences }))}`;
@@ -43999,7 +43984,6 @@ ${key}` : key, async () => {
43999
43984
  * @param key - Model key, for example `AssetKey.parse("models", "minecraft:item/diamond_sword")`.
44000
43985
  */
44001
43986
  static async getMerged(key, context = {}) {
44002
- const checkSources = this.sourceCheck();
44003
43987
  if (!key.assetType) {
44004
43988
  key.assetType = "models";
44005
43989
  }
@@ -44008,15 +43992,12 @@ ${key}` : key, async () => {
44008
43992
  }
44009
43993
  const preview = this.snapshotContext(key, context);
44010
43994
  const dataKey = await this.applyDefaults(key, preview);
44011
- checkSources();
44012
43995
  const keyStr = AssetLoader.persistentKey(key.serialize() + (key.type === "item" ? this.contextKey(preview) + dataKey : ""));
44013
43996
  const model = await Caching.mergedModelCache.get(keyStr, async () => {
44014
43997
  const raw = key.type === "item" ? await this.getPreparedItemModel(key, preview, dataKey) : await this.getRaw(key);
44015
43998
  const merged = raw ? await ModelMerger.mergeWithParents(raw) : void 0;
44016
- checkSources();
44017
43999
  return merged;
44018
44000
  });
44019
- checkSources();
44020
44001
  return model;
44021
44002
  }
44022
44003
  /** Alias for {@link getMerged}. */
@@ -57531,7 +57512,7 @@ ${key}` : key, async () => {
57531
57512
  var base = 36;
57532
57513
  var tMin = 1;
57533
57514
  var tMax = 26;
57534
- var skew = 38;
57515
+ var skew2 = 38;
57535
57516
  var damp2 = 700;
57536
57517
  var initialBias = 72;
57537
57518
  var initialN = 128;
@@ -57620,7 +57601,7 @@ ${key}` : key, async () => {
57620
57601
  ) {
57621
57602
  delta = floor(delta / baseMinusTMin);
57622
57603
  }
57623
- return floor(k4 + (baseMinusTMin + 1) * delta / (delta + skew));
57604
+ return floor(k4 + (baseMinusTMin + 1) * delta / (delta + skew2));
57624
57605
  };
57625
57606
  var decode2 = function decode3(input) {
57626
57607
  var output = [];
@@ -95379,6 +95360,7 @@ ${shader.fragmentShader}`.replace(
95379
95360
  BannerPatterns: () => BannerPatterns,
95380
95361
  BasicAssetKey: () => BasicAssetKey,
95381
95362
  BatchedExecutor: () => BatchedExecutor,
95363
+ Biomes: () => Biomes,
95382
95364
  BlockEntities: () => BlockEntities,
95383
95365
  BlockObject: () => BlockObject,
95384
95366
  BlockStateResolver: () => BlockStateResolver,
@@ -95841,6 +95823,122 @@ ${shader.fragmentShader}`.replace(
95841
95823
  init_Assets();
95842
95824
  init_BannerPatterns();
95843
95825
 
95826
+ // src/assets/Biomes.ts
95827
+ init_dirname();
95828
+ init_buffer2();
95829
+ init_process2();
95830
+ init_AssetKey();
95831
+ init_AssetLoader();
95832
+ init_AssetParsers();
95833
+ init_Caching();
95834
+ init_MineRenderError();
95835
+ init_Colormaps();
95836
+
95837
+ // src/assets/_biomes/SwampGrassColor.ts
95838
+ init_dirname();
95839
+ init_buffer2();
95840
+ init_process2();
95841
+ var permutation;
95842
+ 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]];
95843
+ var skew = (Math.sqrt(3) - 1) / 2;
95844
+ var unskew = (3 - Math.sqrt(3)) / 6;
95845
+ function createPermutation() {
95846
+ const multiplier = 0x5deece66dn, mask = (1n << 48n) - 1n;
95847
+ let seed = 2345n ^ multiplier;
95848
+ const next = (bits2) => {
95849
+ seed = seed * multiplier + 11n & mask;
95850
+ return Number(seed >> BigInt(48 - bits2));
95851
+ };
95852
+ for (let i4 = 0; i4 < 3; i4++) {
95853
+ next(26);
95854
+ next(27);
95855
+ }
95856
+ const values = Uint8Array.from({ length: 256 }, (_4, i4) => i4);
95857
+ for (let i4 = 0; i4 < 256; i4++) {
95858
+ const bound = 256 - i4;
95859
+ let offset;
95860
+ if ((bound & bound - 1) === 0) offset = Math.floor(bound * next(31) / 2147483648);
95861
+ else {
95862
+ let bits2;
95863
+ do {
95864
+ bits2 = next(31);
95865
+ offset = bits2 % bound;
95866
+ } while (bits2 - offset + bound - 1 > 2147483647);
95867
+ }
95868
+ const other = i4 + offset;
95869
+ [values[i4], values[other]] = [values[other], values[i4]];
95870
+ }
95871
+ return values;
95872
+ }
95873
+ function swampGrassColor(x4, z4) {
95874
+ const p13 = permutation ?? (permutation = createPermutation());
95875
+ x4 *= 0.0225;
95876
+ z4 *= 0.0225;
95877
+ const s4 = (x4 + z4) * skew;
95878
+ const i4 = Math.floor(x4 + s4), j4 = Math.floor(z4 + s4);
95879
+ const t5 = (i4 + j4) * unskew;
95880
+ const dx = x4 - (i4 - t5), dz = z4 - (j4 - t5);
95881
+ const stepX = dx > dz ? 1 : 0, stepZ = 1 - stepX;
95882
+ const corner = (gx, gz, a4, b4) => {
95883
+ let falloff = 0.5 - a4 * a4 - b4 * b4;
95884
+ if (falloff < 0) return 0;
95885
+ const gradient = gradients[p13[gx + p13[gz & 255] & 255] % 12];
95886
+ falloff *= falloff;
95887
+ return falloff * falloff * (gradient[0] * a4 + gradient[1] * b4);
95888
+ };
95889
+ 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));
95890
+ return noise < -0.1 ? 5011004 : 6975545;
95891
+ }
95892
+
95893
+ // src/assets/Biomes.ts
95894
+ var Biomes = class {
95895
+ /** Loads a biome registry entry, or returns `undefined` when no source provides it. */
95896
+ static async get(id, origin) {
95897
+ if (!/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(id)) throw new MineRenderError(`Invalid biome identifier: ${id}`);
95898
+ const [namespace, path] = id.includes(":") ? id.split(":") : ["minecraft", id];
95899
+ const key = new AssetKey(namespace, path, "worldgen", "biome", "data", ".json", origin?.root);
95900
+ return Caching.biomeCache.get(key.serialize(), async () => {
95901
+ const asset2 = await AssetLoader.get(key, "json" /* JSON */);
95902
+ if (asset2 === void 0) return void 0;
95903
+ if (!asset2 || typeof asset2 !== "object" || Array.isArray(asset2) || !asset2.effects || typeof asset2.effects !== "object" || Array.isArray(asset2.effects)) {
95904
+ throw new MineRenderError(`Invalid biome effects: ${id}`);
95905
+ }
95906
+ if (typeof asset2.temperature !== "number" || !Number.isFinite(asset2.temperature) || typeof asset2.downfall !== "number" || !Number.isFinite(asset2.downfall)) {
95907
+ throw new MineRenderError(`Invalid biome climate: ${id}`);
95908
+ }
95909
+ for (const kind of ["grass", "foliage", "dry_foliage", "water"]) {
95910
+ const color3 = asset2.effects[`${kind}_color`];
95911
+ if (color3 !== void 0) this.parseColor(color3, id, kind);
95912
+ }
95913
+ const modifier = asset2.effects.grass_color_modifier;
95914
+ if (modifier !== void 0 && modifier !== "none" && modifier !== "dark_forest" && modifier !== "swamp") {
95915
+ throw new MineRenderError(`Invalid biome grass color modifier: ${id}`);
95916
+ }
95917
+ return asset2;
95918
+ });
95919
+ }
95920
+ /** Returns packed sRGB, or `undefined` when the biome or requested water color is missing. */
95921
+ static async getColor(id, kind, origin, x4 = 0, z4 = 0) {
95922
+ const biome = await this.get(id, origin);
95923
+ if (!biome) return void 0;
95924
+ const rawColor = biome.effects[`${kind}_color`];
95925
+ let color3 = rawColor === void 0 ? void 0 : this.parseColor(rawColor, id, kind);
95926
+ if (kind === "water") return color3;
95927
+ const modifier = kind === "grass" ? biome.effects.grass_color_modifier : void 0;
95928
+ if (modifier === "swamp") return swampGrassColor(x4, z4);
95929
+ if (color3 === void 0) {
95930
+ const clamp2 = (value) => Math.max(0, Math.min(1, Math.fround(value)));
95931
+ color3 = await Colormaps.getColor(kind, origin, clamp2(biome.temperature), clamp2(biome.downfall));
95932
+ }
95933
+ return modifier === "dark_forest" ? (color3 & 16711422) + 2634762 >> 1 : color3;
95934
+ }
95935
+ static parseColor(value, id, kind) {
95936
+ if (typeof value === "string" && /^#[0-9a-fA-F]{6}$/.test(value)) return parseInt(value.slice(1), 16);
95937
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 16777215) return value;
95938
+ throw new MineRenderError(`Invalid biome ${kind} color: ${id}`);
95939
+ }
95940
+ };
95941
+
95844
95942
  // src/assets/BlockEntities.ts
95845
95943
  init_dirname();
95846
95944
  init_buffer2();
@@ -100445,8 +100543,20 @@ property list uchar int vertex_index
100445
100543
  init_Colormaps();
100446
100544
  init_MineRenderData();
100447
100545
  var BlockTints = class {
100448
- /** Resolves block preview colors without biome context; explicit tints take precedence. */
100449
- static async get(key, state, model, tints) {
100546
+ /** Returns a block's versioned biome color source, leaving fixed-color blocks unchanged. */
100547
+ static async biomeSource(key) {
100548
+ const rule = key && (await MineRenderData.get("blockTints", key.root))[key.toNamespacedString()];
100549
+ return rule ? rule.biome ?? (rule.source === "grass" ? "grass" : void 0) : void 0;
100550
+ }
100551
+ /** Resolves one block's biome tint at world block coordinates, without biome blending. */
100552
+ static async getBiomeColor(key, biome, position) {
100553
+ if (!biome) return void 0;
100554
+ const x4 = position?.[0], z4 = position?.[2];
100555
+ const source = await this.biomeSource(key);
100556
+ return source ? Biomes.getColor(biome, source, key, x4, z4) : void 0;
100557
+ }
100558
+ /** Resolves used tint indices from biome or preview colors; explicit tints take precedence. */
100559
+ static async get(key, state, model, tints, biomeColor) {
100450
100560
  if (!key) return tints;
100451
100561
  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
100562
  if (!candidates) return tints;
@@ -100462,8 +100572,12 @@ property list uchar int vertex_index
100462
100572
  }
100463
100573
  }
100464
100574
  if (!indices.size) return tints;
100575
+ const source = rule.biome ?? (rule.source === "grass" ? "grass" : void 0);
100576
+ const biome = source ? typeof biomeColor === "function" ? await biomeColor() : biomeColor : void 0;
100465
100577
  let color3;
100466
- switch (rule.source) {
100578
+ if (biome !== void 0) {
100579
+ color3 = biome;
100580
+ } else switch (rule.source) {
100467
100581
  case "constant":
100468
100582
  color3 = rule.color;
100469
100583
  break;
@@ -100508,7 +100622,6 @@ property list uchar int vertex_index
100508
100622
  init_ModelCulling();
100509
100623
  init_FluidGeometry();
100510
100624
  init_MineRenderData();
100511
- init_AssetLoader();
100512
100625
  init_Entities();
100513
100626
  var _BlockObject = class _BlockObject extends SceneObject {
100514
100627
  constructor(blockState3, options) {
@@ -100530,21 +100643,15 @@ property list uchar int vertex_index
100530
100643
  this._entityGeneration = 0;
100531
100644
  this.options = (0, import_ts_deepmerge6.default)({}, _BlockObject.DEFAULT_OPTIONS, options ?? {});
100532
100645
  this.variantPosition = options?.variantPosition ? [...options.variantPosition] : void 0;
100646
+ this.biomePosition = options?.biomePosition ? [...options.biomePosition] : void 0;
100533
100647
  }
100534
100648
  async init() {
100535
- const root = AssetLoader.ROOT, scope = AssetLoader.persistentScope;
100536
- const checkSources = () => {
100537
- if (root !== AssetLoader.ROOT || scope !== AssetLoader.persistentScope) throw new Error("Asset sources changed while loading a block; retry the request");
100538
- };
100539
100649
  this._fluidRules = await MineRenderData.get("fluids", this.blockState.key?.root);
100540
- checkSources();
100541
100650
  if (this.options.applyDefaultState) {
100542
100651
  this._setState(await BlockStateResolver.defaults(this.blockState));
100543
- checkSources();
100544
100652
  }
100545
100653
  if (this.options.initialState !== void 0) this._setState(this.options.initialState);
100546
100654
  await this.recreateModels();
100547
- checkSources();
100548
100655
  }
100549
100656
  dispose() {
100550
100657
  this.clearModels();
@@ -100722,11 +100829,16 @@ property list uchar int vertex_index
100722
100829
  const { FluidModelObject: FluidModelObject2, sampleFluid: sampleFluid2 } = await Promise.resolve().then(() => (init_FluidModelObject(), FluidModelObject_exports));
100723
100830
  this._fluidSampler = sample;
100724
100831
  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;
100832
+ const tint = this.options.tints?.[0] ?? this.waterTint;
100833
+ const fluidKey = `${surface.key}|${tint ?? "default"}`;
100834
+ if (this._fluidKey === fluidKey) return;
100726
100835
  const key = new AssetKey("minecraft", `${kind}/${surface.key}`, "models", "fluid", "assets", ".json", this.blockState.key?.root);
100727
100836
  const replacement = await this.scene.addSceneObject(
100728
100837
  { key },
100729
- () => new FluidModelObject2(kind, surface.sample, this.blockState.key, this.options)
100838
+ () => new FluidModelObject2(kind, surface.sample, this.blockState.key, {
100839
+ ...this.options,
100840
+ tints: tint === void 0 ? this.options.tints : { ...this.options.tints, 0: tint }
100841
+ })
100730
100842
  );
100731
100843
  const previous = this._fluidModel;
100732
100844
  const matrix = previous ? this.getModelMatrix(previous) : new Matrix4().makeTranslation(...this.position.toArray());
@@ -100735,7 +100847,7 @@ property list uchar int vertex_index
100735
100847
  this._models = this._models.filter((model) => model !== previous);
100736
100848
  this._models.push(replacement);
100737
100849
  this._fluidModel = replacement;
100738
- this._fluidKey = surface.key;
100850
+ this._fluidKey = fluidKey;
100739
100851
  this._isInstanced || (this._isInstanced = isInstanceReference(replacement));
100740
100852
  this._instanceCounter = this._isInstanced ? 1 : 0;
100741
100853
  if (previous) this.removeModels([previous]);
@@ -100758,6 +100870,7 @@ property list uchar int vertex_index
100758
100870
  }
100759
100871
  async recreateModels() {
100760
100872
  this.clearModels();
100873
+ 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
100874
  if (resolveBlockFluidState(this.blockState.key, this.state, this._fluidRules)?.renderModel === false) {
100762
100875
  await this.updateFluid(this._fluidSampler);
100763
100876
  return;
@@ -100792,7 +100905,13 @@ property list uchar int vertex_index
100792
100905
  const model = await Models.getMerged(modelKey);
100793
100906
  const options = {
100794
100907
  ...this.options,
100795
- tints: model ? await BlockTints.get(this.blockState.key, this.state, model, this.options.tints) : this.options.tints,
100908
+ tints: model ? await BlockTints.get(
100909
+ this.blockState.key,
100910
+ this.state,
100911
+ model,
100912
+ this.options.tints,
100913
+ () => BlockTints.getBiomeColor(this.blockState.key, this.options.biome, this.biomePosition)
100914
+ ) : this.options.tints,
100796
100915
  uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0,
100797
100916
  cullMask: this.options.displayPosition ? 0 : ModelCulling.toLocalMask(this._cullMask, rotation)
100798
100917
  };
@@ -106527,7 +106646,6 @@ gl_FragDepth=readDepth(vUv);
106527
106646
  init_process2();
106528
106647
  init_MineRenderError();
106529
106648
  init_MineRenderData();
106530
- init_AssetLoader();
106531
106649
  init_NBTHelper();
106532
106650
 
106533
106651
  // src/world/ChunkData.ts
@@ -106804,14 +106922,12 @@ gl_FragDepth=readDepth(vUv);
106804
106922
  /** Decodes all stored chunks. Gzip, zlib, LZ4, and uncompressed payloads are supported. */
106805
106923
  static async parse(data, options = {}) {
106806
106924
  options = { ...options };
106807
- const checkSources = this.assetGuard();
106808
106925
  options.signal?.throwIfAborted();
106809
106926
  const bytes = this.bytes(data);
106810
106927
  const chunks = [];
106811
106928
  for (const location of this.locations(bytes)) {
106812
106929
  chunks.push(await this.abortable(this.readChunk(bytes, location, options), options.signal));
106813
106930
  }
106814
- checkSources();
106815
106931
  return { chunks };
106816
106932
  }
106817
106933
  /**
@@ -106822,22 +106938,12 @@ gl_FragDepth=readDepth(vUv);
106822
106938
  */
106823
106939
  static async parseChunk(data, localX, localZ, options = {}) {
106824
106940
  options = { ...options };
106825
- const checkSources = this.assetGuard();
106826
106941
  options.signal?.throwIfAborted();
106827
106942
  const bytes = this.bytes(data);
106828
106943
  const location = this.location(bytes, localX, localZ);
106829
106944
  const chunk = location ? await this.abortable(this.readChunk(bytes, location, options), options.signal) : void 0;
106830
- checkSources();
106831
106945
  return chunk;
106832
106946
  }
106833
- static assetGuard() {
106834
- const root = AssetLoader.ROOT, scope = AssetLoader.persistentScope;
106835
- return () => {
106836
- if (AssetLoader.ROOT !== root || AssetLoader.persistentScope !== scope) {
106837
- throw new MineRenderError("Asset sources changed while parsing Anvil data; retry the request");
106838
- }
106839
- };
106840
- }
106841
106947
  /**
106842
106948
  * Decodes one chunk from an `entities/r.x.z.mca` file without migrating its NBT.
106843
106949
  * @param localX - Chunk x within the region, from 0 to 31.
@@ -107376,24 +107482,48 @@ gl_FragDepth=readDepth(vUv);
107376
107482
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("maxAtlasSize must be a positive integer");
107377
107483
  }
107378
107484
  /** 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);
107485
+ async get(blockState3, properties = {}, position, biome) {
107486
+ const biomePosition = position ? [...position] : void 0;
107487
+ const prepared = await this.state(blockState3, properties);
107488
+ if (!prepared) return void 0;
107489
+ return Promise.all(prepared.groups.map(async (group) => {
107490
+ const variant = BlockStateResolver.choose(group, biomePosition);
107491
+ const choice = prepared.choices.get(variant);
107492
+ const tints = await BlockTints.get(
107493
+ blockState3.key,
107494
+ prepared.state,
107495
+ choice.model,
107496
+ void 0,
107497
+ () => BlockTints.getBiomeColor(blockState3.key, biome, biomePosition)
107498
+ );
107499
+ return this.template(variant, choice, tints);
107500
+ }));
107501
+ }
107502
+ /** Resolves section templates for one tint palette, with a synchronous weighted choice for each cell. */
107503
+ async prepareState(blockState3, properties = {}, biomeColor) {
107504
+ const prepared = await this.state(blockState3, properties);
107505
+ if (!prepared) return void 0;
107506
+ let color3;
107507
+ const resolveColor = typeof biomeColor === "function" ? () => color3 ?? (color3 = biomeColor()) : biomeColor;
107508
+ const templates = /* @__PURE__ */ new Map();
107509
+ await Promise.all([...prepared.choices].map(async ([variant, choice]) => {
107510
+ const tints = await BlockTints.get(blockState3.key, prepared.state, choice.model, void 0, resolveColor);
107511
+ templates.set(variant, await this.template(variant, choice, tints));
107512
+ }));
107513
+ return { pick: (position) => prepared.groups.map((group) => templates.get(BlockStateResolver.choose(group, position))) };
107382
107514
  }
107383
- /** Resolves section templates once, with a synchronous weighted choice for each cell. */
107384
- async prepareState(blockState3, properties = {}) {
107515
+ async state(blockState3, properties) {
107385
107516
  let states = this.states.get(blockState3);
107386
107517
  if (!states) this.states.set(blockState3, states = /* @__PURE__ */ new Map());
107387
107518
  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))) };
107519
+ return cached(states, key, () => this.prepare(blockState3, properties));
107391
107520
  }
107392
107521
  /** Returns the shared still/flow atlas used by per-block fluid objects. */
107393
107522
  fluidAtlas(kind, root) {
107394
- return cached(this.fluidAtlases, kind, async () => {
107523
+ const key = new AssetKey("minecraft", kind, "models", "fluid", "assets", ".json", root);
107524
+ return cached(this.fluidAtlases, key.serialize(), async () => {
107395
107525
  const atlas = await UVMapper.getAtlas({
107396
- key: new AssetKey("minecraft", kind, "models", "fluid", "assets", ".json", root),
107526
+ key,
107397
107527
  textures: { still: `minecraft:block/${kind}_still`, flow: `minecraft:block/${kind}_flow` },
107398
107528
  elements: []
107399
107529
  });
@@ -107405,64 +107535,67 @@ gl_FragDepth=readDepth(vUv);
107405
107535
  const state = { ...await BlockStateResolver.defaults(blockState3), ...properties };
107406
107536
  const groups = BlockStateResolver.matching(blockState3, state);
107407
107537
  if (!groups.length) return void 0;
107408
- const templates = /* @__PURE__ */ new Map();
107538
+ const prepared = /* @__PURE__ */ new Map();
107409
107539
  for (const group of groups) {
107410
107540
  const choices = Array.isArray(group) ? group : [group];
107411
107541
  if (!choices.length) return void 0;
107412
107542
  for (const variant of choices) {
107413
107543
  if (!variant.model) return void 0;
107414
- const model = await Models.getMerged(AssetKey.parse("models", variant.model));
107544
+ const modelKey = AssetKey.parse("models", variant.model);
107545
+ modelKey.root = blockState3.key?.root;
107546
+ const model = await Models.getMerged(modelKey);
107415
107547
  if (!model?.elements?.length) return void 0;
107416
107548
  const atlas = await UVMapper.getAtlas(model);
107417
107549
  const rotation = BlockStateResolver.rotation(variant);
107418
107550
  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);
107551
+ prepared.set(variant, { model, atlas, rotation });
107463
107552
  }
107464
107553
  }
107465
- return { groups, templates };
107554
+ return { state, groups, choices: prepared };
107555
+ }
107556
+ async template(variant, { model, atlas, rotation }, tints) {
107557
+ let models = this.models.get(model);
107558
+ if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
107559
+ const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
107560
+ return cached(models, key, async () => {
107561
+ const object4 = new SectionModel(model, {
107562
+ instanceMeshes: false,
107563
+ mergeMeshes: true,
107564
+ tints,
107565
+ uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0) ? [rotation.x, rotation.y, rotation.z] : void 0
107566
+ });
107567
+ await object4.init();
107568
+ let geometry = object4.children[0].geometry;
107569
+ geometry.applyMatrix4(new Matrix4().makeRotationFromEuler(rotation));
107570
+ object4.clear();
107571
+ const keep = [];
107572
+ const cullFaces = [];
107573
+ for (const element of atlas.model.elements) {
107574
+ for (const face of CUBE_FACES) {
107575
+ const data = element.faces[face];
107576
+ keep.push(data !== void 0);
107577
+ if (!data) continue;
107578
+ const local = CUBE_FACES.findIndex((name2) => name2 === data.cullface);
107579
+ if (local < 0) {
107580
+ cullFaces.push(0);
107581
+ continue;
107582
+ }
107583
+ const normal = new Vector3(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);
107584
+ 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);
107585
+ cullFaces.push(world < 0 ? 0 : 1 << world);
107586
+ }
107587
+ }
107588
+ geometry = compactQuads(geometry, keep);
107589
+ const template = {
107590
+ geometry,
107591
+ atlas,
107592
+ cullFaces: new Uint8Array(cullFaces),
107593
+ occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
107594
+ layer: atlas.hasTranslucency ? 1 : 0
107595
+ };
107596
+ this.templates.add(template);
107597
+ return template;
107598
+ });
107466
107599
  }
107467
107600
  /** Disposes prepared geometry and clears this world's template caches. */
107468
107601
  clear() {
@@ -107554,7 +107687,6 @@ gl_FragDepth=readDepth(vUv);
107554
107687
 
107555
107688
  // src/world/_entities/WorldEntities.ts
107556
107689
  init_BannerPatterns();
107557
- init_AssetLoader();
107558
107690
  var WorldEntities = class {
107559
107691
  constructor(scene) {
107560
107692
  this.scene = scene;
@@ -107562,10 +107694,6 @@ gl_FragDepth=readDepth(vUv);
107562
107694
  }
107563
107695
  /** Captures column ownership before asynchronous block or asset loading starts. */
107564
107696
  prepare(entities = [], column) {
107565
- const root = AssetLoader.ROOT, scope = AssetLoader.persistentScope;
107566
- const checkSources = () => {
107567
- if (root !== AssetLoader.ROOT || scope !== AssetLoader.persistentScope) throw new Error("Asset sources changed while loading saved entities; retry the request");
107568
- };
107569
107697
  const placements = entities.flatMap((entity) => {
107570
107698
  const resolved = resolveSavedEntity(entity);
107571
107699
  if (!resolved) return [];
@@ -107581,17 +107709,13 @@ gl_FragDepth=readDepth(vUv);
107581
107709
  let object4;
107582
107710
  try {
107583
107711
  if (!current()) return;
107584
- checkSources();
107585
107712
  const models = await Entities.getEntityList();
107586
- checkSources();
107587
107713
  if (!models.includes(placement.key.path) || !current()) return;
107588
107714
  const model = await Entities.getEntity(placement.key, placement.texture, { when: placement.when });
107589
- checkSources();
107590
107715
  if (!model || !current()) return;
107591
107716
  let tints = placement.tints;
107592
107717
  if (placement.woolDye) {
107593
107718
  const dye = (await BannerPatterns.getColors(placement.key.root))[placement.woolDye];
107594
- checkSources();
107595
107719
  const tint = placement.woolDye === "white" ? 15132390 : Math.floor((dye >> 16 & 255) * 0.75) << 16 | Math.floor((dye >> 8 & 255) * 0.75) << 8 | Math.floor((dye & 255) * 0.75);
107596
107720
  tints = { ...tints, wool_color: tint };
107597
107721
  }
@@ -107601,7 +107725,6 @@ gl_FragDepth=readDepth(vUv);
107601
107725
  object4.position.fromArray(placement.position).multiplyScalar(16).addScalar(-8);
107602
107726
  object4.rotation.y = placement.yaw;
107603
107727
  await object4.init();
107604
- checkSources();
107605
107728
  if (!current()) return;
107606
107729
  placement.owner.add(object4);
107607
107730
  this.scene.add(object4);
@@ -107798,7 +107921,6 @@ gl_FragDepth=readDepth(vUv);
107798
107921
  }
107799
107922
  try {
107800
107923
  await this.clearChunkColumn(chunk.x, chunk.z);
107801
- let sliceStart = performance.now();
107802
107924
  for (const section of chunk.sections) {
107803
107925
  if (section.biomes) {
107804
107926
  const position = new Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
@@ -107812,6 +107934,9 @@ gl_FragDepth=readDepth(vUv);
107812
107934
  if (!column) this.biomes.set(key, column = /* @__PURE__ */ new Map());
107813
107935
  column.set(section.y, copy);
107814
107936
  }
107937
+ }
107938
+ let sliceStart = performance.now();
107939
+ for (const section of chunk.sections) {
107815
107940
  if (!executor) {
107816
107941
  if (!occupiedSections.has(section.y)) continue;
107817
107942
  const position = new Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
@@ -107976,18 +108101,34 @@ gl_FragDepth=readDepth(vUv);
107976
108101
  const key = this.worldPosToChunkKey(pos);
107977
108102
  let chunk = this._chunks.get(key);
107978
108103
  if (typeof chunk === "undefined") {
108104
+ const x4 = Math.floor(pos.x / 16), y5 = Math.floor(pos.y / 16), z4 = Math.floor(pos.z / 16);
107979
108105
  chunk = new Chunk(
107980
108106
  this.scene,
107981
- Math.floor(pos.x / 16),
107982
- Math.floor(pos.y / 16),
107983
- Math.floor(pos.z / 16),
108107
+ x4,
108108
+ y5,
108109
+ z4,
107984
108110
  (positions) => this.updateCulling(positions),
107985
108111
  this.sectionModels,
107986
- (x4, y5, z4) => {
107987
- const cx = Math.floor(x4 / 16), cy = Math.floor(y5 / 16), cz = Math.floor(z4 / 16);
108112
+ (x5, y6, z5) => {
108113
+ const cx = Math.floor(x5 / 16), cy = Math.floor(y6 / 16), cz = Math.floor(z5 / 16);
107988
108114
  const section = this._chunks.get(`${cx}_${cy}_${cz}`);
107989
- const index = (y5 - cy * 16) * 256 + (z4 - cz * 16) * 16 + x4 - cx * 16;
108115
+ const index = (y6 - cy * 16) * 256 + (z5 - cz * 16) * 16 + x5 - cx * 16;
107990
108116
  return (section?.fluidByteIndex(index) ?? 0) | (section?.isOccludingIndex(index) ? 64 : 0);
108117
+ },
108118
+ (x5, y6, z5) => this.getBiomeAt(x5, y6, z5),
108119
+ async (biomes) => {
108120
+ if (this._chunks.get(key) !== chunk) return;
108121
+ const columnKey = `${x4}_${z4}`;
108122
+ let column = this.biomes.get(columnKey);
108123
+ if (biomes) {
108124
+ if (!column) this.biomes.set(columnKey, column = /* @__PURE__ */ new Map());
108125
+ column.set(y5, biomes);
108126
+ } else {
108127
+ column?.delete(y5);
108128
+ if (!column?.size) this.biomes.delete(columnKey);
108129
+ }
108130
+ await chunk.refreshBiomes();
108131
+ await this._chunks.get(`${x4}_${y5 + 1}_${z4}`)?.refreshBiomes();
107991
108132
  }
107992
108133
  );
107993
108134
  this._chunks.set(key, chunk);
@@ -108048,6 +108189,7 @@ gl_FragDepth=readDepth(vUv);
108048
108189
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
108049
108190
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
108050
108191
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
108192
+ if (fluids?.waterTints && fluids.waterTints.length !== 4096 * 3) throw new RangeError("Section water tints must contain RGB for 4096 cells");
108051
108193
  for (const template of templateData) {
108052
108194
  const { quads } = template;
108053
108195
  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 +108267,9 @@ gl_FragDepth=readDepth(vUv);
108125
108267
  height
108126
108268
  });
108127
108269
  }
108128
- fluidPages.get(fluid).fluids.push({ quads, x: x4, y: y5, z: z4, data });
108270
+ const tintIndex = (x4 + z4 * 16 + y5 * 256) * 3;
108271
+ const tint = fluid === "water" && fluids.waterTints ? fluids.waterTints.subarray(tintIndex, tintIndex + 3) : data.tint;
108272
+ fluidPages.get(fluid).fluids.push({ quads, x: x4, y: y5, z: z4, data, tint });
108129
108273
  }
108130
108274
  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
108275
  }
@@ -108174,7 +108318,7 @@ gl_FragDepth=readDepth(vUv);
108174
108318
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
108175
108319
  }
108176
108320
  }
108177
- for (const { quads, x: x4, y: y5, z: z4, data } of page.fluids) {
108321
+ for (const { quads, x: x4, y: y5, z: z4, data, tint } of page.fluids) {
108178
108322
  const atlas = atlases[data.atlas];
108179
108323
  const placement = page.placements[0];
108180
108324
  const mapU = (u4) => (placement.x + u4 * atlas.width) / page.width;
@@ -108198,7 +108342,7 @@ gl_FragDepth=readDepth(vUv);
108198
108342
  mapU((rect[0] + rect[2]) / atlas.width),
108199
108343
  mapV(1 - rect[1] / atlas.height)
108200
108344
  ], vertex2 * 4);
108201
- colors.set(data.tint, vertex2 * 3);
108345
+ colors.set(tint, vertex2 * 3);
108202
108346
  }
108203
108347
  outputIndices.set([start, start + 1, start + 2, start, start + 2, start + 3], indexOffset);
108204
108348
  indexOffset += 6;
@@ -108342,7 +108486,7 @@ gl_FragDepth=readDepth(vUv);
108342
108486
  input.cullMasks[i4] = cullMask;
108343
108487
  }
108344
108488
  if (fluids) {
108345
- input.fluids = { cells: fluids.cells };
108489
+ input.fluids = { cells: fluids.cells, waterTints: fluids.waterTints };
108346
108490
  for (const kind of ["water", "lava"]) {
108347
108491
  const atlas = fluids[kind];
108348
108492
  if (!atlas) continue;
@@ -108443,11 +108587,14 @@ gl_FragDepth=readDepth(vUv);
108443
108587
  init_FluidGeometry();
108444
108588
  init_FluidQuads();
108445
108589
  init_CubeFace();
108590
+ var WATER_TINT2 = new Color(4159204).toArray();
108446
108591
  var Chunk = class _Chunk {
108447
- constructor(scene, x4, y5, z4, onBlocksChanged, sectionModels, neighborCell) {
108592
+ constructor(scene, x4, y5, z4, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged) {
108448
108593
  this.onBlocksChanged = onBlocksChanged;
108449
108594
  this.sectionModels = sectionModels;
108450
108595
  this.neighborCell = neighborCell;
108596
+ this.biomeAt = biomeAt;
108597
+ this.onBiomesChanged = onBiomesChanged;
108451
108598
  this.data = new ChunkData();
108452
108599
  this.renderedBlocks = /* @__PURE__ */ new Map();
108453
108600
  this.sectionBlocks = /* @__PURE__ */ new Map();
@@ -108472,6 +108619,56 @@ gl_FragDepth=readDepth(vUv);
108472
108619
  const index = _Chunk.chunkPosToBlockIndex(pos);
108473
108620
  return this.renderedBlocks.get(index);
108474
108621
  }
108622
+ getBiomeAt(posOrX, y5, z4) {
108623
+ if (typeof posOrX === "number") return this.getBiomeAt(new Vector3(posOrX, y5, z4));
108624
+ if (isTripleArray(posOrX)) return this.getBiomeAt(new Vector3(...posOrX));
108625
+ const pos = this.worldPosToChunkPos(posOrX);
108626
+ _Chunk.chunkPosToBlockIndex(pos);
108627
+ 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];
108628
+ }
108629
+ /** Copies 64 biome IDs in x + z * 4 + y * 16 order and refreshes placed blocks. Omit the array to clear biome data. */
108630
+ setBiomes(biomes) {
108631
+ const copy = biomes ? [...biomes] : void 0;
108632
+ if (copy && (copy.length !== 64 || !copy.every((id) => typeof id === "string" && id.length > 0))) {
108633
+ throw new RangeError("Section biomes must contain 64 nonempty biome IDs");
108634
+ }
108635
+ if (this.onBiomesChanged) return this.onBiomesChanged(copy);
108636
+ this.biomes = copy;
108637
+ return this.refreshBiomes();
108638
+ }
108639
+ /** Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects. */
108640
+ async refreshBiomes() {
108641
+ const affected = /* @__PURE__ */ new Map();
108642
+ const blocks = [];
108643
+ for (const index of this.renderedBlocks.keys()) {
108644
+ const block = this.data.get(index);
108645
+ const stateKey = ChunkData.paletteKey(block);
108646
+ let matches = affected.get(stateKey);
108647
+ if (!matches) {
108648
+ const key = AssetKey.parse("blockstates", block.type);
108649
+ matches = BlockStates.get(key).then(async (state) => {
108650
+ const origin = state?.key ?? key;
108651
+ const [source, fluid] = await Promise.all([BlockTints.biomeSource(origin), getBlockFluidState(origin, block.properties)]);
108652
+ return !!source || fluid?.kind === "water";
108653
+ });
108654
+ affected.set(stateKey, matches);
108655
+ }
108656
+ if (await matches) blocks.push({ index, block });
108657
+ }
108658
+ if (!blocks.length) return;
108659
+ const hidden = new Set(this.hiddenBlocks);
108660
+ try {
108661
+ await this.placeBlocks(blocks);
108662
+ } finally {
108663
+ for (const index of hidden) {
108664
+ this.hiddenBlocks.add(index);
108665
+ this.renderedBlocks.get(index)?.object?.setVisible(false);
108666
+ }
108667
+ const positions = blocks.map(({ index }) => new Vector3(...this.positionAt(index)));
108668
+ if (this.onBlocksChanged) await this.onBlocksChanged(positions);
108669
+ else await this.rebuildSectionMesh();
108670
+ }
108671
+ }
108475
108672
  async setBlockAt(posOrX, yOrBlock, z4, block) {
108476
108673
  if (typeof posOrX == "number") {
108477
108674
  return this.setBlockAt(new Vector3(posOrX, yOrBlock, z4), block);
@@ -108510,14 +108707,15 @@ gl_FragDepth=readDepth(vUv);
108510
108707
  nbt: block.nbt === void 0 ? void 0 : structuredClone(block.nbt)
108511
108708
  } : void 0
108512
108709
  }));
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
- }));
108710
+ const states = /* @__PURE__ */ new Map();
108711
+ for (const { index, key, block } of placements) {
108712
+ if (key === void 0) continue;
108713
+ let state = states.get(key);
108714
+ if (!state) states.set(key, state = { stored: block, indices: [] });
108715
+ state.indices.push(index);
108716
+ }
108717
+ const biomes = /* @__PURE__ */ new Map();
108718
+ const settled = await Promise.allSettled([...states.values()].map(({ stored, indices }) => this.resolveState(stored, indices, biomes)));
108521
108719
  const resolutions = new Map([...states.keys()].map((key, i4) => [key, settled[i4]]));
108522
108720
  const positions = [];
108523
108721
  let failed = false, failure;
@@ -108541,7 +108739,8 @@ gl_FragDepth=readDepth(vUv);
108541
108739
  if (!readBlock) continue;
108542
108740
  const resolved = resolutions.get(key);
108543
108741
  if (resolved.status === "rejected") throw resolved.reason;
108544
- const { blockState: blockState3, perBlock, fluid, prepared, stored } = resolved.value;
108742
+ const { blockState: blockState3, perBlock, fluid, prepared, contexts, stored } = resolved.value;
108743
+ const context = contexts.get(index);
108545
108744
  if (!blockState3) {
108546
108745
  this.data.assign(index, 0);
108547
108746
  continue;
@@ -108549,9 +108748,10 @@ gl_FragDepth=readDepth(vUv);
108549
108748
  const variantPosition = worldPos.toArray();
108550
108749
  if (fluid) {
108551
108750
  this.fluidCells[index] = (fluid.kind === "water" ? 16 : 32) | Math.min(fluid.level, 8);
108751
+ if (fluid.kind === "water") this.setWaterTint(index, context?.waterTint);
108552
108752
  this.meshDirty = true;
108553
108753
  }
108554
- const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : prepared?.pick(variantPosition);
108754
+ const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
108555
108755
  if (templates) {
108556
108756
  this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
108557
108757
  this.meshDirty = true;
@@ -108561,7 +108761,9 @@ gl_FragDepth=readDepth(vUv);
108561
108761
  instanceMeshes: true,
108562
108762
  maxInstanceCount: 2e3,
108563
108763
  initialState: stored.properties,
108564
- variantPosition
108764
+ variantPosition,
108765
+ biome: context?.biome,
108766
+ biomePosition: this.biomePosition(index, blockState3, stored)
108565
108767
  });
108566
108768
  object4.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
108567
108769
  }
@@ -108584,24 +108786,21 @@ gl_FragDepth=readDepth(vUv);
108584
108786
  for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
108585
108787
  this.sectionBlocks.clear();
108586
108788
  this.fluidCells.fill(0);
108789
+ this.waterTints = void 0;
108587
108790
  this.renderedBlocks.clear();
108588
108791
  this.hiddenBlocks.clear();
108589
108792
  this.meshDirty = true;
108590
108793
  this.data.copyFrom(data);
108591
- const ids = /* @__PURE__ */ new Set();
108794
+ const ids = /* @__PURE__ */ new Map();
108592
108795
  for (let index = 0; index < 4096; index++) {
108593
108796
  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
- }));
108797
+ if (!id) continue;
108798
+ let indices = ids.get(id);
108799
+ if (!indices) ids.set(id, indices = []);
108800
+ indices.push(index);
108801
+ }
108802
+ const palette = [...ids.keys()], biomes = /* @__PURE__ */ new Map();
108803
+ const settled = await Promise.allSettled(palette.map((id) => this.resolveState(this.data.stateAt(id), ids.get(id), biomes)));
108605
108804
  const resolutions = new Map(palette.map((id, i4) => [id, settled[i4]]));
108606
108805
  const perBlocks = [];
108607
108806
  let failed = false, failure;
@@ -108611,24 +108810,28 @@ gl_FragDepth=readDepth(vUv);
108611
108810
  try {
108612
108811
  const resolved = resolutions.get(id);
108613
108812
  if (resolved.status === "rejected") throw resolved.reason;
108614
- const { blockState: blockState3, perBlock, fluid, prepared } = resolved.value;
108813
+ const { blockState: blockState3, perBlock, fluid, prepared, contexts } = resolved.value;
108814
+ const context = contexts.get(index);
108615
108815
  if (!blockState3) {
108616
108816
  this.data.assign(index, 0);
108617
108817
  this.fluidCells[index] = 0;
108618
108818
  continue;
108619
108819
  }
108620
- if (fluid) this.fluidCells[index] = (fluid.kind === "water" ? 16 : 32) | Math.min(fluid.level, 8);
108820
+ if (fluid) {
108821
+ this.fluidCells[index] = (fluid.kind === "water" ? 16 : 32) | Math.min(fluid.level, 8);
108822
+ if (fluid.kind === "water") this.setWaterTint(index, context?.waterTint);
108823
+ }
108621
108824
  const variantPosition = [
108622
108825
  this.x * 16 + index % 16,
108623
108826
  this.y * 16 + Math.floor(index / 256),
108624
108827
  this.z * 16 + Math.floor(index / 16) % 16
108625
108828
  ];
108626
- const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : prepared?.pick(variantPosition);
108829
+ const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
108627
108830
  if (templates) {
108628
108831
  this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
108629
108832
  this.meshDirty = true;
108630
108833
  } else {
108631
- perBlocks.push({ index, blockState: blockState3 });
108834
+ perBlocks.push({ index, blockState: blockState3, biome: context?.biome });
108632
108835
  }
108633
108836
  const readBlock = this.data.snapshot(index);
108634
108837
  this.renderedBlocks.set(index, { get block() {
@@ -108641,7 +108844,7 @@ gl_FragDepth=readDepth(vUv);
108641
108844
  failed = true;
108642
108845
  }
108643
108846
  }
108644
- for (const { index, blockState: blockState3 } of perBlocks) {
108847
+ for (const { index, blockState: blockState3, biome } of perBlocks) {
108645
108848
  let object4;
108646
108849
  try {
108647
108850
  object4 = await this.scene.addBlock(blockState3, {
@@ -108649,11 +108852,9 @@ gl_FragDepth=readDepth(vUv);
108649
108852
  instanceMeshes: true,
108650
108853
  maxInstanceCount: 2e3,
108651
108854
  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
- ]
108855
+ variantPosition: this.positionAt(index),
108856
+ biome,
108857
+ biomePosition: this.biomePosition(index, blockState3, this.data.stateAt(this.data.idAt(index)))
108657
108858
  });
108658
108859
  object4.setPosition(new Vector3(
108659
108860
  this.x * 256 + index % 16 * 16,
@@ -108672,6 +108873,68 @@ gl_FragDepth=readDepth(vUv);
108672
108873
  }
108673
108874
  if (failed) throw failure;
108674
108875
  }
108876
+ positionAt(index) {
108877
+ return [this.x * 16 + index % 16, this.y * 16 + Math.floor(index / 256), this.z * 16 + Math.floor(index / 16) % 16];
108878
+ }
108879
+ biomePosition(index, blockState3, stored) {
108880
+ const position = this.positionAt(index), name2 = blockState3.key?.toNamespacedString();
108881
+ if (stored.properties?.half === "upper" && (name2 === "minecraft:tall_grass" || name2 === "minecraft:large_fern")) position[1]--;
108882
+ return position;
108883
+ }
108884
+ async resolveState(stored, indices, biomes) {
108885
+ const blockState3 = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
108886
+ const perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
108887
+ const fluid = await getBlockFluidState(blockState3?.key, stored.properties);
108888
+ 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;
108889
+ const source = hasBiomes && blockState3 ? await BlockTints.biomeSource(blockState3.key) : void 0;
108890
+ const prepare = (color3) => perBlock || fluid?.renderModel === false ? void 0 : this.sectionModels.prepareState(blockState3, stored.properties, color3);
108891
+ const contexts = /* @__PURE__ */ new Map();
108892
+ const groups = /* @__PURE__ */ new Map();
108893
+ if (blockState3 && (source || fluid?.kind === "water")) {
108894
+ for (const index of indices) {
108895
+ const position = this.biomePosition(index, blockState3, stored);
108896
+ const biome = this.biomeAt ? this.biomeAt(...position) : position[1] >= this.y * 16 ? this.getBiomeAt(position) : void 0;
108897
+ if (!biome) continue;
108898
+ const key = JSON.stringify(source === "grass" ? [biome, position[0], position[2]] : [biome]);
108899
+ let group = groups.get(key);
108900
+ if (!group) groups.set(key, group = { biome, position, indices: [] });
108901
+ group.indices.push(index);
108902
+ }
108903
+ }
108904
+ const groupedCount = [...groups.values()].reduce((sum, group) => sum + group.indices.length, 0);
108905
+ const prepared = groupedCount < indices.length ? await prepare() : void 0;
108906
+ await Promise.all([...groups.values()].map(async ({ biome, position, indices: indices2 }) => {
108907
+ const available = () => {
108908
+ const key = JSON.stringify([biome, blockState3?.key?.root]);
108909
+ let found = biomes.get(key);
108910
+ if (!found) {
108911
+ found = Biomes.get(biome, blockState3?.key).then((value) => value !== void 0);
108912
+ biomes.set(key, found);
108913
+ }
108914
+ return found;
108915
+ };
108916
+ const context = { biome };
108917
+ if (perBlock) {
108918
+ if (!await available()) context.biome = void 0;
108919
+ } else {
108920
+ context.prepared = await prepare(async () => await available() ? BlockTints.getBiomeColor(blockState3?.key, biome, position) : void 0);
108921
+ if (fluid?.kind === "water" && await available()) {
108922
+ const color3 = await Biomes.getColor(biome, "water", blockState3?.key);
108923
+ if (color3 !== void 0 && color3 !== 4159204) context.waterTint = new Color(color3).toArray();
108924
+ }
108925
+ }
108926
+ for (const index of indices2) contexts.set(index, context);
108927
+ }));
108928
+ return { blockState: blockState3, perBlock, fluid, prepared, contexts, stored };
108929
+ }
108930
+ setWaterTint(index, tint) {
108931
+ if (!this.sectionModels || !this.waterTints && tint === void 0) return;
108932
+ if (!this.waterTints) {
108933
+ this.waterTints = new Float32Array(4096 * 3);
108934
+ for (let cell = 0; cell < 4096; cell++) this.waterTints.set(WATER_TINT2, cell * 3);
108935
+ }
108936
+ this.waterTints.set(tint ?? WATER_TINT2, index * 3);
108937
+ }
108675
108938
  /** Changes visibility at world block coordinates while preserving the block's data. */
108676
108939
  async setBlockVisibleAt(pos, visible) {
108677
108940
  const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
@@ -108775,7 +109038,7 @@ gl_FragDepth=readDepth(vUv);
108775
109038
  if (block && !block.object) origins.set(kind, block.block.type);
108776
109039
  }
108777
109040
  if (origins.size) {
108778
- fluids = { cells: new Uint8Array(18 * 18 * 18) };
109041
+ fluids = { cells: new Uint8Array(18 * 18 * 18), waterTints: this.waterTints?.slice() };
108779
109042
  for (let y5 = -1; y5 <= 16; y5++) {
108780
109043
  for (let z4 = -1; z4 <= 16; z4++) {
108781
109044
  for (let x4 = -1; x4 <= 16; x4++) {
@@ -108817,8 +109080,13 @@ gl_FragDepth=readDepth(vUv);
108817
109080
  this.renderedBlocks.clear();
108818
109081
  this.hiddenBlocks.clear();
108819
109082
  this.fluidCells.fill(0);
109083
+ this.waterTints = void 0;
108820
109084
  this.data.clear();
108821
- if (notify) await this.onBlocksChanged?.(positions);
109085
+ this.biomes = void 0;
109086
+ if (notify) {
109087
+ await this.onBiomesChanged?.(void 0);
109088
+ await this.onBlocksChanged?.(positions);
109089
+ }
108822
109090
  }
108823
109091
  async dispose() {
108824
109092
  await this.clear();