minerender 2.1.0-alpha.127 → 2.1.0-alpha.129

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
@@ -41476,6 +41476,10 @@ void main() {
41476
41476
  init_esm2();
41477
41477
  init_esm();
41478
41478
  _Caching = class _Caching {
41479
+ /** Changes when shared assets are cleared, so derived caches can discard their old values. */
41480
+ static get generation() {
41481
+ return this._generation;
41482
+ }
41479
41483
  static createAssetCache() {
41480
41484
  return Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
41481
41485
  }
@@ -41511,6 +41515,7 @@ void main() {
41511
41515
  }
41512
41516
  /** Invalidates in-memory entries so later lookups reload them. Persisted entries are retained. */
41513
41517
  static clear() {
41518
+ this._generation++;
41514
41519
  for (const cache of this.all) {
41515
41520
  cache.invalidateAll();
41516
41521
  }
@@ -41522,6 +41527,7 @@ void main() {
41522
41527
  * Use {@link shutdown} to also stop requests and the shared Ticker.
41523
41528
  */
41524
41529
  static end() {
41530
+ this._generation++;
41525
41531
  for (const cache of this.all) {
41526
41532
  cache.invalidateAll();
41527
41533
  cache.end();
@@ -41533,6 +41539,7 @@ void main() {
41533
41539
  };
41534
41540
  }
41535
41541
  };
41542
+ _Caching._generation = 0;
41536
41543
  _Caching.rawImageCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(5)).buildAsync();
41537
41544
  _Caching.imageDataCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(10)).buildAsync();
41538
41545
  _Caching.canvasImageDataCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(10)).buildAsync();
@@ -42813,6 +42820,311 @@ void main() {
42813
42820
  }
42814
42821
  });
42815
42822
 
42823
+ // src/assets/AssetKey.ts
42824
+ function isResourceLocation(value) {
42825
+ return typeof value === "string" && /^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(value);
42826
+ }
42827
+ function isBasicAssetKey(obj) {
42828
+ return obj !== null && typeof obj === "object" && typeof obj.namespace === "string" && typeof obj.path === "string";
42829
+ }
42830
+ function isAssetKey(obj) {
42831
+ return isBasicAssetKey(obj) && obj.isAssetKey === true;
42832
+ }
42833
+ var BasicAssetKey, AssetKey;
42834
+ var init_AssetKey = __esm({
42835
+ "src/assets/AssetKey.ts"() {
42836
+ init_dirname();
42837
+ init_buffer2();
42838
+ init_process2();
42839
+ init_Assets();
42840
+ init_AssetLoader();
42841
+ BasicAssetKey = class {
42842
+ constructor(namespaceOrKey, path) {
42843
+ if (path) {
42844
+ this.namespace = namespaceOrKey;
42845
+ this.path = path;
42846
+ } else {
42847
+ this.namespace = namespaceOrKey.namespace;
42848
+ this.path = namespaceOrKey.path;
42849
+ }
42850
+ }
42851
+ toNamespacedString() {
42852
+ return this.namespace + ":" + this.path;
42853
+ }
42854
+ toString() {
42855
+ return this.toNamespacedString();
42856
+ }
42857
+ serialize() {
42858
+ return this.toString();
42859
+ }
42860
+ };
42861
+ AssetKey = class _AssetKey extends BasicAssetKey {
42862
+ /**
42863
+ * Creates a key from separate path components. Use {@link parse} for a combined reference.
42864
+ *
42865
+ * @param assetType - Asset directory, such as `models`, `textures`, or `blockstates`.
42866
+ * @param type - Subdirectory, such as `block` or `item`.
42867
+ * @param rootType - Top-level directory, usually `assets` or `data`.
42868
+ * @param root - Base URL override for hosted asset sources.
42869
+ */
42870
+ constructor(namespace, path, assetType, type, rootType = "assets", extension = ".json", root) {
42871
+ super(namespace, path);
42872
+ this.namespace = namespace;
42873
+ this.path = path;
42874
+ this.assetType = assetType;
42875
+ this.type = type;
42876
+ this.rootType = rootType;
42877
+ this.extension = extension;
42878
+ this.root = root;
42879
+ this.isAssetKey = true;
42880
+ }
42881
+ /**
42882
+ * Parses an asset reference such as `minecraft:block/stone`.
42883
+ * A missing namespace defaults to the origin's namespace or `minecraft`.
42884
+ *
42885
+ * @param assetType - Asset category, such as `models` or `textures`.
42886
+ * @param str - Path with an optional namespace and file extension.
42887
+ * @param origin - Supplies the asset root, default namespace, and non-texture extension.
42888
+ * @returns A key with the matching extension removed from its path. Textures use `.png`;
42889
+ * other assets use the origin's extension or `.json`.
42890
+ */
42891
+ static parse(assetType, str, origin) {
42892
+ let namespace = origin?.namespace || DEFAULT_NAMESPACE;
42893
+ if (str.includes(":")) {
42894
+ let split2 = str.split(":");
42895
+ namespace = split2[0];
42896
+ str = split2[1];
42897
+ }
42898
+ let split = str.split("/");
42899
+ let type = void 0;
42900
+ if (split.length > 1) {
42901
+ type = split[0];
42902
+ split.shift();
42903
+ }
42904
+ let path = split.join("/");
42905
+ const extension = assetType === "textures" ? ".png" : origin?.extension ?? ".json";
42906
+ if (extension && path.endsWith(extension)) {
42907
+ path = path.slice(0, -extension.length);
42908
+ }
42909
+ return new _AssetKey(namespace, path, assetType, type, "assets", extension, origin?.root);
42910
+ }
42911
+ toString() {
42912
+ let a4 = [
42913
+ this.root ?? AssetLoader.ROOT,
42914
+ this.rootType ?? "__rootType__",
42915
+ this.assetType ?? "__assetType__",
42916
+ this.type ?? "__type__",
42917
+ this.namespace,
42918
+ this.path
42919
+ ];
42920
+ return a4.join("/");
42921
+ }
42922
+ /** Joins the type subdirectory and path without adding a namespace or file extension. */
42923
+ getFullPath() {
42924
+ let p14 = [];
42925
+ if (this.type) {
42926
+ p14.push(this.type);
42927
+ }
42928
+ p14.push(this.path);
42929
+ return p14.join("/");
42930
+ }
42931
+ toNamespacedString() {
42932
+ return this.namespace + ":" + this.getFullPath();
42933
+ }
42934
+ /** Returns a cache identifier that includes the explicit root or {@link AssetLoader.ROOT}. */
42935
+ serialize() {
42936
+ return this.toString();
42937
+ }
42938
+ };
42939
+ }
42940
+ });
42941
+
42942
+ // src/assets/source/parser/AssetParsers.ts
42943
+ var AssetParser;
42944
+ var init_AssetParsers = __esm({
42945
+ "src/assets/source/parser/AssetParsers.ts"() {
42946
+ init_dirname();
42947
+ init_buffer2();
42948
+ init_process2();
42949
+ AssetParser = /* @__PURE__ */ ((AssetParser2) => {
42950
+ AssetParser2["MODEL"] = "model";
42951
+ AssetParser2["BLOCKSTATE"] = "blockstate";
42952
+ AssetParser2["META"] = "meta";
42953
+ AssetParser2["NBT"] = "nbt";
42954
+ AssetParser2["IMAGE"] = "image";
42955
+ AssetParser2["LIST"] = "list";
42956
+ AssetParser2["JSON"] = "json";
42957
+ return AssetParser2;
42958
+ })(AssetParser || {});
42959
+ }
42960
+ });
42961
+
42962
+ // src/assets/MineRenderData.ts
42963
+ var object, release, compare, MineRenderData;
42964
+ var init_MineRenderData = __esm({
42965
+ "src/assets/MineRenderData.ts"() {
42966
+ init_dirname();
42967
+ init_buffer2();
42968
+ init_process2();
42969
+ init_AssetKey();
42970
+ init_AssetLoader();
42971
+ init_AssetParsers();
42972
+ init_Caching();
42973
+ object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
42974
+ release = (value) => /^\d+\.\d+(?:\.\d+)?$/.test(value) ? value.split(".").map(Number) : void 0;
42975
+ compare = (left, right) => {
42976
+ for (let i4 = 0; i4 < 3; i4++) {
42977
+ const difference = (left[i4] ?? 0) - (right[i4] ?? 0);
42978
+ if (difference) return difference;
42979
+ }
42980
+ return 0;
42981
+ };
42982
+ MineRenderData = class {
42983
+ /** Loads one registry. Missing files and invalid data reject the request. */
42984
+ static async get(dataset, root) {
42985
+ root = root?.replace(/\/+$/, "");
42986
+ const scope = AssetLoader.persistentScope;
42987
+ const defaultRoot = AssetLoader.ROOT;
42988
+ const resolved = await this.resolve(root);
42989
+ const entry = resolved.manifest.datasets[dataset];
42990
+ if (!entry) throw new Error(`MineRender ${dataset} data is unavailable for ${resolved.version}: ${resolved.manifest.unavailable[dataset] ?? "not listed in the manifest"}`);
42991
+ const file = entry.file;
42992
+ const requestRoot = resolved.root === (root ?? defaultRoot).replace(/\/+$/, "") ? root : resolved.root;
42993
+ return await this.cached(`${requestRoot === void 0 ? "sources" : "explicit"}:${resolved.root}/${file}:${entry.sha256}`, scope, defaultRoot, async () => {
42994
+ const data = await this.load(file.slice(0, -5), requestRoot);
42995
+ if (!this.validDataset(dataset, data)) throw new Error(`Invalid MineRender ${dataset} data at ${resolved.root}/minerender-data/${file}`);
42996
+ return data;
42997
+ });
42998
+ }
42999
+ /**
43000
+ * Uses the exact version when published. Otherwise, selects the nearest patch in the same
43001
+ * release line (older on ties), then the latest earlier release or the oldest published release.
43002
+ * Unpublished snapshots reject.
43003
+ */
43004
+ static async resolve(root) {
43005
+ const requestRoot = root?.replace(/\/+$/, "");
43006
+ const defaultRoot = AssetLoader.ROOT;
43007
+ root = (root ?? defaultRoot).replace(/\/+$/, "");
43008
+ const scope = AssetLoader.persistentScope;
43009
+ return this.cached(`resolve:${requestRoot === void 0 ? "sources" : "explicit"}:${root}`, scope, defaultRoot, async () => {
43010
+ const requestedVersion = root.slice(root.lastIndexOf("/") + 1);
43011
+ let manifest = await this.manifest(root, requestRoot !== void 0 && release(requestedVersion) ? requestedVersion : void 0, requestRoot);
43012
+ if (manifest) return { requestedVersion, version: manifest.minecraftVersion, root, manifest };
43013
+ const requested = release(requestedVersion);
43014
+ if (!requested) {
43015
+ throw new Error(`No MineRender data is published for ${requestedVersion}; automatic fallback requires a release`);
43016
+ }
43017
+ const base = root.slice(0, root.lastIndexOf("/"));
43018
+ const index = await this.cached(`versions:${base}`, scope, defaultRoot, async () => {
43019
+ const value = await this.load("versions", base);
43020
+ if (!object(value) || !Array.isArray(value.versions) || !value.versions.every((entry) => object(entry) && typeof entry.name === "string")) {
43021
+ throw new Error(`Invalid MineRender version index at ${base}/minerender-data/versions.json`);
43022
+ }
43023
+ return value.versions;
43024
+ });
43025
+ const versions2 = index.map((entry) => ({ name: entry.name, number: release(entry.name) })).filter((entry) => !!entry.number).sort((left, right) => compare(left.number, right.number));
43026
+ const sameLine = versions2.filter((entry) => entry.number[0] === requested[0] && entry.number[1] === requested[1]);
43027
+ 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];
43028
+ if (!selected) throw new Error(`No compatible MineRender data is published for ${requestedVersion}`);
43029
+ const resolvedRoot = `${base}/${selected.name}`;
43030
+ manifest = await this.manifest(resolvedRoot, selected.name, resolvedRoot);
43031
+ if (!manifest) throw new Error(`Missing MineRender manifest for published version ${selected.name}`);
43032
+ return { requestedVersion, version: selected.name, root: resolvedRoot, manifest };
43033
+ });
43034
+ }
43035
+ static async manifest(root, expectedVersion, requestRoot) {
43036
+ const data = await this.load("manifest", requestRoot);
43037
+ if (data === void 0) return void 0;
43038
+ if (!object(data) || data.schemaVersion !== 1 || typeof data.minecraftVersion !== "string" || !Number.isInteger(data.dataVersion) || !object(data.unavailable) || !Object.values(data.unavailable).every((value) => typeof value === "string") || !object(data.datasets) || !Object.values(data.datasets).every((entry) => {
43039
+ return object(entry) && typeof entry.file === "string" && /^[a-z0-9][a-z0-9._-]*\.json$/.test(entry.file) && typeof entry.sha256 === "string" && /^[0-9a-f]{64}$/.test(entry.sha256);
43040
+ }) || expectedVersion !== void 0 && data.minecraftVersion !== expectedVersion) {
43041
+ throw new Error(`Invalid MineRender manifest at ${root}/minerender-data/manifest.json`);
43042
+ }
43043
+ return data;
43044
+ }
43045
+ static load(file, root) {
43046
+ const key = new AssetKey("minerender-data", file, void 0, void 0, void 0, ".json", root);
43047
+ key.rootType = void 0;
43048
+ return AssetLoader.get(key, "json" /* JSON */);
43049
+ }
43050
+ static async cached(key, scope, defaultRoot, loader) {
43051
+ const checkSources = () => {
43052
+ if (AssetLoader.persistentScope !== scope || AssetLoader.ROOT !== defaultRoot) throw new Error("Asset sources changed while loading MineRender data; retry the request");
43053
+ };
43054
+ checkSources();
43055
+ return await Caching.mineRenderDataCache.get(scope ? `${scope}
43056
+ ${key}` : key, async () => {
43057
+ const value = await loader();
43058
+ checkSources();
43059
+ return value;
43060
+ });
43061
+ }
43062
+ static validDataset(dataset, data) {
43063
+ if (!object(data)) return false;
43064
+ if (dataset === "potionColors") return object(data.effects) && Object.values(data.effects).every(Number.isInteger) && object(data.potions) && Object.values(data.potions).every((effects) => Array.isArray(effects) && effects.every((effect) => object(effect) && typeof effect.id === "string" && Number.isInteger(effect.amplifier)));
43065
+ if (dataset === "renames") return data.format === "data-fixer-renames" && Number.isInteger(data.baseDataVersion) && ["blocks", "items", "entities"].every((kind) => object(data[kind]) && Object.values(data[kind]).every((entry) => object(entry) && typeof entry.registered === "boolean" && Array.isArray(entry.ranges) && entry.ranges.length > 0 && entry.ranges.every((range) => object(range) && Number.isInteger(range.before) && typeof range.to === "string")));
43066
+ if (dataset === "legacyBlocks") return object(data.blocks) && Object.values(data.blocks).every((value) => typeof value === "string");
43067
+ if (dataset === "blockCulling") return Array.isArray(data.profiles) && data.profiles.every((profile) => object(profile) && typeof profile.type === "string") && object(data.blocks) && Object.values(data.blocks).every((index) => Number.isInteger(index) && index >= 0 && index < data.profiles.length) && (data.groups === void 0 || object(data.groups) && Object.values(data.groups).every((group) => Array.isArray(group) && group.every((type) => typeof type === "string")));
43068
+ return Object.values(data).every((value) => {
43069
+ if (!object(value)) return false;
43070
+ switch (dataset) {
43071
+ case "itemDefaults":
43072
+ return true;
43073
+ case "blockStates":
43074
+ return Object.values(value).every((property) => object(property) && "default" in property && Array.isArray(property.values));
43075
+ case "blockTints":
43076
+ 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));
43077
+ case "fluids":
43078
+ return ["water", "lava"].includes(value.kind) && typeof value.renderModel === "boolean" && (value.levelProperty === void 0 || typeof value.levelProperty === "string");
43079
+ case "dyes":
43080
+ return [value.id, value.textureDiffuseColor, value.fireworkColor, value.textColor].every(Number.isInteger);
43081
+ default:
43082
+ return false;
43083
+ }
43084
+ });
43085
+ }
43086
+ };
43087
+ }
43088
+ });
43089
+
43090
+ // src/assets/Renames.ts
43091
+ var Renames;
43092
+ var init_Renames = __esm({
43093
+ "src/assets/Renames.ts"() {
43094
+ init_dirname();
43095
+ init_buffer2();
43096
+ init_process2();
43097
+ init_AssetKey();
43098
+ init_MineRenderData();
43099
+ Renames = class {
43100
+ /** Loads the selected version's rename table, or `undefined` when its published data has none. */
43101
+ static async get(root) {
43102
+ const resolved = await MineRenderData.resolve(root);
43103
+ return resolved.manifest.datasets.renames ? MineRenderData.get("renames", root) : void 0;
43104
+ }
43105
+ /**
43106
+ * Returns the ID that replaced `id`, or `undefined` when `id` keeps its meaning.
43107
+ * Without a data version, an ID that the selected version still registers keeps its meaning.
43108
+ */
43109
+ static async resolve(kind, id, dataVersion, root) {
43110
+ const data = await this.get(root);
43111
+ if (!data || !Object.prototype.hasOwnProperty.call(data[kind], id)) return void 0;
43112
+ const entry = data[kind][id];
43113
+ if (dataVersion === void 0) return entry.registered ? void 0 : entry.ranges[entry.ranges.length - 1].to;
43114
+ if (dataVersion < data.baseDataVersion) return void 0;
43115
+ return entry.ranges.find((range) => dataVersion < range.before)?.to;
43116
+ }
43117
+ /** Returns a copy of `key` for the replacement ID, or `undefined` when the ID keeps its meaning. */
43118
+ static async key(kind, key) {
43119
+ const renamed = await this.resolve(kind, `${key.namespace}:${key.path}`, void 0, key.root);
43120
+ if (!renamed) return void 0;
43121
+ const [namespace, path] = renamed.split(":");
43122
+ return new AssetKey(namespace, path, key.assetType, key.type, key.rootType, key.extension, key.root);
43123
+ }
43124
+ };
43125
+ }
43126
+ });
43127
+
42816
43128
  // src/model/Model.ts
42817
43129
  function isDoubleArray(obj) {
42818
43130
  return Array.isArray(obj) && obj.length === 2;
@@ -42931,125 +43243,6 @@ void main() {
42931
43243
  }
42932
43244
  });
42933
43245
 
42934
- // src/assets/AssetKey.ts
42935
- function isResourceLocation(value) {
42936
- return typeof value === "string" && /^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(value);
42937
- }
42938
- function isBasicAssetKey(obj) {
42939
- return obj !== null && typeof obj === "object" && typeof obj.namespace === "string" && typeof obj.path === "string";
42940
- }
42941
- function isAssetKey(obj) {
42942
- return isBasicAssetKey(obj) && obj.isAssetKey === true;
42943
- }
42944
- var BasicAssetKey, AssetKey;
42945
- var init_AssetKey = __esm({
42946
- "src/assets/AssetKey.ts"() {
42947
- init_dirname();
42948
- init_buffer2();
42949
- init_process2();
42950
- init_Assets();
42951
- init_AssetLoader();
42952
- BasicAssetKey = class {
42953
- constructor(namespaceOrKey, path) {
42954
- if (path) {
42955
- this.namespace = namespaceOrKey;
42956
- this.path = path;
42957
- } else {
42958
- this.namespace = namespaceOrKey.namespace;
42959
- this.path = namespaceOrKey.path;
42960
- }
42961
- }
42962
- toNamespacedString() {
42963
- return this.namespace + ":" + this.path;
42964
- }
42965
- toString() {
42966
- return this.toNamespacedString();
42967
- }
42968
- serialize() {
42969
- return this.toString();
42970
- }
42971
- };
42972
- AssetKey = class _AssetKey extends BasicAssetKey {
42973
- /**
42974
- * Creates a key from separate path components. Use {@link parse} for a combined reference.
42975
- *
42976
- * @param assetType - Asset directory, such as `models`, `textures`, or `blockstates`.
42977
- * @param type - Subdirectory, such as `block` or `item`.
42978
- * @param rootType - Top-level directory, usually `assets` or `data`.
42979
- * @param root - Base URL override for hosted asset sources.
42980
- */
42981
- constructor(namespace, path, assetType, type, rootType = "assets", extension = ".json", root) {
42982
- super(namespace, path);
42983
- this.namespace = namespace;
42984
- this.path = path;
42985
- this.assetType = assetType;
42986
- this.type = type;
42987
- this.rootType = rootType;
42988
- this.extension = extension;
42989
- this.root = root;
42990
- this.isAssetKey = true;
42991
- }
42992
- /**
42993
- * Parses an asset reference such as `minecraft:block/stone`.
42994
- * A missing namespace defaults to the origin's namespace or `minecraft`.
42995
- *
42996
- * @param assetType - Asset category, such as `models` or `textures`.
42997
- * @param str - Path with an optional namespace and file extension.
42998
- * @param origin - Supplies the asset root, default namespace, and non-texture extension.
42999
- * @returns A key with the matching extension removed from its path. Textures use `.png`;
43000
- * other assets use the origin's extension or `.json`.
43001
- */
43002
- static parse(assetType, str, origin) {
43003
- let namespace = origin?.namespace || DEFAULT_NAMESPACE;
43004
- if (str.includes(":")) {
43005
- let split2 = str.split(":");
43006
- namespace = split2[0];
43007
- str = split2[1];
43008
- }
43009
- let split = str.split("/");
43010
- let type = void 0;
43011
- if (split.length > 1) {
43012
- type = split[0];
43013
- split.shift();
43014
- }
43015
- let path = split.join("/");
43016
- const extension = assetType === "textures" ? ".png" : origin?.extension ?? ".json";
43017
- if (extension && path.endsWith(extension)) {
43018
- path = path.slice(0, -extension.length);
43019
- }
43020
- return new _AssetKey(namespace, path, assetType, type, "assets", extension, origin?.root);
43021
- }
43022
- toString() {
43023
- let a4 = [
43024
- this.root ?? AssetLoader.ROOT,
43025
- this.rootType ?? "__rootType__",
43026
- this.assetType ?? "__assetType__",
43027
- this.type ?? "__type__",
43028
- this.namespace,
43029
- this.path
43030
- ];
43031
- return a4.join("/");
43032
- }
43033
- /** Joins the type subdirectory and path without adding a namespace or file extension. */
43034
- getFullPath() {
43035
- let p14 = [];
43036
- if (this.type) {
43037
- p14.push(this.type);
43038
- }
43039
- p14.push(this.path);
43040
- return p14.join("/");
43041
- }
43042
- toNamespacedString() {
43043
- return this.namespace + ":" + this.getFullPath();
43044
- }
43045
- /** Returns a cache identifier that includes the explicit root or {@link AssetLoader.ROOT}. */
43046
- serialize() {
43047
- return this.toString();
43048
- }
43049
- };
43050
- }
43051
- });
43052
-
43053
43246
  // src/model/DisplayPosition.ts
43054
43247
  var DisplayPosition, DISPLAY_POSITIONS;
43055
43248
  var init_DisplayPosition = __esm({
@@ -43181,153 +43374,6 @@ void main() {
43181
43374
  }
43182
43375
  });
43183
43376
 
43184
- // src/assets/source/parser/AssetParsers.ts
43185
- var AssetParser;
43186
- var init_AssetParsers = __esm({
43187
- "src/assets/source/parser/AssetParsers.ts"() {
43188
- init_dirname();
43189
- init_buffer2();
43190
- init_process2();
43191
- AssetParser = /* @__PURE__ */ ((AssetParser2) => {
43192
- AssetParser2["MODEL"] = "model";
43193
- AssetParser2["BLOCKSTATE"] = "blockstate";
43194
- AssetParser2["META"] = "meta";
43195
- AssetParser2["NBT"] = "nbt";
43196
- AssetParser2["IMAGE"] = "image";
43197
- AssetParser2["LIST"] = "list";
43198
- AssetParser2["JSON"] = "json";
43199
- return AssetParser2;
43200
- })(AssetParser || {});
43201
- }
43202
- });
43203
-
43204
- // src/assets/MineRenderData.ts
43205
- var object, release, compare, MineRenderData;
43206
- var init_MineRenderData = __esm({
43207
- "src/assets/MineRenderData.ts"() {
43208
- init_dirname();
43209
- init_buffer2();
43210
- init_process2();
43211
- init_AssetKey();
43212
- init_AssetLoader();
43213
- init_AssetParsers();
43214
- init_Caching();
43215
- object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
43216
- release = (value) => /^\d+\.\d+(?:\.\d+)?$/.test(value) ? value.split(".").map(Number) : void 0;
43217
- compare = (left, right) => {
43218
- for (let i4 = 0; i4 < 3; i4++) {
43219
- const difference = (left[i4] ?? 0) - (right[i4] ?? 0);
43220
- if (difference) return difference;
43221
- }
43222
- return 0;
43223
- };
43224
- MineRenderData = class {
43225
- /** Loads one registry. Missing files and invalid data reject the request. */
43226
- static async get(dataset, root) {
43227
- root = root?.replace(/\/+$/, "");
43228
- const scope = AssetLoader.persistentScope;
43229
- const defaultRoot = AssetLoader.ROOT;
43230
- const resolved = await this.resolve(root);
43231
- const entry = resolved.manifest.datasets[dataset];
43232
- if (!entry) throw new Error(`MineRender ${dataset} data is unavailable for ${resolved.version}: ${resolved.manifest.unavailable[dataset] ?? "not listed in the manifest"}`);
43233
- const file = entry.file;
43234
- const requestRoot = resolved.root === (root ?? defaultRoot).replace(/\/+$/, "") ? root : resolved.root;
43235
- return await this.cached(`${requestRoot === void 0 ? "sources" : "explicit"}:${resolved.root}/${file}:${entry.sha256}`, scope, defaultRoot, async () => {
43236
- const data = await this.load(file.slice(0, -5), requestRoot);
43237
- if (!this.validDataset(dataset, data)) throw new Error(`Invalid MineRender ${dataset} data at ${resolved.root}/minerender-data/${file}`);
43238
- return data;
43239
- });
43240
- }
43241
- /**
43242
- * Uses the exact version when published. Otherwise, selects the nearest patch in the same
43243
- * release line (older on ties), then the latest earlier release or the oldest published release.
43244
- * Unpublished snapshots reject.
43245
- */
43246
- static async resolve(root) {
43247
- const requestRoot = root?.replace(/\/+$/, "");
43248
- const defaultRoot = AssetLoader.ROOT;
43249
- root = (root ?? defaultRoot).replace(/\/+$/, "");
43250
- const scope = AssetLoader.persistentScope;
43251
- return this.cached(`resolve:${requestRoot === void 0 ? "sources" : "explicit"}:${root}`, scope, defaultRoot, async () => {
43252
- const requestedVersion = root.slice(root.lastIndexOf("/") + 1);
43253
- let manifest = await this.manifest(root, requestRoot !== void 0 && release(requestedVersion) ? requestedVersion : void 0, requestRoot);
43254
- if (manifest) return { requestedVersion, version: manifest.minecraftVersion, root, manifest };
43255
- const requested = release(requestedVersion);
43256
- if (!requested) {
43257
- throw new Error(`No MineRender data is published for ${requestedVersion}; automatic fallback requires a release`);
43258
- }
43259
- const base = root.slice(0, root.lastIndexOf("/"));
43260
- const index = await this.cached(`versions:${base}`, scope, defaultRoot, async () => {
43261
- const value = await this.load("versions", base);
43262
- if (!object(value) || !Array.isArray(value.versions) || !value.versions.every((entry) => object(entry) && typeof entry.name === "string")) {
43263
- throw new Error(`Invalid MineRender version index at ${base}/minerender-data/versions.json`);
43264
- }
43265
- return value.versions;
43266
- });
43267
- const versions2 = index.map((entry) => ({ name: entry.name, number: release(entry.name) })).filter((entry) => !!entry.number).sort((left, right) => compare(left.number, right.number));
43268
- const sameLine = versions2.filter((entry) => entry.number[0] === requested[0] && entry.number[1] === requested[1]);
43269
- 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];
43270
- if (!selected) throw new Error(`No compatible MineRender data is published for ${requestedVersion}`);
43271
- const resolvedRoot = `${base}/${selected.name}`;
43272
- manifest = await this.manifest(resolvedRoot, selected.name, resolvedRoot);
43273
- if (!manifest) throw new Error(`Missing MineRender manifest for published version ${selected.name}`);
43274
- return { requestedVersion, version: selected.name, root: resolvedRoot, manifest };
43275
- });
43276
- }
43277
- static async manifest(root, expectedVersion, requestRoot) {
43278
- const data = await this.load("manifest", requestRoot);
43279
- if (data === void 0) return void 0;
43280
- if (!object(data) || data.schemaVersion !== 1 || typeof data.minecraftVersion !== "string" || !Number.isInteger(data.dataVersion) || !object(data.unavailable) || !Object.values(data.unavailable).every((value) => typeof value === "string") || !object(data.datasets) || !Object.values(data.datasets).every((entry) => {
43281
- return object(entry) && typeof entry.file === "string" && /^[a-z0-9][a-z0-9._-]*\.json$/.test(entry.file) && typeof entry.sha256 === "string" && /^[0-9a-f]{64}$/.test(entry.sha256);
43282
- }) || expectedVersion !== void 0 && data.minecraftVersion !== expectedVersion) {
43283
- throw new Error(`Invalid MineRender manifest at ${root}/minerender-data/manifest.json`);
43284
- }
43285
- return data;
43286
- }
43287
- static load(file, root) {
43288
- const key = new AssetKey("minerender-data", file, void 0, void 0, void 0, ".json", root);
43289
- key.rootType = void 0;
43290
- return AssetLoader.get(key, "json" /* JSON */);
43291
- }
43292
- static async cached(key, scope, defaultRoot, loader) {
43293
- const checkSources = () => {
43294
- if (AssetLoader.persistentScope !== scope || AssetLoader.ROOT !== defaultRoot) throw new Error("Asset sources changed while loading MineRender data; retry the request");
43295
- };
43296
- checkSources();
43297
- return await Caching.mineRenderDataCache.get(scope ? `${scope}
43298
- ${key}` : key, async () => {
43299
- const value = await loader();
43300
- checkSources();
43301
- return value;
43302
- });
43303
- }
43304
- static validDataset(dataset, data) {
43305
- if (!object(data)) return false;
43306
- if (dataset === "potionColors") return object(data.effects) && Object.values(data.effects).every(Number.isInteger) && object(data.potions) && Object.values(data.potions).every((effects) => Array.isArray(effects) && effects.every((effect) => object(effect) && typeof effect.id === "string" && Number.isInteger(effect.amplifier)));
43307
- if (dataset === "legacyBlocks") return object(data.blocks) && Object.values(data.blocks).every((value) => typeof value === "string");
43308
- if (dataset === "blockCulling") return Array.isArray(data.profiles) && data.profiles.every((profile) => object(profile) && typeof profile.type === "string") && object(data.blocks) && Object.values(data.blocks).every((index) => Number.isInteger(index) && index >= 0 && index < data.profiles.length) && (data.groups === void 0 || object(data.groups) && Object.values(data.groups).every((group) => Array.isArray(group) && group.every((type) => typeof type === "string")));
43309
- return Object.values(data).every((value) => {
43310
- if (!object(value)) return false;
43311
- switch (dataset) {
43312
- case "itemDefaults":
43313
- return true;
43314
- case "blockStates":
43315
- return Object.values(value).every((property) => object(property) && "default" in property && Array.isArray(property.values));
43316
- case "blockTints":
43317
- 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));
43318
- case "fluids":
43319
- return ["water", "lava"].includes(value.kind) && typeof value.renderModel === "boolean" && (value.levelProperty === void 0 || typeof value.levelProperty === "string");
43320
- case "dyes":
43321
- return [value.id, value.textureDiffuseColor, value.fireworkColor, value.textColor].every(Number.isInteger);
43322
- default:
43323
- return false;
43324
- }
43325
- });
43326
- }
43327
- };
43328
- }
43329
- });
43330
-
43331
43377
  // src/assets/BannerPatterns.ts
43332
43378
  var DYE_COLORS, BannerPatterns;
43333
43379
  var init_BannerPatterns = __esm({
@@ -43668,6 +43714,7 @@ ${key}` : key, async () => {
43668
43714
  init_dirname();
43669
43715
  init_buffer2();
43670
43716
  init_process2();
43717
+ init_Renames();
43671
43718
  init_Caching();
43672
43719
  init_ModelMerger();
43673
43720
  init_AssetLoader();
@@ -43997,6 +44044,11 @@ ${key}` : key, async () => {
43997
44044
  key.extension = ".json";
43998
44045
  }
43999
44046
  const preview = this.snapshotContext(key, context);
44047
+ if (key.type === "item") {
44048
+ const renamed = await Renames.key("items", key);
44049
+ const replacement = renamed && await this.getMerged(renamed, preview);
44050
+ if (replacement) return replacement;
44051
+ }
44000
44052
  const dataKey = await this.applyDefaults(key, preview);
44001
44053
  const keyStr = AssetLoader.persistentKey(key.serialize() + (key.type === "item" ? this.contextKey(preview) + dataKey : ""));
44002
44054
  const model = await Caching.mergedModelCache.get(keyStr, async () => {
@@ -44033,6 +44085,7 @@ ${key}` : key, async () => {
44033
44085
  init_AssetKey();
44034
44086
  init_PersistentCache();
44035
44087
  init_AssetParsers();
44088
+ init_Renames();
44036
44089
  init_MineRenderData();
44037
44090
  BlockStates = class {
44038
44091
  static get PERSISTENT_CACHE() {
@@ -44073,6 +44126,9 @@ ${key}` : key, async () => {
44073
44126
  if (!key.extension) {
44074
44127
  key.extension = ".json";
44075
44128
  }
44129
+ const renamed = await Renames.key("blocks", key);
44130
+ const replacement = renamed && await this.get(renamed);
44131
+ if (replacement) return replacement;
44076
44132
  const keyStr = key.serialize();
44077
44133
  return Caching.blockStateCache.get(keyStr, (k4) => {
44078
44134
  return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
@@ -95450,6 +95506,7 @@ ${shader.fragmentShader}`.replace(
95450
95506
  PackMetadata: () => PackMetadata,
95451
95507
  PersistentCache: () => PersistentCache,
95452
95508
  PlayerHeadTextures: () => PlayerHeadTextures,
95509
+ Renames: () => Renames,
95453
95510
  Renderer: () => Renderer,
95454
95511
  RequestError: () => RequestError,
95455
95512
  Requests: () => Requests,
@@ -96144,6 +96201,7 @@ ${shader.fragmentShader}`.replace(
96144
96201
  init_MineRenderData();
96145
96202
  init_ModelTextures();
96146
96203
  init_Models();
96204
+ init_Renames();
96147
96205
  init_source();
96148
96206
 
96149
96207
  // src/bedrock/index.ts
@@ -107683,18 +107741,23 @@ gl_FragDepth=readDepth(vUv);
107683
107741
  return this.template(variant, choice, tints);
107684
107742
  }));
107685
107743
  }
107686
- /** Resolves section templates for one tint palette, with a synchronous weighted choice for each cell. */
107687
- async prepareState(blockState3, properties = {}, biomeColor) {
107744
+ /** Resolves shared templates; dynamic biome tints are supplied on section placements instead of cached geometry. */
107745
+ async prepareState(blockState3, properties = {}, biomeColor, dynamicBiomeTint = false) {
107688
107746
  const prepared = await this.state(blockState3, properties);
107689
107747
  if (!prepared) return void 0;
107690
107748
  let color3;
107691
107749
  const resolveColor = typeof biomeColor === "function" ? () => color3 ?? (color3 = biomeColor()) : biomeColor;
107692
107750
  const templates = /* @__PURE__ */ new Map();
107751
+ const source = dynamicBiomeTint && await BlockTints.biomeSource(blockState3.key);
107693
107752
  await Promise.all([...prepared.choices].map(async ([variant, choice]) => {
107694
107753
  const tints = await BlockTints.get(blockState3.key, prepared.state, choice.model, void 0, resolveColor);
107695
- templates.set(variant, await this.template(variant, choice, tints));
107754
+ const biomeTints = source ? await BlockTints.get(blockState3.key, prepared.state, choice.model, void 0, 16777215) : void 0;
107755
+ templates.set(variant, await this.template(variant, choice, tints, biomeTints));
107696
107756
  }));
107697
- return { pick: (position) => prepared.groups.map((group) => templates.get(BlockStateResolver.choose(group, position))) };
107757
+ return {
107758
+ pick: (position) => prepared.groups.map((group) => templates.get(BlockStateResolver.choose(group, position))),
107759
+ biomeTint: source ? [...templates.values()].some((template) => template.biomeTint?.some((value) => value === 1)) || void 0 : void 0
107760
+ };
107698
107761
  }
107699
107762
  async state(blockState3, properties) {
107700
107763
  let states = this.states.get(blockState3);
@@ -107737,10 +107800,10 @@ gl_FragDepth=readDepth(vUv);
107737
107800
  }
107738
107801
  return { state, groups, choices: prepared };
107739
107802
  }
107740
- async template(variant, { model, atlas, rotation }, tints) {
107803
+ async template(variant, { model, atlas, rotation }, tints, biomeTints) {
107741
107804
  let models = this.models.get(model);
107742
107805
  if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
107743
- const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
107806
+ const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints, biomeTints]);
107744
107807
  return cached(models, key, async () => {
107745
107808
  const object4 = new SectionModel(model, {
107746
107809
  instanceMeshes: false,
@@ -107754,11 +107817,13 @@ gl_FragDepth=readDepth(vUv);
107754
107817
  object4.clear();
107755
107818
  const keep = [];
107756
107819
  const cullFaces = [];
107820
+ const biomeTint = biomeTints ? [] : void 0;
107757
107821
  for (const element of atlas.model.elements) {
107758
107822
  for (const face of CUBE_FACES) {
107759
107823
  const data = element.faces[face];
107760
107824
  keep.push(data !== void 0);
107761
107825
  if (!data) continue;
107826
+ biomeTint?.push(Number(data.tintindex !== void 0 && biomeTints?.[data.tintindex] !== void 0));
107762
107827
  const local = CUBE_FACES.findIndex((name2) => name2 === data.cullface);
107763
107828
  if (local < 0) {
107764
107829
  cullFaces.push(0);
@@ -107774,6 +107839,7 @@ gl_FragDepth=readDepth(vUv);
107774
107839
  geometry,
107775
107840
  atlas,
107776
107841
  cullFaces: new Uint8Array(cullFaces),
107842
+ biomeTint: biomeTint?.some(Boolean) ? new Uint8Array(biomeTint) : void 0,
107777
107843
  occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
107778
107844
  layer: atlas.hasTranslucency ? 1 : 0,
107779
107845
  ambientOcclusion: model.ambientocclusion !== false
@@ -107796,6 +107862,7 @@ gl_FragDepth=readDepth(vUv);
107796
107862
  init_dirname();
107797
107863
  init_buffer2();
107798
107864
  init_process2();
107865
+ init_Renames();
107799
107866
  init_Entities();
107800
107867
  init_EntityObject();
107801
107868
 
@@ -107961,8 +108028,10 @@ gl_FragDepth=readDepth(vUv);
107961
108028
  try {
107962
108029
  if (!current()) return;
107963
108030
  const models = await Entities.getEntityList();
107964
- if (!models.includes(placement.key.path) || !current()) return;
107965
- const model = await Entities.getEntity(placement.key, placement.texture, { when: placement.when });
108031
+ const renamed = models.includes(placement.key.path) ? void 0 : await Renames.key("entities", placement.key);
108032
+ const key = renamed && models.includes(renamed.path) ? renamed : placement.key;
108033
+ if (!models.includes(key.path) || !current()) return;
108034
+ const model = await Entities.getEntity(key, placement.texture, { when: placement.when });
107966
108035
  if (!model || !current()) return;
107967
108036
  let tints = placement.tints;
107968
108037
  if (placement.woolDye) {
@@ -107977,7 +108046,7 @@ gl_FragDepth=readDepth(vUv);
107977
108046
  object4.rotation.y = placement.yaw;
107978
108047
  await object4.init();
107979
108048
  if (!current()) return;
107980
- placement.owner.set(object4, { type: placement.key.toNamespacedString(), position: placement.position });
108049
+ placement.owner.set(object4, { type: key.toNamespacedString(), position: placement.position });
107981
108050
  this.scene.add(object4);
107982
108051
  } catch (error2) {
107983
108052
  console.warn(`Could not draw saved entity ${placement.key.toNamespacedString()}, skipping it`, error2);
@@ -108009,6 +108078,135 @@ gl_FragDepth=readDepth(vUv);
108009
108078
  // src/world/MineRenderWorld.ts
108010
108079
  init_FluidQuads();
108011
108080
 
108081
+ // src/world/_biomes/BiomeBlend.ts
108082
+ init_dirname();
108083
+ init_buffer2();
108084
+ init_process2();
108085
+ init_AssetKey();
108086
+ init_AssetLoader();
108087
+ init_Caching();
108088
+ var MAX_TILES = 256;
108089
+ function validateBiomeBlendRadius(radius) {
108090
+ if (!Number.isInteger(radius) || radius < 0 || radius > 7) {
108091
+ throw new RangeError("biomeBlendRadius must be an integer from 0 to 7");
108092
+ }
108093
+ }
108094
+ var BiomeBlend = class {
108095
+ constructor(biomeAt, radius) {
108096
+ this.biomeAt = biomeAt;
108097
+ this.tiles = /* @__PURE__ */ new Map();
108098
+ this.generation = Caching.generation;
108099
+ validateBiomeBlendRadius(radius);
108100
+ this._radius = radius;
108101
+ }
108102
+ get radius() {
108103
+ return this._radius;
108104
+ }
108105
+ clear() {
108106
+ this.tiles.clear();
108107
+ }
108108
+ invalidateColumn(x4, z4) {
108109
+ for (const [key, tile] of this.tiles) {
108110
+ if (Math.abs(tile.x - x4) <= 1 && Math.abs(tile.z - z4) <= 1) this.tiles.delete(key);
108111
+ }
108112
+ }
108113
+ async getColor(kind, origin, position) {
108114
+ const [x4, y5, z4] = position;
108115
+ const biome = this.biomeAt(x4, y5, z4);
108116
+ if (biome === void 0) return void 0;
108117
+ if (this.generation !== Caching.generation) {
108118
+ this.clear();
108119
+ this.generation = Caching.generation;
108120
+ }
108121
+ const tileX = Math.floor(x4 / 16), tileZ = Math.floor(z4 / 16), quartY = Math.floor(y5 / 4);
108122
+ const root = origin?.root ?? AssetLoader.ROOT;
108123
+ const key = JSON.stringify([root, AssetLoader.persistentScope, kind, this._radius, tileX, quartY, tileZ]);
108124
+ let tile = this.tiles.get(key);
108125
+ if (tile) {
108126
+ this.tiles.delete(key);
108127
+ this.tiles.set(key, tile);
108128
+ } else {
108129
+ const snapshot = new AssetKey("minecraft", "", void 0, void 0, "assets", ".json", root);
108130
+ const colors = this.buildTile(kind, snapshot, tileX, quartY, tileZ, this._radius).then((result2) => {
108131
+ if (!result2.complete && this.tiles.get(key)?.colors === colors) this.tiles.delete(key);
108132
+ return result2;
108133
+ }, (error2) => {
108134
+ if (this.tiles.get(key)?.colors === colors) this.tiles.delete(key);
108135
+ throw error2;
108136
+ });
108137
+ tile = { x: tileX, z: tileZ, colors };
108138
+ this.tiles.set(key, tile);
108139
+ if (this.tiles.size > MAX_TILES) this.tiles.delete(this.tiles.keys().next().value);
108140
+ }
108141
+ const result = await tile.colors;
108142
+ if (result.failures.has(biome)) throw result.failures.get(biome);
108143
+ const color3 = result.colors[(z4 - tileZ * 16) * 16 + x4 - tileX * 16];
108144
+ return color3 === -1 ? void 0 : color3;
108145
+ }
108146
+ async buildTile(kind, origin, tileX, quartY, tileZ, radius) {
108147
+ const size = 16 + radius * 2, stride = size + 1;
108148
+ const startX = tileX * 16 - radius, startZ = tileZ * 16 - radius;
108149
+ const quartX = Math.floor(startX / 4), quartZ = Math.floor(startZ / 4);
108150
+ const quartSize = Math.floor((startX + size - 1) / 4) - quartX + 1;
108151
+ const ids = new Array(quartSize * quartSize);
108152
+ const biomes = /* @__PURE__ */ new Map();
108153
+ for (let z4 = 0; z4 < quartSize; z4++) {
108154
+ for (let x4 = 0; x4 < quartSize; x4++) {
108155
+ const id = this.biomeAt((quartX + x4) * 4, quartY * 4, (quartZ + z4) * 4);
108156
+ ids[z4 * quartSize + x4] = id;
108157
+ if (id !== void 0 && !biomes.has(id)) {
108158
+ biomes.set(id, (async () => {
108159
+ const swamp = kind === "grass" && (await Biomes.get(id, origin))?.effects.grass_color_modifier === "swamp";
108160
+ return { color: swamp ? void 0 : await Biomes.getColor(id, kind, origin), swamp };
108161
+ })());
108162
+ }
108163
+ }
108164
+ }
108165
+ const failures = /* @__PURE__ */ new Map();
108166
+ const loaded = new Map(await Promise.all([...biomes].map(async ([id, color3]) => {
108167
+ try {
108168
+ return [id, await color3];
108169
+ } catch (error2) {
108170
+ failures.set(id, error2);
108171
+ return [id, { color: void 0, swamp: false }];
108172
+ }
108173
+ })));
108174
+ const complete = [...loaded.values()].every((value) => value.swamp || value.color !== void 0);
108175
+ const samples = new Int32Array(size * size).fill(-1);
108176
+ const sums = Array.from({ length: 4 }, () => new Uint32Array(stride * stride));
108177
+ for (let z4 = 0; z4 < size; z4++) {
108178
+ const row = [0, 0, 0, 0];
108179
+ for (let x4 = 0; x4 < size; x4++) {
108180
+ const id = ids[(Math.floor((startZ + z4) / 4) - quartZ) * quartSize + Math.floor((startX + x4) / 4) - quartX];
108181
+ const value = id === void 0 ? void 0 : loaded.get(id);
108182
+ const color3 = value?.swamp ? swampGrassColor(startX + x4, startZ + z4) : value?.color;
108183
+ if (color3 !== void 0) {
108184
+ samples[z4 * size + x4] = color3;
108185
+ row[0] += color3 >> 16 & 255;
108186
+ row[1] += color3 >> 8 & 255;
108187
+ row[2] += color3 & 255;
108188
+ row[3]++;
108189
+ }
108190
+ for (let channel = 0; channel < 4; channel++) {
108191
+ sums[channel][(z4 + 1) * stride + x4 + 1] = sums[channel][z4 * stride + x4 + 1] + row[channel];
108192
+ }
108193
+ }
108194
+ }
108195
+ const colors = new Int32Array(256).fill(-1), width = radius * 2 + 1;
108196
+ for (let z4 = 0; z4 < 16; z4++) {
108197
+ for (let x4 = 0; x4 < 16; x4++) {
108198
+ if (samples[(z4 + radius) * size + x4 + radius] === -1) continue;
108199
+ const topLeft = z4 * stride + x4, topRight = topLeft + width;
108200
+ const bottomLeft = (z4 + width) * stride + x4, bottomRight = bottomLeft + width;
108201
+ const sum = (values) => values[bottomRight] - values[bottomLeft] - values[topRight] + values[topLeft];
108202
+ const count = sum(sums[3]);
108203
+ if (count) colors[z4 * 16 + x4] = Math.floor(sum(sums[0]) / count) << 16 | Math.floor(sum(sums[1]) / count) << 8 | Math.floor(sum(sums[2]) / count);
108204
+ }
108205
+ }
108206
+ return { colors, complete, failures };
108207
+ }
108208
+ };
108209
+
108012
108210
  // src/world/WorldLight.ts
108013
108211
  init_dirname();
108014
108212
  init_buffer2();
@@ -108078,12 +108276,17 @@ gl_FragDepth=readDepth(vUv);
108078
108276
  this.lightValidity = /* @__PURE__ */ new Map();
108079
108277
  this.highestSkySection = /* @__PURE__ */ new Map();
108080
108278
  this.ambientOcclusionPending = false;
108279
+ this.pendingBiomes = /* @__PURE__ */ new Map();
108081
108280
  this.pendingCulling = /* @__PURE__ */ new Map();
108082
108281
  if (options.savedLighting && !options.sectionMeshing) throw new TypeError("savedLighting requires sectionMeshing");
108083
108282
  if (options.ambientOcclusion && !options.sectionMeshing) throw new TypeError("ambientOcclusion requires sectionMeshing");
108084
108283
  if (options.ambientLight !== void 0 && (!Number.isFinite(options.ambientLight) || options.ambientLight < 0 || options.ambientLight > 1)) {
108085
108284
  throw new RangeError("ambientLight must be a number from 0 to 1");
108086
108285
  }
108286
+ this._biomeBlendRadius = options.biomeBlendRadius ?? 0;
108287
+ validateBiomeBlendRadius(this._biomeBlendRadius);
108288
+ if (this._biomeBlendRadius > 0 && !options.sectionMeshing) throw new TypeError("biomeBlendRadius requires sectionMeshing");
108289
+ if (this._biomeBlendRadius) this.biomeBlend = new BiomeBlend((x4, y5, z4) => this.getBiomeAt(x4, y5, z4), this._biomeBlendRadius);
108087
108290
  this.scene = scene;
108088
108291
  this.savedLighting = options.savedLighting ?? false;
108089
108292
  this.ambientLight = options.ambientLight ?? 0;
@@ -108124,6 +108327,65 @@ gl_FragDepth=readDepth(vUv);
108124
108327
  await this.updateCulling([]);
108125
108328
  this.ambientOcclusionPending = false;
108126
108329
  }
108330
+ /** Horizontal biome-color sampling radius, from 0 to 7 blocks. Zero disables blending. */
108331
+ get biomeBlendRadius() {
108332
+ return this._biomeBlendRadius;
108333
+ }
108334
+ /** Refreshes loaded grass, foliage, and water colors without reloading their source chunks. */
108335
+ async setBiomeBlendRadius(radius) {
108336
+ validateBiomeBlendRadius(radius);
108337
+ if (radius > 0 && !this.sectionModels) throw new TypeError("biomeBlendRadius requires sectionMeshing");
108338
+ if (radius !== this._biomeBlendRadius) {
108339
+ this._biomeBlendRadius = radius;
108340
+ this.biomeBlend?.clear();
108341
+ this.biomeBlend = radius ? new BiomeBlend((x4, y5, z4) => this.getBiomeAt(x4, y5, z4), radius) : void 0;
108342
+ for (const chunk of this._chunks.values()) this.pendingBiomes.set(chunk, void 0);
108343
+ }
108344
+ const failures = await this.refreshBiomes();
108345
+ if (failures.size) throw failures.values().next().value;
108346
+ }
108347
+ markBiomesChanged(x4, z4, y5, includeColumn = true) {
108348
+ const queued = [];
108349
+ if (!this.biomeBlend) return queued;
108350
+ this.biomeBlend.invalidateColumn(x4, z4);
108351
+ for (const chunk of this._chunks.values()) {
108352
+ if (Math.abs(chunk.x - x4) > 1 || Math.abs(chunk.z - z4) > 1 || !includeColumn && chunk.x === x4 && chunk.z === z4 || y5 !== void 0 && chunk.y !== y5 && chunk.y !== y5 + 1) continue;
108353
+ queued.push(chunk);
108354
+ if (!this.pendingBiomes.has(chunk)) this.pendingBiomes.set(chunk, []);
108355
+ const columns = this.pendingBiomes.get(chunk);
108356
+ if (columns && !columns.some((column) => column.x === x4 && column.z === z4)) columns.push({ x: x4, z: z4 });
108357
+ }
108358
+ return queued;
108359
+ }
108360
+ async refreshBiomes() {
108361
+ if (this.biomeRefreshing) await this.biomeRefreshing;
108362
+ if (!this.pendingBiomes.size) return /* @__PURE__ */ new Map();
108363
+ return this.biomeRefreshing ?? (this.biomeRefreshing = Promise.resolve().then(async () => {
108364
+ const failures = /* @__PURE__ */ new Map();
108365
+ const batch = [...this.pendingBiomes.keys()];
108366
+ let sliceStart = performance.now();
108367
+ try {
108368
+ for (const chunk of batch) {
108369
+ const columns = this.pendingBiomes.get(chunk);
108370
+ this.pendingBiomes.delete(chunk);
108371
+ if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) !== chunk) continue;
108372
+ try {
108373
+ await chunk.refreshBiomes(columns);
108374
+ } catch (error2) {
108375
+ failures.set(chunk, error2);
108376
+ if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingBiomes.set(chunk, void 0);
108377
+ }
108378
+ if (performance.now() - sliceStart > 8) {
108379
+ await yieldToEventLoop();
108380
+ sliceStart = performance.now();
108381
+ }
108382
+ }
108383
+ return failures;
108384
+ } finally {
108385
+ this.biomeRefreshing = void 0;
108386
+ }
108387
+ }));
108388
+ }
108127
108389
  getBlockAt(posOrX, y5, z4) {
108128
108390
  if (typeof posOrX == "number") {
108129
108391
  return this.getBlockAt(new Vector3(posOrX, y5, z4));
@@ -108359,6 +108621,9 @@ gl_FragDepth=readDepth(vUv);
108359
108621
  }
108360
108622
  await placeEntities?.();
108361
108623
  } finally {
108624
+ this.markBiomesChanged(chunk.x, chunk.z, void 0, false);
108625
+ const failures = await this.refreshBiomes();
108626
+ if (failures.size) console.warn("Failed to refresh biome colors", failures.values().next().value);
108362
108627
  await this.updateCulling([...changes.values()]);
108363
108628
  }
108364
108629
  }
@@ -108375,11 +108640,14 @@ gl_FragDepth=readDepth(vUv);
108375
108640
  try {
108376
108641
  await this.clearChunkColumn(x4, z4);
108377
108642
  } finally {
108643
+ const failures = await this.refreshBiomes();
108644
+ if (failures.size) console.warn("Failed to refresh biome colors", failures.values().next().value);
108378
108645
  await this.updateCulling([]);
108379
108646
  }
108380
108647
  }
108381
108648
  async clearChunkColumn(x4, z4) {
108382
108649
  this.biomes.delete(`${x4}_${z4}`);
108650
+ this.markBiomesChanged(x4, z4, void 0, false);
108383
108651
  if (this.savedLighting) {
108384
108652
  for (const y5 of this.light.get(`${x4}_${z4}`)?.keys() ?? []) this.markSectionChanged({ x: x4, y: y5, z: z4 });
108385
108653
  }
@@ -108390,6 +108658,7 @@ gl_FragDepth=readDepth(vUv);
108390
108658
  for (const [key, section] of previous) {
108391
108659
  this._chunks.delete(key);
108392
108660
  this.pendingCulling.delete(section);
108661
+ this.pendingBiomes.delete(section);
108393
108662
  await section.clear(false);
108394
108663
  this.markSectionChanged(section);
108395
108664
  }
@@ -108424,6 +108693,8 @@ gl_FragDepth=readDepth(vUv);
108424
108693
  const chunks = [...this._chunks.values()];
108425
108694
  this._chunks.clear();
108426
108695
  this.biomes.clear();
108696
+ this.biomeBlend?.clear();
108697
+ this.pendingBiomes.clear();
108427
108698
  this.light.clear();
108428
108699
  this.lightValidity.clear();
108429
108700
  this.highestSkySection.clear();
@@ -108542,8 +108813,14 @@ gl_FragDepth=readDepth(vUv);
108542
108813
  column?.delete(y5);
108543
108814
  if (!column?.size) this.biomes.delete(columnKey);
108544
108815
  }
108545
- await chunk.refreshBiomes();
108546
- await this._chunks.get(`${x4}_${y5 + 1}_${z4}`)?.refreshBiomes();
108816
+ if (this.biomeBlend) {
108817
+ const queued = this.markBiomesChanged(x4, z4, y5);
108818
+ const failures = await this.refreshBiomes();
108819
+ for (const chunk2 of queued) if (failures.has(chunk2)) throw failures.get(chunk2);
108820
+ } else {
108821
+ await chunk.refreshBiomes();
108822
+ await this._chunks.get(`${x4}_${y5 + 1}_${z4}`)?.refreshBiomes();
108823
+ }
108547
108824
  },
108548
108825
  this.savedLighting ? (x5, y6, z5) => this.lightLevelAt(x5, y6, z5) : void 0,
108549
108826
  async () => {
@@ -108560,7 +108837,8 @@ gl_FragDepth=readDepth(vUv);
108560
108837
  await this.updateCulling([]);
108561
108838
  }
108562
108839
  },
108563
- this.ambientLight
108840
+ this.ambientLight,
108841
+ () => this.biomeBlend
108564
108842
  );
108565
108843
  this._chunks.set(key, chunk);
108566
108844
  }
@@ -108691,9 +108969,10 @@ gl_FragDepth=readDepth(vUv);
108691
108969
 
108692
108970
  // src/world/SectionGeometry.ts
108693
108971
  function buildSectionGeometry(input) {
108694
- const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion } = input;
108972
+ const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion, biomeTints } = input;
108695
108973
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
108696
108974
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
108975
+ if (biomeTints && biomeTints.length !== count * 3) throw new RangeError("Section biome tints must contain RGB for each placement");
108697
108976
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
108698
108977
  if (fluids?.waterTints && fluids.waterTints.length !== 4096 * 3) throw new RangeError("Section water tints must contain RGB for 4096 cells");
108699
108978
  if (light && (light.length !== 5832 || light.some((level) => level > 15 && level !== 255))) {
@@ -108708,7 +108987,7 @@ gl_FragDepth=readDepth(vUv);
108708
108987
  const cornerBrightness2 = ambientOcclusion ? new Float32Array(4) : void 0;
108709
108988
  for (const template of templateData) {
108710
108989
  const { quads } = template;
108711
- 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) {
108990
+ 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 || template.biomeTint && template.biomeTint.length !== quads) {
108712
108991
  throw new RangeError("Section template arrays do not match its quad count");
108713
108992
  }
108714
108993
  }
@@ -108818,6 +109097,7 @@ gl_FragDepth=readDepth(vUv);
108818
109097
  blockIndices[indexOffset / 6] = indices[entry];
108819
109098
  if (ambientMask) quadAmbientOcclusion(template.positions, template.normals, quad, ambientMask, cornerBrightness2);
108820
109099
  const brightness = light ? lightBrightness(light[lightIndex + LIGHT_SAMPLE_OFFSETS[quadLightIndex(template.positions, template.normals, quad)]], ambientLight) : 1;
109100
+ const tint = biomeTints && template.biomeTint?.[quad] && !Number.isNaN(biomeTints[entry * 3]) ? entry * 3 : void 0;
108821
109101
  const start = vertex2;
108822
109102
  for (let corner = 0; corner < 4; corner++, vertex2++) {
108823
109103
  const source = quad * 4 + corner;
@@ -108834,9 +109114,9 @@ gl_FragDepth=readDepth(vUv);
108834
109114
  uvBounds[vertex2 * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
108835
109115
  uvBounds[vertex2 * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
108836
109116
  const factor = brightness * (ambientMask ? cornerBrightness2[corner] : 1);
108837
- colors[vertex2 * 3] = template.colors[source * 3] * factor;
108838
- colors[vertex2 * 3 + 1] = template.colors[source * 3 + 1] * factor;
108839
- colors[vertex2 * 3 + 2] = template.colors[source * 3 + 2] * factor;
109117
+ colors[vertex2 * 3] = (tint === void 0 ? template.colors[source * 3] : biomeTints[tint]) * factor;
109118
+ colors[vertex2 * 3 + 1] = (tint === void 0 ? template.colors[source * 3 + 1] : biomeTints[tint + 1]) * factor;
109119
+ colors[vertex2 * 3 + 2] = (tint === void 0 ? template.colors[source * 3 + 2] : biomeTints[tint + 2]) * factor;
108840
109120
  }
108841
109121
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
108842
109122
  }
@@ -108960,7 +109240,11 @@ gl_FragDepth=readDepth(vUv);
108960
109240
  ...lighting
108961
109241
  };
108962
109242
  for (let i4 = 0; i4 < entries.length; i4++) {
108963
- const { index, template, cullMask } = entries[i4];
109243
+ const { index, template, cullMask, biomeTint } = entries[i4];
109244
+ if (biomeTint) {
109245
+ input.biomeTints ?? (input.biomeTints = new Float32Array(entries.length * 3).fill(NaN));
109246
+ input.biomeTints.set(biomeTint, i4 * 3);
109247
+ }
108964
109248
  let id = templateIds.get(template);
108965
109249
  if (id === void 0) {
108966
109250
  let atlas = atlasIds.get(template.atlas);
@@ -108987,6 +109271,7 @@ gl_FragDepth=readDepth(vUv);
108987
109271
  colors: new Float32Array(quads * 12),
108988
109272
  indices: new Uint16Array(quads * 6),
108989
109273
  cullFaces: new Uint8Array(template.cullFaces),
109274
+ biomeTint: template.biomeTint ? new Uint8Array(template.biomeTint) : void 0,
108990
109275
  atlas: 0,
108991
109276
  layer: template.layer,
108992
109277
  ambientOcclusion: template.ambientOcclusion
@@ -109131,7 +109416,7 @@ gl_FragDepth=readDepth(vUv);
109131
109416
  init_InstanceReference();
109132
109417
  var WATER_TINT2 = new Color(4159204).toArray();
109133
109418
  var Chunk = class _Chunk {
109134
- constructor(scene, x4, y5, z4, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0) {
109419
+ constructor(scene, x4, y5, z4, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0, biomeBlend) {
109135
109420
  this.onBlocksChanged = onBlocksChanged;
109136
109421
  this.sectionModels = sectionModels;
109137
109422
  this.neighborCell = neighborCell;
@@ -109140,6 +109425,7 @@ gl_FragDepth=readDepth(vUv);
109140
109425
  this.lightAt = lightAt;
109141
109426
  this.onLightCleared = onLightCleared;
109142
109427
  this.ambientLight = ambientLight;
109428
+ this.biomeBlend = biomeBlend;
109143
109429
  this.data = new ChunkData();
109144
109430
  this.renderedBlocks = /* @__PURE__ */ new Map();
109145
109431
  this.objectBlocks = /* @__PURE__ */ new Set();
@@ -109149,6 +109435,7 @@ gl_FragDepth=readDepth(vUv);
109149
109435
  this.blockPosition = new Vector3();
109150
109436
  this.meshDirty = false;
109151
109437
  this.meshGeneration = 0;
109438
+ this.blockChanges = /* @__PURE__ */ new Set();
109152
109439
  this.scene = scene;
109153
109440
  this.x = x4;
109154
109441
  this.y = y5;
@@ -109247,10 +109534,20 @@ gl_FragDepth=readDepth(vUv);
109247
109534
  return this.refreshBiomes();
109248
109535
  }
109249
109536
  /** Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects. */
109250
- async refreshBiomes() {
109537
+ refreshBiomes(columns) {
109538
+ return this.changeBlocks(() => this.refreshBiomeBlocks(columns), true);
109539
+ }
109540
+ async refreshBiomeBlocks(columns) {
109541
+ const blend = columns && this.biomeBlend?.();
109542
+ const ownColumn = columns?.some((column) => column.x === this.x && column.z === this.z);
109251
109543
  const affected = /* @__PURE__ */ new Map();
109252
109544
  const blocks = [];
109253
109545
  for (const index of this.renderedBlocks.keys()) {
109546
+ if (blend) {
109547
+ if (this.objectBlocks.has(index) && !ownColumn) continue;
109548
+ const [x4, , z4] = this.positionAt(index), radius = blend.radius;
109549
+ if (!columns.some((column) => x4 >= column.x * 16 - radius && x4 <= column.x * 16 + 15 + radius && z4 >= column.z * 16 - radius && z4 <= column.z * 16 + 15 + radius)) continue;
109550
+ }
109254
109551
  const block = this.data.get(index);
109255
109552
  const stateKey = ChunkData.paletteKey(block);
109256
109553
  let matches = affected.get(stateKey);
@@ -109265,10 +109562,32 @@ gl_FragDepth=readDepth(vUv);
109265
109562
  }
109266
109563
  if (await matches) blocks.push({ index, block });
109267
109564
  }
109565
+ if (blend) {
109566
+ const groups = /* @__PURE__ */ new Map();
109567
+ for (const entry of blocks) {
109568
+ const key = ChunkData.paletteKey(entry.block);
109569
+ let group = groups.get(key);
109570
+ if (!group) groups.set(key, group = []);
109571
+ group.push(entry);
109572
+ }
109573
+ blocks.length = 0;
109574
+ const biomes = /* @__PURE__ */ new Map();
109575
+ for (const group of groups.values()) {
109576
+ const { contexts, fluid } = await this.resolveState(group[0].block, group.map((entry) => entry.index), biomes);
109577
+ for (const entry of group) {
109578
+ const context = contexts.get(entry.index);
109579
+ const current = this.sectionBlocks.get(entry.index)?.[0]?.biomeTint;
109580
+ const tint = context?.tint;
109581
+ const tintChanged = !!current !== !!tint || tint?.some((value, channel) => value !== current?.[channel]);
109582
+ const waterChanged = fluid?.kind === "water" && (context?.waterTint ?? WATER_TINT2).some((value, channel) => Math.fround(value) !== (this.waterTints?.[entry.index * 3 + channel] ?? Math.fround(WATER_TINT2[channel])));
109583
+ if (this.objectBlocks.has(entry.index) || tintChanged || waterChanged) blocks.push(entry);
109584
+ }
109585
+ }
109586
+ }
109268
109587
  if (!blocks.length) return;
109269
109588
  const hidden = new Set(this.hiddenBlocks);
109270
109589
  try {
109271
- await this.placeBlocks(blocks);
109590
+ await this.replaceBlocks(blocks, true);
109272
109591
  } finally {
109273
109592
  for (const index of hidden) {
109274
109593
  this.hiddenBlocks.add(index);
@@ -109308,6 +109627,20 @@ gl_FragDepth=readDepth(vUv);
109308
109627
  }
109309
109628
  /** Places cells in input order and returns changed world positions without refreshing neighbors. */
109310
109629
  async placeBlocks(blocks) {
109630
+ return this.changeBlocks(() => this.replaceBlocks(blocks));
109631
+ }
109632
+ changeBlocks(change, refreshBiomes = false) {
109633
+ const before = refreshBiomes ? Promise.allSettled(this.blockChanges) : this.biomeChange;
109634
+ const result = before ? before.catch(() => void 0).then(change) : change();
109635
+ const pending = result.finally(() => {
109636
+ this.blockChanges.delete(pending);
109637
+ if (this.biomeChange === pending) this.biomeChange = void 0;
109638
+ });
109639
+ this.blockChanges.add(pending);
109640
+ if (refreshBiomes) this.biomeChange = pending;
109641
+ return pending;
109642
+ }
109643
+ async replaceBlocks(blocks, preserveDataOnFailure = false) {
109311
109644
  const placements = blocks.map(({ index, block }) => ({
109312
109645
  index,
109313
109646
  key: block && !ChunkData.isAir(block) ? ChunkData.paletteKey(block) : void 0,
@@ -109327,6 +109660,10 @@ gl_FragDepth=readDepth(vUv);
109327
109660
  const biomes = /* @__PURE__ */ new Map();
109328
109661
  const settled = await Promise.allSettled([...states.values()].map(({ stored, indices }) => this.resolveState(stored, indices, biomes)));
109329
109662
  const resolutions = new Map([...states.keys()].map((key, i4) => [key, settled[i4]]));
109663
+ if (preserveDataOnFailure) {
109664
+ const rejected = settled.find((result) => result.status === "rejected");
109665
+ if (rejected?.status === "rejected") throw rejected.reason;
109666
+ }
109330
109667
  const positions = [];
109331
109668
  let failed = false, failure;
109332
109669
  for (const { index, key, block } of placements) {
@@ -109364,7 +109701,7 @@ gl_FragDepth=readDepth(vUv);
109364
109701
  }
109365
109702
  const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
109366
109703
  if (templates) {
109367
- this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
109704
+ this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0, biomeTint: context?.tint })));
109368
109705
  this.meshDirty = true;
109369
109706
  } else {
109370
109707
  object4 = await this.scene.addBlock(blockState3, {
@@ -109383,7 +109720,11 @@ gl_FragDepth=readDepth(vUv);
109383
109720
  }, object: object4 });
109384
109721
  if (object4) this.objectBlocks.add(index);
109385
109722
  } catch (error2) {
109386
- this.data.assign(index, 0);
109723
+ if (preserveDataOnFailure && readBlock) {
109724
+ this.renderedBlocks.set(index, { get block() {
109725
+ return readBlock();
109726
+ }, object: void 0 });
109727
+ } else this.data.assign(index, 0);
109387
109728
  this.fluidCells[index] = 0;
109388
109729
  object4?.removeFromScene();
109389
109730
  if (!failed) failure = error2;
@@ -109394,7 +109735,10 @@ gl_FragDepth=readDepth(vUv);
109394
109735
  return positions;
109395
109736
  }
109396
109737
  /** Replaces every cell from parsed section data. The caller refreshes neighbor culling. */
109397
- async placeSection(data) {
109738
+ placeSection(data) {
109739
+ return this.changeBlocks(() => this.replaceSection(data));
109740
+ }
109741
+ async replaceSection(data) {
109398
109742
  for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
109399
109743
  this.sectionBlocks.clear();
109400
109744
  this.fluidCells.fill(0);
@@ -109441,7 +109785,7 @@ gl_FragDepth=readDepth(vUv);
109441
109785
  ];
109442
109786
  const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
109443
109787
  if (templates) {
109444
- this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
109788
+ this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0, biomeTint: context?.tint })));
109445
109789
  this.meshDirty = true;
109446
109790
  } else {
109447
109791
  perBlocks.push({ index, blockState: blockState3, biome: context?.biome });
@@ -109525,12 +109869,35 @@ gl_FragDepth=readDepth(vUv);
109525
109869
  }
109526
109870
  async resolveState(stored, indices, biomes) {
109527
109871
  const blockState3 = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
109528
- const perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
109872
+ let perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
109529
109873
  const fluid = await getBlockFluidState(blockState3?.key, stored.properties);
109530
109874
  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;
109531
109875
  const source = hasBiomes && blockState3 ? await BlockTints.biomeSource(blockState3.key) : void 0;
109532
109876
  const prepare = (color3) => perBlock || fluid?.renderModel === false ? void 0 : this.sectionModels.prepareState(blockState3, stored.properties, color3);
109533
109877
  const contexts = /* @__PURE__ */ new Map();
109878
+ const blend = this.biomeBlend?.();
109879
+ if (blend && !perBlock && blockState3 && (source || fluid?.kind === "water")) {
109880
+ const prepared2 = fluid?.renderModel === false ? void 0 : await this.sectionModels.prepareState(blockState3, stored.properties, void 0, true);
109881
+ if (!prepared2 && fluid?.renderModel !== false) {
109882
+ perBlock = true;
109883
+ } else {
109884
+ await Promise.all(indices.map(async (index) => {
109885
+ const position = this.biomePosition(index, blockState3, stored);
109886
+ const biomeColor = (kind) => blend.getColor(kind, blockState3.key, position);
109887
+ const context = {};
109888
+ if (source && prepared2?.biomeTint) {
109889
+ const color3 = await biomeColor(source);
109890
+ if (color3 !== void 0) context.tint = new Color(color3).toArray();
109891
+ }
109892
+ if (fluid?.kind === "water") {
109893
+ const color3 = await biomeColor("water");
109894
+ if (color3 !== void 0 && color3 !== 4159204) context.waterTint = new Color(color3).toArray();
109895
+ }
109896
+ contexts.set(index, context);
109897
+ }));
109898
+ return { blockState: blockState3, perBlock, fluid, prepared: prepared2, contexts, stored };
109899
+ }
109900
+ }
109534
109901
  const groups = /* @__PURE__ */ new Map();
109535
109902
  if (blockState3 && (source || fluid?.kind === "water")) {
109536
109903
  for (const index of indices) {
@@ -109578,7 +109945,11 @@ gl_FragDepth=readDepth(vUv);
109578
109945
  this.waterTints.set(tint ?? WATER_TINT2, index * 3);
109579
109946
  }
109580
109947
  /** Changes visibility at world block coordinates while preserving the block's data. */
109581
- async setBlockVisibleAt(pos, visible) {
109948
+ setBlockVisibleAt(pos, visible) {
109949
+ const position = pos.clone();
109950
+ return this.changeBlocks(() => this.setBlockVisibility(position, visible));
109951
+ }
109952
+ async setBlockVisibility(pos, visible) {
109582
109953
  const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
109583
109954
  const block = this.renderedBlocks.get(index);
109584
109955
  if (!block || visible === !this.hiddenBlocks.has(index)) {
@@ -109748,6 +110119,7 @@ gl_FragDepth=readDepth(vUv);
109748
110119
  }
109749
110120
  /** Removes stored blocks and render objects. Set `notify` to false to skip the owning world's update. */
109750
110121
  async clear(notify = true) {
110122
+ if (this.blockChanges.size) await Promise.allSettled(this.blockChanges);
109751
110123
  const positions = notify && this.onBlocksChanged ? [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
109752
110124
  new Vector3(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
109753
110125
  )) : [];