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/browser/index.d.mts +77 -5
- package/dist/browser/index.d.ts +77 -5
- package/dist/browser/index.js +653 -287
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/index.mjs +653 -287
- package/dist/browser/index.mjs.map +1 -1
- package/dist/browser/section.worker.js +7 -5
- package/dist/browser/section.worker.js.map +1 -1
- package/dist/browser/section.worker.mjs +7 -5
- package/dist/browser/section.worker.mjs.map +1 -1
- package/dist/bundle.js +665 -293
- package/dist/bundle.js.map +1 -1
- package/dist/node/index.d.mts +77 -5
- package/dist/node/index.d.ts +77 -5
- package/dist/node/index.js +656 -287
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +655 -287
- package/dist/node/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
107965
|
-
const
|
|
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:
|
|
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
|
-
|
|
108546
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
)) : [];
|